mirror of
https://github.com/libretro/RetroArch
synced 2025-01-31 15:32:59 +00:00
893b0d142e
* cloud sync - algorithm and abstract cloud storage API * WebDAV cloud sync driver, and associated net_http improvements * Cloud sync settings menu
804 lines
26 KiB
C
804 lines
26 KiB
C
/* RetroArch - A frontend for libretro.
|
|
*
|
|
* RetroArch 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 Found-
|
|
* ation, either version 3 of the License, or (at your option) any later version.
|
|
*
|
|
* RetroArch 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 RetroArch.
|
|
* If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <encodings/base64.h>
|
|
#include <lrc_hash.h>
|
|
#include <net/net_http.h>
|
|
#include <string/stdstring.h>
|
|
#include <time/rtime.h>
|
|
|
|
#include "../cloud_sync_driver.h"
|
|
#include "../../retroarch.h"
|
|
#include "../../tasks/tasks_internal.h"
|
|
#include "../../verbosity.h"
|
|
|
|
typedef struct
|
|
{
|
|
char path[PATH_MAX_LENGTH];
|
|
char file[PATH_MAX_LENGTH];
|
|
cloud_sync_complete_handler_t cb;
|
|
void *user_data;
|
|
RFILE *rfile;
|
|
} webdav_cb_state_t;
|
|
|
|
typedef void (*webdav_mkdir_cb_t)(bool success, webdav_cb_state_t *state);
|
|
|
|
typedef struct
|
|
{
|
|
char url[PATH_MAX_LENGTH];
|
|
char *last_slash;
|
|
webdav_mkdir_cb_t cb;
|
|
webdav_cb_state_t *cb_st;
|
|
} webdav_mkdir_state_t;
|
|
|
|
// TODO: all of this HTTP auth stuff should probably live in libretro-common/net?
|
|
typedef struct
|
|
{
|
|
char url[PATH_MAX_LENGTH];
|
|
|
|
bool basic;
|
|
char *basic_auth_header;
|
|
|
|
char *username;
|
|
char *ha1hash;
|
|
char *realm;
|
|
char *nonce;
|
|
char *algo;
|
|
char *opaque;
|
|
char *cnonce;
|
|
bool qop_auth;
|
|
unsigned nc;
|
|
char *digest_auth_header;
|
|
} webdav_state_t;
|
|
|
|
static webdav_state_t webdav_driver_st = {0};
|
|
|
|
webdav_state_t *webdav_state_get_ptr(void)
|
|
{
|
|
return &webdav_driver_st;
|
|
}
|
|
|
|
static char *webdav_create_basic_auth(void)
|
|
{
|
|
settings_t *settings = config_get_ptr();
|
|
size_t len = 0;
|
|
char userpass[512];
|
|
char *base64auth;
|
|
int flen;
|
|
|
|
if (!string_is_empty(settings->arrays.webdav_username))
|
|
len += strlcpy(userpass + len, settings->arrays.webdav_username, sizeof(userpass) - len);
|
|
userpass[len++] = ':';
|
|
if (!string_is_empty(settings->arrays.webdav_password))
|
|
len += strlcpy(userpass + len, settings->arrays.webdav_password, sizeof(userpass) - len);
|
|
userpass[len] = '\0';
|
|
base64auth = base64(userpass, (int)len, &flen);
|
|
len = strlcpy(userpass, "Authorization: Basic ", sizeof(userpass));
|
|
len += strlcpy(userpass + len, base64auth, sizeof(userpass) - len);
|
|
free(base64auth);
|
|
userpass[len++] = '\r';
|
|
userpass[len++] = '\n';
|
|
userpass[len ] = '\0';
|
|
|
|
return strdup(userpass);
|
|
}
|
|
|
|
static void webdav_cleanup_digest(void)
|
|
{
|
|
webdav_state_t *webdav_st = webdav_state_get_ptr();
|
|
|
|
if (webdav_st->ha1hash)
|
|
free(webdav_st->ha1hash);
|
|
webdav_st->ha1hash = NULL;
|
|
|
|
if (webdav_st->realm)
|
|
free(webdav_st->realm);
|
|
webdav_st->realm = NULL;
|
|
|
|
if (webdav_st->nonce)
|
|
free(webdav_st->nonce);
|
|
webdav_st->nonce = NULL;
|
|
|
|
if (webdav_st->algo)
|
|
free(webdav_st->algo);
|
|
webdav_st->algo = NULL;
|
|
|
|
if (webdav_st->opaque)
|
|
free(webdav_st->opaque);
|
|
webdav_st->opaque = NULL;
|
|
|
|
webdav_st->qop_auth = false;
|
|
webdav_st->nc = 1;
|
|
|
|
if (webdav_st->digest_auth_header)
|
|
free(webdav_st->digest_auth_header);
|
|
webdav_st->digest_auth_header = NULL;
|
|
}
|
|
|
|
static char *webdav_create_ha1_hash(char *user, char *realm, char *pass)
|
|
{
|
|
char *hash = malloc(33);
|
|
MD5_CTX md5;
|
|
unsigned char digest[16];
|
|
|
|
MD5_Init(&md5);
|
|
MD5_Update(&md5, user, strlen(user));
|
|
MD5_Update(&md5, ":", 1);
|
|
MD5_Update(&md5, realm, strlen(realm));
|
|
MD5_Update(&md5, ":", 1);
|
|
MD5_Update(&md5, pass, strlen(pass));
|
|
MD5_Final(digest, &md5);
|
|
|
|
snprintf(hash, 33, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
|
|
digest[0], digest[1], digest[2], digest[3], digest[4], digest[5], digest[6], digest[7],
|
|
digest[8], digest[9], digest[10], digest[11], digest[12], digest[13], digest[14], digest[15]
|
|
);
|
|
|
|
return hash;
|
|
}
|
|
|
|
static bool webdav_create_digest_auth(char *digest)
|
|
{
|
|
webdav_state_t *webdav_st = webdav_state_get_ptr();
|
|
settings_t *settings = config_get_ptr();
|
|
char *ptr = digest + strlen("WWW-Authenticate: Digest ");
|
|
char *end = ptr + strlen(ptr);
|
|
size_t sz;
|
|
|
|
if (string_is_empty(settings->arrays.webdav_username) &&
|
|
string_is_empty(settings->arrays.webdav_password))
|
|
return false;
|
|
|
|
webdav_cleanup_digest();
|
|
|
|
webdav_st->username = settings->arrays.webdav_username;
|
|
|
|
while (ptr < end)
|
|
{
|
|
while (ISSPACE(*ptr))
|
|
++ptr;
|
|
|
|
if (!*ptr)
|
|
break;
|
|
|
|
if (string_starts_with(ptr, "realm=\""))
|
|
{
|
|
ptr += strlen("realm=\"");
|
|
sz = strchr(ptr, '"') + 1 - ptr;
|
|
webdav_st->realm = malloc(sz);
|
|
strlcpy(webdav_st->realm, ptr, sz);
|
|
ptr += sz;
|
|
|
|
webdav_st->ha1hash = webdav_create_ha1_hash(webdav_st->username, webdav_st->realm, settings->arrays.webdav_password);
|
|
}
|
|
else if (string_starts_with(ptr, "qop=\""))
|
|
{
|
|
char *tail;
|
|
ptr += strlen("qop=\"");
|
|
tail = strchr(ptr, '"');
|
|
while (ptr < tail)
|
|
{
|
|
if (string_starts_with(ptr, "auth") &&
|
|
(ptr[4] == ',' || ptr[4] == '"'))
|
|
{
|
|
webdav_st->qop_auth = true;
|
|
break;
|
|
}
|
|
while (*ptr != ',' && *ptr != '"' && *ptr != '\0')
|
|
ptr++;
|
|
ptr++;
|
|
}
|
|
/* not even going to try for auth-int, sorry */
|
|
if (!webdav_st->qop_auth)
|
|
return false;
|
|
while (*ptr != ',' && *ptr != '"' && *ptr != '\0')
|
|
ptr++;
|
|
ptr++;
|
|
}
|
|
else if (string_starts_with(ptr, "nonce=\""))
|
|
{
|
|
ptr += strlen("nonce=\"");
|
|
sz = strchr(ptr, '"') + 1 - ptr;
|
|
webdav_st->nonce = malloc(sz);
|
|
strlcpy(webdav_st->nonce, ptr, sz);
|
|
ptr += sz;
|
|
}
|
|
else if (string_starts_with(ptr, "algorithm="))
|
|
{
|
|
ptr += strlen("algorithm=");
|
|
sz = strchr(ptr, ',') + 1 - ptr;
|
|
webdav_st->algo = malloc(sz);
|
|
strlcpy(webdav_st->algo, ptr, sz);
|
|
ptr += sz;
|
|
}
|
|
else if (string_starts_with(ptr, "opaque=\""))
|
|
{
|
|
ptr += strlen("opaque=\"");
|
|
sz = strchr(ptr, '"') + 1 - ptr;
|
|
webdav_st->opaque = malloc(sz);
|
|
strlcpy(webdav_st->opaque, ptr, sz);
|
|
ptr += sz;
|
|
}
|
|
else
|
|
{
|
|
while (*ptr != '=' && *ptr != '\0')
|
|
ptr++;
|
|
ptr++;
|
|
if (*ptr == '"')
|
|
{
|
|
ptr++;
|
|
while (*ptr != '"' && *ptr != '\0')
|
|
ptr++;
|
|
ptr++;
|
|
}
|
|
else
|
|
{
|
|
while (*ptr != ',' && *ptr != ',')
|
|
ptr++;
|
|
}
|
|
}
|
|
|
|
while (ISSPACE(*ptr))
|
|
++ptr;
|
|
if (*ptr == ',')
|
|
ptr++;
|
|
}
|
|
|
|
if (!webdav_st->ha1hash || !webdav_st->nonce)
|
|
return false;
|
|
|
|
webdav_st->cnonce = "1a2b3c4f";
|
|
|
|
return true;
|
|
}
|
|
|
|
static char *webdav_create_ha1(void)
|
|
{
|
|
webdav_state_t *webdav_st = webdav_state_get_ptr();
|
|
char *hash;
|
|
MD5_CTX md5;
|
|
unsigned char digest[16];
|
|
|
|
if (!string_is_equal(webdav_st->algo, "MD5-sess"))
|
|
return strdup(webdav_st->ha1hash);
|
|
|
|
hash = malloc(33);
|
|
|
|
MD5_Init(&md5);
|
|
MD5_Update(&md5, webdav_st->ha1hash, 32);
|
|
MD5_Update(&md5, ":", 1);
|
|
MD5_Update(&md5, webdav_st->nonce, strlen(webdav_st->nonce));
|
|
MD5_Update(&md5, ":", 1);
|
|
MD5_Update(&md5, webdav_st->cnonce, strlen(webdav_st->cnonce));
|
|
MD5_Final(digest, &md5);
|
|
|
|
snprintf(hash, 33, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
|
|
digest[0], digest[1], digest[2], digest[3], digest[4], digest[5], digest[6], digest[7],
|
|
digest[8], digest[9], digest[10], digest[11], digest[12], digest[13], digest[14], digest[15]
|
|
);
|
|
|
|
return hash;
|
|
}
|
|
|
|
static char *webdav_create_ha2(const char *method, const char *path)
|
|
{
|
|
/* no attempt at supporting auth-int, everything else uses this */
|
|
char *hash = malloc(33);
|
|
MD5_CTX md5;
|
|
unsigned char digest[16];
|
|
|
|
MD5_Init(&md5);
|
|
MD5_Update(&md5, method, strlen(method));
|
|
MD5_Update(&md5, ":", 1);
|
|
MD5_Update(&md5, path, strlen(path));
|
|
MD5_Final(digest, &md5);
|
|
|
|
snprintf(hash, 33, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
|
|
digest[0], digest[1], digest[2], digest[3], digest[4], digest[5], digest[6], digest[7],
|
|
digest[8], digest[9], digest[10], digest[11], digest[12], digest[13], digest[14], digest[15]
|
|
);
|
|
|
|
return hash;
|
|
}
|
|
|
|
static char *webdav_create_digest_response(const char *method, const char *path)
|
|
{
|
|
webdav_state_t *webdav_st = webdav_state_get_ptr();
|
|
char *ha1 = webdav_create_ha1();
|
|
char *ha2 = webdav_create_ha2(method, path);
|
|
char *hash = malloc(33);
|
|
MD5_CTX md5;
|
|
unsigned char digest[16];
|
|
|
|
MD5_Init(&md5);
|
|
MD5_Update(&md5, ha1, 32);
|
|
MD5_Update(&md5, ":", 1);
|
|
MD5_Update(&md5, webdav_st->nonce, strlen(webdav_st->nonce));
|
|
if (webdav_st->qop_auth)
|
|
{
|
|
char nonceCount[10];
|
|
snprintf(nonceCount, sizeof(nonceCount), "%08x", webdav_st->nc);
|
|
MD5_Update(&md5, ":", 1);
|
|
MD5_Update(&md5, nonceCount, strlen(nonceCount));
|
|
MD5_Update(&md5, ":", 1);
|
|
MD5_Update(&md5, webdav_st->cnonce, strlen(webdav_st->cnonce));
|
|
MD5_Update(&md5, ":", 1);
|
|
MD5_Update(&md5, "auth", strlen("auth"));
|
|
}
|
|
MD5_Update(&md5, ":", 1);
|
|
MD5_Update(&md5, ha2, 32);
|
|
MD5_Final(digest, &md5);
|
|
|
|
snprintf(hash, 33, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
|
|
digest[0], digest[1], digest[2], digest[3], digest[4], digest[5], digest[6], digest[7],
|
|
digest[8], digest[9], digest[10], digest[11], digest[12], digest[13], digest[14], digest[15]
|
|
);
|
|
|
|
free(ha1);
|
|
free(ha2);
|
|
|
|
return hash;
|
|
}
|
|
|
|
static char *webdav_create_digest_auth_header(const char *method, const char *url)
|
|
{
|
|
webdav_state_t *webdav_st = webdav_state_get_ptr();
|
|
char *header;
|
|
char *response;
|
|
char nonceCount[10];
|
|
const char *path = url;
|
|
int count = 0;
|
|
size_t len = 0;
|
|
size_t total = 0;
|
|
|
|
do
|
|
{
|
|
path++;
|
|
path = strchr(path, '/');
|
|
count++;
|
|
} while (count < 3 && *path != '\0');
|
|
|
|
response = webdav_create_digest_response(method, path);
|
|
snprintf(nonceCount, sizeof(nonceCount), "%08x", webdav_st->nc);
|
|
|
|
len = strlen("Authorization: Digest ");
|
|
len += strlen("username=\"") + strlen(webdav_st->username) + strlen("\", ");
|
|
len += strlen("realm=\"") + strlen(webdav_st->realm) + strlen("\", ");
|
|
len += strlen("nonce=\"") + strlen(webdav_st->nonce) + strlen("\", ");
|
|
len += strlen("uri=\"") + strlen(path) + strlen("\", ");
|
|
len += strlen("nc=\"") + strlen(nonceCount) + strlen("\", ");
|
|
len += strlen("cnonce=\"") + strlen(webdav_st->cnonce) + strlen("\", ");
|
|
if (webdav_st->qop_auth)
|
|
len += strlen("qop=\"auth\", ");
|
|
if (webdav_st->opaque)
|
|
len += strlen("opaque=\"") + strlen(webdav_st->opaque) + strlen("\", ");
|
|
len += strlen("response=\"") + strlen(response) + strlen("\"\r\n");
|
|
len += 1;
|
|
|
|
total = len;
|
|
len = 0;
|
|
header = malloc(total);
|
|
len = strlcpy(header, "Authorization: Digest username=\"", total - len);
|
|
len += strlcpy(header + len, webdav_st->username, total - len);
|
|
len += strlcpy(header + len, "\", realm=\"", total - len);
|
|
len += strlcpy(header + len, webdav_st->realm, total - len);
|
|
len += strlcpy(header + len, "\", nonce=\"", total - len);
|
|
len += strlcpy(header + len, webdav_st->nonce, total - len);
|
|
len += strlcpy(header + len, "\", uri=\"", total - len);
|
|
len += strlcpy(header + len, path, total - len);
|
|
len += strlcpy(header + len, "\", nc=\"", total - len);
|
|
len += strlcpy(header + len, nonceCount, total - len);
|
|
len += strlcpy(header + len, "\", cnonce=\"", total - len);
|
|
len += strlcpy(header + len, webdav_st->cnonce, total - len);
|
|
if (webdav_st->qop_auth)
|
|
len += strlcpy(header + len, "\", qop=\"auth", total - len);
|
|
if (webdav_st->opaque)
|
|
{
|
|
len += strlcpy(header + len, "\", opaque=\"", total - len);
|
|
len += strlcpy(header + len, webdav_st->opaque, total - len);
|
|
}
|
|
len += strlcpy(header + len, "\", response=\"", total - len);
|
|
len += strlcpy(header + len, response, total - len);
|
|
len += strlcpy(header + len, "\"\r\n", total - len);
|
|
|
|
free(response);
|
|
|
|
return header;
|
|
}
|
|
|
|
static char *webdav_get_auth_header(const char *method, const char *url)
|
|
{
|
|
webdav_state_t *webdav_st = webdav_state_get_ptr();
|
|
settings_t *settings = config_get_ptr();
|
|
|
|
if (string_is_empty(settings->arrays.webdav_username) &&
|
|
string_is_empty(settings->arrays.webdav_password))
|
|
return NULL;
|
|
|
|
if (webdav_st->basic)
|
|
{
|
|
if (!webdav_st->basic_auth_header)
|
|
webdav_st->basic_auth_header = webdav_create_basic_auth();
|
|
return webdav_st->basic_auth_header;
|
|
}
|
|
|
|
if (webdav_st->digest_auth_header)
|
|
free(webdav_st->digest_auth_header);
|
|
webdav_st->digest_auth_header = webdav_create_digest_auth_header(method, url);
|
|
return webdav_st->digest_auth_header;
|
|
}
|
|
|
|
static void webdav_stat_cb(retro_task_t *task, void *task_data, void *user_data, const char *err)
|
|
{
|
|
webdav_state_t *webdav_st = webdav_state_get_ptr();
|
|
webdav_cb_state_t *webdav_cb_st = (webdav_cb_state_t *)user_data;
|
|
http_transfer_data_t *data = (http_transfer_data_t*)task_data;
|
|
bool success = (data && data->status >= 200 && data->status < 300);
|
|
|
|
if (!webdav_cb_st)
|
|
return;
|
|
|
|
if (data && data->status == 401 && data->headers && webdav_st->basic == true)
|
|
{
|
|
int i;
|
|
webdav_st->basic = false;
|
|
for (i = 0; i < data->headers->size; i++)
|
|
{
|
|
if (!string_starts_with(data->headers->elems[i].data, "WWW-Authenticate: Digest "))
|
|
continue;
|
|
|
|
if (webdav_create_digest_auth(data->headers->elems[i].data))
|
|
{
|
|
task_push_webdav_stat(webdav_st->url, true,
|
|
webdav_get_auth_header("OPTIONS", webdav_st->url),
|
|
webdav_stat_cb, webdav_cb_st);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
webdav_cb_st->cb(webdav_cb_st->user_data, NULL, success, NULL);
|
|
free(webdav_cb_st);
|
|
}
|
|
|
|
static bool webdav_sync_begin(cloud_sync_complete_handler_t cb, void *user_data)
|
|
{
|
|
settings_t *settings = config_get_ptr();
|
|
const char *url = settings->arrays.webdav_url;
|
|
webdav_state_t *webdav_st = webdav_state_get_ptr();
|
|
const char *auth_header;
|
|
|
|
if (string_is_empty(url))
|
|
return false;
|
|
|
|
// TODO: LOCK?
|
|
|
|
strlcpy(webdav_st->url, url, sizeof(webdav_st->url));
|
|
fill_pathname_slash(webdav_st->url, sizeof(webdav_st->url));
|
|
|
|
/* url/username/password may have changed, redo auth check */
|
|
webdav_st->basic = true;
|
|
auth_header = webdav_get_auth_header(NULL, NULL);
|
|
|
|
if (auth_header)
|
|
{
|
|
webdav_cb_state_t *webdav_cb_st = (webdav_cb_state_t*)calloc(1, sizeof(webdav_cb_state_t));
|
|
webdav_cb_st->cb = cb;
|
|
webdav_cb_st->user_data = user_data;
|
|
task_push_webdav_stat(webdav_st->url, true, auth_header, webdav_stat_cb, webdav_cb_st);
|
|
}
|
|
else
|
|
cb(user_data, NULL, true, NULL);
|
|
return true;
|
|
}
|
|
|
|
static bool webdav_sync_end(cloud_sync_complete_handler_t cb, void *user_data)
|
|
{
|
|
webdav_state_t *webdav_st = webdav_state_get_ptr();
|
|
|
|
// TODO: UNLOCK?
|
|
|
|
if (webdav_st->basic_auth_header)
|
|
free(webdav_st->basic_auth_header);
|
|
webdav_st->basic_auth_header = NULL;
|
|
|
|
webdav_cleanup_digest();
|
|
|
|
cb(user_data, NULL, true, NULL);
|
|
return true;
|
|
}
|
|
|
|
static void webdav_read_cb(retro_task_t *task, void *task_data, void *user_data, const char *err)
|
|
{
|
|
webdav_cb_state_t *webdav_cb_st = (webdav_cb_state_t *)user_data;
|
|
http_transfer_data_t *data = (http_transfer_data_t*)task_data;
|
|
RFILE *file = NULL;
|
|
bool success;
|
|
|
|
success = (data &&
|
|
((data->status >= 200 && data->status < 300) || data->status == 404));
|
|
|
|
// TODO: it's possible we get a 401 here and need to redo the auth check with this request
|
|
if (data && data->data && webdav_cb_st)
|
|
{
|
|
// TODO: it would be better if writing to the file happened during the network reads
|
|
file = filestream_open(webdav_cb_st->file,
|
|
RETRO_VFS_FILE_ACCESS_READ_WRITE,
|
|
RETRO_VFS_FILE_ACCESS_HINT_NONE);
|
|
if (file)
|
|
{
|
|
filestream_write(file, data->data, data->len);
|
|
filestream_seek(file, 0, SEEK_SET);
|
|
}
|
|
}
|
|
|
|
if (webdav_cb_st)
|
|
{
|
|
webdav_cb_st->cb(webdav_cb_st->user_data, webdav_cb_st->path, success, file);
|
|
free(webdav_cb_st);
|
|
}
|
|
}
|
|
|
|
static bool webdav_read(const char *path, const char *file, cloud_sync_complete_handler_t cb, void *user_data)
|
|
{
|
|
webdav_state_t *webdav_st = webdav_state_get_ptr();
|
|
webdav_cb_state_t *webdav_cb_st = (webdav_cb_state_t*)calloc(1, sizeof(webdav_cb_state_t));
|
|
char url[PATH_MAX_LENGTH];
|
|
char url_encoded[PATH_MAX_LENGTH];
|
|
|
|
fill_pathname_join_special(url, webdav_st->url, path, sizeof(url));
|
|
net_http_urlencode_full(url_encoded, url, sizeof(url_encoded));
|
|
|
|
webdav_cb_st->cb = cb;
|
|
webdav_cb_st->user_data = user_data;
|
|
strlcpy(webdav_cb_st->path, path, sizeof(webdav_cb_st->path));
|
|
strlcpy(webdav_cb_st->file, file, sizeof(webdav_cb_st->file));
|
|
|
|
task_push_http_transfer_with_headers(url_encoded, true, NULL,
|
|
webdav_get_auth_header("GET", url_encoded),
|
|
webdav_read_cb, webdav_cb_st);
|
|
return true;
|
|
}
|
|
|
|
static void webdav_mkdir_cb(retro_task_t *task, void *task_data, void *user_data, const char *err)
|
|
{
|
|
webdav_mkdir_state_t *webdav_mkdir_st = (webdav_mkdir_state_t *)user_data;
|
|
http_transfer_data_t *data = (http_transfer_data_t*)task_data;
|
|
|
|
if (!webdav_mkdir_st)
|
|
return;
|
|
|
|
// TODO: it's possible we get a 401 here and need to redo the auth check with this request
|
|
if (!data || data->status < 200 || data->status >= 400)
|
|
{
|
|
webdav_mkdir_st->cb(false, webdav_mkdir_st->cb_st);
|
|
free(webdav_mkdir_st);
|
|
return;
|
|
}
|
|
|
|
*webdav_mkdir_st->last_slash++ = '/';
|
|
webdav_mkdir_st->last_slash = strchr(webdav_mkdir_st->last_slash, '/');
|
|
if (webdav_mkdir_st->last_slash)
|
|
{
|
|
*webdav_mkdir_st->last_slash = '\0';
|
|
task_push_webdav_mkdir(webdav_mkdir_st->url, true,
|
|
webdav_get_auth_header("MVCOL", webdav_mkdir_st->url),
|
|
webdav_mkdir_cb, webdav_mkdir_st);
|
|
}
|
|
else
|
|
{
|
|
webdav_mkdir_st->cb(true, webdav_mkdir_st->cb_st);
|
|
free(webdav_mkdir_st);
|
|
}
|
|
}
|
|
|
|
static void webdav_ensure_dir(const char *dir, webdav_mkdir_cb_t cb, webdav_cb_state_t *webdav_cb_st)
|
|
{
|
|
webdav_state_t *webdav_st = webdav_state_get_ptr();
|
|
webdav_mkdir_state_t *webdav_mkdir_st = (webdav_mkdir_state_t *)malloc(sizeof(webdav_mkdir_state_t));
|
|
http_transfer_data_t data;
|
|
char url[PATH_MAX_LENGTH];
|
|
|
|
fill_pathname_join_special(url, webdav_st->url, dir, sizeof(url));
|
|
net_http_urlencode_full(webdav_mkdir_st->url, url, sizeof(webdav_mkdir_st->url));
|
|
webdav_mkdir_st->last_slash = strchr(webdav_mkdir_st->url + strlen(webdav_st->url) - 1, '/');
|
|
webdav_mkdir_st->cb = cb;
|
|
webdav_mkdir_st->cb_st = webdav_cb_st;
|
|
|
|
/* this is a recursive callback, set it up so it looks like it's still proceeding */
|
|
data.status = 200;
|
|
webdav_mkdir_cb(NULL, &data, webdav_mkdir_st, NULL);
|
|
}
|
|
|
|
static void webdav_update_cb(retro_task_t *task, void *task_data, void *user_data, const char *err)
|
|
{
|
|
webdav_cb_state_t *webdav_cb_st = (webdav_cb_state_t *)user_data;
|
|
http_transfer_data_t *data = (http_transfer_data_t*)task_data;
|
|
bool success = (data && data->status >= 200 && data->status < 300);
|
|
|
|
// TODO: it's possible we get a 401 here and need to redo the auth check with this request
|
|
if (webdav_cb_st)
|
|
{
|
|
webdav_cb_st->cb(webdav_cb_st->user_data, webdav_cb_st->path, success, webdav_cb_st->rfile);
|
|
free(webdav_cb_st);
|
|
}
|
|
}
|
|
|
|
static void webdav_do_update(bool success, webdav_cb_state_t *webdav_cb_st)
|
|
{
|
|
webdav_state_t *webdav_st = webdav_state_get_ptr();
|
|
char url_encoded[PATH_MAX_LENGTH];
|
|
char url[PATH_MAX_LENGTH];
|
|
void *buf;
|
|
int64_t len;
|
|
|
|
if (!webdav_cb_st)
|
|
return;
|
|
|
|
if (!success)
|
|
{
|
|
webdav_cb_st->cb(webdav_cb_st->user_data, webdav_cb_st->path, false, webdav_cb_st->rfile);
|
|
free(webdav_cb_st);
|
|
return;
|
|
}
|
|
|
|
// TODO: would be better to read file as it's being written to wire, this is very inefficient
|
|
len = filestream_get_size(webdav_cb_st->rfile);
|
|
buf = malloc((size_t)(len + 1));
|
|
filestream_read(webdav_cb_st->rfile, buf, len);
|
|
|
|
fill_pathname_join_special(url, webdav_st->url, webdav_cb_st->path, sizeof(url));
|
|
net_http_urlencode_full(url_encoded, url, sizeof(url_encoded));
|
|
|
|
task_push_webdav_put(url_encoded, buf, len, true,
|
|
webdav_get_auth_header("PUT", url_encoded),
|
|
webdav_update_cb, webdav_cb_st);
|
|
|
|
free(buf);
|
|
}
|
|
|
|
static bool webdav_update(const char *path, RFILE *rfile, cloud_sync_complete_handler_t cb, void *user_data)
|
|
{
|
|
webdav_cb_state_t *webdav_cb_st = (webdav_cb_state_t*)calloc(1, sizeof(webdav_cb_state_t));
|
|
char dir[PATH_MAX_LENGTH];
|
|
|
|
// TODO: if !settings->bools.cloud_sync_destructive, should move to deleted/ first
|
|
|
|
webdav_cb_st->cb = cb;
|
|
webdav_cb_st->user_data = user_data;
|
|
strlcpy(webdav_cb_st->path, path, sizeof(webdav_cb_st->path));
|
|
webdav_cb_st->rfile = rfile;
|
|
|
|
if (strchr(path, '/'))
|
|
{
|
|
fill_pathname_basedir(dir, path, sizeof(dir));
|
|
webdav_ensure_dir(dir, webdav_do_update, webdav_cb_st);
|
|
}
|
|
else
|
|
webdav_do_update(true, webdav_cb_st);
|
|
|
|
return true;
|
|
}
|
|
|
|
static void webdav_delete_cb(retro_task_t *task, void *task_data, void *user_data, const char *err)
|
|
{
|
|
webdav_cb_state_t *webdav_cb_st = (webdav_cb_state_t *)user_data;
|
|
http_transfer_data_t *data = (http_transfer_data_t*)task_data;
|
|
bool success = (data != NULL && data->status >= 200 && data->status < 300);
|
|
|
|
// TODO: it's possible we get a 401 here and need to redo the auth check with this request
|
|
if (webdav_cb_st)
|
|
{
|
|
webdav_cb_st->cb(webdav_cb_st->user_data, webdav_cb_st->path, success, NULL);
|
|
free(webdav_cb_st);
|
|
}
|
|
}
|
|
|
|
static void webdav_backup_cb(retro_task_t *task, void *task_data, void *user_data, const char *err)
|
|
{
|
|
webdav_cb_state_t *webdav_cb_st = (webdav_cb_state_t *)user_data;
|
|
http_transfer_data_t *data = (http_transfer_data_t*)task_data;
|
|
bool success = (data != NULL && data->status >= 200 && data->status < 300);
|
|
|
|
// TODO: it's possible we get a 401 here and need to redo the auth check with this request
|
|
if (webdav_cb_st)
|
|
{
|
|
webdav_cb_st->cb(webdav_cb_st->user_data, webdav_cb_st->path, success, NULL);
|
|
free(webdav_cb_st);
|
|
}
|
|
}
|
|
|
|
static void webdav_do_backup(bool success, webdav_cb_state_t *webdav_cb_st)
|
|
{
|
|
webdav_state_t *webdav_st = webdav_state_get_ptr();
|
|
char dest_encoded[PATH_MAX_LENGTH];
|
|
char dest[PATH_MAX_LENGTH];
|
|
char url_encoded[PATH_MAX_LENGTH];
|
|
char url[PATH_MAX_LENGTH];
|
|
size_t len;
|
|
struct tm tm_;
|
|
time_t cur_time = time(NULL);
|
|
|
|
if (!webdav_cb_st)
|
|
return;
|
|
|
|
if (!success)
|
|
{
|
|
webdav_cb_st->cb(webdav_cb_st->user_data, webdav_cb_st->path, false, NULL);
|
|
free(webdav_cb_st);
|
|
return;
|
|
}
|
|
|
|
fill_pathname_join_special(url, webdav_st->url, webdav_cb_st->path, sizeof(url));
|
|
net_http_urlencode_full(url_encoded, url, sizeof(url_encoded));
|
|
|
|
fill_pathname_join_special(url, webdav_st->url, "deleted/", sizeof(url));
|
|
len = fill_pathname_join_special(dest, url, webdav_cb_st->path, sizeof(dest));
|
|
rtime_localtime(&cur_time, &tm_);
|
|
strftime(dest + len, sizeof(dest) - len, "-%y%m%d-%H%M%S", &tm_);
|
|
net_http_urlencode_full(dest_encoded, dest, sizeof(dest_encoded));
|
|
|
|
task_push_webdav_move(url_encoded, dest_encoded, true,
|
|
webdav_get_auth_header("MOVE", url_encoded),
|
|
webdav_backup_cb, webdav_cb_st);
|
|
}
|
|
|
|
static bool webdav_delete(const char *path, cloud_sync_complete_handler_t cb, void *user_data)
|
|
{
|
|
webdav_cb_state_t *webdav_cb_st = (webdav_cb_state_t*)calloc(1, sizeof(webdav_cb_state_t));
|
|
settings_t *settings = config_get_ptr();
|
|
|
|
webdav_cb_st->cb = cb;
|
|
webdav_cb_st->user_data = user_data;
|
|
strlcpy(webdav_cb_st->path, path, sizeof(webdav_cb_st->path));
|
|
|
|
/*
|
|
* Should all cloud_sync_destructive handling be done in task_cloudsync? I
|
|
* think not because it gives each driver a chance to do a move rather than a
|
|
* delete/update. Or we could add a cloud_sync_move() API to the driver.
|
|
*/
|
|
if (settings->bools.cloud_sync_destructive)
|
|
{
|
|
webdav_state_t *webdav_st = webdav_state_get_ptr();
|
|
char url_encoded[PATH_MAX_LENGTH];
|
|
char url[PATH_MAX_LENGTH];
|
|
|
|
fill_pathname_join_special(url, webdav_st->url, path, sizeof(url));
|
|
net_http_urlencode_full(url_encoded, url, sizeof(url_encoded));
|
|
|
|
task_push_webdav_delete(url_encoded, true,
|
|
webdav_get_auth_header("DELETE", url_encoded),
|
|
webdav_delete_cb, webdav_cb_st);
|
|
}
|
|
else
|
|
{
|
|
char dir[PATH_MAX_LENGTH] = {0};
|
|
size_t _len;
|
|
_len = strlcat(dir, "deleted/", sizeof(dir));
|
|
fill_pathname_basedir(dir + _len, path, sizeof(dir) - _len);
|
|
webdav_ensure_dir(dir, webdav_do_backup, webdav_cb_st);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
cloud_sync_driver_t cloud_sync_webdav = {
|
|
webdav_sync_begin,
|
|
webdav_sync_end,
|
|
webdav_read,
|
|
webdav_update,
|
|
webdav_delete,
|
|
"webdav" /* ident */
|
|
};
|