diff --git a/config.sh b/config.sh

index fc1134c0c98d9f57cb2cfdbb3b9d6faf35001dfd..70c4489bf5fd48fc0eb636bf739a5d82dcff8663 100644

--- a/config.sh

+++ b/config.sh

@@ -5,6 +5,7 @@ AS=${AS:-as}

CC=${CC:-cc}

CFLAGS=${CFLAGS:-}

LD=${LD:-ld}

+LIBSSL=

for arg

do

@@ -12,6 +13,9 @@ # TODO: Add args for install directories

case "$arg" in

--prefix=*)

PREFIX=${arg#*=}

+ ;;

+ --with-libssl=*)

+ LIBSSL=${arg#*=}

;;

esac

done

@@ -72,12 +76,27 @@ return 1

fi

}

+find_library() {

+ name="$1"

+ pc="$2"

+ printf "Checking for %s... " "$name"

+ if ! pkg-config "$pc" 2>/dev/null

+ then

+ printf "NOT FOUND\n"

+ printf "Tried pkg-config %s\n" "$pc"

+ return 1

+ fi

+ printf "OK\n"

+ CFLAGS="$CFLAGS $(pkg-config --cflags "$pc")"

+ LIBS="$LIBS $(pkg-config --libs "$pc")"

+}

+

run_configure() {

mkdir -p $outdir

for flag in -g -std=c11 -D_XOPEN_SOURCE=700 -Wall -Wextra -Werror -pedantic

do

- printf "Checking for $flag... "

+ printf "Checking for %s... " "$flag"

if test_cflags "$flag"

then

echo yes

@@ -86,9 +105,13 @@ echo no

fi

done

+ find_library OpenSSL libssl

+ find_library OpenSSL libcrypto

+

printf "Creating $outdir/config.mk... "

cat <<-EOF > "$outdir"/config.mk

CC=$CC

+ LIBS=$LIBS

PREFIX=${PREFIX:-/usr/local}

OUTDIR=${outdir}

_INSTDIR=\$(DESTDIR)\$(PREFIX)

diff --git a/configure b/configure

index 7b1a48b785b7ab1a8811cb36ea9ee00a68fc9ff8..680b57fc9e319c2707e0cc2ded0bc22597544d78 100755

--- a/configure

+++ b/configure

@@ -4,7 +4,10 @@ eval ". $srcdir/config.sh"

gmni() {

genrules gmnic \

- src/gmnic.c

+ src/client.c \

+ src/escape.c \

+ src/gmnic.c \

+ src/url.c

}

all="gmnic"

diff --git a/include/client.h b/include/client.h

new file mode 100644

index 0000000000000000000000000000000000000000..dbd73234311331ea52650d59dbd9cf535b73298b

--- /dev/null

+++ b/include/client.h

@@ -0,0 +1,67 @@

+#ifndef GEMINI_CLIENT_H

+#define GEMINI_CLIENT_H

+#include <netdb.h>

+#include <openssl/ssl.h>

+#include <sys/socket.h>

+

+struct gemini_response {

+ int status;

+ char *meta;

+

+ // Response body may be read from here if appropriate:

+ BIO *bio;

+

+ // Connection state

+ SSL_CTX *ssl_ctx;

+ SSL *ssl;

+ int fd;

+};

+

+struct gemini_options {

+ // If NULL, an SSL context will be created. If unset, the ssl field

+ // must also be NULL.

+ SSL_CTX *ssl_ctx;

+

+ // If NULL, an SSL connection will be established. If set, it is

+ // presumed that the caller pre-established the SSL connection.

+ SSL *ssl;

+

+ // If ai_family != AF_UNSPEC (the default value on most systems), the

+ // client will connect to this address and skip name resolution.

+ struct addrinfo *addr;

+

+ // If non-NULL, these hints are provided to getaddrinfo. Useful, for

+ // example, to force IPv4/IPv6.

+ struct addrinfo *hints;

+};

+

+enum gemini_result {

+ GEMINI_OK,

+ GEMINI_ERR_OOM,

+ GEMINI_ERR_INVALID_URL,

+ // status is set to the return value from getaddrinfo

+ GEMINI_ERR_RESOLVE,

+ // status is set to errno

+ GEMINI_ERR_CONNECT,

+ // use SSL_get_error(resp->ssl, resp->status) to get details

+ GEMINI_ERR_SSL,

+ GEMINI_ERR_IO,

+};

+

+// Requests the specified URL via the gemini protocol. If options is non-NULL,

+// it may specify some additional configuration to adjust client behavior.

+//

+// Returns a value indicating the success of the request. If GEMINI_OK is

+// returned, the response details shall be written to the gemini_response

+// argument.

+enum gemini_result gemini_request(const char *url,

+ struct gemini_options *options,

+ struct gemini_response *resp);

+

+// Must be called after gemini_request in order to free up the resources

+// allocated during the request. If you intend to re-use the SSL_CTX provided by

+// gemini_options, set the ctx pointer to NULL before calling

+// gemini_response_finish.

+void gemini_response_finish(struct gemini_response *resp);

+

+#endif

diff --git a/include/escape.h b/include/escape.h

new file mode 100644

index 0000000000000000000000000000000000000000..a1184ba3141b2992b0b18e44105bdc4474a7d8c9

--- /dev/null

+++ b/include/escape.h

@@ -0,0 +1,175 @@

+#ifndef ESCAPE_H

+#define ESCAPE_H

+/***************************************************************************

+ * _ _ ____ _

+ * Project ___| | | | _ \| |

+ * / __| | | | |_) | |

+ * | (__| |_| | _ <| |___

+ * \___|\___/|_| \_\_____|

+ *

+ * Copyright (C) 1998 - 2018, Daniel Stenberg, <daniel@haxx.se>, et al.

+ *

+ * This software is licensed as described in the file COPYING, which

+ * you should have received as part of this distribution. The terms

+ * are also available at https://curl.haxx.se/docs/copyright.html.

+ *

+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell

+ * copies of the Software, and permit persons to whom the Software is

+ * furnished to do so, under the terms of the COPYING file.

+ *

+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY

+ * KIND, either express or implied.

+ *

+ ***************************************************************************/

+

+/* from curl.h */

+typedef enum {

+ CURLE_OK = 0,

+ CURLE_UNSUPPORTED_PROTOCOL, /* 1 */

+ CURLE_FAILED_INIT, /* 2 */

+ CURLE_URL_MALFORMAT, /* 3 */

+ CURLE_NOT_BUILT_IN, /* 4 - [was obsoleted in August 2007 for

+ 7.17.0, reused in April 2011 for 7.21.5] */

+ CURLE_COULDNT_RESOLVE_PROXY, /* 5 */

+ CURLE_COULDNT_RESOLVE_HOST, /* 6 */

+ CURLE_COULDNT_CONNECT, /* 7 */

+ CURLE_WEIRD_SERVER_REPLY, /* 8 */

+ CURLE_REMOTE_ACCESS_DENIED, /* 9 a service was denied by the server

+ due to lack of access - when login fails

+ this is not returned. */

+ CURLE_FTP_ACCEPT_FAILED, /* 10 - [was obsoleted in April 2006 for

+ 7.15.4, reused in Dec 2011 for 7.24.0]*/

+ CURLE_FTP_WEIRD_PASS_REPLY, /* 11 */

+ CURLE_FTP_ACCEPT_TIMEOUT, /* 12 - timeout occurred accepting server

+ [was obsoleted in August 2007 for 7.17.0,

+ reused in Dec 2011 for 7.24.0]*/

+ CURLE_FTP_WEIRD_PASV_REPLY, /* 13 */

+ CURLE_FTP_WEIRD_227_FORMAT, /* 14 */

+ CURLE_FTP_CANT_GET_HOST, /* 15 */

+ CURLE_HTTP2, /* 16 - A problem in the http2 framing layer.

+ [was obsoleted in August 2007 for 7.17.0,

+ reused in July 2014 for 7.38.0] */

+ CURLE_FTP_COULDNT_SET_TYPE, /* 17 */

+ CURLE_PARTIAL_FILE, /* 18 */

+ CURLE_FTP_COULDNT_RETR_FILE, /* 19 */

+ CURLE_OBSOLETE20, /* 20 - NOT USED */

+ CURLE_QUOTE_ERROR, /* 21 - quote command failure */

+ CURLE_HTTP_RETURNED_ERROR, /* 22 */

+ CURLE_WRITE_ERROR, /* 23 */

+ CURLE_OBSOLETE24, /* 24 - NOT USED */

+ CURLE_UPLOAD_FAILED, /* 25 - failed upload "command" */

+ CURLE_READ_ERROR, /* 26 - couldn't open/read from file */

+ CURLE_OUT_OF_MEMORY, /* 27 */

+ /* Note: CURLE_OUT_OF_MEMORY may sometimes indicate a conversion error

+ instead of a memory allocation error if CURL_DOES_CONVERSIONS

+ is defined

+ */

+ CURLE_OPERATION_TIMEDOUT, /* 28 - the timeout time was reached */

+ CURLE_OBSOLETE29, /* 29 - NOT USED */

+ CURLE_FTP_PORT_FAILED, /* 30 - FTP PORT operation failed */

+ CURLE_FTP_COULDNT_USE_REST, /* 31 - the REST command failed */

+ CURLE_OBSOLETE32, /* 32 - NOT USED */

+ CURLE_RANGE_ERROR, /* 33 - RANGE "command" didn't work */

+ CURLE_HTTP_POST_ERROR, /* 34 */

+ CURLE_SSL_CONNECT_ERROR, /* 35 - wrong when connecting with SSL */

+ CURLE_BAD_DOWNLOAD_RESUME, /* 36 - couldn't resume download */

+ CURLE_FILE_COULDNT_READ_FILE, /* 37 */

+ CURLE_LDAP_CANNOT_BIND, /* 38 */

+ CURLE_LDAP_SEARCH_FAILED, /* 39 */

+ CURLE_OBSOLETE40, /* 40 - NOT USED */

+ CURLE_FUNCTION_NOT_FOUND, /* 41 - NOT USED starting with 7.53.0 */

+ CURLE_ABORTED_BY_CALLBACK, /* 42 */

+ CURLE_BAD_FUNCTION_ARGUMENT, /* 43 */

+ CURLE_OBSOLETE44, /* 44 - NOT USED */

+ CURLE_INTERFACE_FAILED, /* 45 - CURLOPT_INTERFACE failed */

+ CURLE_OBSOLETE46, /* 46 - NOT USED */

+ CURLE_TOO_MANY_REDIRECTS, /* 47 - catch endless re-direct loops */

+ CURLE_UNKNOWN_OPTION, /* 48 - User specified an unknown option */

+ CURLE_TELNET_OPTION_SYNTAX, /* 49 - Malformed telnet option */

+ CURLE_OBSOLETE50, /* 50 - NOT USED */

+ CURLE_OBSOLETE51, /* 51 - NOT USED */

+ CURLE_GOT_NOTHING, /* 52 - when this is a specific error */

+ CURLE_SSL_ENGINE_NOTFOUND, /* 53 - SSL crypto engine not found */

+ CURLE_SSL_ENGINE_SETFAILED, /* 54 - can not set SSL crypto engine as

+ default */

+ CURLE_SEND_ERROR, /* 55 - failed sending network data */

+ CURLE_RECV_ERROR, /* 56 - failure in receiving network data */

+ CURLE_OBSOLETE57, /* 57 - NOT IN USE */

+ CURLE_SSL_CERTPROBLEM, /* 58 - problem with the local certificate */

+ CURLE_SSL_CIPHER, /* 59 - couldn't use specified cipher */

+ CURLE_PEER_FAILED_VERIFICATION, /* 60 - peer's certificate or fingerprint

+ wasn't verified fine */

+ CURLE_BAD_CONTENT_ENCODING, /* 61 - Unrecognized/bad encoding */

+ CURLE_LDAP_INVALID_URL, /* 62 - Invalid LDAP URL */

+ CURLE_FILESIZE_EXCEEDED, /* 63 - Maximum file size exceeded */

+ CURLE_USE_SSL_FAILED, /* 64 - Requested FTP SSL level failed */

+ CURLE_SEND_FAIL_REWIND, /* 65 - Sending the data requires a rewind

+ that failed */

+ CURLE_SSL_ENGINE_INITFAILED, /* 66 - failed to initialise ENGINE */

+ CURLE_LOGIN_DENIED, /* 67 - user, password or similar was not

+ accepted and we failed to login */

+ CURLE_TFTP_NOTFOUND, /* 68 - file not found on server */

+ CURLE_TFTP_PERM, /* 69 - permission problem on server */

+ CURLE_REMOTE_DISK_FULL, /* 70 - out of disk space on server */

+ CURLE_TFTP_ILLEGAL, /* 71 - Illegal TFTP operation */

+ CURLE_TFTP_UNKNOWNID, /* 72 - Unknown transfer ID */

+ CURLE_REMOTE_FILE_EXISTS, /* 73 - File already exists */

+ CURLE_TFTP_NOSUCHUSER, /* 74 - No such user */

+ CURLE_CONV_FAILED, /* 75 - conversion failed */

+ CURLE_CONV_REQD, /* 76 - caller must register conversion

+ callbacks using curl_easy_setopt options

+ CURLOPT_CONV_FROM_NETWORK_FUNCTION,

+ CURLOPT_CONV_TO_NETWORK_FUNCTION, and

+ CURLOPT_CONV_FROM_UTF8_FUNCTION */

+ CURLE_SSL_CACERT_BADFILE, /* 77 - could not load CACERT file, missing

+ or wrong format */

+ CURLE_REMOTE_FILE_NOT_FOUND, /* 78 - remote file not found */

+ CURLE_SSH, /* 79 - error from the SSH layer, somewhat

+ generic so the error message will be of

+ interest when this has happened */

+

+ CURLE_SSL_SHUTDOWN_FAILED, /* 80 - Failed to shut down the SSL

+ connection */

+ CURLE_AGAIN, /* 81 - socket is not ready for send/recv,

+ wait till it's ready and try again (Added

+ in 7.18.2) */

+ CURLE_SSL_CRL_BADFILE, /* 82 - could not load CRL file, missing or

+ wrong format (Added in 7.19.0) */

+ CURLE_SSL_ISSUER_ERROR, /* 83 - Issuer check failed. (Added in

+ 7.19.0) */

+ CURLE_FTP_PRET_FAILED, /* 84 - a PRET command failed */

+ CURLE_RTSP_CSEQ_ERROR, /* 85 - mismatch of RTSP CSeq numbers */

+ CURLE_RTSP_SESSION_ERROR, /* 86 - mismatch of RTSP Session Ids */

+ CURLE_FTP_BAD_FILE_LIST, /* 87 - unable to parse FTP file list */

+ CURLE_CHUNK_FAILED, /* 88 - chunk callback reported error */

+ CURLE_NO_CONNECTION_AVAILABLE, /* 89 - No connection available, the

+ session will be queued */

+ CURLE_SSL_PINNEDPUBKEYNOTMATCH, /* 90 - specified pinned public key did not

+ match */

+ CURLE_SSL_INVALIDCERTSTATUS, /* 91 - invalid certificate status */

+ CURLE_HTTP2_STREAM, /* 92 - stream error in HTTP/2 framing layer

+ */

+ CURLE_RECURSIVE_API_CALL, /* 93 - an api function was called from

+ inside a callback */

+ CURL_LAST /* never use! */

+} CURLcode;

+

+/* Escape and unescape URL encoding in strings. The functions return a new

+ * allocated string or NULL if an error occurred. */

+

+bool Curl_isunreserved(unsigned char in);

+CURLcode Curl_urldecode(const char *string, size_t length,

+ char **ostring, size_t *olen,

+ bool reject_crlf);

+

+char *curl_easy_escape(const char *string, int length);

+

+char *curl_escape(const char *string, int length);

+

+char *curl_easy_unescape(const char *string,

+ int length, int *outlength);

+

+char *curl_unescape(const char *string, int length);

+

+

+#endif /* HEADER_CURL_ESCAPE_H */

diff --git a/include/url.h b/include/url.h

new file mode 100644

index 0000000000000000000000000000000000000000..155fd55740dbe47a498062713aca217ab259734f

--- /dev/null

+++ b/include/url.h

@@ -0,0 +1,103 @@

+#ifndef URLAPI_H

+#define URLAPI_H

+/***************************************************************************

+ * _ _ ____ _

+ * Project ___| | | | _ \| |

+ * / __| | | | |_) | |

+ * | (__| |_| | _ <| |___

+ * \___|\___/|_| \_\_____|

+ *

+ * Copyright (C) 2018, Daniel Stenberg, <daniel@haxx.se>, et al.

+ *

+ * This software is licensed as described in the file COPYING, which

+ * you should have received as part of this distribution. The terms

+ * are also available at https://curl.haxx.se/docs/copyright.html.

+ *

+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell

+ * copies of the Software, and permit persons to whom the Software is

+ * furnished to do so, under the terms of the COPYING file.

+ *

+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY

+ * KIND, either express or implied.

+ *

+ ***************************************************************************/

+

+/* the error codes for the URL API */

+typedef enum {

+ CURLUE_OK,

+ CURLUE_BAD_HANDLE, /* 1 */

+ CURLUE_BAD_PARTPOINTER, /* 2 */

+ CURLUE_MALFORMED_INPUT, /* 3 */

+ CURLUE_BAD_PORT_NUMBER, /* 4 */

+ CURLUE_UNSUPPORTED_SCHEME, /* 5 */

+ CURLUE_URLDECODE, /* 6 */

+ CURLUE_OUT_OF_MEMORY, /* 7 */

+ CURLUE_USER_NOT_ALLOWED, /* 8 */

+ CURLUE_UNKNOWN_PART, /* 9 */

+ CURLUE_NO_SCHEME, /* 10 */

+ CURLUE_NO_USER, /* 11 */

+ CURLUE_NO_PASSWORD, /* 12 */

+ CURLUE_NO_OPTIONS, /* 13 */

+ CURLUE_NO_HOST, /* 14 */

+ CURLUE_NO_PORT, /* 15 */

+ CURLUE_NO_QUERY, /* 16 */

+ CURLUE_NO_FRAGMENT /* 17 */

+} CURLUcode;

+

+typedef enum {

+ CURLUPART_URL,

+ CURLUPART_SCHEME,

+ CURLUPART_USER,

+ CURLUPART_PASSWORD,

+ CURLUPART_OPTIONS,

+ CURLUPART_HOST,

+ CURLUPART_PORT,

+ CURLUPART_PATH,

+ CURLUPART_QUERY,

+ CURLUPART_FRAGMENT

+} CURLUPart;

+

+#define CURLU_PATH_AS_IS (1<<4) /* leave dot sequences */

+#define CURLU_DISALLOW_USER (1<<5) /* no user+password allowed */

+#define CURLU_URLDECODE (1<<6) /* URL decode on get */

+#define CURLU_URLENCODE (1<<7) /* URL encode on set */

+#define CURLU_APPENDQUERY (1<<8) /* append a form style part */

+

+typedef struct Curl_URL CURLU;

+

+/*

+ * curl_url() creates a new CURLU handle and returns a pointer to it.

+ * Must be freed with curl_url_cleanup().

+ */

+struct Curl_URL *curl_url(void);

+

+/*

+ * curl_url_cleanup() frees the CURLU handle and related resources used for

+ * the URL parsing. It will not free strings previously returned with the URL

+ * API.

+ */

+void curl_url_cleanup(struct Curl_URL *handle);

+

+/*

+ * curl_url_dup() duplicates a CURLU handle and returns a new copy. The new

+ * handle must also be freed with curl_url_cleanup().

+ */

+struct Curl_URL *curl_url_dup(struct Curl_URL *in);

+

+/*

+ * curl_url_get() extracts a specific part of the URL from a CURLU

+ * handle. Returns error code. The returned pointer MUST be freed with

+ * free() afterwards.

+ */

+CURLUcode curl_url_get(struct Curl_URL *handle, CURLUPart what,

+ char **part, unsigned int flags);

+

+/*

+ * curl_url_set() sets a specific part of the URL in a CURLU handle. Returns

+ * error code. The passed in string will be copied. Passing a NULL instead of

+ * a part string, clears that part.

+ */

+CURLUcode curl_url_set(struct Curl_URL *handle, CURLUPart what,

+ const char *part, unsigned int flags);

+

+#endif

diff --git a/src/client.c b/src/client.c

new file mode 100644

index 0000000000000000000000000000000000000000..5f2debb52c310f88e82ad83ca2251ba233d6a704

--- /dev/null

+++ b/src/client.c

@@ -0,0 +1,190 @@

+#include <assert.h>

+#include <errno.h>

+#include <netdb.h>

+#include <openssl/bio.h>

+#include <openssl/ssl.h>

+#include <stdlib.h>

+#include <string.h>

+#include <sys/socket.h>

+#include <sys/types.h>

+#include <unistd.h>

+#include "client.h"

+#include "url.h"

+

+static enum gemini_result

+gemini_get_addrinfo(struct Curl_URL *uri, struct gemini_options *options,

+ struct gemini_response *resp, struct addrinfo **addr)

+{

+ int port = 1965;

+ char *uport;

+ if (curl_url_get(uri, CURLUPART_PORT, &uport, 0) == CURLUE_OK) {

+ port = (int)strtol(uport, NULL, 10);

+ free(uport);

+ }

+

+ if (options && options->addr->ai_family != AF_UNSPEC) {

+ *addr = options->addr;

+ } else {

+ struct addrinfo hints = {0};

+ if (options && options->hints) {

+ hints = *options->hints;

+ } else {

+ hints.ai_family = AF_UNSPEC;

+ hints.ai_socktype = SOCK_STREAM;

+ }

+

+ char pbuf[7];

+ snprintf(pbuf, sizeof(pbuf), "%d", port);

+

+ char *domain;

+ CURLUcode uc = curl_url_get(uri, CURLUPART_HOST, &domain, 0);

+ assert(uc == CURLUE_OK);

+

+ int r = getaddrinfo(domain, pbuf, &hints, addr);

+ free(domain);

+ if (r != 0) {

+ resp->status = r;

+ return GEMINI_ERR_RESOLVE;

+ }

+ }

+

+ return GEMINI_OK;

+}

+

+static enum gemini_result

+gemini_connect(struct Curl_URL *uri, struct gemini_options *options,

+ struct gemini_response *resp, int *sfd)

+{

+ struct addrinfo *addr;

+ enum gemini_result res = gemini_get_addrinfo(uri, options, resp, &addr);

+ if (res != GEMINI_OK) {

+ goto cleanup;

+ }

+

+ struct addrinfo *rp;

+ for (rp = addr; rp != NULL; rp = rp->ai_next) {

+ *sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);

+ if (*sfd == -1) {

+ continue;

+ }

+ if (connect(*sfd, rp->ai_addr, rp->ai_addrlen) != -1) {

+ break;

+ }

+ close(*sfd);

+ }

+ if (rp == NULL) {

+ resp->status = errno;

+ res = GEMINI_ERR_CONNECT;

+ return res;

+ }

+

+cleanup:

+ if (!options || !options->addr) {

+ freeaddrinfo(addr);

+ }

+ return res;

+}

+

+#define GEMINI_META_MAXLEN 1024

+#define GEMINI_STATUS_MAXLEN 2

+

+enum gemini_result

+gemini_request(const char *url, struct gemini_options *options,

+ struct gemini_response *resp)

+{

+ assert(url);

+ assert(resp);

+ resp->meta = NULL;

+ if (strlen(url) > 1024) {

+ return GEMINI_ERR_INVALID_URL;

+ }

+

+ struct Curl_URL *uri = curl_url();

+ if (!uri) {

+ return GEMINI_ERR_OOM;

+ }

+ if (curl_url_set(uri, CURLUPART_URL, url, 0) != CURLUE_OK) {

+ return GEMINI_ERR_INVALID_URL;

+ }

+

+ enum gemini_result res = GEMINI_OK;

+ if (options && options->ssl_ctx) {

+ resp->ssl_ctx = options->ssl_ctx;

+ SSL_CTX_up_ref(options->ssl_ctx);

+ } else {

+ resp->ssl_ctx = SSL_CTX_new(TLS_method());

+ assert(resp->ssl_ctx);

+ }

+

+ BIO *sbio = BIO_new(BIO_f_ssl());

+ if (options && options->ssl) {

+ resp->ssl = options->ssl;

+ SSL_up_ref(resp->ssl);

+ BIO_set_ssl(sbio, resp->ssl, 0);

+ resp->fd = -1;

+ } else {

+ res = gemini_connect(uri, options, resp, &resp->fd);

+ if (res != GEMINI_OK) {

+ goto cleanup;

+ }

+

+ resp->ssl = SSL_new(resp->ssl_ctx);

+ assert(resp->ssl);

+ int r = SSL_set_fd(resp->ssl, resp->fd);

+ if (r != 1) {

+ resp->status = r;

+ res = GEMINI_ERR_SSL;

+ goto cleanup;

+ }

+ r = SSL_connect(resp->ssl);

+ if (r != 1) {

+ resp->status = r;

+ res = GEMINI_ERR_SSL;

+ goto cleanup;

+ }

+ BIO_set_ssl(sbio, resp->ssl, 0);

+ }

+

+ resp->bio = BIO_new(BIO_f_buffer());

+ BIO_push(resp->bio, sbio);

+

+ char req[1024 + 3];

+ int r = snprintf(req, sizeof(req), "%s\r\n", url);

+ assert(r > 0);

+

+ r = BIO_puts(sbio, req);

+ if (r == -1) {

+ res = GEMINI_ERR_IO;

+ goto cleanup;

+ }

+ assert(r == (int)strlen(req));

+

+ char buf[GEMINI_META_MAXLEN

+ + GEMINI_STATUS_MAXLEN

+ + 2 /* CRLF */ + 1 /* NUL */];

+ r = BIO_gets(resp->bio, buf, sizeof(buf));

+ if (r == -1) {

+ res = GEMINI_ERR_IO;

+ goto cleanup;

+ }

+

+cleanup:

+ curl_url_cleanup(uri);

+ return res;

+}

+

+void

+gemini_response_finish(struct gemini_response *resp)

+{

+ if (!resp) {

+ return;

+ }

+ if (resp->fd != -1) {

+ close(resp->fd);

+ }

+ BIO_free(BIO_pop(resp->bio)); // ssl bio

+ BIO_free(resp->bio); // buffered bio

+ SSL_free(resp->ssl);

+ SSL_CTX_free(resp->ssl_ctx);

+ free(resp->meta);

+}

diff --git a/src/escape.c b/src/escape.c

new file mode 100644

index 0000000000000000000000000000000000000000..d083da699d96f01a94a6ce8a86e41f0a47c54ffe

--- /dev/null

+++ b/src/escape.c

@@ -0,0 +1,213 @@

+/***************************************************************************

+ * _ _ ____ _

+ * Project ___| | | | _ \| |

+ * / __| | | | |_) | |

+ * | (__| |_| | _ <| |___

+ * \___|\___/|_| \_\_____|

+ *

+ * Copyright (C) 1998 - 2018, Daniel Stenberg, <daniel@haxx.se>, et al.

+ *

+ * This software is licensed as described in the file COPYING, which

+ * you should have received as part of this distribution. The terms

+ * are also available at https://curl.haxx.se/docs/copyright.html.

+ *

+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell

+ * copies of the Software, and permit persons to whom the Software is

+ * furnished to do so, under the terms of the COPYING file.

+ *

+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY

+ * KIND, either express or implied.

+ *

+ ***************************************************************************/

+

+/* Escape and unescape URL encoding in strings. The functions return a new

+ * allocated string or NULL if an error occurred. */

+#include <ctype.h>

+#include <limits.h>

+#include <stdbool.h>

+#include <stddef.h>

+#include <stdio.h>

+#include <stdlib.h>

+#include <string.h>

+#include "escape.h"

+

+/* Portable character check (remember EBCDIC). Do not use isalnum() because

+ its behavior is altered by the current locale.

+ See https://tools.ietf.org/html/rfc3986#section-2.3

+*/

+bool Curl_isunreserved(unsigned char in)

+{

+ switch(in) {

+ case '0': case '1': case '2': case '3': case '4':

+ case '5': case '6': case '7': case '8': case '9':

+ case 'a': case 'b': case 'c': case 'd': case 'e':

+ case 'f': case 'g': case 'h': case 'i': case 'j':

+ case 'k': case 'l': case 'm': case 'n': case 'o':

+ case 'p': case 'q': case 'r': case 's': case 't':

+ case 'u': case 'v': case 'w': case 'x': case 'y': case 'z':

+ case 'A': case 'B': case 'C': case 'D': case 'E':

+ case 'F': case 'G': case 'H': case 'I': case 'J':

+ case 'K': case 'L': case 'M': case 'N': case 'O':

+ case 'P': case 'Q': case 'R': case 'S': case 'T':

+ case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z':

+ case '-': case '.': case '_': case '~':

+ return true;

+ default:

+ break;

+ }

+ return false;

+}

+

+/* for ABI-compatibility with previous versions */

+char *curl_escape(const char *string, int inlength)

+{

+ return curl_easy_escape(string, inlength);

+}

+

+/* for ABI-compatibility with previous versions */

+char *curl_unescape(const char *string, int length)

+{

+ return curl_easy_unescape(string, length, NULL);

+}

+

+char *curl_easy_escape(const char *string, int inlength)

+{

+ size_t alloc;

+ char *ns;

+ char *testing_ptr = NULL;

+ size_t newlen;

+ size_t strindex = 0;

+ size_t length;

+

+ if(inlength < 0)

+ return NULL;

+

+ alloc = (inlength?(size_t)inlength:strlen(string)) + 1;

+ newlen = alloc;

+

+ ns = malloc(alloc);

+ if(!ns)

+ return NULL;

+

+ length = alloc-1;

+ while(length--) {

+ unsigned char in = *string; /* we need to treat the characters unsigned */

+

+ if(Curl_isunreserved(in))

+ /* just copy this */

+ ns[strindex++] = in;

+ else {

+ /* encode it */

+ newlen += 2; /* the size grows with two, since this'll become a %XX */

+ if(newlen > alloc) {

+ alloc *= 2;

+ testing_ptr = realloc(ns, alloc);

+ if(!testing_ptr)

+ return NULL;

+ ns = testing_ptr;

+ }

+

+ snprintf(&ns[strindex], 4, "%%%02X", in);

+

+ strindex += 3;

+ }

+ string++;

+ }

+ ns[strindex] = 0; /* terminate it */

+ return ns;

+}

+

+/*

+ * Curl_urldecode() URL decodes the given string.

+ *

+ * Optionally detects control characters (byte codes lower than 32) in the

+ * data and rejects such data.

+ *

+ * Returns a pointer to a malloced string in *ostring with length given in

+ * *olen. If length == 0, the length is assumed to be strlen(string).

+ */

+CURLcode Curl_urldecode(const char *string, size_t length,

+ char **ostring, size_t *olen,

+ bool reject_ctrl)

+{

+ size_t alloc = (length?length:strlen(string)) + 1;

+ char *ns = malloc(alloc);

+ size_t strindex = 0;

+ unsigned long hex;

+

+ if(!ns)

+ return CURLE_OUT_OF_MEMORY;

+

+ while(--alloc > 0) {

+ unsigned char in = *string;

+ if(('%' == in) && (alloc > 2) &&

+ isxdigit(string[1]) && isxdigit(string[2])) {

+ /* this is two hexadecimal digits following a '%' */

+ char hexstr[3];

+ char *ptr;

+ hexstr[0] = string[1];

+ hexstr[1] = string[2];

+ hexstr[2] = 0;

+

+ hex = strtoul(hexstr, &ptr, 16);

+

+ in = (unsigned char)hex; /* this long is never bigger than 255 anyway */

+

+ string += 2;

+ alloc -= 2;

+ }

+

+ if(reject_ctrl && (in < 0x20)) {

+ free(ns);

+ return CURLE_URL_MALFORMAT;

+ }

+

+ ns[strindex++] = in;

+ string++;

+ }

+ ns[strindex] = 0; /* terminate it */

+

+ if(olen)

+ /* store output size */

+ *olen = strindex;

+

+ /* store output string */

+ *ostring = ns;

+

+ return CURLE_OK;

+}

+

+/*

+ * Unescapes the given URL escaped string of given length. Returns a

+ * pointer to a malloced string with length given in *olen.

+ * If length == 0, the length is assumed to be strlen(string).

+ * If olen == NULL, no output length is stored.

+ */

+char *curl_easy_unescape(const char *string, int length, int *olen)

+{

+ char *str = NULL;

+ if(length >= 0) {

+ size_t inputlen = length;

+ size_t outputlen;

+ CURLcode res = Curl_urldecode(string, inputlen, &str, &outputlen, false);

+ if(res)

+ return NULL;

+

+ if(olen) {

+ if(outputlen <= (size_t) INT_MAX)

+ *olen = (int)outputlen;

+ else

+ /* too large to return in an int, fail! */

+ free(str);

+ }

+ }

+ return str;

+}

+

+/* For operating systems/environments that use different malloc/free

+ systems for the app and for this library, we provide a free that uses

+ the library's memory system */

+void curl_free(void *p)

+{

+ free(p);

+}

diff --git a/src/gmnic.c b/src/gmnic.c

index 35a49493cc6af2486d055c9bde5a576942c2e40a..7b2eb183ebfe67dce0aaf8c6010bf6785b503df6 100644

--- a/src/gmnic.c

+++ b/src/gmnic.c

@@ -1,8 +1,91 @@

+#include <assert.h>

+#include <getopt.h>

+#include <openssl/err.h>

+#include <stdbool.h>

#include <stdio.h>

+#include <stdlib.h>

+#include "client.h"

+

+static void

+usage(char *argv_0)

+{

+ fprintf(stderr,

+ "usage: %s [-LI] [-C cert] [-d input] gemini://...\n",

+ argv_0);

+}

int

-main(int argc, char *argv[]) {

- (void)argc; (void)argv;

- printf("Hello, world!\n");

+main(int argc, char *argv[])

+{

+ bool headers = false, follow_redirect = false;

+ char *certificate = NULL, *input = NULL;

+

+ int c;

+ while ((c = getopt(argc, argv, "46C:d:hLI")) != -1) {

+ switch (c) {

+ case '4':

+ assert(0); // TODO

+ break;

+ case '6':

+ assert(0); // TODO

+ break;

+ case 'C':

+ certificate = optarg;

+ break;

+ case 'd':

+ input = optarg;

+ break;

+ case 'h':

+ usage(argv[0]);

+ return 0;

+ case 'L':

+ follow_redirect = true;

+ break;

+ case 'I':

+ headers = true;

+ break;

+ default:

+ fprintf(stderr, "fatal: unknown flag %c", c);

+ return 1;

+ }

+ }

+ if (optind != argc - 1) {

+ usage(argv[0]);

+ return 1;

+ }

+

+ SSL_load_error_strings();

+ ERR_load_crypto_strings();

+

+ struct gemini_response resp;

+ enum gemini_result r = gemini_request(argv[optind], NULL, &resp);

+ switch (r) {

+ case GEMINI_OK:

+ printf("OK\n");

+ break;

+ case GEMINI_ERR_OOM:

+ printf("OOM\n");

+ break;

+ case GEMINI_ERR_INVALID_URL:

+ printf("INVALID_URL\n");

+ break;

+ case GEMINI_ERR_RESOLVE:

+ printf("RESOLVE\n");

+ break;

+ case GEMINI_ERR_CONNECT:

+ printf("CONNECT\n");

+ break;

+ case GEMINI_ERR_SSL:

+ fprintf(stderr, "SSL error: %s\n", ERR_error_string(

+ SSL_get_error(resp.ssl, resp.status), NULL));

+ break;

+ }

+

+ gemini_response_finish(&resp);

+

+ (void)headers;

+ (void)follow_redirect;

+ (void)certificate;

+ (void)input;

return 0;

}

diff --git a/src/url.c b/src/url.c

new file mode 100644

index 0000000000000000000000000000000000000000..47e31b5fcbeeecb5bc3962585311b20e3518bb73

--- /dev/null

+++ b/src/url.c

@@ -0,0 +1,1448 @@

+/***************************************************************************

+ * _ _ ____ _

+ * Project ___| | | | _ \| |

+ * / __| | | | |_) | |

+ * | (__| |_| | _ <| |___

+ * \___|\___/|_| \_\_____|

+ *

+ * Copyright (C) 1998 - 2018, Daniel Stenberg, <daniel@haxx.se>, et al.

+ *

+ * This software is licensed as described in the file COPYING, which

+ * you should have received as part of this distribution. The terms

+ * are also available at https://curl.haxx.se/docs/copyright.html.

+ *

+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell

+ * copies of the Software, and permit persons to whom the Software is

+ * furnished to do so, under the terms of the COPYING file.

+ *

+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY

+ * KIND, either express or implied.

+ *

+ ***************************************************************************/

+

+#define MAX_SCHEME_LEN 8

+

+#include <assert.h>

+#include <ctype.h>

+#include <stdarg.h>

+#include <stdbool.h>

+#include <stdio.h>

+#include <stdlib.h>

+#include <string.h>

+#include <strings.h>

+#include "escape.h"

+#include "url.h"

+

+/* Provided by gmni */

+static char *

+aprintf(const char *fmt, ...)

+{

+ va_list ap;

+ va_start(ap, fmt);

+ int n = vsnprintf(NULL, 0, fmt, ap);

+ va_end(ap);

+

+ char *strp = calloc(n + 1, 1);

+ assert(strp);

+

+ va_start(ap, fmt);

+ n = vsnprintf(strp, n + 1, fmt, ap);

+ va_end(ap);

+ return strp;

+}

+

+/* via lib/dotdot.c */

+char *Curl_dedotdotify(const char *input)

+{

+ size_t inlen = strlen(input);

+ char *clone;

+ size_t clen = inlen; /* the length of the cloned input */

+ char *out = malloc(inlen + 1);

+ char *outptr;

+ char *orgclone;

+ char *queryp;

+ if(!out)

+ return NULL; /* out of memory */

+

+ *out = 0; /* zero terminates, for inputs like "./" */

+

+ /* get a cloned copy of the input */

+ clone = strdup(input);

+ if(!clone) {

+ free(out);

+ return NULL;

+ }

+ orgclone = clone;

+ outptr = out;

+

+ if(!*clone) {

+ /* zero length string, return that */

+ free(out);

+ return clone;

+ }

+

+ /*

+ * To handle query-parts properly, we must find it and remove it during the

+ * dotdot-operation and then append it again at the end to the output

+ * string.

+ */

+ queryp = strchr(clone, '?');

+ if(queryp)

+ *queryp = 0;

+

+ do {

+

+ /* A. If the input buffer begins with a prefix of "../" or "./", then

+ remove that prefix from the input buffer; otherwise, */

+

+ if(!strncmp("./", clone, 2)) {

+ clone += 2;

+ clen -= 2;

+ }

+ else if(!strncmp("../", clone, 3)) {

+ clone += 3;

+ clen -= 3;

+ }

+

+ /* B. if the input buffer begins with a prefix of "/./" or "/.", where

+ "." is a complete path segment, then replace that prefix with "/" in

+ the input buffer; otherwise, */

+ else if(!strncmp("/./", clone, 3)) {

+ clone += 2;

+ clen -= 2;

+ }

+ else if(!strcmp("/.", clone)) {

+ clone[1]='/';

+ clone++;

+ clen -= 1;

+ }

+

+ /* C. if the input buffer begins with a prefix of "/../" or "/..", where

+ ".." is a complete path segment, then replace that prefix with "/" in

+ the input buffer and remove the last segment and its preceding "/" (if

+ any) from the output buffer; otherwise, */

+

+ else if(!strncmp("/../", clone, 4)) {

+ clone += 3;

+ clen -= 3;

+ /* remove the last segment from the output buffer */

+ while(outptr > out) {

+ outptr--;

+ if(*outptr == '/')

+ break;

+ }

+ *outptr = 0; /* zero-terminate where it stops */

+ }

+ else if(!strcmp("/..", clone)) {

+ clone[2]='/';

+ clone += 2;

+ clen -= 2;

+ /* remove the last segment from the output buffer */

+ while(outptr > out) {

+ outptr--;

+ if(*outptr == '/')

+ break;

+ }

+ *outptr = 0; /* zero-terminate where it stops */

+ }

+

+ /* D. if the input buffer consists only of "." or "..", then remove

+ that from the input buffer; otherwise, */

+

+ else if(!strcmp(".", clone) || !strcmp("..", clone)) {

+ *clone = 0;

+ *out = 0;

+ }

+

+ else {

+ /* E. move the first path segment in the input buffer to the end of

+ the output buffer, including the initial "/" character (if any) and

+ any subsequent characters up to, but not including, the next "/"

+ character or the end of the input buffer. */

+

+ do {

+ *outptr++ = *clone++;

+ clen--;

+ } while(*clone && (*clone != '/'));

+ *outptr = 0;

+ }

+

+ } while(*clone);

+

+ if(queryp) {

+ size_t qlen;

+ /* There was a query part, append that to the output. The 'clone' string

+ may now have been altered so we copy from the original input string

+ from the correct index. */

+ size_t oindex = queryp - orgclone;

+ qlen = strlen(&input[oindex]);

+ memcpy(outptr, &input[oindex], qlen + 1); /* include the end zero byte */

+ }

+

+ free(orgclone);

+ return out;

+}

+

+/* via lib/url.c */

+CURLcode Curl_parse_login_details(const char *login, const size_t len,

+ char **userp, char **passwdp,

+ char **optionsp)

+{

+ CURLcode result = CURLE_OK;

+ char *ubuf = NULL;

+ char *pbuf = NULL;

+ char *obuf = NULL;

+ const char *psep = NULL;

+ const char *osep = NULL;

+ size_t ulen;

+ size_t plen;

+ size_t olen;

+

+ /* Attempt to find the password separator */

+ if(passwdp) {

+ psep = strchr(login, ':');

+

+ /* Within the constraint of the login string */

+ if(psep >= login + len)

+ psep = NULL;

+ }

+

+ /* Attempt to find the options separator */

+ if(optionsp) {

+ osep = strchr(login, ';');

+

+ /* Within the constraint of the login string */

+ if(osep >= login + len)

+ osep = NULL;

+ }

+

+ /* Calculate the portion lengths */

+ ulen = (psep ?

+ (size_t)(osep && psep > osep ? osep - login : psep - login) :

+ (osep ? (size_t)(osep - login) : len));

+ plen = (psep ?

+ (osep && osep > psep ? (size_t)(osep - psep) :

+ (size_t)(login + len - psep)) - 1 : 0);

+ olen = (osep ?

+ (psep && psep > osep ? (size_t)(psep - osep) :

+ (size_t)(login + len - osep)) - 1 : 0);

+

+ /* Allocate the user portion buffer */

+ if(userp && ulen) {

+ ubuf = malloc(ulen + 1);

+ if(!ubuf)

+ result = CURLE_OUT_OF_MEMORY;

+ }

+

+ /* Allocate the password portion buffer */

+ if(!result && passwdp && plen) {

+ pbuf = malloc(plen + 1);

+ if(!pbuf) {

+ free(ubuf);

+ result = CURLE_OUT_OF_MEMORY;

+ }

+ }

+

+ /* Allocate the options portion buffer */

+ if(!result && optionsp && olen) {

+ obuf = malloc(olen + 1);

+ if(!obuf) {

+ free(pbuf);

+ free(ubuf);

+ result = CURLE_OUT_OF_MEMORY;

+ }

+ }

+

+ if(!result) {

+ /* Store the user portion if necessary */

+ if(ubuf) {

+ memcpy(ubuf, login, ulen);

+ ubuf[ulen] = '\0';

+ free(*userp);

+ *userp = ubuf;

+ }

+

+ /* Store the password portion if necessary */

+ if(pbuf) {

+ memcpy(pbuf, psep + 1, plen);

+ pbuf[plen] = '\0';

+ free(*passwdp);

+ *passwdp = pbuf;

+ }

+

+ /* Store the options portion if necessary */

+ if(obuf) {

+ memcpy(obuf, osep + 1, olen);

+ obuf[olen] = '\0';

+ free(*optionsp);

+ *optionsp = obuf;

+ }

+ }

+

+ return result;

+}

+

+/* Internal representation of CURLU. Point to URL-encoded strings. */

+struct Curl_URL {

+ char *scheme;

+ char *user;

+ char *password;

+ char *options; /* IMAP only? */

+ char *host;

+ char *port;

+ char *path;

+ char *query;

+ char *fragment;

+

+ char *scratch; /* temporary scratch area */

+ long portnum; /* the numerical version */

+};

+

+#define DEFAULT_SCHEME "https"

+

+static void free_urlhandle(struct Curl_URL *u)

+{

+ free(u->scheme);

+ free(u->user);

+ free(u->password);

+ free(u->options);

+ free(u->host);

+ free(u->port);

+ free(u->path);

+ free(u->query);

+ free(u->fragment);

+ free(u->scratch);

+}

+

+/* move the full contents of one handle onto another and

+ free the original */

+static void mv_urlhandle(struct Curl_URL *from,

+ struct Curl_URL *to)

+{

+ free_urlhandle(to);

+ *to = *from;

+ free(from);

+}

+

+/*

+ * Find the separator at the end of the host name, or the '?' in cases like

+ * http://www.url.com?id=2380

+ */

+static const char *find_host_sep(const char *url)

+{

+ const char *sep;

+ const char *query;

+

+ /* Find the start of the hostname */

+ sep = strstr(url, "//");

+ if(!sep)

+ sep = url;

+ else

+ sep += 2;

+

+ query = strchr(sep, '?');

+ sep = strchr(sep, '/');

+

+ if(!sep)

+ sep = url + strlen(url);

+

+ if(!query)

+ query = url + strlen(url);

+

+ return sep < query ? sep : query;

+}

+

+/*

+ * Decide in an encoding-independent manner whether a character in an

+ * URL must be escaped. The same criterion must be used in strlen_url()

+ * and strcpy_url().

+ */

+static bool urlchar_needs_escaping(int c)

+{

+ return !(iscntrl(c) || isspace(c) || isgraph(c));

+}

+

+/*

+ * strlen_url() returns the length of the given URL if the spaces within the

+ * URL were properly URL encoded.

+ * URL encoding should be skipped for host names, otherwise IDN resolution

+ * will fail.

+ */

+size_t Curl_strlen_url(const char *url, bool relative)

+{

+ const unsigned char *ptr;

+ size_t newlen = 0;

+ bool left = true; /* left side of the ? */

+ const unsigned char *host_sep = (const unsigned char *) url;

+

+ if(!relative)

+ host_sep = (const unsigned char *) find_host_sep(url);

+

+ for(ptr = (unsigned char *)url; *ptr; ptr++) {

+

+ if(ptr < host_sep) {

+ ++newlen;

+ continue;

+ }

+

+ switch(*ptr) {

+ case '?':

+ left = false;

+ /* FALLTHROUGH */

+ default:

+ if(urlchar_needs_escaping(*ptr))

+ newlen += 2;

+ newlen++;

+ break;

+ case ' ':

+ if(left)

+ newlen += 3;

+ else

+ newlen++;

+ break;

+ }

+ }

+ return newlen;

+}

+

+/* strcpy_url() copies a url to a output buffer and URL-encodes the spaces in

+ * the source URL accordingly.

+ * URL encoding should be skipped for host names, otherwise IDN resolution

+ * will fail.

+ */

+void Curl_strcpy_url(char *output, const char *url, bool relative)

+{

+ /* we must add this with whitespace-replacing */

+ bool left = true;

+ const unsigned char *iptr;

+ char *optr = output;

+ const unsigned char *host_sep = (const unsigned char *) url;

+

+ if(!relative)

+ host_sep = (const unsigned char *) find_host_sep(url);

+

+ for(iptr = (unsigned char *)url; /* read from here */

+ *iptr; /* until zero byte */

+ iptr++) {

+

+ if(iptr < host_sep) {

+ *optr++ = *iptr;

+ continue;

+ }

+

+ switch(*iptr) {

+ case '?':

+ left = false;

+ /* FALLTHROUGH */

+ default:

+ if(urlchar_needs_escaping(*iptr)) {

+ snprintf(optr, 4, "%%%02x", *iptr);

+ optr += 3;

+ }

+ else

+ *optr++=*iptr;

+ break;

+ case ' ':

+ if(left) {

+ *optr++='%'; /* add a '%' */

+ *optr++='2'; /* add a '2' */

+ *optr++='0'; /* add a '0' */

+ }

+ else

+ *optr++='+'; /* add a '+' here */

+ break;

+ }

+ }

+ *optr = 0; /* zero terminate output buffer */

+

+}

+

+/*

+ * Returns true if the given URL is absolute (as opposed to relative) within

+ * the buffer size. Returns the scheme in the buffer if true and 'buf' is

+ * non-NULL.

+ */

+bool Curl_is_absolute_url(const char *url, char *buf, size_t buflen)

+{

+ size_t i;

+ for(i = 0; i < buflen && url[i]; ++i) {

+ char s = url[i];

+ if((s == ':') && (url[i + 1] == '/')) {

+ if(buf)

+ buf[i] = 0;

+ return true;

+ }

+ /* RFC 3986 3.1 explains:

+ scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )

+ */

+ else if(isalnum(s) || (s == '+') || (s == '-') || (s == '.') ) {

+ if(buf)

+ buf[i] = (char)tolower(s);

+ }

+ else

+ break;

+ }

+ return false;

+}

+

+/*

+ * Concatenate a relative URL to a base URL making it absolute.

+ * URL-encodes any spaces.

+ * The returned pointer must be freed by the caller unless NULL

+ * (returns NULL on out of memory).

+ */

+char *Curl_concat_url(const char *base, const char *relurl)

+{

+ /***

+ TRY to append this new path to the old URL

+ to the right of the host part. Oh crap, this is doomed to cause

+ problems in the future...

+ */

+ char *newest;

+ char *protsep;

+ char *pathsep;

+ size_t newlen;

+ bool host_changed = false;

+

+ const char *useurl = relurl;

+ size_t urllen;

+

+ /* we must make our own copy of the URL to play with, as it may

+ point to read-only data */

+ char *url_clone = strdup(base);

+

+ if(!url_clone)

+ return NULL; /* skip out of this NOW */

+

+ /* protsep points to the start of the host name */

+ protsep = strstr(url_clone, "//");

+ if(!protsep)

+ protsep = url_clone;

+ else

+ protsep += 2; /* pass the slashes */

+

+ if('/' != relurl[0]) {

+ int level = 0;

+

+ /* First we need to find out if there's a ?-letter in the URL,

+ and cut it and the right-side of that off */

+ pathsep = strchr(protsep, '?');

+ if(pathsep)

+ *pathsep = 0;

+

+ /* we have a relative path to append to the last slash if there's one

+ available, or if the new URL is just a query string (starts with a

+ '?') we append the new one at the end of the entire currently worked

+ out URL */

+ if(useurl[0] != '?') {

+ pathsep = strrchr(protsep, '/');

+ if(pathsep)

+ *pathsep = 0;

+ }

+

+ /* Check if there's any slash after the host name, and if so, remember

+ that position instead */

+ pathsep = strchr(protsep, '/');

+ if(pathsep)

+ protsep = pathsep + 1;

+ else

+ protsep = NULL;

+

+ /* now deal with one "./" or any amount of "../" in the newurl

+ and act accordingly */

+

+ if((useurl[0] == '.') && (useurl[1] == '/'))

+ useurl += 2; /* just skip the "./" */

+

+ while((useurl[0] == '.') &&

+ (useurl[1] == '.') &&

+ (useurl[2] == '/')) {

+ level++;

+ useurl += 3; /* pass the "../" */

+ }

+

+ if(protsep) {

+ while(level--) {

+ /* cut off one more level from the right of the original URL */

+ pathsep = strrchr(protsep, '/');

+ if(pathsep)

+ *pathsep = 0;

+ else {

+ *protsep = 0;

+ break;

+ }

+ }

+ }

+ }

+ else {

+ /* We got a new absolute path for this server */

+

+ if((relurl[0] == '/') && (relurl[1] == '/')) {

+ /* the new URL starts with //, just keep the protocol part from the

+ original one */

+ *protsep = 0;

+ useurl = &relurl[2]; /* we keep the slashes from the original, so we

+ skip the new ones */

+ host_changed = true;

+ }

+ else {

+ /* cut off the original URL from the first slash, or deal with URLs

+ without slash */

+ pathsep = strchr(protsep, '/');

+ if(pathsep) {

+ /* When people use badly formatted URLs, such as

+ "http://www.url.com?dir=/home/daniel" we must not use the first

+ slash, if there's a ?-letter before it! */

+ char *sep = strchr(protsep, '?');

+ if(sep && (sep < pathsep))

+ pathsep = sep;

+ *pathsep = 0;

+ }

+ else {

+ /* There was no slash. Now, since we might be operating on a badly

+ formatted URL, such as "http://www.url.com?id=2380" which doesn't

+ use a slash separator as it is supposed to, we need to check for a

+ ?-letter as well! */

+ pathsep = strchr(protsep, '?');

+ if(pathsep)

+ *pathsep = 0;

+ }

+ }

+ }

+

+ /* If the new part contains a space, this is a mighty stupid redirect

+ but we still make an effort to do "right". To the left of a '?'

+ letter we replace each space with %20 while it is replaced with '+'

+ on the right side of the '?' letter.

+ */

+ newlen = Curl_strlen_url(useurl, !host_changed);

+

+ urllen = strlen(url_clone);

+

+ newest = malloc(urllen + 1 + /* possible slash */

+ newlen + 1 /* zero byte */);

+

+ if(!newest) {

+ free(url_clone); /* don't leak this */

+ return NULL;

+ }

+

+ /* copy over the root url part */

+ memcpy(newest, url_clone, urllen);

+

+ /* check if we need to append a slash */

+ if(('/' == useurl[0]) || (protsep && !*protsep) || ('?' == useurl[0]))

+ ;

+ else

+ newest[urllen++]='/';

+

+ /* then append the new piece on the right side */

+ Curl_strcpy_url(&newest[urllen], useurl, !host_changed);

+

+ free(url_clone);

+

+ return newest;

+}

+

+/*

+ * parse_hostname_login()

+ *

+ * Parse the login details (user name, password and options) from the URL and

+ * strip them out of the host name

+ *

+ */

+static CURLUcode parse_hostname_login(struct Curl_URL *u,

+ char **hostname,

+ unsigned int flags)

+{

+ CURLUcode result = CURLUE_OK;

+ CURLcode ccode;

+ char *userp = NULL;

+ char *passwdp = NULL;

+ char *optionsp = NULL;

+

+ /* At this point, we're hoping all the other special cases have

+ * been taken care of, so conn->host.name is at most

+ * [user[:password][;options]]@]hostname

+ *

+ * We need somewhere to put the embedded details, so do that first.

+ */

+

+ char *ptr = strchr(*hostname, '@');

+ char *login = *hostname;

+

+ if(!ptr)

+ goto out;

+

+ /* We will now try to extract the

+ * possible login information in a string like:

+ * ftp://user:password@ftp.my.site:8021/README */

+ *hostname = ++ptr;

+

+ /* We could use the login information in the URL so extract it. Only parse

+ options if the handler says we should. Note that 'h' might be NULL! */

+ ccode = Curl_parse_login_details(login, ptr - login - 1,

+ &userp, &passwdp, NULL);

+ if(ccode) {

+ result = CURLUE_MALFORMED_INPUT;

+ goto out;

+ }

+

+ if(userp) {

+ if(flags & CURLU_DISALLOW_USER) {

+ /* Option DISALLOW_USER is set and url contains username. */

+ result = CURLUE_USER_NOT_ALLOWED;

+ goto out;

+ }

+

+ u->user = userp;

+ }

+

+ if(passwdp)

+ u->password = passwdp;

+

+ if(optionsp)

+ u->options = optionsp;

+

+ return CURLUE_OK;

+ out:

+

+ free(userp);

+ free(passwdp);

+ free(optionsp);

+

+ return result;

+}

+

+static CURLUcode parse_port(struct Curl_URL *u, char *hostname)

+{

+ char *portptr;

+ char endbracket;

+ int len;

+

+ if((1 == sscanf(hostname, "[%*45[0123456789abcdefABCDEF:.%%]%c%n",

+ &endbracket, &len)) &&

+ (']' == endbracket)) {

+ /* this is a RFC2732-style specified IP-address */

+ portptr = &hostname[len];

+ if(*portptr) {

+ if(*portptr != ':')

+ return CURLUE_MALFORMED_INPUT;

+ }

+ else

+ portptr = NULL;

+ }

+ else

+ portptr = strchr(hostname, ':');

+

+ if(portptr) {

+ char *rest;

+ long port;

+ char portbuf[7];

+

+ if(!isdigit(portptr[1]))

+ return CURLUE_BAD_PORT_NUMBER;

+

+ port = strtol(portptr + 1, &rest, 10); /* Port number must be decimal */

+

+ if((port <= 0) || (port > 0xffff))

+ /* Single unix standard says port numbers are 16 bits long, but we don't

+ treat port zero as OK. */

+ return CURLUE_BAD_PORT_NUMBER;

+

+ if(rest[0])

+ return CURLUE_BAD_PORT_NUMBER;

+

+ if(rest != &portptr[1]) {

+ *portptr++ = '\0'; /* cut off the name there */

+ *rest = 0;

+ /* generate a new to get rid of leading zeroes etc */

+ snprintf(portbuf, sizeof(portbuf), "%ld", port);

+ u->portnum = port;

+ u->port = strdup(portbuf);

+ if(!u->port)

+ return CURLUE_OUT_OF_MEMORY;

+ }

+ else {

+ /* Browser behavior adaptation. If there's a colon with no digits after,

+ just cut off the name there which makes us ignore the colon and just

+ use the default port. Firefox and Chrome both do that. */

+ *portptr = '\0';

+ }

+ }

+

+ return CURLUE_OK;

+}

+

+/* scan for byte values < 31 or 127 */

+static CURLUcode junkscan(char *part)

+{

+ char badbytes[]={

+ /* */ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,

+ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,

+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,

+ 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,

+ 0x7f,

+ 0x00 /* zero terminate */

+ };

+ if(part) {

+ size_t n = strlen(part);

+ size_t nfine = strcspn(part, badbytes);

+ if(nfine != n)

+ /* since we don't know which part is scanned, return a generic error

+ code */

+ return CURLUE_MALFORMED_INPUT;

+ }

+ return CURLUE_OK;

+}

+

+static CURLUcode hostname_check(char *hostname, unsigned int flags)

+{

+ const char *l = NULL; /* accepted characters */

+ size_t len;

+ size_t hlen = strlen(hostname);

+ (void)flags;

+

+ if(hostname[0] == '[') {

+ hostname++;

+ l = "0123456789abcdefABCDEF::.%";

+ hlen -= 2;

+ }

+

+ if(l) {

+ /* only valid letters are ok */

+ len = strspn(hostname, l);

+ if(hlen != len)

+ /* hostname with bad content */

+ return CURLUE_MALFORMED_INPUT;

+ }

+ else {

+ /* letters from the second string is not ok */

+ len = strcspn(hostname, " ");

+ if(hlen != len)

+ /* hostname with bad content */

+ return CURLUE_MALFORMED_INPUT;

+ }

+ return CURLUE_OK;

+}

+

+#define HOSTNAME_END(x) (((x) == '/') || ((x) == '?') || ((x) == '#'))

+

+static CURLUcode seturl(const char *url, struct Curl_URL *u, unsigned int flags)

+{

+ char *path;

+ bool path_alloced = false;

+ char *hostname;

+ char *query = NULL;

+ char *fragment = NULL;

+ CURLUcode result;

+ bool url_has_scheme = false;

+ char schemebuf[MAX_SCHEME_LEN];

+ char *schemep = NULL;

+ size_t schemelen = 0;

+ size_t urllen;

+

+ if(!url)

+ return CURLUE_MALFORMED_INPUT;

+

+ /*************************************************************

+ * Parse the URL.

+ ************************************************************/

+ /* allocate scratch area */

+ urllen = strlen(url);

+ path = u->scratch = malloc(urllen * 2 + 2);

+ if(!path)

+ return CURLUE_OUT_OF_MEMORY;

+

+ hostname = &path[urllen + 1];

+ hostname[0] = 0;

+

+ if(Curl_is_absolute_url(url, schemebuf, sizeof(schemebuf))) {

+ url_has_scheme = true;

+ schemelen = strlen(schemebuf);

+ }

+

+ /* handle the file: scheme */

+ if(url_has_scheme && strcasecmp(schemebuf, "file") == 0) {

+ /* path has been allocated large enough to hold this */

+ strcpy(path, &url[5]);

+

+ hostname = NULL; /* no host for file: URLs */

+ u->scheme = strdup("file");

+ if(!u->scheme)

+ return CURLUE_OUT_OF_MEMORY;

+

+ /* Extra handling URLs with an authority component (i.e. that start with

+ * "file://")

+ *

+ * We allow omitted hostname (e.g. file:/<path>) -- valid according to

+ * RFC 8089, but not the (current) WHAT-WG URL spec.

+ */

+ if(path[0] == '/' && path[1] == '/') {

+ /* swallow the two slashes */

+ char *ptr = &path[2];

+ path = ptr;

+ }

+ }

+ else {

+ /* clear path */

+ const char *p;

+ const char *hostp;

+ size_t len;

+ path[0] = 0;

+

+ if(url_has_scheme) {

+ int i = 0;

+ p = &url[schemelen + 1];

+ while(p && (*p == '/') && (i < 4)) {

+ p++;

+ i++;

+ }

+ if((i < 1) || (i>3))

+ /* less than one or more than three slashes */

+ return CURLUE_MALFORMED_INPUT;

+

+ schemep = schemebuf;

+ if(junkscan(schemep))

+ return CURLUE_MALFORMED_INPUT;

+ }

+ else {

+ /* no scheme! */

+ return CURLUE_MALFORMED_INPUT;

+ }

+ hostp = p; /* host name starts here */

+

+ while(*p && !HOSTNAME_END(*p)) /* find end of host name */

+ p++;

+

+ len = p - hostp;

+ if(!len)

+ return CURLUE_MALFORMED_INPUT;

+

+ memcpy(hostname, hostp, len);

+ hostname[len] = 0;

+

+ len = strlen(p);

+ memcpy(path, p, len);

+ path[len] = 0;

+

+ u->scheme = strdup(schemep);

+ if(!u->scheme)

+ return CURLUE_OUT_OF_MEMORY;

+ }

+

+ if(junkscan(path))

+ return CURLUE_MALFORMED_INPUT;

+

+ query = strchr(path, '?');

+ if(query)

+ *query++ = 0;

+

+ fragment = strchr(query?query:path, '#');

+ if(fragment)

+ *fragment++ = 0;

+

+ if(!path[0])

+ /* if there's no path set, unset */

+ path = NULL;

+ else if(!(flags & CURLU_PATH_AS_IS)) {

+ /* sanitise paths and remove ../ and ./ sequences according to RFC3986 */

+ char *newp = Curl_dedotdotify(path);

+ if(!newp)

+ return CURLUE_OUT_OF_MEMORY;

+

+ if(strcmp(newp, path)) {

+ /* if we got a new version */

+ path = newp;

+ path_alloced = true;

+ }

+ else

+ free(newp);

+ }

+ if(path) {

+ u->path = path_alloced?path:strdup(path);

+ if(!u->path)

+ return CURLUE_OUT_OF_MEMORY;

+ }

+

+ if(hostname) {

+ /*

+ * Parse the login details and strip them out of the host name.

+ */

+ if(junkscan(hostname))

+ return CURLUE_MALFORMED_INPUT;

+

+ result = parse_hostname_login(u, &hostname, flags);

+ if(result)

+ return result;

+

+ result = parse_port(u, hostname);

+ if(result)

+ return result;

+

+ result = hostname_check(hostname, flags);

+ if(result)

+ return result;

+

+ u->host = strdup(hostname);

+ if(!u->host)

+ return CURLUE_OUT_OF_MEMORY;

+ }

+

+ if(query && query[0]) {

+ u->query = strdup(query);

+ if(!u->query)

+ return CURLUE_OUT_OF_MEMORY;

+ }

+ if(fragment && fragment[0]) {

+ u->fragment = strdup(fragment);

+ if(!u->fragment)

+ return CURLUE_OUT_OF_MEMORY;

+ }

+

+ free(u->scratch);

+ u->scratch = NULL;

+

+ return CURLUE_OK;

+}

+

+/*

+ * Parse the URL and set the relevant members of the Curl_URL struct.

+ */

+static CURLUcode parseurl(const char *url, struct Curl_URL *u, unsigned int flags)

+{

+ CURLUcode result = seturl(url, u, flags);

+ if(result) {

+ free_urlhandle(u);

+ memset(u, 0, sizeof(struct Curl_URL));

+ }

+ return result;

+}

+

+/*

+ */

+struct Curl_URL *curl_url(void)

+{

+ return calloc(sizeof(struct Curl_URL), 1);

+}

+

+void curl_url_cleanup(struct Curl_URL *u)

+{

+ if(u) {

+ free_urlhandle(u);

+ free(u);

+ }

+}

+

+#define DUP(dest, src, name) \

+ if(src->name) { \

+ dest->name = strdup(src->name); \

+ if(!dest->name) \

+ goto fail; \

+ }

+

+struct Curl_URL *curl_url_dup(struct Curl_URL *in)

+{

+ struct Curl_URL *u = calloc(sizeof(struct Curl_URL), 1);

+ if(u) {

+ DUP(u, in, scheme);

+ DUP(u, in, user);

+ DUP(u, in, password);

+ DUP(u, in, options);

+ DUP(u, in, host);

+ DUP(u, in, port);

+ DUP(u, in, path);

+ DUP(u, in, query);

+ DUP(u, in, fragment);

+ u->portnum = in->portnum;

+ }

+ return u;

+ fail:

+ curl_url_cleanup(u);

+ return NULL;

+}

+

+CURLUcode curl_url_get(struct Curl_URL *u, CURLUPart what,

+ char **part, unsigned int flags)

+{

+ char *ptr;

+ CURLUcode ifmissing = CURLUE_UNKNOWN_PART;

+ bool urldecode = (flags & CURLU_URLDECODE)?1:0;

+ bool plusdecode = false;

+ (void)flags;

+ if(!u)

+ return CURLUE_BAD_HANDLE;

+ if(!part)

+ return CURLUE_BAD_PARTPOINTER;

+ *part = NULL;

+

+ switch(what) {

+ case CURLUPART_SCHEME:

+ ptr = u->scheme;

+ ifmissing = CURLUE_NO_SCHEME;

+ urldecode = false; /* never for schemes */

+ break;

+ case CURLUPART_USER:

+ ptr = u->user;

+ ifmissing = CURLUE_NO_USER;

+ break;

+ case CURLUPART_PASSWORD:

+ ptr = u->password;

+ ifmissing = CURLUE_NO_PASSWORD;

+ break;

+ case CURLUPART_OPTIONS:

+ ptr = u->options;

+ ifmissing = CURLUE_NO_OPTIONS;

+ break;

+ case CURLUPART_HOST:

+ ptr = u->host;

+ ifmissing = CURLUE_NO_HOST;

+ break;

+ case CURLUPART_PORT:

+ ptr = u->port;

+ ifmissing = CURLUE_NO_PORT;

+ urldecode = false; /* never for port */

+ break;

+ case CURLUPART_PATH:

+ ptr = u->path;

+ if(!ptr) {

+ ptr = u->path = strdup("/");

+ if(!u->path)

+ return CURLUE_OUT_OF_MEMORY;

+ }

+ break;

+ case CURLUPART_QUERY:

+ ptr = u->query;

+ ifmissing = CURLUE_NO_QUERY;

+ plusdecode = urldecode;

+ break;

+ case CURLUPART_FRAGMENT:

+ ptr = u->fragment;

+ ifmissing = CURLUE_NO_FRAGMENT;

+ break;

+ case CURLUPART_URL: {

+ char *url;

+ char *scheme;

+ char *options = u->options;

+ char *port = u->port;

+ if(u->scheme && strcasecmp("file", u->scheme) == 0) {

+ url = aprintf("file://%s%s%s",

+ u->path,

+ u->fragment? "#": "",

+ u->fragment? u->fragment : "");

+ }

+ else if(!u->host)

+ return CURLUE_NO_HOST;

+ else {

+ if(u->scheme)

+ scheme = u->scheme;

+ else

+ return CURLUE_NO_SCHEME;

+

+ options = NULL;

+

+ url = aprintf("%s://%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s",

+ scheme,

+ u->user ? u->user : "",

+ u->password ? ":": "",

+ u->password ? u->password : "",

+ options ? ";" : "",

+ options ? options : "",

+ (u->user || u->password || options) ? "@": "",

+ u->host,

+ port ? ":": "",

+ port ? port : "",

+ (u->path && (u->path[0] != '/')) ? "/": "",

+ u->path ? u->path : "/",

+ u->query? "?": "",

+ u->query? u->query : "",

+ u->fragment? "#": "",

+ u->fragment? u->fragment : "");

+ }

+ if(!url)

+ return CURLUE_OUT_OF_MEMORY;

+ *part = url;

+ return CURLUE_OK;

+ break;

+ }

+ default:

+ ptr = NULL;

+ }

+ if(ptr) {

+ *part = strdup(ptr);

+ if(!*part)

+ return CURLUE_OUT_OF_MEMORY;

+ if(plusdecode) {

+ /* convert + to space */

+ char *plus;

+ for(plus = *part; *plus; ++plus) {

+ if(*plus == '+')

+ *plus = ' ';

+ }

+ }

+ if(urldecode) {

+ char *decoded;

+ size_t dlen;

+ CURLcode res = Curl_urldecode(*part, 0, &decoded, &dlen, true);

+ free(*part);

+ if(res) {

+ *part = NULL;

+ return CURLUE_URLDECODE;

+ }

+ *part = decoded;

+ }

+ return CURLUE_OK;

+ }

+ else

+ return ifmissing;

+}

+

+CURLUcode curl_url_set(struct Curl_URL *u, CURLUPart what,

+ const char *part, unsigned int flags)

+{

+ char **storep = NULL;

+ long port = 0;

+ bool urlencode = (flags & CURLU_URLENCODE)? 1 : 0;

+ bool plusencode = false;

+ bool urlskipslash = false;

+ bool appendquery = false;

+ bool equalsencode = false;

+

+ if(!u)

+ return CURLUE_BAD_HANDLE;

+ if(!part) {

+ /* setting a part to NULL clears it */

+ switch(what) {

+ case CURLUPART_URL:

+ break;

+ case CURLUPART_SCHEME:

+ storep = &u->scheme;

+ break;

+ case CURLUPART_USER:

+ storep = &u->user;

+ break;

+ case CURLUPART_PASSWORD:

+ storep = &u->password;

+ break;

+ case CURLUPART_OPTIONS:

+ storep = &u->options;

+ break;

+ case CURLUPART_HOST:

+ storep = &u->host;

+ break;

+ case CURLUPART_PORT:

+ storep = &u->port;

+ break;

+ case CURLUPART_PATH:

+ storep = &u->path;

+ break;

+ case CURLUPART_QUERY:

+ storep = &u->query;

+ break;

+ case CURLUPART_FRAGMENT:

+ storep = &u->fragment;

+ break;

+ default:

+ return CURLUE_UNKNOWN_PART;

+ }

+ if(storep && *storep) {

+ free(*storep);

+ *storep = NULL;

+ }

+ return CURLUE_OK;

+ }

+

+ switch(what) {

+ case CURLUPART_SCHEME:

+ storep = &u->scheme;

+ urlencode = false; /* never */

+ break;

+ case CURLUPART_USER:

+ storep = &u->user;

+ break;

+ case CURLUPART_PASSWORD:

+ storep = &u->password;

+ break;

+ case CURLUPART_OPTIONS:

+ storep = &u->options;

+ break;

+ case CURLUPART_HOST:

+ storep = &u->host;

+ break;

+ case CURLUPART_PORT:

+ urlencode = false; /* never */

+ port = strtol(part, NULL, 10); /* Port number must be decimal */

+ if((port <= 0) || (port > 0xffff))

+ return CURLUE_BAD_PORT_NUMBER;

+ storep = &u->port;

+ break;

+ case CURLUPART_PATH:

+ urlskipslash = true;

+ storep = &u->path;

+ break;

+ case CURLUPART_QUERY:

+ plusencode = urlencode;

+ appendquery = (flags & CURLU_APPENDQUERY)?1:0;

+ equalsencode = appendquery;

+ storep = &u->query;

+ break;

+ case CURLUPART_FRAGMENT:

+ storep = &u->fragment;

+ break;

+ case CURLUPART_URL: {

+ /*

+ * Allow a new URL to replace the existing (if any) contents.

+ *

+ * If the existing contents is enough for a URL, allow a relative URL to

+ * replace it.

+ */

+ CURLUcode result;

+ char *oldurl;

+ char *redired_url;

+ struct Curl_URL *handle2;

+

+ if(Curl_is_absolute_url(part, NULL, MAX_SCHEME_LEN)) {

+ handle2 = curl_url();

+ if(!handle2)

+ return CURLUE_OUT_OF_MEMORY;

+ result = parseurl(part, handle2, flags);

+ if(!result)

+ mv_urlhandle(handle2, u);

+ else

+ curl_url_cleanup(handle2);

+ return result;

+ }

+ /* extract the full "old" URL to do the redirect on */

+ result = curl_url_get(u, CURLUPART_URL, &oldurl, flags);

+ if(result) {

+ /* couldn't get the old URL, just use the new! */

+ handle2 = curl_url();

+ if(!handle2)

+ return CURLUE_OUT_OF_MEMORY;

+ result = parseurl(part, handle2, flags);

+ if(!result)

+ mv_urlhandle(handle2, u);

+ else

+ curl_url_cleanup(handle2);

+ return result;

+ }

+

+ /* apply the relative part to create a new URL */

+ redired_url = Curl_concat_url(oldurl, part);

+ free(oldurl);

+ if(!redired_url)

+ return CURLUE_OUT_OF_MEMORY;

+

+ /* now parse the new URL */

+ handle2 = curl_url();

+ if(!handle2) {

+ free(redired_url);

+ return CURLUE_OUT_OF_MEMORY;

+ }

+ result = parseurl(redired_url, handle2, flags);

+ free(redired_url);

+ if(!result)

+ mv_urlhandle(handle2, u);

+ else

+ curl_url_cleanup(handle2);

+ return result;

+ }

+ default:

+ return CURLUE_UNKNOWN_PART;

+ }

+ if(storep) {

+ const char *newp = part;

+ size_t nalloc = strlen(part);

+

+ if(urlencode) {

+ const char *i;

+ char *o;

+ bool free_part = false;

+ char *enc = malloc(nalloc * 3 + 1); /* for worst case! */

+ if(!enc)

+ return CURLUE_OUT_OF_MEMORY;

+ if(plusencode) {

+ /* space to plus */

+ i = part;

+ for(o = enc; *i; ++o, ++i)

+ *o = (*i == ' ') ? '+' : *i;

+ *o = 0; /* zero terminate */

+ part = strdup(enc);

+ if(!part) {

+ free(enc);

+ return CURLUE_OUT_OF_MEMORY;

+ }

+ free_part = true;

+ }

+ for(i = part, o = enc; *i; i++) {

+ if(Curl_isunreserved(*i) ||

+ ((*i == '/') && urlskipslash) ||

+ ((*i == '=') && equalsencode) ||

+ ((*i == '+') && plusencode)) {

+ if((*i == '=') && equalsencode)

+ /* only skip the first equals sign */

+ equalsencode = false;

+ *o = *i;

+ o++;

+ }

+ else {

+ snprintf(o, 4, "%%%02x", *i);

+ o += 3;

+ }

+ }

+ *o = 0; /* zero terminate */

+ newp = enc;

+ if(free_part)

+ free((char *)part);

+ }

+ else {

+ char *p;

+ newp = strdup(part);

+ if(!newp)

+ return CURLUE_OUT_OF_MEMORY;

+ p = (char *)newp;

+ while(*p) {

+ /* make sure percent encoded are lower case */

+ if((*p == '%') && isxdigit(p[1]) && isxdigit(p[2]) &&

+ (isupper(p[1]) || isupper(p[2]))) {

+ p[1] = (char)tolower(p[1]);

+ p[2] = (char)tolower(p[2]);

+ p += 3;

+ }

+ else

+ p++;

+ }

+ }

+

+ if(appendquery) {

+ /* Append the string onto the old query. Add a '&' separator if none is

+ present at the end of the exsting query already */

+ size_t querylen = u->query ? strlen(u->query) : 0;

+ bool addamperand = querylen && (u->query[querylen -1] != '&');

+ if(querylen) {

+ size_t newplen = strlen(newp);

+ char *p = malloc(querylen + addamperand + newplen + 1);

+ if(!p) {

+ free((char *)newp);

+ return CURLUE_OUT_OF_MEMORY;

+ }

+ strcpy(p, u->query); /* original query */

+ if(addamperand)

+ p[querylen] = '&'; /* ampersand */

+ strcpy(&p[querylen + addamperand], newp); /* new suffix */

+ free((char *)newp);

+ free(*storep);

+ *storep = p;

+ return CURLUE_OK;

+ }

+ }

+

+ free(*storep);

+ *storep = (char *)newp;

+ }

+ /* set after the string, to make it not assigned if the allocation above

+ fails */

+ if(port)

+ u->portnum = port;

+ return CURLUE_OK;

+}