/* * uclient - ustream based protocol client library * * Copyright (C) 2014 Felix Fietkau * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "uclient.h" #include "uclient-utils.h" #include "uclient-backend.h" #ifdef __APPLE__ #define LIB_EXT "dylib" #else #define LIB_EXT "so" #endif char *uclient_get_addr(char *dest, int *port, union uclient_addr *a) { int portval; void *ptr; switch(a->sa.sa_family) { case AF_INET: ptr = &a->sin.sin_addr; portval = a->sin.sin_port; break; case AF_INET6: ptr = &a->sin6.sin6_addr; portval = a->sin6.sin6_port; break; default: return strcpy(dest, "Unknown"); } inet_ntop(a->sa.sa_family, ptr, dest, INET6_ADDRSTRLEN); if (port) *port = ntohs(portval); return dest; } static struct uclient_url * __uclient_get_url(const struct uclient_backend *backend, const char *host, int host_len, const char *location, const char *auth_str) { struct uclient_url *url; char *host_buf, *uri_buf, *auth_buf, *next; url = calloc_a(sizeof(*url), &host_buf, host_len + 1, &uri_buf, strlen(location) + 1, &auth_buf, auth_str ? strlen(auth_str) + 1 : 0); if (!url) return NULL; url->backend = backend; url->location = strcpy(uri_buf, location); if (host) url->host = strncpy(host_buf, host, host_len); next = strchr(host_buf, '@'); if (next) { *next = 0; url->host = next + 1; if (uclient_urldecode(host_buf, host_buf, false) < 0) goto free; url->auth = host_buf; } if (!url->auth && auth_str) url->auth = strcpy(auth_buf, auth_str); /* Literal IPv6 address */ if (*url->host == '[') { url->host++; next = strrchr(url->host, ']'); if (!next) goto free; *(next++) = 0; if (*next == ':') url->port = next + 1; } else { next = strrchr(url->host, ':'); if (next) { *next = 0; url->port = next + 1; } } return url; free: free(url); return NULL; } static const char * uclient_split_host(const char *base, int *host_len) { char *next, *location; next = strchr(base, '/'); if (next) { location = next; *host_len = next - base; } else { location = "/"; *host_len = strlen(base); } return location; } struct uclient_url __hidden * uclient_get_url_location(struct uclient_url *url, const char *location) { struct uclient_url *new_url; char *host_buf, *uri_buf, *auth_buf, *port_buf; int host_len = strlen(url->host) + 1; int auth_len = url->auth ? strlen(url->auth) + 1 : 0; int port_len = url->port ? strlen(url->port) + 1 : 0; int uri_len; if (strstr(location, "://")) return uclient_get_url(location, url->auth); if (location[0] == '/') uri_len = strlen(location) + 1; else uri_len = strlen(url->location) + strlen(location) + 2; new_url = calloc_a(sizeof(*url), &host_buf, host_len, &port_buf, port_len, &uri_buf, uri_len, &auth_buf, auth_len); if (!new_url) return NULL; new_url->backend = url->backend; new_url->prefix = url->prefix; new_url->host = strcpy(host_buf, url->host); if (url->port) new_url->port = strcpy(port_buf, url->port); if (url->auth) new_url->auth = strcpy(auth_buf, url->auth); new_url->location = uri_buf; if (location[0] == '/') strcpy(uri_buf, location); else { int len = strcspn(url->location, "?#"); char *buf = uri_buf; memcpy(buf, url->location, len); if (buf[len - 1] != '/') { buf[len] = '/'; len++; } buf += len; strcpy(buf, location); } return new_url; } struct uclient_url __hidden * uclient_get_url(const char *url_str, const char *auth_str) { static const struct uclient_backend *backends[] = { &uclient_backend_http, }; const struct uclient_backend *backend; const char * const *prefix = NULL; struct uclient_url *url; const char *location; int host_len; unsigned int i; for (i = 0; i < ARRAY_SIZE(backends); i++) { int prefix_len = 0; for (prefix = backends[i]->prefix; *prefix; prefix++) { prefix_len = strlen(*prefix); if (!strncmp(url_str, *prefix, prefix_len)) break; } if (!*prefix) continue; url_str += prefix_len; backend = backends[i]; break; } if (!*prefix) return NULL; location = uclient_split_host(url_str, &host_len); url = __uclient_get_url(backend, url_str, host_len, location, auth_str); if (!url) return NULL; url->prefix = prefix - backend->prefix; return url; } static void uclient_connection_timeout(struct uloop_timeout *timeout) { struct uclient *cl = container_of(timeout, struct uclient, connection_timeout); if (cl->backend->disconnect) cl->backend->disconnect(cl); uclient_backend_set_error(cl, UCLIENT_ERROR_TIMEDOUT); } static void __uclient_read_notify(struct uloop_timeout *timeout) { struct uclient *cl = container_of(timeout, struct uclient, read_notify); if (cl->cb->data_read) cl->cb->data_read(cl); } struct uclient *uclient_new(const char *url_str, const char *auth_str, const struct uclient_cb *cb) { struct uclient *cl; struct uclient_url *url; url = uclient_get_url(url_str, auth_str); if (!url) return NULL; cl = url->backend->alloc(); if (!cl) return NULL; cl->backend = url->backend; cl->cb = cb; cl->url = url; cl->timeout_msecs = UCLIENT_DEFAULT_TIMEOUT_MS; cl->connection_timeout.cb = uclient_connection_timeout; cl->read_notify.cb = __uclient_read_notify; return cl; } int uclient_set_proxy_url(struct uclient *cl, const char *url_str, const char *auth_str) { const struct uclient_backend *backend = cl->backend; struct uclient_url *url; int host_len; char *next, *host; if (!backend->update_proxy_url) return -1; next = strstr(url_str, "://"); if (!next) return -1; host = next + 3; uclient_split_host(host, &host_len); url = __uclient_get_url(NULL, host, host_len, url_str, auth_str); if (!url) return -1; free(cl->proxy_url); cl->proxy_url = url; if (backend->update_proxy_url) backend->update_proxy_url(cl); return 0; } int uclient_set_url(struct uclient *cl, const char *url_str, const char *auth_str) { const struct uclient_backend *backend = cl->backend; struct uclient_url *url; url = uclient_get_url(url_str, auth_str); if (!url) return -1; if (url->backend != cl->backend) { free(url); return -1; } free(cl->proxy_url); cl->proxy_url = NULL; free(cl->url); cl->url = url; if (backend->update_url) backend->update_url(cl); return 0; } int uclient_set_timeout(struct uclient *cl, int msecs) { if (msecs <= 0) return -EINVAL; cl->timeout_msecs = msecs; return 0; } int uclient_connect(struct uclient *cl) { return cl->backend->connect(cl); } void uclient_free(struct uclient *cl) { struct uclient_url *url = cl->url; if (cl->backend->free) cl->backend->free(cl); else free(cl); free(url); } int uclient_write(struct uclient *cl, const char *buf, int len) { if (!cl->backend->write) return -1; return cl->backend->write(cl, buf, len); } int uclient_request(struct uclient *cl) { int err; if (!cl->backend->request) return -1; err = cl->backend->request(cl); if (err) return err; uloop_timeout_set(&cl->connection_timeout, cl->timeout_msecs); return 0; } struct ustream_ssl_ctx *uclient_new_ssl_context(const struct ustream_ssl_ops **ops) { static const struct ustream_ssl_ops *ssl_ops; void *dlh; if (!ssl_ops) { dlh = dlopen("libustream-ssl." LIB_EXT, RTLD_LAZY | RTLD_LOCAL); if (!dlh) return NULL; ssl_ops = dlsym(dlh, "ustream_ssl_ops"); if (!ssl_ops) { dlclose(dlh); return NULL; } } *ops = ssl_ops; return ssl_ops->context_new(false); } int uclient_read(struct uclient *cl, char *buf, int len) { if (!cl->backend->read) return -1; return cl->backend->read(cl, buf, len); } int uclient_pending_bytes(struct uclient *cl, bool write) { if (!cl->backend->pending_bytes) return -1; return cl->backend->pending_bytes(cl, write); } void uclient_disconnect(struct uclient *cl) { uloop_timeout_cancel(&cl->connection_timeout); uloop_timeout_cancel(&cl->timeout); uloop_timeout_cancel(&cl->read_notify); if (!cl->backend->disconnect) return; cl->backend->disconnect(cl); } static void __uclient_backend_change_state(struct uloop_timeout *timeout) { struct uclient *cl = container_of(timeout, struct uclient, timeout); if (cl->error_code && cl->cb->error) cl->cb->error(cl, cl->error_code); else if (cl->eof && cl->cb->data_eof) cl->cb->data_eof(cl); } static void uclient_backend_change_state(struct uclient *cl) { cl->timeout.cb = __uclient_backend_change_state; uloop_timeout_set(&cl->timeout, 1); } void __hidden uclient_backend_set_error(struct uclient *cl, int code) { if (cl->error_code) return; uloop_timeout_cancel(&cl->connection_timeout); cl->error_code = code; uclient_backend_change_state(cl); } void __hidden uclient_backend_set_eof(struct uclient *cl) { if (cl->eof || cl->error_code) return; uloop_timeout_cancel(&cl->connection_timeout); cl->eof = true; uclient_backend_change_state(cl); } void __hidden uclient_backend_reset_state(struct uclient *cl) { cl->data_eof = false; cl->eof = false; cl->error_code = 0; uloop_timeout_cancel(&cl->timeout); uloop_timeout_cancel(&cl->read_notify); } const char * uclient_strerror(unsigned err) { switch (err) { case UCLIENT_ERROR_UNKNOWN: return "unknown error"; case UCLIENT_ERROR_CONNECT: return "connect failed"; case UCLIENT_ERROR_TIMEDOUT: return "timeout"; case UCLIENT_ERROR_SSL_INVALID_CERT: return "ssl invalid cert"; case UCLIENT_ERROR_SSL_CN_MISMATCH: return "ssl cn mismatch"; case UCLIENT_ERROR_MISSING_SSL_CONTEXT: return "missing ssl context"; default: return "invalid error code"; } }