mirror of
https://github.com/falk-werner/webfuse
synced 2024-10-27 20:34:10 +00:00
feature: removed example
This commit is contained in:
parent
f504a01cb2
commit
bd82b06c8a
@ -190,81 +190,6 @@ install(FILES include/webfuse_provider.h DESTINATION include)
|
|||||||
install(DIRECTORY include/webfuse/provider DESTINATION include/webfuse)
|
install(DIRECTORY include/webfuse/provider DESTINATION include/webfuse)
|
||||||
install(FILES "${PROJECT_BINARY_DIR}/libwebfuse-provider.pc" DESTINATION lib${LIB_SUFFIX}/pkgconfig)
|
install(FILES "${PROJECT_BINARY_DIR}/libwebfuse-provider.pc" DESTINATION lib${LIB_SUFFIX}/pkgconfig)
|
||||||
|
|
||||||
|
|
||||||
# examples
|
|
||||||
|
|
||||||
pkg_check_modules(OPENSSL REQUIRED openssl)
|
|
||||||
|
|
||||||
if(NOT WITHOUT_EXAMPLE)
|
|
||||||
|
|
||||||
# libuserdb
|
|
||||||
|
|
||||||
add_library(userdb STATIC
|
|
||||||
example/lib/userdb/src/userdb.c
|
|
||||||
)
|
|
||||||
|
|
||||||
target_include_directories(userdb PUBLIC
|
|
||||||
example/lib/userdb/include
|
|
||||||
${OPENSSL_INCLUDE_DIRS}
|
|
||||||
${JANSSON_INCLUDE_DIRS}
|
|
||||||
)
|
|
||||||
|
|
||||||
target_compile_options(userdb PUBLIC ${OPENSSL_CFLAGS_OTHER})
|
|
||||||
|
|
||||||
# daemon
|
|
||||||
|
|
||||||
add_executable(webfused
|
|
||||||
example/daemon/main.c
|
|
||||||
)
|
|
||||||
|
|
||||||
target_link_libraries(webfused PUBLIC webfuse-adapter userdb ${OPENSSL_LIBRARIES} ${EXTRA_LIBS})
|
|
||||||
target_compile_options(webfused PUBLIC ${OPENSSL_CFLAGS_OTHER})
|
|
||||||
|
|
||||||
# provider
|
|
||||||
|
|
||||||
add_executable(webfuse-provider-app
|
|
||||||
example/provider/main.c
|
|
||||||
)
|
|
||||||
|
|
||||||
set_target_properties(webfuse-provider-app PROPERTIES OUTPUT_NAME webfuse-provider)
|
|
||||||
|
|
||||||
target_link_libraries(webfuse-provider-app PUBLIC webfuse-provider ${EXTRA_LIBS})
|
|
||||||
target_include_directories(webfuse-provider-app PUBLIC ${EXTRA_INCLUDE_DIRS})
|
|
||||||
|
|
||||||
# static-filesystem-provider
|
|
||||||
|
|
||||||
add_executable(static-filesystem-provider
|
|
||||||
example/provider/static_filesystem.c
|
|
||||||
)
|
|
||||||
|
|
||||||
target_link_libraries(static-filesystem-provider PUBLIC webfuse-provider ${EXTRA_LIBS})
|
|
||||||
target_include_directories(static-filesystem-provider PUBLIC ${EXTRA_INCLUDE_DIRS})
|
|
||||||
target_compile_options(static-filesystem-provider PUBLIC ${EXTRA_CFLAGS})
|
|
||||||
|
|
||||||
# webfuse-passwd
|
|
||||||
|
|
||||||
add_executable(webfuse-passwd
|
|
||||||
example/passwd/main.c
|
|
||||||
)
|
|
||||||
|
|
||||||
target_link_libraries(webfuse-passwd PUBLIC
|
|
||||||
userdb
|
|
||||||
${OPENSSL_LIBRARIES}
|
|
||||||
${JANSSON_LIBRARIES}
|
|
||||||
)
|
|
||||||
|
|
||||||
target_include_directories(webfuse-passwd PUBLIC
|
|
||||||
example/passwd
|
|
||||||
example/lib/userdb/include
|
|
||||||
${OPENSSL_INCLUDE_DIRS}
|
|
||||||
${JANSSON_INCLUDE_DIRS}
|
|
||||||
)
|
|
||||||
|
|
||||||
target_compile_options(webfuse-passwd PUBLIC ${OPENSSL_CFLAGS_OTHER})
|
|
||||||
|
|
||||||
|
|
||||||
endif(NOT WITHOUT_EXAMPLE)
|
|
||||||
|
|
||||||
# tests
|
# tests
|
||||||
|
|
||||||
if(NOT WITHOUT_TESTS)
|
if(NOT WITHOUT_TESTS)
|
||||||
|
@ -9,6 +9,7 @@ webfuse combines libwebsockets and libfuse. It allows ot attach a remote filesys
|
|||||||
## Contents
|
## Contents
|
||||||
|
|
||||||
- [Motivation](#Motivation)
|
- [Motivation](#Motivation)
|
||||||
|
- [Fellow Repositories](#Fellow-Repositories)
|
||||||
- [Concept](#Concept)
|
- [Concept](#Concept)
|
||||||
- [Similar Projects](#Similar-Projects)
|
- [Similar Projects](#Similar-Projects)
|
||||||
- [API](#API)
|
- [API](#API)
|
||||||
@ -32,6 +33,12 @@ To avoid Steps 1 and 2, it would be great to keep the update file entirely in we
|
|||||||
|
|
||||||
webfuse solves this problem by using the [WebSocket](https://en.wikipedia.org/wiki/WebSocket) protocol. The emdedded device runs a service, known as webfuse adapter, awaiting incoming connections, e.g. from a web browser. The browser acts as a file system provider, providing the update file to the device.
|
webfuse solves this problem by using the [WebSocket](https://en.wikipedia.org/wiki/WebSocket) protocol. The emdedded device runs a service, known as webfuse adapter, awaiting incoming connections, e.g. from a web browser. The browser acts as a file system provider, providing the update file to the device.
|
||||||
|
|
||||||
|
## Fellow Repositories
|
||||||
|
|
||||||
|
- **[webfuse-example](https://github.com/falk-werner/webfuse-example)**: Example of webfuse
|
||||||
|
- **[webfused](https://github.com/falk-werner/webfused)**: Reference implementation of webfuse daemon
|
||||||
|
- **[webfuse-provider](https://github.com/falk-werner/webfuse-provider)**: Reference implementation of webfuse provider
|
||||||
|
|
||||||
## Concept
|
## Concept
|
||||||
|
|
||||||
![concept](doc/concept.png)
|
![concept](doc/concept.png)
|
||||||
|
@ -1,196 +0,0 @@
|
|||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <signal.h>
|
|
||||||
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <getopt.h>
|
|
||||||
|
|
||||||
#include <webfuse_adapter.h>
|
|
||||||
#include <userdb.h>
|
|
||||||
|
|
||||||
#define SERVICE_TIMEOUT (1 * 1000)
|
|
||||||
|
|
||||||
struct args
|
|
||||||
{
|
|
||||||
struct wf_server_config * config;
|
|
||||||
char * passwd_path;
|
|
||||||
bool show_help;
|
|
||||||
};
|
|
||||||
|
|
||||||
static bool shutdown_requested = false;
|
|
||||||
|
|
||||||
static void show_help(void)
|
|
||||||
{
|
|
||||||
printf(
|
|
||||||
"webfused, Copyright (c) 2019, webfuse authors <https://github.com/falk-werner/webfuse>\n"
|
|
||||||
"Websocket file system daemon\n"
|
|
||||||
"\n"
|
|
||||||
"Usage: webfused [m <mount_point>] [-d <document_root] [-n <vhost_name>] [-p <port>]\n"
|
|
||||||
" [-c <server_cert_path>] [-k <server_key_path>] [-P <passwd_path>]\n"
|
|
||||||
"\n"
|
|
||||||
"Options:\n"
|
|
||||||
"\t-m, --mount_point Path of mount point (required)\n"
|
|
||||||
"\t-d, --document_root Path of www directory (default: not set, www disabled)\n"
|
|
||||||
"\t-c, --server_cert_path Path of servers own certificate (default: not set, TLS disabled)\n"
|
|
||||||
"\t-k, --server_key_path Path of servers private key (default: not set, TLS disabled)\n"
|
|
||||||
"\t-n, --vhost_name Name of virtual host (default: \"localhost\")\n"
|
|
||||||
"\t-p, --port Number of servers port (default: 8080)\n"
|
|
||||||
"\t-P, --passwd_path Path to password file (default: not set, authentication disabled)\n"
|
|
||||||
"\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool authenticate(struct wf_credentials * creds, void * user_data)
|
|
||||||
{
|
|
||||||
bool result = false;
|
|
||||||
struct args * args = user_data;
|
|
||||||
|
|
||||||
char const * username = wf_credentials_get(creds, "username");
|
|
||||||
char const * password = wf_credentials_get(creds, "password");
|
|
||||||
if ((NULL != username) && (NULL != password))
|
|
||||||
{
|
|
||||||
struct userdb * db = userdb_create("");
|
|
||||||
result = userdb_load(db, args->passwd_path);
|
|
||||||
if (result)
|
|
||||||
{
|
|
||||||
result = userdb_check(db, username, password);
|
|
||||||
}
|
|
||||||
|
|
||||||
userdb_dispose(db);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int parse_arguments(int argc, char * argv[], struct args * args)
|
|
||||||
{
|
|
||||||
static struct option const options[] =
|
|
||||||
{
|
|
||||||
{"mount_point", required_argument, NULL, 'm'},
|
|
||||||
{"document_root", required_argument, NULL, 'd'},
|
|
||||||
{"server_cert_path", required_argument, NULL, 'c'},
|
|
||||||
{"server_key_path", required_argument, NULL, 'k'},
|
|
||||||
{"vhost_name", required_argument, NULL, 'n'},
|
|
||||||
{"port", required_argument, NULL, 'p'},
|
|
||||||
{"passwd_path", required_argument, NULL, 'P'},
|
|
||||||
{"help", no_argument, NULL, 'h'},
|
|
||||||
{NULL, 0, NULL, 0}
|
|
||||||
};
|
|
||||||
|
|
||||||
bool result = EXIT_SUCCESS;
|
|
||||||
bool finished = false;
|
|
||||||
bool has_mountpoint = false;
|
|
||||||
while ((!finished) && (EXIT_SUCCESS == result))
|
|
||||||
{
|
|
||||||
int option_index = 0;
|
|
||||||
int const c = getopt_long(argc, argv, "m:d:c:k:n:p:P:h", options, &option_index);
|
|
||||||
|
|
||||||
switch (c)
|
|
||||||
{
|
|
||||||
case -1:
|
|
||||||
finished = true;
|
|
||||||
break;
|
|
||||||
case 'h':
|
|
||||||
args->show_help = true;
|
|
||||||
finished = true;
|
|
||||||
break;
|
|
||||||
case 'm':
|
|
||||||
wf_server_config_set_mountpoint(args->config, optarg);
|
|
||||||
has_mountpoint = true;
|
|
||||||
break;
|
|
||||||
case 'd':
|
|
||||||
wf_server_config_set_documentroot(args->config, optarg);
|
|
||||||
break;
|
|
||||||
case 'c':
|
|
||||||
wf_server_config_set_certpath(args->config, optarg);
|
|
||||||
break;
|
|
||||||
case 'k':
|
|
||||||
wf_server_config_set_keypath(args->config, optarg);
|
|
||||||
break;
|
|
||||||
case 'n':
|
|
||||||
wf_server_config_set_vhostname(args->config, optarg);
|
|
||||||
break;
|
|
||||||
case 'p':
|
|
||||||
wf_server_config_set_port(args->config, atoi(optarg));
|
|
||||||
break;
|
|
||||||
case 'P':
|
|
||||||
free(args->passwd_path);
|
|
||||||
args->passwd_path = strdup(optarg);
|
|
||||||
wf_server_config_add_authenticator(args->config,
|
|
||||||
"username",
|
|
||||||
&authenticate,
|
|
||||||
args);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
fprintf(stderr, "error: unknown argument\n");
|
|
||||||
result = EXIT_FAILURE;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((EXIT_SUCCESS == result) && (!args->show_help))
|
|
||||||
{
|
|
||||||
if (!has_mountpoint)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "error: missing mount point\n");
|
|
||||||
result = EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (EXIT_SUCCESS != result)
|
|
||||||
{
|
|
||||||
args->show_help = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void on_interrupt(int signal_id)
|
|
||||||
{
|
|
||||||
(void) signal_id;
|
|
||||||
|
|
||||||
shutdown_requested = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char * argv[])
|
|
||||||
{
|
|
||||||
struct args args;
|
|
||||||
args.config = wf_server_config_create();
|
|
||||||
wf_server_config_set_vhostname(args.config, "localhost");
|
|
||||||
wf_server_config_set_port(args.config, 8080);
|
|
||||||
args.passwd_path = NULL;
|
|
||||||
args.show_help = false;
|
|
||||||
|
|
||||||
int result = parse_arguments(argc, argv, &args);
|
|
||||||
|
|
||||||
if (!args.show_help)
|
|
||||||
{
|
|
||||||
signal(SIGINT, on_interrupt);
|
|
||||||
struct wf_server * server = wf_server_create(args.config);
|
|
||||||
if (NULL != server)
|
|
||||||
{
|
|
||||||
while (!shutdown_requested)
|
|
||||||
{
|
|
||||||
wf_server_service(server, SERVICE_TIMEOUT);
|
|
||||||
}
|
|
||||||
|
|
||||||
wf_server_dispose(server);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
fprintf(stderr, "fatal: unable start server\n");
|
|
||||||
result = EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
show_help();
|
|
||||||
}
|
|
||||||
|
|
||||||
free(args.passwd_path);
|
|
||||||
wf_server_config_dispose(args.config);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
|||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>WebFuse Example</title>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<link rel="stylesheet" type="text/css" href="style/main.css">
|
|
||||||
<script type="module" src="js/startup.js"></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<div class="page">
|
|
||||||
<div class="window">
|
|
||||||
<div class="title">Connection</div>
|
|
||||||
<div id="connection"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,287 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
"env": {
|
|
||||||
"browser": true,
|
|
||||||
"es6": true
|
|
||||||
},
|
|
||||||
"extends": "eslint:recommended",
|
|
||||||
"globals": {
|
|
||||||
"Atomics": "readonly",
|
|
||||||
"SharedArrayBuffer": "readonly"
|
|
||||||
},
|
|
||||||
"parserOptions": {
|
|
||||||
"ecmaVersion": 2018,
|
|
||||||
"sourceType": "module"
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"accessor-pairs": "error",
|
|
||||||
"array-bracket-newline": "error",
|
|
||||||
"array-bracket-spacing": "error",
|
|
||||||
"array-callback-return": "error",
|
|
||||||
"array-element-newline": ["error", "consistent"],
|
|
||||||
"arrow-body-style": "error",
|
|
||||||
"arrow-parens": [
|
|
||||||
"error",
|
|
||||||
"always"
|
|
||||||
],
|
|
||||||
"arrow-spacing": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"after": true,
|
|
||||||
"before": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"block-scoped-var": "error",
|
|
||||||
"block-spacing": [
|
|
||||||
"error",
|
|
||||||
"always"
|
|
||||||
],
|
|
||||||
"brace-style": "off",
|
|
||||||
"callback-return": "off",
|
|
||||||
"camelcase": "error",
|
|
||||||
"capitalized-comments": [
|
|
||||||
"error",
|
|
||||||
"never"
|
|
||||||
],
|
|
||||||
"class-methods-use-this": "off",
|
|
||||||
"comma-dangle": "error",
|
|
||||||
"comma-spacing": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"after": true,
|
|
||||||
"before": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"comma-style": [
|
|
||||||
"error",
|
|
||||||
"last"
|
|
||||||
],
|
|
||||||
"complexity": "error",
|
|
||||||
"computed-property-spacing": [
|
|
||||||
"error",
|
|
||||||
"never"
|
|
||||||
],
|
|
||||||
"consistent-return": "error",
|
|
||||||
"consistent-this": "error",
|
|
||||||
"curly": "error",
|
|
||||||
"default-case": "error",
|
|
||||||
"dot-location": "error",
|
|
||||||
"dot-notation": "error",
|
|
||||||
"eol-last": "off",
|
|
||||||
"eqeqeq": "off",
|
|
||||||
"func-call-spacing": "error",
|
|
||||||
"func-name-matching": "error",
|
|
||||||
"func-names": "error",
|
|
||||||
"func-style": [
|
|
||||||
"error",
|
|
||||||
"declaration"
|
|
||||||
],
|
|
||||||
"function-paren-newline": "error",
|
|
||||||
"generator-star-spacing": "error",
|
|
||||||
"global-require": "error",
|
|
||||||
"guard-for-in": "error",
|
|
||||||
"handle-callback-err": "error",
|
|
||||||
"id-blacklist": "error",
|
|
||||||
"id-length": "error",
|
|
||||||
"id-match": "error",
|
|
||||||
"implicit-arrow-linebreak": [
|
|
||||||
"error",
|
|
||||||
"beside"
|
|
||||||
],
|
|
||||||
"indent": "off",
|
|
||||||
"indent-legacy": "off",
|
|
||||||
"init-declarations": "off",
|
|
||||||
"jsx-quotes": "error",
|
|
||||||
"key-spacing": "off",
|
|
||||||
"keyword-spacing": "off",
|
|
||||||
"line-comment-position": "error",
|
|
||||||
"linebreak-style": [
|
|
||||||
"error",
|
|
||||||
"unix"
|
|
||||||
],
|
|
||||||
"lines-around-comment": "error",
|
|
||||||
"lines-around-directive": "error",
|
|
||||||
"lines-between-class-members": "off",
|
|
||||||
"max-classes-per-file": "error",
|
|
||||||
"max-depth": "error",
|
|
||||||
"max-len": "off",
|
|
||||||
"max-lines": "error",
|
|
||||||
"max-lines-per-function": "off",
|
|
||||||
"max-nested-callbacks": "error",
|
|
||||||
"max-params": "off",
|
|
||||||
"max-statements": "off",
|
|
||||||
"max-statements-per-line": "off",
|
|
||||||
"multiline-comment-style": "error",
|
|
||||||
"new-cap": "error",
|
|
||||||
"new-parens": "error",
|
|
||||||
"newline-after-var": "off",
|
|
||||||
"newline-before-return": "error",
|
|
||||||
"newline-per-chained-call": "error",
|
|
||||||
"no-alert": "error",
|
|
||||||
"no-array-constructor": "error",
|
|
||||||
"no-async-promise-executor": "error",
|
|
||||||
"no-await-in-loop": "error",
|
|
||||||
"no-bitwise": "off",
|
|
||||||
"no-buffer-constructor": "error",
|
|
||||||
"no-caller": "error",
|
|
||||||
"no-catch-shadow": "error",
|
|
||||||
"no-confusing-arrow": "error",
|
|
||||||
"no-continue": "error",
|
|
||||||
"no-div-regex": "error",
|
|
||||||
"no-duplicate-imports": "error",
|
|
||||||
"no-else-return": "off",
|
|
||||||
"no-empty-function": "off",
|
|
||||||
"no-eq-null": "error",
|
|
||||||
"no-eval": "error",
|
|
||||||
"no-extend-native": "error",
|
|
||||||
"no-extra-bind": "error",
|
|
||||||
"no-extra-label": "error",
|
|
||||||
"no-extra-parens": "off",
|
|
||||||
"no-floating-decimal": "error",
|
|
||||||
"no-implicit-coercion": "error",
|
|
||||||
"no-implicit-globals": "off",
|
|
||||||
"no-implied-eval": "error",
|
|
||||||
"no-inline-comments": "error",
|
|
||||||
"no-invalid-this": "error",
|
|
||||||
"no-iterator": "error",
|
|
||||||
"no-label-var": "error",
|
|
||||||
"no-labels": "error",
|
|
||||||
"no-lone-blocks": "error",
|
|
||||||
"no-lonely-if": "error",
|
|
||||||
"no-loop-func": "error",
|
|
||||||
"no-magic-numbers": "off",
|
|
||||||
"no-misleading-character-class": "error",
|
|
||||||
"no-mixed-operators": "error",
|
|
||||||
"no-mixed-requires": "error",
|
|
||||||
"no-multi-assign": "error",
|
|
||||||
"no-multi-spaces": "off",
|
|
||||||
"no-multi-str": "error",
|
|
||||||
"no-multiple-empty-lines": "error",
|
|
||||||
"no-native-reassign": "error",
|
|
||||||
"no-negated-condition": "off",
|
|
||||||
"no-negated-in-lhs": "error",
|
|
||||||
"no-nested-ternary": "error",
|
|
||||||
"no-new": "error",
|
|
||||||
"no-new-func": "error",
|
|
||||||
"no-new-object": "error",
|
|
||||||
"no-new-require": "error",
|
|
||||||
"no-new-wrappers": "error",
|
|
||||||
"no-octal-escape": "error",
|
|
||||||
"no-param-reassign": "error",
|
|
||||||
"no-path-concat": "error",
|
|
||||||
"no-plusplus": "error",
|
|
||||||
"no-process-env": "error",
|
|
||||||
"no-process-exit": "error",
|
|
||||||
"no-proto": "error",
|
|
||||||
"no-prototype-builtins": "error",
|
|
||||||
"no-restricted-globals": "error",
|
|
||||||
"no-restricted-imports": "error",
|
|
||||||
"no-restricted-modules": "error",
|
|
||||||
"no-restricted-properties": "error",
|
|
||||||
"no-restricted-syntax": "error",
|
|
||||||
"no-return-assign": "error",
|
|
||||||
"no-return-await": "error",
|
|
||||||
"no-script-url": "error",
|
|
||||||
"no-self-compare": "error",
|
|
||||||
"no-sequences": "error",
|
|
||||||
"no-shadow": "off",
|
|
||||||
"no-shadow-restricted-names": "error",
|
|
||||||
"no-spaced-func": "error",
|
|
||||||
"no-sync": "error",
|
|
||||||
"no-tabs": "off",
|
|
||||||
"no-template-curly-in-string": "error",
|
|
||||||
"no-ternary": "off",
|
|
||||||
"no-throw-literal": "error",
|
|
||||||
"no-trailing-spaces": "off",
|
|
||||||
"no-undef-init": "error",
|
|
||||||
"no-undefined": "error",
|
|
||||||
"no-unmodified-loop-condition": "error",
|
|
||||||
"no-unneeded-ternary": "error",
|
|
||||||
"no-unused-expressions": "error",
|
|
||||||
"no-use-before-define": "error",
|
|
||||||
"no-useless-call": "error",
|
|
||||||
// "no-useless-catch": "error",
|
|
||||||
"no-useless-computed-key": "error",
|
|
||||||
"no-useless-concat": "error",
|
|
||||||
"no-useless-constructor": "error",
|
|
||||||
"no-useless-rename": "error",
|
|
||||||
"no-useless-return": "error",
|
|
||||||
"no-var": "error",
|
|
||||||
"no-void": "error",
|
|
||||||
"no-warning-comments": "error",
|
|
||||||
"no-whitespace-before-property": "error",
|
|
||||||
"no-with": "error",
|
|
||||||
"nonblock-statement-body-position": "error",
|
|
||||||
"object-curly-newline": "error",
|
|
||||||
"object-curly-spacing": "off",
|
|
||||||
"object-shorthand": "off",
|
|
||||||
"one-var": "off",
|
|
||||||
"one-var-declaration-per-line": "error",
|
|
||||||
"operator-assignment": "error",
|
|
||||||
"operator-linebreak": "error",
|
|
||||||
"padded-blocks": "off",
|
|
||||||
"padding-line-between-statements": "error",
|
|
||||||
"prefer-arrow-callback": "error",
|
|
||||||
"prefer-const": "off",
|
|
||||||
"prefer-destructuring": "off",
|
|
||||||
"prefer-numeric-literals": "off",
|
|
||||||
"prefer-object-spread": "error",
|
|
||||||
"prefer-promise-reject-errors": "error",
|
|
||||||
"prefer-reflect": "error",
|
|
||||||
"prefer-rest-params": "error",
|
|
||||||
"prefer-spread": "error",
|
|
||||||
"prefer-template": "error",
|
|
||||||
"quote-props": "off",
|
|
||||||
"quotes": "off",
|
|
||||||
"radix": "error",
|
|
||||||
"require-atomic-updates": "error",
|
|
||||||
"require-await": "off",
|
|
||||||
"require-jsdoc": "off",
|
|
||||||
"require-unicode-regexp": "off",
|
|
||||||
"rest-spread-spacing": "error",
|
|
||||||
"semi": "error",
|
|
||||||
"semi-spacing": "error",
|
|
||||||
"semi-style": [
|
|
||||||
"error",
|
|
||||||
"last"
|
|
||||||
],
|
|
||||||
"sort-imports": "error",
|
|
||||||
"sort-keys": "off",
|
|
||||||
"sort-vars": "error",
|
|
||||||
"space-before-blocks": "error",
|
|
||||||
"space-before-function-paren": "off",
|
|
||||||
"space-in-parens": [
|
|
||||||
"error",
|
|
||||||
"never"
|
|
||||||
],
|
|
||||||
"space-infix-ops": "error",
|
|
||||||
"space-unary-ops": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"nonwords": false,
|
|
||||||
"words": false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"spaced-comment": [
|
|
||||||
"error",
|
|
||||||
"always"
|
|
||||||
],
|
|
||||||
"strict": [
|
|
||||||
"error",
|
|
||||||
"never"
|
|
||||||
],
|
|
||||||
"switch-colon-spacing": "error",
|
|
||||||
"symbol-description": "error",
|
|
||||||
"template-curly-spacing": "error",
|
|
||||||
"template-tag-spacing": "error",
|
|
||||||
"unicode-bom": [
|
|
||||||
"error",
|
|
||||||
"never"
|
|
||||||
],
|
|
||||||
"valid-jsdoc": "error",
|
|
||||||
"vars-on-top": "error",
|
|
||||||
"wrap-iife": "error",
|
|
||||||
"wrap-regex": "error",
|
|
||||||
"yield-star-spacing": "error",
|
|
||||||
"yoda": "off"
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,93 +0,0 @@
|
|||||||
export class ConnectionView {
|
|
||||||
constructor(client, provider) {
|
|
||||||
this._provider = provider;
|
|
||||||
this._client = client;
|
|
||||||
this._client.onopen = () => { this._onConnectionOpened(); };
|
|
||||||
this._client.onclose = () => { this._onConnectionClosed(); };
|
|
||||||
|
|
||||||
this.element = document.createElement("div");
|
|
||||||
|
|
||||||
const connectBox = document.createElement("div");
|
|
||||||
this.element.appendChild(connectBox);
|
|
||||||
|
|
||||||
const urlLabel = document.createElement("span");
|
|
||||||
urlLabel.textContent = "URL:";
|
|
||||||
connectBox.appendChild(urlLabel);
|
|
||||||
|
|
||||||
this.urlTextbox = document.createElement("input");
|
|
||||||
this.urlTextbox.type = "text";
|
|
||||||
this.urlTextbox.value = window.location.href.replace(/^http/, "ws");
|
|
||||||
connectBox.appendChild(this.urlTextbox);
|
|
||||||
|
|
||||||
this.connectButton = document.createElement("input");
|
|
||||||
this.connectButton.type = "button";
|
|
||||||
this.connectButton.value = "connect";
|
|
||||||
this.connectButton.addEventListener("click", () => { this._onConnectButtonClicked(); });
|
|
||||||
connectBox.appendChild(this.connectButton);
|
|
||||||
|
|
||||||
|
|
||||||
const authenticateBox = document.createElement("div");
|
|
||||||
this.element.appendChild(authenticateBox);
|
|
||||||
|
|
||||||
const authLabel = document.createElement("span");
|
|
||||||
authLabel.textContent = "use authentication:";
|
|
||||||
authenticateBox.appendChild(authLabel);
|
|
||||||
|
|
||||||
this.authenticateCheckbox = document.createElement("input");
|
|
||||||
this.authenticateCheckbox.type = "checkbox";
|
|
||||||
authenticateBox.appendChild(this.authenticateCheckbox);
|
|
||||||
|
|
||||||
const usernameLabel = document.createElement("span");
|
|
||||||
usernameLabel.textContent = "user:";
|
|
||||||
authenticateBox.appendChild(usernameLabel);
|
|
||||||
|
|
||||||
this.usernameTextbox = document.createElement("input");
|
|
||||||
this.usernameTextbox.type = "text";
|
|
||||||
this.usernameTextbox.value = "bob";
|
|
||||||
authenticateBox.appendChild(this.usernameTextbox);
|
|
||||||
|
|
||||||
const passwordLabel = document.createElement("span");
|
|
||||||
passwordLabel.textContent = "user:";
|
|
||||||
authenticateBox.appendChild(passwordLabel);
|
|
||||||
|
|
||||||
this.passwordTextbox = document.createElement("input");
|
|
||||||
this.passwordTextbox.type = "password";
|
|
||||||
this.passwordTextbox.value = "secret";
|
|
||||||
authenticateBox.appendChild(this.passwordTextbox);
|
|
||||||
}
|
|
||||||
|
|
||||||
_onConnectButtonClicked() {
|
|
||||||
if (!this._client.isConnected()) {
|
|
||||||
let url = this.urlTextbox.value;
|
|
||||||
this._client.connectTo(url);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this._client.disconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_onAuthenticateButtonClicked() {
|
|
||||||
if (this._client.isConnected()) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_onConnectionOpened() {
|
|
||||||
if (this.authenticateCheckbox.checked) {
|
|
||||||
const username = this.usernameTextbox.value;
|
|
||||||
const password = this.passwordTextbox.value;
|
|
||||||
|
|
||||||
const promise = this._client.authenticate("username", { username, password });
|
|
||||||
promise.then(() => { this._client.addProvider("test", this._provider); });
|
|
||||||
} else {
|
|
||||||
this._client.addProvider("test", this._provider);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.connectButton.value = "disconnect";
|
|
||||||
}
|
|
||||||
|
|
||||||
_onConnectionClosed() {
|
|
||||||
this.connectButton.value = "connect";
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,122 +0,0 @@
|
|||||||
/* eslint no-unused-vars: ["error", { "argsIgnorePattern": "^_" }] */
|
|
||||||
|
|
||||||
import { BadState } from "./webfuse/bad_state.js";
|
|
||||||
import { FileMode } from "./webfuse/file_mode.js";
|
|
||||||
import { Provider } from "./webfuse/provider.js";
|
|
||||||
|
|
||||||
export class FileSystemProvider extends Provider {
|
|
||||||
constructor(root) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.root = root;
|
|
||||||
this._inodes = { };
|
|
||||||
|
|
||||||
this._walk(this.root, (entry) => { this._inodes[entry.inode] = entry; });
|
|
||||||
}
|
|
||||||
|
|
||||||
_walk(node, callback) {
|
|
||||||
callback(node);
|
|
||||||
|
|
||||||
const entries = node.entries;
|
|
||||||
if (entries) {
|
|
||||||
for(let entry of Object.entries(entries)) {
|
|
||||||
this._walk(entry[1], callback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async lookup(parent, name) {
|
|
||||||
const parentEntry = this._inodes[parent];
|
|
||||||
const entry = (parentEntry && parentEntry.entries && parentEntry.entries[name]) || null;
|
|
||||||
if (entry) {
|
|
||||||
return {
|
|
||||||
inode: entry.inode,
|
|
||||||
mode: entry.mode || parseInt("755", 8),
|
|
||||||
type: entry.type || "file",
|
|
||||||
size: entry.size || (entry.contents && entry.contents.length) || 0,
|
|
||||||
atime: entry.atime || 0,
|
|
||||||
mtime: entry.mtime || 0,
|
|
||||||
ctime: entry.ctime || 0
|
|
||||||
};
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw new BadState(BadState.NO_ENTRY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async getattr(inode) {
|
|
||||||
let entry = this._inodes[inode];
|
|
||||||
if (entry) {
|
|
||||||
return {
|
|
||||||
mode: entry.mode || parseInt("755", 8),
|
|
||||||
type: entry.type || "file",
|
|
||||||
size: entry.size || (entry.contents && entry.contents.length) || 0,
|
|
||||||
atime: entry.atime || 0,
|
|
||||||
mtime: entry.mtime || 0,
|
|
||||||
ctime: entry.ctime || 0
|
|
||||||
};
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw new BadState(BadState.NO_ENTRY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async readdir(inode) {
|
|
||||||
let entry = this._inodes[inode];
|
|
||||||
|
|
||||||
if ((entry) && ("dir" === entry.type)) {
|
|
||||||
let result = [
|
|
||||||
{name: ".", inode: entry.inode},
|
|
||||||
{name: "..", inode: entry.inode}
|
|
||||||
];
|
|
||||||
for(let subdir of Object.entries(entry.entries)) {
|
|
||||||
const name = subdir[0];
|
|
||||||
const inode = subdir[1].inode;
|
|
||||||
result.push({name, inode});
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw new BadState(BadState.NO_ENTRY);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
async open(inode, mode) {
|
|
||||||
let entry = this._inodes[inode];
|
|
||||||
|
|
||||||
if (entry.type === "file") {
|
|
||||||
if ((mode & FileMode.ACCESS_MODE) === FileMode.READONLY) {
|
|
||||||
return {handle: 1337};
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw new BadState(BadState.NO_ACCESS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw new BadState(BadState.NO_ENTRY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
close(_inode, _handle, _mode) {
|
|
||||||
// do nothing
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async read(inode, handle, offset, length) {
|
|
||||||
let entry = this._inodes[inode];
|
|
||||||
|
|
||||||
if (entry.type === "file") {
|
|
||||||
const end = Math.min(offset + length, entry.contents.length);
|
|
||||||
const data = (offset < entry.contents.length) ? entry.contents.substring(offset, end) : "";
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw new BadState(BadState.NO_ENTRY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "webfuse-provider",
|
|
||||||
"version": "0.2.0",
|
|
||||||
"description": "Provider for websocket filesystem (webfuse)",
|
|
||||||
"main": "startup.js",
|
|
||||||
"scripts": {
|
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
|
||||||
},
|
|
||||||
"author": "Falk Werner",
|
|
||||||
"license": "LGPL-3.0"
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
import { Client } from "./webfuse/client.js";
|
|
||||||
import { ConnectionView } from "./connection_view.js";
|
|
||||||
import { FileSystemProvider } from "./filesystem_provider.js";
|
|
||||||
|
|
||||||
|
|
||||||
function mode(value) {
|
|
||||||
return parseInt(value, 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
function startup() {
|
|
||||||
const provider = new FileSystemProvider({
|
|
||||||
inode: 1,
|
|
||||||
mode: mode("0755"),
|
|
||||||
type: "dir",
|
|
||||||
entries: {
|
|
||||||
"hello.txt" : { inode: 2, mode: mode("0444"), type: "file", contents: "Hello, World!"},
|
|
||||||
"say_hello.sh": { inode: 3, mode: mode("0555"), type: "file", contents: "#!/bin/sh\necho hello\n"}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const client = new Client();
|
|
||||||
const connectionView = new ConnectionView(client, provider);
|
|
||||||
document.getElementById('connection').appendChild(connectionView.element);
|
|
||||||
}
|
|
||||||
|
|
||||||
window.onload = startup;
|
|
@ -1,15 +0,0 @@
|
|||||||
export class BadState extends Error {
|
|
||||||
static get BAD() { return 1; }
|
|
||||||
|
|
||||||
static get NOT_IMPLEMENTED() { return 2; }
|
|
||||||
static get TIMEOUT() { return 3; }
|
|
||||||
static get FORMAT() { return 4; }
|
|
||||||
|
|
||||||
static get NO_ENTRY() { return 101; }
|
|
||||||
static get NO_ACCESS() { return 102; }
|
|
||||||
|
|
||||||
constructor(code) {
|
|
||||||
super("Bad State");
|
|
||||||
this.code = code;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,223 +0,0 @@
|
|||||||
import { BadState } from "./bad_state.js";
|
|
||||||
|
|
||||||
export class Client {
|
|
||||||
static get _PROTOCOL() { return "fs"; }
|
|
||||||
|
|
||||||
constructor(provider) {
|
|
||||||
this._provider = { };
|
|
||||||
this._pendingRequests = {};
|
|
||||||
this._id = 0;
|
|
||||||
this._ws = null;
|
|
||||||
this.onopen = () => { };
|
|
||||||
this.onclose = () => { };
|
|
||||||
this.onerror = () => { };
|
|
||||||
}
|
|
||||||
|
|
||||||
connectTo(url) {
|
|
||||||
this.disconnect();
|
|
||||||
|
|
||||||
this._ws = new WebSocket(url, Client._PROTOCOL);
|
|
||||||
this._ws.onopen = this.onopen;
|
|
||||||
this._ws.onclose = this.onclose;
|
|
||||||
this._ws.onerror = this.onerror;
|
|
||||||
|
|
||||||
this._ws.onmessage = (message) => {
|
|
||||||
this._onmessage(message);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
_invokeRequest(method, params) {
|
|
||||||
this._id += 1;
|
|
||||||
const id = this._id;
|
|
||||||
const request = {method, params, id};
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
this._pendingRequests[id] = {resolve, reject};
|
|
||||||
this._ws.send(JSON.stringify(request));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
authenticate(type, credentials) {
|
|
||||||
return this._invokeRequest("authenticate", [type, credentials]);
|
|
||||||
}
|
|
||||||
|
|
||||||
addProvider(name, provider) {
|
|
||||||
this._provider[name] = provider;
|
|
||||||
const request = {
|
|
||||||
"method": "add_filesystem",
|
|
||||||
"params": [name],
|
|
||||||
"id": 23
|
|
||||||
};
|
|
||||||
|
|
||||||
this._ws.send(JSON.stringify(request));
|
|
||||||
}
|
|
||||||
|
|
||||||
disconnect() {
|
|
||||||
if (this._ws) {
|
|
||||||
this._ws.close();
|
|
||||||
this._ws = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
isConnected() {
|
|
||||||
return ((this._ws) && (this._ws.readyState === WebSocket.OPEN));
|
|
||||||
}
|
|
||||||
|
|
||||||
_isRequest(request) {
|
|
||||||
const method = request.method;
|
|
||||||
|
|
||||||
return (("string" === typeof(method)) && ("params" in request));
|
|
||||||
}
|
|
||||||
|
|
||||||
_isResponse(response) {
|
|
||||||
const id = response.id;
|
|
||||||
|
|
||||||
return (("number" === typeof(id)) && (("result" in response) || ("error" in response)));
|
|
||||||
}
|
|
||||||
|
|
||||||
_removePendingRequest(id) {
|
|
||||||
let result = null;
|
|
||||||
|
|
||||||
if (id in this._pendingRequests) {
|
|
||||||
result = this._pendingRequests[id];
|
|
||||||
Reflect.deleteProperty(this._pendingRequests, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
_onmessage(message) {
|
|
||||||
try {
|
|
||||||
const data = JSON.parse(message.data);
|
|
||||||
|
|
||||||
if (this._isRequest(data)) {
|
|
||||||
const method = data.method;
|
|
||||||
const id = data.id;
|
|
||||||
const params = data.params;
|
|
||||||
|
|
||||||
if ("number" === typeof(id)) {
|
|
||||||
this._invoke(method, params, id);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this._notify(method, params);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (this._isResponse(data)) {
|
|
||||||
const id = data.id;
|
|
||||||
const result = data.result;
|
|
||||||
const error = data.error;
|
|
||||||
|
|
||||||
const request = this._removePendingRequest(id);
|
|
||||||
if (request) {
|
|
||||||
if (result) {
|
|
||||||
request.resolve(result);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
request.reject(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
catch (ex) {
|
|
||||||
// swallow
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_invoke(method, params, id) {
|
|
||||||
this._invokeAsync(method, params).
|
|
||||||
then((result) => {
|
|
||||||
const response = { result, id };
|
|
||||||
this._ws.send(JSON.stringify(response));
|
|
||||||
}).
|
|
||||||
catch((ex) => {
|
|
||||||
const code = ex.code || BadState.BAD;
|
|
||||||
const response = {error: {code}, id};
|
|
||||||
this._ws.send(JSON.stringify(response));
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
async _invokeAsync(method, params) {
|
|
||||||
switch(method)
|
|
||||||
{
|
|
||||||
case "lookup":
|
|
||||||
return this._lookup(params);
|
|
||||||
case "getattr":
|
|
||||||
return this._getattr(params);
|
|
||||||
case "readdir":
|
|
||||||
return this._readdir(params);
|
|
||||||
case "open":
|
|
||||||
return this._open(params);
|
|
||||||
case "read":
|
|
||||||
return this._read(params);
|
|
||||||
default:
|
|
||||||
throw new BadState(BadState.NOT_IMPLEMENTED);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_notify(method, params) {
|
|
||||||
switch(method) {
|
|
||||||
case 'close':
|
|
||||||
this._close(params);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new Error(`Invalid method: "${method}"`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_getProvider(name) {
|
|
||||||
if (name in this._provider) {
|
|
||||||
return this._provider[name];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw new Error('Unknown provider');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async _lookup([providerName, parent, name]) {
|
|
||||||
const provider = this._getProvider(providerName);
|
|
||||||
|
|
||||||
return provider.lookup(parent, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
async _getattr([providerName, inode]) {
|
|
||||||
const provider = this._getProvider(providerName);
|
|
||||||
|
|
||||||
return provider.getattr(inode);
|
|
||||||
}
|
|
||||||
|
|
||||||
async _readdir([providerName, inode]) {
|
|
||||||
const provider = this._getProvider(providerName);
|
|
||||||
|
|
||||||
return provider.readdir(inode);
|
|
||||||
}
|
|
||||||
|
|
||||||
async _open([providerName, inode, mode]) {
|
|
||||||
const provider = this._getProvider(providerName);
|
|
||||||
|
|
||||||
return provider.open(inode, mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
_close([providerName, inode, handle, mode]) {
|
|
||||||
const provider = this._getProvider(providerName);
|
|
||||||
|
|
||||||
provider.close(inode, handle, mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
async _read([providerName, inode, handle, offset, length]) {
|
|
||||||
const provider = this._getProvider(providerName);
|
|
||||||
const data = await provider.read(inode, handle, offset, length);
|
|
||||||
|
|
||||||
if ("string" === typeof(data)) {
|
|
||||||
return {
|
|
||||||
data: btoa(data),
|
|
||||||
format: "base64",
|
|
||||||
count: data.length
|
|
||||||
};
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw new BadState(BadState.BAD);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
export class FileMode {
|
|
||||||
static get ACCESS_MODE() { return 0x003; }
|
|
||||||
static get READONLY() { return 0x000; }
|
|
||||||
static get WRITEONLY() { return 0x001; }
|
|
||||||
static get READWRITE() { return 0x002; }
|
|
||||||
static get CREATE() { return 0x040; }
|
|
||||||
static get EXCLUSIVE() { return 0x080; }
|
|
||||||
static get TRUNKATE() { return 0x200; }
|
|
||||||
static get APPEND() { return 0x400; }
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
/* eslint no-unused-vars: ["error", { "argsIgnorePattern": "^_" }] */
|
|
||||||
|
|
||||||
import { BadState } from "./bad_state.js";
|
|
||||||
|
|
||||||
export class Provider {
|
|
||||||
|
|
||||||
async lookup(_parent, _name) {
|
|
||||||
throw new BadState(BadState.NOT_IMPLEMENTED);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getattr(_inode) {
|
|
||||||
throw new BadState(BadState.NOT_IMPLEMENTED);
|
|
||||||
}
|
|
||||||
|
|
||||||
async readdir(_inode) {
|
|
||||||
throw new BadState(BadState.NOT_IMPLEMENTED);
|
|
||||||
}
|
|
||||||
|
|
||||||
async open(_inode, _mode) {
|
|
||||||
throw new BadState(BadState.NOT_IMPLEMENTED);
|
|
||||||
}
|
|
||||||
|
|
||||||
close(_inode, _handle, _mode) {
|
|
||||||
// empty
|
|
||||||
}
|
|
||||||
|
|
||||||
async read(_inode, _handle, _offset, _length) {
|
|
||||||
throw new BadState(BadState.NOT_IMPLEMENTED);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,47 +0,0 @@
|
|||||||
html, body {
|
|
||||||
font-family: monospace;
|
|
||||||
background-color: #c0c0c0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page {
|
|
||||||
margin-left: 50px;
|
|
||||||
margin-right: 50px;
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.window {
|
|
||||||
border: 1px solid black;
|
|
||||||
background-color: black;
|
|
||||||
border-radius: 5px;
|
|
||||||
padding: 10px;
|
|
||||||
margin-bottom: 25px;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.window .title {
|
|
||||||
text-align: center;
|
|
||||||
color: #dba329;
|
|
||||||
font-weight: bold;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
border-bottom: 1px solid #dba329;
|
|
||||||
}
|
|
||||||
|
|
||||||
.commands {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
column-count: 2;
|
|
||||||
column-width: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content > div {
|
|
||||||
display: inline-block;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#connection {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
|||||||
#ifndef USERDB_H
|
|
||||||
#define USERDB_H
|
|
||||||
|
|
||||||
#ifndef __cplusplus
|
|
||||||
#include <stdbool.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C"
|
|
||||||
{
|
|
||||||
#endif
|
|
||||||
|
|
||||||
struct userdb;
|
|
||||||
|
|
||||||
extern struct userdb * userdb_create(
|
|
||||||
char const * pepper);
|
|
||||||
|
|
||||||
extern void userdb_dispose(struct userdb * db);
|
|
||||||
|
|
||||||
extern bool userdb_save(
|
|
||||||
struct userdb * db,
|
|
||||||
char const * filename);
|
|
||||||
|
|
||||||
extern bool userdb_load(
|
|
||||||
struct userdb * db,
|
|
||||||
char const * filename);
|
|
||||||
|
|
||||||
extern void userdb_add(
|
|
||||||
struct userdb * db,
|
|
||||||
char const * username,
|
|
||||||
char const * password);
|
|
||||||
|
|
||||||
extern void userdb_remove(
|
|
||||||
struct userdb * db,
|
|
||||||
char const * user);
|
|
||||||
|
|
||||||
extern bool userdb_check(
|
|
||||||
struct userdb * db,
|
|
||||||
char const * username,
|
|
||||||
char const * password);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
|
@ -1,277 +0,0 @@
|
|||||||
#include "userdb.h"
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <jansson.h>
|
|
||||||
|
|
||||||
#include <openssl/rand.h>
|
|
||||||
#include <openssl/evp.h>
|
|
||||||
|
|
||||||
#define USERDB_HASH_ALGORITHM "sha512"
|
|
||||||
#define USERDB_MAJOR 1
|
|
||||||
#define USERDB_MINOR 0
|
|
||||||
|
|
||||||
#define USERDB_SALT_SIZE 32
|
|
||||||
|
|
||||||
struct userdb
|
|
||||||
{
|
|
||||||
json_t * users;
|
|
||||||
char * pepper;
|
|
||||||
char * hash_algorithm;
|
|
||||||
};
|
|
||||||
|
|
||||||
static bool is_compatible(json_t * meta)
|
|
||||||
{
|
|
||||||
bool result = false;
|
|
||||||
if (json_is_object(meta))
|
|
||||||
{
|
|
||||||
json_t * type = json_object_get(meta, "type");
|
|
||||||
json_t * major = json_object_get(meta, "major");
|
|
||||||
json_t * minor = json_object_get(meta, "minor");
|
|
||||||
json_t * hash_algorithm = json_object_get(meta, "hash_algorithm");
|
|
||||||
|
|
||||||
result = (
|
|
||||||
json_is_string(type) &&
|
|
||||||
(0 == strcmp(json_string_value(type), "wf-userdb")) &&
|
|
||||||
json_is_integer(major) &&
|
|
||||||
(USERDB_MAJOR == json_integer_value(major)) &&
|
|
||||||
json_is_integer(minor) &&
|
|
||||||
json_is_string(hash_algorithm)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result)
|
|
||||||
{
|
|
||||||
char const * algorithm = json_string_value(hash_algorithm);
|
|
||||||
result = (NULL != EVP_get_digestbyname(algorithm));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
static char hex_char(unsigned char value)
|
|
||||||
{
|
|
||||||
switch (value)
|
|
||||||
{
|
|
||||||
case 0x00: return '0';
|
|
||||||
case 0x01: return '1';
|
|
||||||
case 0x02: return '2';
|
|
||||||
case 0x03: return '3';
|
|
||||||
case 0x04: return '4';
|
|
||||||
case 0x05: return '5';
|
|
||||||
case 0x06: return '6';
|
|
||||||
case 0x07: return '7';
|
|
||||||
case 0x08: return '8';
|
|
||||||
case 0x09: return '9';
|
|
||||||
case 0x0a: return 'a';
|
|
||||||
case 0x0b: return 'b';
|
|
||||||
case 0x0c: return 'c';
|
|
||||||
case 0x0d: return 'd';
|
|
||||||
case 0x0e: return 'e';
|
|
||||||
case 0x0f: return 'f';
|
|
||||||
default: return '?';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static char * to_hex(unsigned char const * value, size_t length)
|
|
||||||
{
|
|
||||||
char * result = malloc((2 * length) + 1);
|
|
||||||
if (NULL != result)
|
|
||||||
{
|
|
||||||
for (size_t i = 0, j = 0; i < length; i++, j+=2)
|
|
||||||
{
|
|
||||||
unsigned char high = (value[i] >> 4) & 0x0f;
|
|
||||||
unsigned char low = value[i] & 0x0f;
|
|
||||||
|
|
||||||
result[j ] = hex_char(high);
|
|
||||||
result[j + 1] = hex_char(low);
|
|
||||||
}
|
|
||||||
|
|
||||||
result[2 * length] = '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
static char * generate_salt(void)
|
|
||||||
{
|
|
||||||
unsigned char buffer[USERDB_SALT_SIZE];
|
|
||||||
int rc = RAND_bytes(buffer, USERDB_SALT_SIZE);
|
|
||||||
if (1 != rc)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "fatal: failed to generate salt (OpenSSL RAND_bytes failed)\n");
|
|
||||||
exit(EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
|
|
||||||
return to_hex(buffer, USERDB_SALT_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
static char * compute_hash(
|
|
||||||
struct userdb * db,
|
|
||||||
char const * password,
|
|
||||||
char const * salt)
|
|
||||||
{
|
|
||||||
EVP_MD const * digest = EVP_get_digestbyname(db->hash_algorithm);
|
|
||||||
if (NULL == digest)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "error: hash algorithm %s not supported\n", db->hash_algorithm);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
char * result = NULL;
|
|
||||||
unsigned int hash_size = EVP_MD_size(digest);
|
|
||||||
unsigned char * hash = malloc(hash_size);
|
|
||||||
|
|
||||||
if (NULL != hash)
|
|
||||||
{
|
|
||||||
EVP_MD_CTX * context = EVP_MD_CTX_new();
|
|
||||||
EVP_DigestInit_ex(context, digest, NULL);
|
|
||||||
EVP_DigestUpdate(context, password, strlen(password));
|
|
||||||
EVP_DigestUpdate(context, salt, strlen(salt));
|
|
||||||
EVP_DigestUpdate(context, db->pepper, strlen(db->pepper));
|
|
||||||
EVP_DigestFinal_ex(context, hash, &hash_size);
|
|
||||||
EVP_MD_CTX_free(context);
|
|
||||||
|
|
||||||
result = to_hex(hash, hash_size);
|
|
||||||
free(hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct userdb * userdb_create(
|
|
||||||
char const * pepper)
|
|
||||||
{
|
|
||||||
struct userdb * db = malloc(sizeof(struct userdb));
|
|
||||||
if (NULL != db)
|
|
||||||
{
|
|
||||||
db->users = json_object();
|
|
||||||
db->pepper = strdup(pepper);
|
|
||||||
db->hash_algorithm = strdup(USERDB_HASH_ALGORITHM);
|
|
||||||
}
|
|
||||||
|
|
||||||
return db;
|
|
||||||
}
|
|
||||||
|
|
||||||
void userdb_dispose(
|
|
||||||
struct userdb * db)
|
|
||||||
{
|
|
||||||
json_decref(db->users);
|
|
||||||
free(db->pepper);
|
|
||||||
free(db->hash_algorithm);
|
|
||||||
free(db);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool userdb_save(
|
|
||||||
struct userdb * db,
|
|
||||||
char const * filename)
|
|
||||||
{
|
|
||||||
json_t * container = json_object();
|
|
||||||
|
|
||||||
json_t * meta = json_object();
|
|
||||||
json_object_set_new(meta, "type", json_string("wf-userdb"));
|
|
||||||
json_object_set_new(meta, "major", json_integer(USERDB_MAJOR));
|
|
||||||
json_object_set_new(meta, "minor", json_integer(USERDB_MINOR));
|
|
||||||
json_object_set_new(meta, "hash_algorithm", json_string(db->hash_algorithm));
|
|
||||||
json_object_set_new(container, "meta", meta);
|
|
||||||
|
|
||||||
json_object_set(container, "users", db->users);
|
|
||||||
|
|
||||||
int result = json_dump_file(container, filename, JSON_INDENT(2));
|
|
||||||
json_decref(container);
|
|
||||||
|
|
||||||
return (0 == result);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool userdb_load(
|
|
||||||
struct userdb * db,
|
|
||||||
char const * filename)
|
|
||||||
{
|
|
||||||
bool result = false;
|
|
||||||
json_t * container = json_load_file(filename, 0, NULL);
|
|
||||||
if (NULL != container)
|
|
||||||
{
|
|
||||||
json_t * meta = json_object_get(container, "meta");
|
|
||||||
json_t * users = json_object_get(container, "users");
|
|
||||||
|
|
||||||
if ((is_compatible(meta)) && (json_is_object(users))) {
|
|
||||||
json_t * hash_algorithm = json_object_get(meta, "hash_algorithm");
|
|
||||||
free(db->hash_algorithm);
|
|
||||||
db->hash_algorithm = strdup(json_string_value(hash_algorithm));
|
|
||||||
|
|
||||||
json_decref(db->users);
|
|
||||||
json_incref(users);
|
|
||||||
db->users = users;
|
|
||||||
|
|
||||||
result = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
json_decref(container);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void userdb_add(
|
|
||||||
struct userdb * db,
|
|
||||||
char const * username,
|
|
||||||
char const * password)
|
|
||||||
{
|
|
||||||
char * salt = generate_salt();
|
|
||||||
char * hash = compute_hash(db, password, salt);
|
|
||||||
|
|
||||||
json_t * user = json_object();
|
|
||||||
json_object_set_new(user, "password_hash", json_string(hash));
|
|
||||||
json_object_set_new(user, "salt", json_string(salt));
|
|
||||||
|
|
||||||
json_object_set_new(db->users, username, user);
|
|
||||||
|
|
||||||
free(salt);
|
|
||||||
free(hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
void userdb_remove(
|
|
||||||
struct userdb * db,
|
|
||||||
char const * user)
|
|
||||||
{
|
|
||||||
json_object_del(db->users, user);
|
|
||||||
}
|
|
||||||
|
|
||||||
static char const * json_object_get_string(
|
|
||||||
json_t * object,
|
|
||||||
char const * key)
|
|
||||||
{
|
|
||||||
char const * result = NULL;
|
|
||||||
|
|
||||||
json_t * string_holder = json_object_get(object, key);
|
|
||||||
if (json_is_string(string_holder))
|
|
||||||
{
|
|
||||||
result = json_string_value(string_holder);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool userdb_check(
|
|
||||||
struct userdb * db,
|
|
||||||
char const * username,
|
|
||||||
char const * password)
|
|
||||||
{
|
|
||||||
bool result = false;
|
|
||||||
|
|
||||||
json_t * user = json_object_get(db->users, username);
|
|
||||||
if (json_is_object(user))
|
|
||||||
{
|
|
||||||
char const * salt = json_object_get_string(user, "salt");
|
|
||||||
char const * hash = json_object_get_string(user, "password_hash");
|
|
||||||
|
|
||||||
char * computed_hash = compute_hash(db, password, salt);
|
|
||||||
|
|
||||||
result = (0 == strcmp(computed_hash, hash));
|
|
||||||
free(computed_hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
@ -1,304 +0,0 @@
|
|||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <getopt.h>
|
|
||||||
|
|
||||||
#include <openssl/conf.h>
|
|
||||||
#include <openssl/opensslv.h>
|
|
||||||
#include <openssl/engine.h>
|
|
||||||
#include <openssl/evp.h>
|
|
||||||
#include <jansson.h>
|
|
||||||
#include <userdb.h>
|
|
||||||
|
|
||||||
|
|
||||||
struct args
|
|
||||||
{
|
|
||||||
char * file;
|
|
||||||
char * command;
|
|
||||||
char * username;
|
|
||||||
char * password;
|
|
||||||
char * pepper;
|
|
||||||
bool show_help;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef int command_invoke_fn(
|
|
||||||
struct args * args);
|
|
||||||
|
|
||||||
struct command
|
|
||||||
{
|
|
||||||
char const * name;
|
|
||||||
command_invoke_fn * invoke;
|
|
||||||
};
|
|
||||||
|
|
||||||
static void print_usage(void)
|
|
||||||
{
|
|
||||||
printf(
|
|
||||||
"webfuse-passwd, Copyright (c) 2019, webfuse authors <https://github.com/falk-werner/webfuse>\n"
|
|
||||||
"Manage webfuse passwd file\n"
|
|
||||||
"\n"
|
|
||||||
"Usage: webfuse-passwd -f <file> -c <command> [-u <username>] [-p <password>] [-P <pepper>]\n"
|
|
||||||
"\n"
|
|
||||||
"Options:\n"
|
|
||||||
"\t-f, --file Path of wf passwd file\n"
|
|
||||||
"\t-c, --command Command to execute\n"
|
|
||||||
"\t-u, --username Name of user\n"
|
|
||||||
"\t-p, --password Password of user\n"
|
|
||||||
"\t-P, --pepper pepper\n"
|
|
||||||
"\t-h, --help Shows this message\n"
|
|
||||||
"\n"
|
|
||||||
"Commands:\n"
|
|
||||||
"\tcreate Creates an empty passwd file (or cleans an existing)\n"
|
|
||||||
"\t Example: webfuse-passwd -f passwd.json -c create\n"
|
|
||||||
"\tadd Adds or replaces a user\n"
|
|
||||||
"\t Example: webfuse-passwd -f passwd.json -c add -u bob -p secret\n"
|
|
||||||
"\tremove Removes a user\n"
|
|
||||||
"\t Example: webfuse-passwd -f passwd.json -c remove -u bob\n"
|
|
||||||
"\tcheck Checks password of a user\n"
|
|
||||||
"\t Example: webfuse-passwd -f passwd.json -c check -u bob -p secret\n"
|
|
||||||
"\n"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int parse_args(struct args * args, int argc, char * argv[])
|
|
||||||
{
|
|
||||||
static struct option const options[] =
|
|
||||||
{
|
|
||||||
{"file", required_argument, NULL, 'f'},
|
|
||||||
{"command", required_argument, NULL, 'c'},
|
|
||||||
{"username", required_argument, NULL, 'u'},
|
|
||||||
{"password", required_argument, NULL, 'p'},
|
|
||||||
{"Pepper", required_argument, NULL, 'P'},
|
|
||||||
{"help", required_argument, NULL, 'h'},
|
|
||||||
{NULL, 0, NULL, 0}
|
|
||||||
};
|
|
||||||
|
|
||||||
int result = EXIT_SUCCESS;
|
|
||||||
bool finished = false;
|
|
||||||
while ((!finished) && (EXIT_SUCCESS == result))
|
|
||||||
{
|
|
||||||
int option_index = 0;
|
|
||||||
int const c = getopt_long(argc, argv, "f:c:u:p:P:h", options, &option_index);
|
|
||||||
|
|
||||||
switch (c)
|
|
||||||
{
|
|
||||||
case -1:
|
|
||||||
finished = true;
|
|
||||||
break;
|
|
||||||
case 'h':
|
|
||||||
args->show_help = true;
|
|
||||||
finished = true;
|
|
||||||
break;
|
|
||||||
case 'f':
|
|
||||||
free(args->file);
|
|
||||||
args->file = strdup(optarg);
|
|
||||||
break;
|
|
||||||
case 'c':
|
|
||||||
free(args->command);
|
|
||||||
args->command = strdup(optarg);
|
|
||||||
break;
|
|
||||||
case 'u':
|
|
||||||
free(args->username);
|
|
||||||
args->username = strdup(optarg);
|
|
||||||
break;
|
|
||||||
case 'p':
|
|
||||||
free(args->password);
|
|
||||||
args->password = strdup(optarg);
|
|
||||||
break;
|
|
||||||
case 'P':
|
|
||||||
free(args->pepper);
|
|
||||||
args->pepper = strdup(optarg);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
fprintf(stderr, "error: unknown argument\n");
|
|
||||||
result = EXIT_FAILURE;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((result == EXIT_SUCCESS) && (!args->show_help))
|
|
||||||
{
|
|
||||||
if (NULL == args->file)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "error: missing file\n");
|
|
||||||
args->show_help = true;
|
|
||||||
result = EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
else if (NULL == args->command)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "error: missing command\n");
|
|
||||||
args->show_help = true;
|
|
||||||
result = EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void args_init(struct args * args)
|
|
||||||
{
|
|
||||||
args->file = NULL;
|
|
||||||
args->command = NULL;
|
|
||||||
args->username = NULL;
|
|
||||||
args->password = NULL;
|
|
||||||
args->pepper = strdup("");
|
|
||||||
args->show_help = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int create_passwd(struct args * args)
|
|
||||||
{
|
|
||||||
struct userdb * db = userdb_create(args->pepper);
|
|
||||||
bool result = userdb_save(db, args->file);
|
|
||||||
userdb_dispose(db);
|
|
||||||
|
|
||||||
return (result) ? EXIT_SUCCESS : EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int add_user(struct args * args)
|
|
||||||
{
|
|
||||||
if (NULL == args->username)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "error: missing username");
|
|
||||||
args->show_help = true;
|
|
||||||
return EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (NULL == args->password)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "error: missing password");
|
|
||||||
args->show_help = true;
|
|
||||||
return EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct userdb * db = userdb_create(args->pepper);
|
|
||||||
userdb_load(db, args->file);
|
|
||||||
userdb_add(db, args->username, args->password);
|
|
||||||
bool result = userdb_save(db, args->file);
|
|
||||||
userdb_dispose(db);
|
|
||||||
|
|
||||||
return (result) ? EXIT_SUCCESS : EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int remove_user(struct args * args)
|
|
||||||
{
|
|
||||||
if (NULL == args->username)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "error: missing username");
|
|
||||||
args->show_help = true;
|
|
||||||
return EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct userdb * db = userdb_create(args->pepper);
|
|
||||||
userdb_load(db, args->file);
|
|
||||||
userdb_remove(db, args->username);
|
|
||||||
bool result = userdb_save(db, args->file);
|
|
||||||
userdb_dispose(db);
|
|
||||||
|
|
||||||
return (result) ? EXIT_SUCCESS : EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int check_password(struct args * args)
|
|
||||||
{
|
|
||||||
if (NULL == args->username)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "error: missing username");
|
|
||||||
args->show_help = true;
|
|
||||||
return EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (NULL == args->password)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "error: missing password");
|
|
||||||
args->show_help = true;
|
|
||||||
return EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct userdb * db = userdb_create(args->pepper);
|
|
||||||
userdb_load(db, args->file);
|
|
||||||
bool result = userdb_check(db, args->username, args->password);
|
|
||||||
userdb_dispose(db);
|
|
||||||
|
|
||||||
printf("%s\n", (result) ? "OK" : "FAILURE");
|
|
||||||
return (result) ? EXIT_SUCCESS : EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int invoke_invalid_command(struct args * args)
|
|
||||||
{
|
|
||||||
(void) args;
|
|
||||||
|
|
||||||
fprintf(stderr, "error: unknown command\n");
|
|
||||||
return EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct command const commands[] =
|
|
||||||
{
|
|
||||||
{"create", &create_passwd},
|
|
||||||
{"add", &add_user},
|
|
||||||
{"remove", &remove_user},
|
|
||||||
{"check", &check_password},
|
|
||||||
{NULL, NULL}
|
|
||||||
};
|
|
||||||
|
|
||||||
static struct command const invalid_command =
|
|
||||||
{
|
|
||||||
"<invalid>",
|
|
||||||
&invoke_invalid_command
|
|
||||||
};
|
|
||||||
|
|
||||||
static struct command const * get_command(char const * name)
|
|
||||||
{
|
|
||||||
for(size_t i = 0; NULL != commands[i].name; i++)
|
|
||||||
{
|
|
||||||
if (0 == strcmp(name, commands[i].name))
|
|
||||||
{
|
|
||||||
return &commands[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &invalid_command;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void args_cleanup(struct args * args)
|
|
||||||
{
|
|
||||||
free(args->file);
|
|
||||||
free(args->command);
|
|
||||||
free(args->username);
|
|
||||||
free(args->password);
|
|
||||||
free(args->pepper);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void openssl_cleanup(void)
|
|
||||||
{
|
|
||||||
FIPS_mode_set(0);
|
|
||||||
ENGINE_cleanup();
|
|
||||||
CONF_modules_unload(1);
|
|
||||||
EVP_cleanup();
|
|
||||||
CRYPTO_cleanup_all_ex_data();
|
|
||||||
ERR_free_strings();
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char * argv[])
|
|
||||||
{
|
|
||||||
OPENSSL_init();
|
|
||||||
OPENSSL_add_all_algorithms_conf();
|
|
||||||
|
|
||||||
struct args args;
|
|
||||||
args_init(&args);
|
|
||||||
int result = parse_args(&args, argc, argv);
|
|
||||||
if ((EXIT_SUCCESS == result) && (!args.show_help))
|
|
||||||
{
|
|
||||||
struct command const * command = get_command(args.command);
|
|
||||||
result = command->invoke(&args);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args.show_help)
|
|
||||||
{
|
|
||||||
print_usage();
|
|
||||||
}
|
|
||||||
|
|
||||||
args_cleanup(&args);
|
|
||||||
openssl_cleanup();
|
|
||||||
return result;
|
|
||||||
}
|
|
@ -1,385 +0,0 @@
|
|||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <signal.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <getopt.h>
|
|
||||||
|
|
||||||
#include "webfuse_provider.h"
|
|
||||||
|
|
||||||
#define SERVICE_TIMEOUT (1 * 1000)
|
|
||||||
|
|
||||||
struct config
|
|
||||||
{
|
|
||||||
char * url;
|
|
||||||
struct wfp_client_config * client_config;
|
|
||||||
bool show_help;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum fs_entry_type
|
|
||||||
{
|
|
||||||
FS_FILE,
|
|
||||||
FS_DIR
|
|
||||||
};
|
|
||||||
|
|
||||||
struct fs_entry
|
|
||||||
{
|
|
||||||
ino_t parent;
|
|
||||||
ino_t inode;
|
|
||||||
char const * name;
|
|
||||||
int mode;
|
|
||||||
enum fs_entry_type type;
|
|
||||||
size_t content_length;
|
|
||||||
char const * content;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct fs
|
|
||||||
{
|
|
||||||
struct fs_entry const * entries;
|
|
||||||
};
|
|
||||||
|
|
||||||
static void show_help()
|
|
||||||
{
|
|
||||||
printf(
|
|
||||||
"webfuse-provider, Copyright (c) 2019, webfuse authors <https://github.com/falk-werner/webfuse>\n"
|
|
||||||
"Example for websocket file system provider\n"
|
|
||||||
"\n"
|
|
||||||
"Usage: webfuse-provider -u <url> [-k <key_path>] [-c <cert_path>]\n"
|
|
||||||
"\n"
|
|
||||||
"Options:\n"
|
|
||||||
"\t-u, --url URL of webfuse server (required)\n"
|
|
||||||
"\t-k, --key_path Path to private key of provider (default: not set, TLS disabled)\n"
|
|
||||||
"\t-c, --cert_path Path to certificate of provider (defautl: not set, TLS disabled)\n"
|
|
||||||
"\t-h, --help prints this message\n"
|
|
||||||
"\n"
|
|
||||||
"Example:\n"
|
|
||||||
"\twebfuse-provider -u ws://localhost:8080/\n"
|
|
||||||
"\n"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int parse_arguments(
|
|
||||||
int argc,
|
|
||||||
char* argv[],
|
|
||||||
struct config * config)
|
|
||||||
{
|
|
||||||
static struct option const options[] =
|
|
||||||
{
|
|
||||||
{"url", required_argument, NULL, 'u'},
|
|
||||||
{"key_path", required_argument, NULL, 'k'},
|
|
||||||
{"cert_path", required_argument, NULL, 'c'},
|
|
||||||
{"help", no_argument, NULL, 'h'},
|
|
||||||
{NULL, 0, NULL, 0}
|
|
||||||
};
|
|
||||||
|
|
||||||
int result = EXIT_SUCCESS;
|
|
||||||
bool finished = false;
|
|
||||||
while (!finished)
|
|
||||||
{
|
|
||||||
int option_index = 0;
|
|
||||||
int const c = getopt_long(argc, argv, "u:k:c:h", options, &option_index);
|
|
||||||
|
|
||||||
switch (c)
|
|
||||||
{
|
|
||||||
case -1:
|
|
||||||
finished = true;
|
|
||||||
break;
|
|
||||||
case 'h':
|
|
||||||
config->show_help = true;
|
|
||||||
finished = true;
|
|
||||||
break;
|
|
||||||
case 'u':
|
|
||||||
free(config->url);
|
|
||||||
config->url = strdup(optarg);
|
|
||||||
break;
|
|
||||||
case 'k':
|
|
||||||
wfp_client_config_set_keypath(config->client_config, optarg);
|
|
||||||
break;
|
|
||||||
case 'c':
|
|
||||||
wfp_client_config_set_certpath(config->client_config, optarg);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
fprintf(stderr, "error: unknown argument\n");
|
|
||||||
finished = true;
|
|
||||||
result = EXIT_FAILURE;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (NULL == config->url)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "error: missing required argument \"-u\"\n");
|
|
||||||
result = EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result != EXIT_SUCCESS)
|
|
||||||
{
|
|
||||||
config->show_help = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct fs_entry const * fs_getentry(
|
|
||||||
struct fs * fs,
|
|
||||||
ino_t inode)
|
|
||||||
{
|
|
||||||
for (size_t i = 0; 0 != fs->entries[i].inode; i++)
|
|
||||||
{
|
|
||||||
struct fs_entry const * entry = &fs->entries[i];
|
|
||||||
if (inode == entry->inode)
|
|
||||||
{
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct fs_entry const * fs_getentry_byname(
|
|
||||||
struct fs * fs,
|
|
||||||
ino_t parent,
|
|
||||||
char const * name)
|
|
||||||
{
|
|
||||||
for( size_t i = 0; 0 != fs->entries[i].inode; i++)
|
|
||||||
{
|
|
||||||
struct fs_entry const * entry = &fs->entries[i];
|
|
||||||
if ((parent == entry->parent) && (0 == strcmp(name, entry->name)))
|
|
||||||
{
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void fs_stat(
|
|
||||||
struct fs_entry const * entry,
|
|
||||||
struct stat * stat)
|
|
||||||
{
|
|
||||||
memset(stat, 0, sizeof(struct stat));
|
|
||||||
|
|
||||||
stat->st_ino = entry->inode;
|
|
||||||
stat->st_mode = entry->mode;
|
|
||||||
|
|
||||||
if (FS_DIR == entry->type)
|
|
||||||
{
|
|
||||||
stat->st_mode |= S_IFDIR;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (FS_FILE == entry->type)
|
|
||||||
{
|
|
||||||
stat->st_mode |= S_IFREG;
|
|
||||||
stat->st_size = entry->content_length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void fs_lookup(
|
|
||||||
struct wfp_request * request,
|
|
||||||
ino_t parent,
|
|
||||||
char const * name,
|
|
||||||
void * user_data)
|
|
||||||
{
|
|
||||||
struct fs * fs = (struct fs*) user_data;
|
|
||||||
struct fs_entry const * entry = fs_getentry_byname(fs, parent, name);
|
|
||||||
if (NULL != entry)
|
|
||||||
{
|
|
||||||
struct stat stat;
|
|
||||||
fs_stat(entry, &stat);
|
|
||||||
|
|
||||||
wfp_respond_lookup(request, &stat);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
wfp_respond_error(request, WF_BAD_NOENTRY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static void fs_getattr(
|
|
||||||
struct wfp_request * request,
|
|
||||||
ino_t inode,
|
|
||||||
void * user_data)
|
|
||||||
{
|
|
||||||
struct fs * fs = (struct fs*) user_data;
|
|
||||||
struct fs_entry const * entry = fs_getentry(fs, inode);
|
|
||||||
|
|
||||||
if (NULL != entry)
|
|
||||||
{
|
|
||||||
struct stat stat;
|
|
||||||
fs_stat(entry, &stat);
|
|
||||||
|
|
||||||
wfp_respond_getattr(request, &stat);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
wfp_respond_error(request, WF_BAD_NOENTRY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void fs_readdir(
|
|
||||||
struct wfp_request * request,
|
|
||||||
ino_t directory,
|
|
||||||
void * user_data)
|
|
||||||
{
|
|
||||||
struct fs * fs = (struct fs*) user_data;
|
|
||||||
|
|
||||||
struct fs_entry const * dir = fs_getentry(fs, directory);
|
|
||||||
if ((NULL != dir) && (FS_DIR == dir->type))
|
|
||||||
{
|
|
||||||
struct wfp_dirbuffer * buffer = wfp_dirbuffer_create();
|
|
||||||
wfp_dirbuffer_add(buffer, ".", dir->inode);
|
|
||||||
wfp_dirbuffer_add(buffer, "..", dir->inode);
|
|
||||||
|
|
||||||
for(size_t i = 0; 0 != fs->entries[i].inode; i++)
|
|
||||||
{
|
|
||||||
struct fs_entry const * entry = &fs->entries[i];
|
|
||||||
if (directory == entry->parent)
|
|
||||||
{
|
|
||||||
wfp_dirbuffer_add(buffer, entry->name, entry->inode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
wfp_respond_readdir(request, buffer);
|
|
||||||
wfp_dirbuffer_dispose(buffer);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
wfp_respond_error(request, WF_BAD_NOENTRY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void fs_open(
|
|
||||||
struct wfp_request * request,
|
|
||||||
ino_t inode,
|
|
||||||
int flags,
|
|
||||||
void * user_data)
|
|
||||||
{
|
|
||||||
struct fs * fs = (struct fs*) user_data;
|
|
||||||
|
|
||||||
struct fs_entry const * entry = fs_getentry(fs, inode);
|
|
||||||
if ((NULL != entry) && (FS_FILE == entry->type))
|
|
||||||
{
|
|
||||||
if (O_RDONLY == (flags & O_ACCMODE))
|
|
||||||
{
|
|
||||||
wfp_respond_open(request, 0U);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
wfp_respond_error(request, WF_BAD_ACCESS_DENIED);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
wfp_respond_error(request, WF_BAD_NOENTRY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static size_t min(size_t const a, size_t const b)
|
|
||||||
{
|
|
||||||
return (a < b) ? a : b;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void fs_read(
|
|
||||||
struct wfp_request * request,
|
|
||||||
ino_t inode,
|
|
||||||
uint32_t handle,
|
|
||||||
size_t offset,
|
|
||||||
size_t length,
|
|
||||||
void * user_data)
|
|
||||||
{
|
|
||||||
(void) handle;
|
|
||||||
|
|
||||||
struct fs * fs = (struct fs*) user_data;
|
|
||||||
struct fs_entry const * entry = fs_getentry(fs, inode);
|
|
||||||
if ((NULL != entry) && (FS_FILE == entry->type))
|
|
||||||
{
|
|
||||||
if (entry->content_length > offset)
|
|
||||||
{
|
|
||||||
size_t const remaining = entry->content_length - offset;
|
|
||||||
size_t const count = min(remaining, length);
|
|
||||||
|
|
||||||
wfp_respond_read(request, &entry->content[offset], count);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
wfp_respond_error(request, WF_BAD);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
wfp_respond_error(request, WF_BAD_NOENTRY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static volatile bool shutdown_requested = false;
|
|
||||||
|
|
||||||
static void on_interrupt(int signal_id)
|
|
||||||
{
|
|
||||||
(void) signal_id;
|
|
||||||
shutdown_requested = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char* argv[])
|
|
||||||
{
|
|
||||||
struct config config;
|
|
||||||
config.url = NULL;
|
|
||||||
config.show_help = false;
|
|
||||||
config.client_config = wfp_client_config_create();
|
|
||||||
int result = parse_arguments(argc, argv, &config);
|
|
||||||
|
|
||||||
if (EXIT_SUCCESS == result)
|
|
||||||
{
|
|
||||||
static struct fs_entry const entries[]=
|
|
||||||
{
|
|
||||||
{.parent = 0, .inode = 1, .name = "<root>", .mode = 0555, .type = FS_DIR},
|
|
||||||
{
|
|
||||||
.parent = 1,
|
|
||||||
.inode = 2,
|
|
||||||
.name = "hello.txt",
|
|
||||||
.mode = 0444,
|
|
||||||
.type = FS_FILE,
|
|
||||||
.content="hello, world!",
|
|
||||||
.content_length = 13,
|
|
||||||
},
|
|
||||||
{.parent = 0, .inode = 0, .name = NULL}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct fs fs =
|
|
||||||
{
|
|
||||||
.entries = entries
|
|
||||||
};
|
|
||||||
|
|
||||||
signal(SIGINT, &on_interrupt);
|
|
||||||
|
|
||||||
wfp_client_config_set_userdata(config.client_config, &fs);
|
|
||||||
wfp_client_config_set_onlookup(config.client_config, &fs_lookup);
|
|
||||||
wfp_client_config_set_ongetattr(config.client_config, &fs_getattr);
|
|
||||||
wfp_client_config_set_onreaddir(config.client_config, &fs_readdir);
|
|
||||||
wfp_client_config_set_onopen(config.client_config, &fs_open);
|
|
||||||
wfp_client_config_set_onread(config.client_config, &fs_read);
|
|
||||||
|
|
||||||
struct wfp_client * client = wfp_client_create(config.client_config);
|
|
||||||
wfp_client_connect(client, config.url);
|
|
||||||
|
|
||||||
while (!shutdown_requested)
|
|
||||||
{
|
|
||||||
wfp_client_service(client, SERVICE_TIMEOUT);
|
|
||||||
}
|
|
||||||
|
|
||||||
wfp_client_dispose(client);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.show_help)
|
|
||||||
{
|
|
||||||
show_help();
|
|
||||||
}
|
|
||||||
|
|
||||||
free(config.url);
|
|
||||||
wfp_client_config_dispose(config.client_config);
|
|
||||||
return result;
|
|
||||||
}
|
|
@ -1,109 +0,0 @@
|
|||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <signal.h>
|
|
||||||
|
|
||||||
#include "webfuse_provider.h"
|
|
||||||
|
|
||||||
#define SERVICE_TIMEOUT (1 * 1000)
|
|
||||||
|
|
||||||
struct args
|
|
||||||
{
|
|
||||||
char const * url;
|
|
||||||
bool show_help;
|
|
||||||
};
|
|
||||||
|
|
||||||
static int
|
|
||||||
parse_args(
|
|
||||||
struct args * args,
|
|
||||||
int argc,
|
|
||||||
char * argv[])
|
|
||||||
{
|
|
||||||
int result = EXIT_FAILURE;
|
|
||||||
args->show_help = true;
|
|
||||||
args->url = NULL;
|
|
||||||
|
|
||||||
if (2 == argc)
|
|
||||||
{
|
|
||||||
result = EXIT_SUCCESS;
|
|
||||||
|
|
||||||
char const * url = argv[1];
|
|
||||||
if ((0 != strcmp(url, "-h")) && (0 != strcmp(url, "--help")))
|
|
||||||
{
|
|
||||||
args->show_help = false;
|
|
||||||
args->url = url;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
fprintf(stderr, "error: missing argument\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
static volatile bool shutdown_requested = false;
|
|
||||||
|
|
||||||
static void on_interrupt(int signal_id)
|
|
||||||
{
|
|
||||||
(void) signal_id;
|
|
||||||
shutdown_requested = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void print_usage()
|
|
||||||
{
|
|
||||||
printf(
|
|
||||||
"static-filesystem-provider Copyright (c) 2019, webfuse authors <https://github.com/falk-werner/webfuse>\n"
|
|
||||||
"Example of webfuse static filesystem provider\n"
|
|
||||||
"\n"
|
|
||||||
"Usage: static-filesystem-provider <url>\n"
|
|
||||||
"\n"
|
|
||||||
"Arguments:\n"
|
|
||||||
"\t<url> URL of webfuse server (required)\n"
|
|
||||||
"\t-h, --help prints this message\n"
|
|
||||||
"\n"
|
|
||||||
"Example:\n"
|
|
||||||
"\tstatic-filesystem-provider ws://localhost:8080/\n"
|
|
||||||
"\n"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char* argv[])
|
|
||||||
{
|
|
||||||
signal(SIGINT, &on_interrupt);
|
|
||||||
|
|
||||||
struct args args;
|
|
||||||
int result = parse_args(&args, argc, argv);
|
|
||||||
if ((EXIT_SUCCESS == result) && (!args.show_help))
|
|
||||||
{
|
|
||||||
struct wfp_client_config * config = wfp_client_config_create();
|
|
||||||
|
|
||||||
struct wfp_static_filesystem * fs = wfp_static_filesystem_create(config);
|
|
||||||
wfp_static_filesystem_add_text(fs, "brummni/hello_world.txt", 0444, "Hello, World!\n");
|
|
||||||
wfp_static_filesystem_add_text(fs, "brummni/hello_bob.txt", 0444, "Hello, Bob!\n");
|
|
||||||
wfp_static_filesystem_add_text(fs, "brummni/hello_bob.txt", 0444, "Hello, Alice!\n");
|
|
||||||
wfp_static_filesystem_add_text(fs, "bla/hello_world.txt", 0444, "Hello, World!\n");
|
|
||||||
wfp_static_filesystem_add_text(fs, "foo.txt", 0444, "foo\n");
|
|
||||||
wfp_static_filesystem_add_text(fs, "bar.txt", 0444, "bar\n");
|
|
||||||
|
|
||||||
struct wfp_client * client = wfp_client_create(config);
|
|
||||||
wfp_client_connect(client, args.url);
|
|
||||||
|
|
||||||
while (!shutdown_requested)
|
|
||||||
{
|
|
||||||
wfp_client_service(client, SERVICE_TIMEOUT);
|
|
||||||
}
|
|
||||||
|
|
||||||
wfp_client_dispose(client);
|
|
||||||
wfp_static_filesystem_dispose(fs);
|
|
||||||
wfp_client_config_dispose(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args.show_help)
|
|
||||||
{
|
|
||||||
print_usage();
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user