webdav-gtk/neon.c

220 lines
6.3 KiB
C

/*
webdav-gtk A WebDAV client written with gtk-3.0 and libneon
Copyright (C) 2022 Xavier Del Campo
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <backend.h>
#include <neon/ne_auth.h>
#include <neon/ne_basic.h>
#include <neon/ne_locks.h>
#include <neon/ne_session.h>
#include <neon/ne_socket.h>
#include <neon/ne_uri.h>
#include <readline/readline.h>
#include <errno.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static const char *nestrerror(const int error)
{
static const char *const s[] =
{
[NE_OK] = "Success",
[NE_ERROR] = "Generic error; use ne_get_error(session) for message",
[NE_LOOKUP] = "Server or proxy hostname lookup failed",
[NE_AUTH] = "User authentication failed on server",
[NE_PROXYAUTH] = "User authentication failed on proxy",
[NE_CONNECT] = "Could not connect to server",
[NE_TIMEOUT] = "Connection timed out",
[NE_FAILED] = "The precondition failed",
[NE_RETRY] = "Retry request (ne_end_request ONLY)",
[NE_REDIRECT] = "See ne_redirect.h"
};
if (error > 0 && error < sizeof s / sizeof *s)
return s[error];
return "unknown error";
}
static void notifier(void *const userdata, const ne_session_status status,
const ne_session_status_info *const info)
{
const struct backend_cfg *const cfg = userdata;
char buf[128];
switch (status)
{
case ne_status_lookup:
snprintf(buf, sizeof buf, "looking up hostname %s\n",
info->lu.hostname);
break;
case ne_status_connecting:
snprintf(buf, sizeof buf, "connecting to host %s\n", info->ci.hostname);
break;
case ne_status_connected:
snprintf(buf, sizeof buf, "connected to host %s\n", info->cd.hostname);
break;
case ne_status_sending:
if (info->sr.total != -1)
snprintf(buf, sizeof buf, "sending a request body [%ld/%ld]\n",
info->sr.progress, info->sr.total);
else
snprintf(buf, sizeof buf, "sending a request body (unknown size)\n");
break;
case ne_status_recving:
if (info->sr.total != -1)
snprintf(buf, sizeof buf, "receiving a response body [%ld/%ld]\n",
info->sr.progress, info->sr.total);
else
snprintf(buf, sizeof buf, "receiving a request body (unknown size)\n");
break;
case ne_status_disconnected:
snprintf(buf, sizeof buf, "disconnected from host\n");
break;
}
if (cfg->conn_status)
cfg->conn_status(buf, cfg->user_data);
}
static int server_auth(void *const userdata, const char *const realm,
const int attempt, char *const username, char *const password)
{
const struct backend_cfg *const cfg = userdata;
printf("%s: realm: %s, attempt: %d\n", __func__, realm, attempt);
const size_t ulen = strlen(cfg->username), plen = strlen(cfg->password);
if (ulen >= NE_ABUFSIZ)
{
fprintf(stderr, "username exceeds maximum size (%zu/%d bytes)\n",
ulen, NE_ABUFSIZ);
return -1;
}
else if (plen >= NE_ABUFSIZ)
{
fprintf(stderr, "password exceeds maximum size (%zu/%d bytes)\n",
plen, NE_ABUFSIZ);
return -1;
}
strcpy(username, cfg->username);
strcpy(password, cfg->password);
return 0;
}
static int format_uri(char *buf, const size_t n, const struct backend_cfg *const cfg)
{
#define COMMON_FMT "://%s/remote.php/dav/files/%s"
static const char https_fmt[] = "https" COMMON_FMT,
http_fmt[] = "http" COMMON_FMT;
#undef COMMON_FMT
int ret;
if (cfg->use_encryption)
ret = snprintf(buf, n, https_fmt,
cfg->hostname, cfg->username);
else
ret = snprintf(buf, n, http_fmt,
cfg->hostname, cfg->username);
return ret;
}
backend_session backend_connect(const struct backend_cfg *const cfg, const char **error)
{
int result;
ne_session *s = NULL;
ne_uri uri;
char *buf = NULL;
int n = format_uri(NULL, 0, cfg);
*error = NULL;
if (n < 0)
{
fprintf(stderr, "%s: fprintf(3) failed\n", __func__);
goto failure;
}
else if (!(buf = malloc(sizeof *buf * ++n)))
{
fprintf(stderr, "%s: failed to allocate URI: %s\n", __func__,
strerror(errno));
goto failure;
}
format_uri(buf, n, cfg);
if ((result = ne_uri_parse(buf, &uri)) != NE_OK)
{
fprintf(stderr, "ne_uri_parse failed: %d\n", result);
goto failure;
}
else if (!uri.host)
{
fprintf(stderr, "expected non-NULL uri.host\n");
goto failure;
}
else if (!(uri.port = ne_uri_defaultport(uri.scheme)))
{
fprintf(stderr, "ne_uri_defaultport failed\n");
goto failure;
}
else if (!(s = ne_session_create(uri.scheme, uri.host, uri.port)))
{
fprintf(stderr, "ne_session_create failed\n");
goto failure;
}
ne_set_notifier(s, notifier, (void *)cfg);
ne_lock_store *const store = ne_lockstore_create();
ne_lockstore_register(store, s);
ne_set_useragent(s, "gtk-webdav/0.1.0");
ne_set_server_auth(s, server_auth, (void *)cfg);
if (cfg->use_encryption)
ne_ssl_trust_default_ca(s);
unsigned int caps;
if ((result = ne_options2(s, uri.path, &caps)) != NE_OK)
{
if (result == NE_ERROR)
fprintf(stderr, "ne_options2 failed: %s\n", *error = ne_get_error(s));
else
fprintf(stderr, "ne_options2 failed: %s\n", *error = nestrerror(result));
goto failure;
}
free(buf);
return s;
failure:
if (s)
ne_session_destroy(s);
free(buf);
return NULL;
}