From fb30ac5a2d63773c529c19259754e2b306ac2e2e Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Sun, 5 Aug 2018 11:51:07 +0200 Subject: [PATCH] URL-API See header file and man pages for API. All documented API details work and are tested in the 1560 test case. Closes #2842 --- docs/libcurl/Makefile.inc | 103 ++- docs/libcurl/curl_url.3 | 61 ++ docs/libcurl/curl_url_cleanup.3 | 44 + docs/libcurl/curl_url_dup.3 | 52 ++ docs/libcurl/curl_url_get.3 | 110 +++ docs/libcurl/curl_url_set.3 | 120 +++ docs/libcurl/symbols-in-versions | 30 + include/curl/Makefile.am | 4 +- include/curl/curl.h | 1 + include/curl/urlapi.h | 121 +++ lib/Makefile.inc | 4 +- lib/escape.c | 20 +- lib/escape.h | 3 +- lib/imap.c | 3 +- lib/transfer.c | 314 +------ lib/url.c | 44 +- lib/url.h | 2 + lib/urlapi-int.h | 29 + lib/urlapi.c | 1315 ++++++++++++++++++++++++++++++ tests/data/Makefile.inc | 2 + tests/data/test1560 | 28 + tests/libtest/Makefile.am | 5 + tests/libtest/Makefile.inc | 4 + tests/libtest/lib1560.c | 760 +++++++++++++++++ 24 files changed, 2814 insertions(+), 365 deletions(-) create mode 100644 docs/libcurl/curl_url.3 create mode 100644 docs/libcurl/curl_url_cleanup.3 create mode 100644 docs/libcurl/curl_url_dup.3 create mode 100644 docs/libcurl/curl_url_get.3 create mode 100644 docs/libcurl/curl_url_set.3 create mode 100644 include/curl/urlapi.h create mode 100644 lib/urlapi-int.h create mode 100644 lib/urlapi.c create mode 100644 tests/data/test1560 create mode 100644 tests/libtest/lib1560.c diff --git a/docs/libcurl/Makefile.inc b/docs/libcurl/Makefile.inc index f41bbe098..ba4724b4d 100644 --- a/docs/libcurl/Makefile.inc +++ b/docs/libcurl/Makefile.inc @@ -1,25 +1,82 @@ # Shared between Makefile.am and CMakeLists.txt -man_MANS = curl_easy_cleanup.3 curl_easy_getinfo.3 curl_easy_init.3 \ - curl_easy_perform.3 curl_easy_setopt.3 curl_easy_duphandle.3 \ - curl_formadd.3 curl_formfree.3 curl_getdate.3 curl_getenv.3 \ - curl_slist_append.3 curl_slist_free_all.3 curl_version.3 \ - curl_version_info.3 curl_escape.3 curl_unescape.3 curl_free.3 \ - curl_strequal.3 curl_strnequal.3 curl_mprintf.3 curl_global_init.3 \ - curl_global_cleanup.3 curl_multi_add_handle.3 curl_multi_cleanup.3 \ - curl_multi_fdset.3 curl_multi_info_read.3 curl_multi_init.3 \ - curl_multi_perform.3 curl_multi_remove_handle.3 curl_share_cleanup.3 \ - curl_share_init.3 curl_share_setopt.3 libcurl.3 libcurl-easy.3 \ - libcurl-multi.3 libcurl-share.3 libcurl-errors.3 curl_easy_strerror.3 \ - curl_multi_strerror.3 curl_share_strerror.3 curl_global_init_mem.3 \ - libcurl-tutorial.3 curl_easy_reset.3 curl_easy_escape.3 \ - curl_easy_unescape.3 curl_multi_setopt.3 curl_multi_socket.3 \ - curl_multi_timeout.3 curl_formget.3 curl_multi_assign.3 \ - curl_easy_pause.3 curl_easy_recv.3 curl_easy_send.3 \ - curl_multi_socket_action.3 curl_multi_wait.3 libcurl-symbols.3 \ - libcurl-thread.3 curl_multi_socket_all.3 curl_global_sslset.3 \ - curl_mime_init.3 curl_mime_free.3 curl_mime_addpart.3 curl_mime_name.3 \ - curl_mime_data.3 curl_mime_data_cb.3 curl_mime_filedata.3 \ - curl_mime_filename.3 curl_mime_subparts.3 \ - curl_mime_type.3 curl_mime_headers.3 curl_mime_encoder.3 libcurl-env.3 \ - libcurl-security.3 curl_easy_upkeep.3 +man_MANS = \ + curl_easy_cleanup.3 \ + curl_easy_duphandle.3 \ + curl_easy_escape.3 \ + curl_easy_getinfo.3 \ + curl_easy_init.3 \ + curl_easy_pause.3 \ + curl_easy_perform.3 \ + curl_easy_recv.3 \ + curl_easy_reset.3 \ + curl_easy_send.3 \ + curl_easy_setopt.3 \ + curl_easy_strerror.3 \ + curl_easy_unescape.3 \ + curl_easy_upkeep.3 \ + curl_escape.3 \ + curl_formadd.3 \ + curl_formfree.3 \ + curl_formget.3 \ + curl_free.3 \ + curl_getdate.3 \ + curl_getenv.3 \ + curl_global_cleanup.3 \ + curl_global_init.3 \ + curl_global_init_mem.3 \ + curl_global_sslset.3 \ + curl_mime_addpart.3 \ + curl_mime_data.3 \ + curl_mime_data_cb.3 \ + curl_mime_encoder.3 \ + curl_mime_filedata.3 \ + curl_mime_filename.3 \ + curl_mime_free.3 \ + curl_mime_headers.3 \ + curl_mime_init.3 \ + curl_mime_name.3 \ + curl_mime_subparts.3 \ + curl_mime_type.3 \ + curl_mprintf.3 \ + curl_multi_add_handle.3 \ + curl_multi_assign.3 \ + curl_multi_cleanup.3 \ + curl_multi_fdset.3 \ + curl_multi_info_read.3 \ + curl_multi_init.3 \ + curl_multi_perform.3 \ + curl_multi_remove_handle.3 \ + curl_multi_setopt.3 \ + curl_multi_socket.3 \ + curl_multi_socket_action.3 \ + curl_multi_socket_all.3 \ + curl_multi_strerror.3 \ + curl_multi_timeout.3 \ + curl_multi_wait.3 \ + curl_share_cleanup.3 \ + curl_share_init.3 \ + curl_share_setopt.3 \ + curl_share_strerror.3 \ + curl_slist_append.3 \ + curl_slist_free_all.3 \ + curl_strequal.3 \ + curl_strnequal.3 \ + curl_unescape.3 \ + curl_url.3 \ + curl_url_cleanup.3 \ + curl_url_dup.3 \ + curl_url_get.3 \ + curl_url_set.3 \ + curl_version.3 \ + curl_version_info.3 \ + libcurl-easy.3 \ + libcurl-env.3 \ + libcurl-errors.3 \ + libcurl-multi.3 \ + libcurl-security.3 \ + libcurl-share.3 \ + libcurl-symbols.3 \ + libcurl-thread.3 \ + libcurl-tutorial.3 \ + libcurl.3 diff --git a/docs/libcurl/curl_url.3 b/docs/libcurl/curl_url.3 new file mode 100644 index 000000000..0a5626477 --- /dev/null +++ b/docs/libcurl/curl_url.3 @@ -0,0 +1,61 @@ +.\" ************************************************************************** +.\" * _ _ ____ _ +.\" * Project ___| | | | _ \| | +.\" * / __| | | | |_) | | +.\" * | (__| |_| | _ <| |___ +.\" * \___|\___/|_| \_\_____| +.\" * +.\" * Copyright (C) 1998 - 2018, Daniel Stenberg, , 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. +.\" * +.\" ************************************************************************** +.TH curl_url 3 "6 Aug 2018" "libcurl" "libcurl Manual" +.SH NAME +curl_url - returns a new CURLU handle +.SH SYNOPSIS +.B #include + +CURLU *curl_url(); +.SH EXPERIMENTAL +The URL API is considered \fBEXPERIMENTAL\fP until further notice. Please test +it, report bugs and help us perfect it. Once proven to be reliable, the +experimental label will be removed. + +While this API is marked experimental, we reserve the right to modify the API +slightly if we deem it necessary and it makes it notably better or easier to +use. +.SH DESCRIPTION +This function will allocates and returns a pointer to a fresh CURLU handle, to +be used for further use of the URL API. +.SH RETURN VALUE +Returns a \fBCURLU *\fP if successful, or NULL if out of memory. +.SH EXAMPLE +.nf + CURLUcode rc; + CURLU *url = curl_url(); + rc = curl_url_set(url, CURLUPART_URL, "https://example.com", 0); + if(!rc) { + char *scheme; + rc = curl_url_get(url, CURLUPART_SCHEME, &scheme, 0); + if(!rc) { + printf("the scheme is %s\n", scheme); + curl_free(scheme); + } + curl_url_cleanup(url); + } +.fi +.SH AVAILABILITY +Added in curl 7.63.0 +.SH "SEE ALSO" +.BR curl_url_cleanup "(3), " curl_url_get "(3), " curl_url_set "(3), " +.BR curl_url_dup "(3), " diff --git a/docs/libcurl/curl_url_cleanup.3 b/docs/libcurl/curl_url_cleanup.3 new file mode 100644 index 000000000..a8158b7b8 --- /dev/null +++ b/docs/libcurl/curl_url_cleanup.3 @@ -0,0 +1,44 @@ +.\" ************************************************************************** +.\" * _ _ ____ _ +.\" * Project ___| | | | _ \| | +.\" * / __| | | | |_) | | +.\" * | (__| |_| | _ <| |___ +.\" * \___|\___/|_| \_\_____| +.\" * +.\" * Copyright (C) 1998 - 2018, Daniel Stenberg, , 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. +.\" * +.\" ************************************************************************** +.TH curl_url_cleanup 3 "6 Aug 2018" "libcurl" "libcurl Manual" +.SH NAME +curl_url_cleanup - free a CURLU handle +.SH SYNOPSIS +.B #include + +void curl_url_cleanup(CURLU *handle); +.fi +.SH DESCRIPTION +Frees all the resources associated with the given CURLU handle! +.SH RETURN VALUE +none +.SH EXAMPLE +.nf + CURLU *url = curl_url(); + curl_url_set(url, CURLUPART_URL, "https://example.com", 0); + curl_url_cleanup(url); +.fi +.SH AVAILABILITY +Added in curl 7.63.0 +.SH "SEE ALSO" +.BR curl_url_dup "(3), " curl_url "(3), " curl_url_set "(3), " +.BR curl_url_get "(3), " diff --git a/docs/libcurl/curl_url_dup.3 b/docs/libcurl/curl_url_dup.3 new file mode 100644 index 000000000..4815dbd2b --- /dev/null +++ b/docs/libcurl/curl_url_dup.3 @@ -0,0 +1,52 @@ +.\" ************************************************************************** +.\" * _ _ ____ _ +.\" * Project ___| | | | _ \| | +.\" * / __| | | | |_) | | +.\" * | (__| |_| | _ <| |___ +.\" * \___|\___/|_| \_\_____| +.\" * +.\" * Copyright (C) 1998 - 2018, Daniel Stenberg, , 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. +.\" * +.\" ************************************************************************** +.TH curl_url_dup 3 "6 Aug 2018" "libcurl" "libcurl Manual" +.SH NAME +curl_url_dup - duplicate a CURLU handle +.SH SYNOPSIS +.B #include + +CURLU *curl_url_dup(CURLU *inhandle); +.fi +.SH DESCRIPTION +Duplicates a given CURLU \fIinhandle\fP and all its contents and returns a +pointer to a new CURLU handle. The new handle also needs to be freed with +\fIcurl_url_cleanup(3)\fP. +.SH RETURN VALUE +Returns a new handle or NULL if out of memory. +.SH EXAMPLE +.nf + CURLUcode rc; + CURLU *url = curl_url(); + CURLU *url2; + rc = curl_url_set(url, CURLUPART_URL, "https://example.com", 0); + if(!rc) { + url2 = curl_url_dup(url); /* clone it! */ + curl_url_cleanup(url2); + } + curl_url_cleanup(url); +.fi +.SH AVAILABILITY +Added in curl 7.63.0 +.SH "SEE ALSO" +.BR curl_url_cleanup "(3), " curl_url "(3), " curl_url_set "(3), " +.BR curl_url_get "(3), " diff --git a/docs/libcurl/curl_url_get.3 b/docs/libcurl/curl_url_get.3 new file mode 100644 index 000000000..824d4964f --- /dev/null +++ b/docs/libcurl/curl_url_get.3 @@ -0,0 +1,110 @@ +.\" ************************************************************************** +.\" * _ _ ____ _ +.\" * Project ___| | | | _ \| | +.\" * / __| | | | |_) | | +.\" * | (__| |_| | _ <| |___ +.\" * \___|\___/|_| \_\_____| +.\" * +.\" * Copyright (C) 1998 - 2018, Daniel Stenberg, , 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. +.\" * +.\" ************************************************************************** +.TH curl_url_get 3 "6 Aug 2018" "libcurl" "libcurl Manual" +.SH NAME +curl_url_get - extract a part from a URL +.SH SYNOPSIS +.B #include + +.nf +CURLUcode curl_url_get(CURLU *url, + CURLUPart what, + char **part, + unsigned int flags) +.fi +.SH DESCRIPTION +Given the \fIurl\fP handle of an already parsed URL, this function lets the +user extract individual pieces from it. + +The \fIwhat\fP argument should be the particular part to extract (see list +below) and \fIpart\fP points to a 'char *' to get updated to point to a newly +allocated string with the contents. + +The \fIflags\fP argument is a bitmask with individual features. + +The returned part pointer must be freed with \fIcurl_free(3)\fP after use. +.SH FLAGS +The flags argument is zero, one or more bits set in a bitmask. +.IP CURLU_DEFAULT_PORT +If the handle has no port stored, this option will make \fIcurl_url_get(3)\fP +return the default port for the used scheme. +.IP CURLU_DEFAULT_SCHEME +If the handle has no scheme stored, this option will make +\fIcurl_url_get(3)\fP return the default scheme instead of error. +.IP CURLU_NO_DEFAULT_PORT +Instructs \fIcurl_url_get(3)\fP to not return a port number if it matches the +default port for the scheme. +.IP CURLU_URLDECODE +Asks \fIcurl_url_get(3)\fP to URL decode the contents before returning it. It +will not attempt to decode the scheme, the port number or the full URL. + +The query component will also get plus-to-space convertion as a bonus when +this bit is set. + +Note that this URL decoding is charset unaware and you will get a zero +terminated string back with data that could be intended for a particular +encoding. + +If there's any byte values lower than 32 in the decoded string, the get +operation will return an error instead. +.SH PARTS +.IP CURLUPART_URL +When asked to return the full URL, \fIcurl_url_get(3)\fP will return a +normalized and possibly cleaned up version of what was previously parsed. +.IP CURLUPART_SCHEME +Scheme cannot be URL decoded on get. +.IP CURLUPART_USER +.IP CURLUPART_PASSWORD +.IP CURLUPART_OPTIONS +.IP CURLUPART_HOST +.IP CURLUPART_PORT +Port cannot be URL decoded on get. +.IP CURLUPART_PATH +.IP CURLUPART_QUERY +The query part will also get pluses converted to space when asked to URL +decode on get with the CURLU_URLDECODE bit. +.IP CURLUPART_FRAGMENT +.SH RETURN VALUE +Returns a CURLUcode error value, which is CURLUE_OK (0) if everything went +fine. + +If this function returns an error, no URL part is returned. +.SH EXAMPLE +.nf + CURLUcode rc; + CURLU *url = curl_url(); + rc = curl_url_set(url, CURLUPART_URL, "https://example.com", 0); + if(!rc) { + char *scheme; + rc = curl_url_get(url, CURLUPART_SCHEME, &scheme, 0); + if(!rc) { + printf("the scheme is %s\n", scheme); + curl_free(scheme); + } + curl_url_cleanup(url); + } +.fi +.SH AVAILABILITY +Added in curl 7.63.0 +.SH "SEE ALSO" +.BR curl_url_cleanup "(3), " curl_url "(3), " curl_url_set "(3), " +.BR curl_url_dup "(3), " diff --git a/docs/libcurl/curl_url_set.3 b/docs/libcurl/curl_url_set.3 new file mode 100644 index 000000000..75fc0d963 --- /dev/null +++ b/docs/libcurl/curl_url_set.3 @@ -0,0 +1,120 @@ +.\" ************************************************************************** +.\" * _ _ ____ _ +.\" * Project ___| | | | _ \| | +.\" * / __| | | | |_) | | +.\" * | (__| |_| | _ <| |___ +.\" * \___|\___/|_| \_\_____| +.\" * +.\" * Copyright (C) 1998 - 2018, Daniel Stenberg, , 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. +.\" * +.\" ************************************************************************** +.TH curl_url_set 3 "6 Aug 2018" "libcurl" "libcurl Manual" +.SH NAME +curl_url_set - set a part from a URL +.SH SYNOPSIS +.B #include + +CURLUcode curl_url_set(CURLU *url, + CURLUPart part, + const char *content, + unsigned int flags) +.fi +.SH DESCRIPTION +Given the \fIurl\fP handle of an already parsed URL, this function lets the +user set/update individual pieces of it. + +The \fIpart\fP argument should identify the particular URL part (see list +below) to set or change, with \fIcontent\fP pointing to a zero terminated +string with the new contents for that URL part. The contents should be in the +form and encoding they'd use in a URL: URL encoded. + +Setting a part to a NULL pointer will effectively remove that part's contents +from the CURLU handle. + +The \fIflags\fP argument is a bitmask with independent features. +.SH PARTS +.IP CURLUPART_URL +Allows the full URL of the handle to be replaced. If the handle already is +populated with a URL, the new URL can be relative to the previous. + +When successfully setting a new URL, relative or absolute, the handle contents +will be replaced with the information of the newly set URL. + +Pass a pointer to a zero terminated string to the \fIurl\fP parameter. The +string must point to a correctly formatted "RFC 3986+" URL or be a NULL +pointer. +.IP CURLUPART_SCHEME +Scheme cannot be URL decoded on set. +.IP CURLUPART_USER +.IP CURLUPART_PASSWORD +.IP CURLUPART_OPTIONS +.IP CURLUPART_HOST +The host name can use IDNA. The string must then be encoded as your locale +says or UTF-8 (when winidn is used). +.IP CURLUPART_PORT +Port cannot be URL encoded on set. +.IP CURLUPART_PATH +If a path is set in the URL without a leading slash, a slash will be inserted +automatically when this URL is read from the handle. +.IP CURLUPART_QUERY +The query part will also get spaces converted to pluses when asked to URL +encode on set with the CURLU_URLENCODE bit. + +If used in with \fICURLU_APPENDQUERY\fP, the provided part will be appended on +the end of the existing query - and if the previous part didn't end with an +ampersand (&), an ampersand will be inserted before the new appended part. + +When \fCURLU_APPENDQUERY\fP is used together with \fICURLU_URLENCODE\fP, +the '=' symbols will not be URL encoded. + +The question mark in the URL is not part of the actual query contents. +.IP CURLUPART_FRAGMENT +The hash sign in the URL is not part of the actual fragment contents. +.SH FLAGS +The flags argument is zero, one or more bits set in a bitmask. +.IP CURLU_NON_SUPPORT_SCHEME +If set, allows \fIcurl_url_set(3)\fP to set a non-supported scheme. +.IP CURLU_URLENCODE +When set, \fIcurl_url_set(3)\fP URL encodes the part on entry, except for +scheme, port and URL. + +When setting the path component with URL encoding enabled, the slash character +will be skipped. + +The query part gets space-to-plus conversion before the URL conversion. + +This URL encoding is charset unaware and will convert the input on a +byte-by-byte manner. +.SH RETURN VALUE +Returns a CURLUcode error value, which is CURLUE_OK (0) if everything went +fine. + +If this function returns an error, no URL part is returned. +.SH EXAMPLE +.nf + CURLUcode rc; + CURLU *url = curl_url(); + rc = curl_url_set(url, CURLUPART_URL, "https://example.com", 0); + if(!rc) { + char *scheme; + /* change it to an FTP URL */ + rc = curl_url_set(url, CURLUPART_SCHEME, "ftp", 0); + } + curl_url_cleanup(url); +.fi +.SH AVAILABILITY +Added in curl 7.63.0 +.SH "SEE ALSO" +.BR curl_url_cleanup "(3), " curl_url "(3), " curl_url_get "(3), " +.BR curl_url_dup "(3), " diff --git a/docs/libcurl/symbols-in-versions b/docs/libcurl/symbols-in-versions index 2803a40d5..950eb5bc6 100644 --- a/docs/libcurl/symbols-in-versions +++ b/docs/libcurl/symbols-in-versions @@ -721,6 +721,36 @@ CURLSSLSET_NO_BACKENDS 7.56.0 CURLSSLSET_OK 7.56.0 CURLSSLSET_TOO_LATE 7.56.0 CURLSSLSET_UNKNOWN_BACKEND 7.56.0 +CURLUPART_FRAGMENT 7.62.0 +CURLUPART_HOST 7.62.0 +CURLUPART_OPTIONS 7.62.0 +CURLUPART_PASSWORD 7.62.0 +CURLUPART_PATH 7.62.0 +CURLUPART_PORT 7.62.0 +CURLUPART_QUERY 7.62.0 +CURLUPART_SCHEME 7.62.0 +CURLUPART_URL 7.62.0 +CURLUPART_USER 7.62.0 +CURLUE_BAD_HANDLE 7.62.0 +CURLUE_BAD_PARTPOINTER 7.62.0 +CURLUE_BAD_PORT_NUMBER 7.62.0 +CURLUE_MALFORMED_INPUT 7.62.0 +CURLUE_NO_FRAGMENT 7.62.0 +CURLUE_NO_HOST 7.62.0 +CURLUE_NO_OPTIONS 7.62.0 +CURLUE_NO_PASSWORD 7.62.0 +CURLUE_NO_PATH 7.62.0 +CURLUE_NO_PORT 7.62.0 +CURLUE_NO_QUERY 7.62.0 +CURLUE_NO_SCHEME 7.62.0 +CURLUE_NO_USER 7.62.0 +CURLUE_OK 7.62.0 +CURLUE_OUT_OF_MEMORY 7.62.0 +CURLUE_RELATIVE 7.62.0 +CURLUE_UNKNOWN_PART 7.62.0 +CURLUE_UNSUPPORTED_SCHEME 7.62.0 +CURLUE_URLDECODE 7.62.0 +CURLUE_USER_NOT_ALLOWED 7.62.0 CURLUSESSL_ALL 7.17.0 CURLUSESSL_CONTROL 7.17.0 CURLUSESSL_NONE 7.17.0 diff --git a/include/curl/Makefile.am b/include/curl/Makefile.am index 989d4a218..bf5f06120 100644 --- a/include/curl/Makefile.am +++ b/include/curl/Makefile.am @@ -5,7 +5,7 @@ # | (__| |_| | _ <| |___ # \___|\___/|_| \_\_____| # -# Copyright (C) 1998 - 2017, Daniel Stenberg, , et al. +# Copyright (C) 1998 - 2018, Daniel Stenberg, , et al. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms @@ -21,7 +21,7 @@ ########################################################################### pkginclude_HEADERS = \ curl.h curlver.h easy.h mprintf.h stdcheaders.h multi.h \ - typecheck-gcc.h system.h + typecheck-gcc.h system.h urlapi.h pkgincludedir= $(includedir)/curl diff --git a/include/curl/curl.h b/include/curl/curl.h index 999fd963a..2cd611818 100644 --- a/include/curl/curl.h +++ b/include/curl/curl.h @@ -2794,6 +2794,7 @@ CURL_EXTERN CURLcode curl_easy_pause(CURL *handle, int bitmask); stuff before they can be included! */ #include "easy.h" /* nothing in curl is fun without the easy stuff */ #include "multi.h" +#include "urlapi.h" /* the typechecker doesn't work in C++ (yet) */ #if defined(__GNUC__) && defined(__GNUC_MINOR__) && \ diff --git a/include/curl/urlapi.h b/include/curl/urlapi.h new file mode 100644 index 000000000..b16cfce56 --- /dev/null +++ b/include/curl/urlapi.h @@ -0,0 +1,121 @@ +#ifndef __CURL_URLAPI_H +#define __CURL_URLAPI_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 2018, Daniel Stenberg, , 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. + * + ***************************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +/* 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_RELATIVE, /* 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_PATH, /* 16 */ + CURLUE_NO_QUERY, /* 17 */ + CURLUE_NO_FRAGMENT, /* 18 */ + CURLUE_OUT_OF_MEMORY /* 19 */ +} 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_DEFAULT_PORT (1<<0) /* return default port number */ +#define CURLU_NO_DEFAULT_PORT (1<<1) /* act as if no port number was set, + if the port number matches the + default for the scheme */ +#define CURLU_DEFAULT_SCHEME (1<<2) /* return default scheme if + missing */ +#define CURLU_NON_SUPPORT_SCHEME (1<<3) /* allow non-supported scheme */ +#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(). + */ +CURL_EXTERN CURLU *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. + */ +CURL_EXTERN void curl_url_cleanup(CURLU *handle); + +/* + * curl_url_dup() duplicates a CURLU handle and returns a new copy. The new + * handle must also be freed with curl_url_cleanup(). + */ +CURL_EXTERN CURLU *curl_url_dup(CURLU *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 + * curl_free() afterwards. + */ +CURL_EXTERN CURLUcode curl_url_get(CURLU *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. + */ +CURL_EXTERN CURLUcode curl_url_set(CURLU *handle, CURLUPart what, + const char *part, unsigned int flags); + + +#ifdef __cplusplus +} /* end of extern "C" */ +#endif + +#endif diff --git a/lib/Makefile.inc b/lib/Makefile.inc index ac0affeae..83a27dd0b 100644 --- a/lib/Makefile.inc +++ b/lib/Makefile.inc @@ -55,7 +55,7 @@ LIB_CFILES = file.c timeval.c base64.c hostip.c progress.c formdata.c \ curl_multibyte.c hostcheck.c conncache.c pipeline.c dotdot.c \ x509asn1.c http2.c smb.c curl_endian.c curl_des.c system_win32.c \ mime.c sha256.c setopt.c curl_path.c curl_ctype.c curl_range.c psl.c \ - doh.c + doh.c urlapi.c LIB_HFILES = arpa_telnet.h netrc.h file.h timeval.h hostip.h progress.h \ formdata.h cookie.h http.h sendf.h ftp.h url.h dict.h if2ip.h \ @@ -75,7 +75,7 @@ LIB_HFILES = arpa_telnet.h netrc.h file.h timeval.h hostip.h progress.h \ curl_setup_once.h multihandle.h setup-vms.h pipeline.h dotdot.h \ x509asn1.h http2.h sigpipe.h smb.h curl_endian.h curl_des.h \ curl_printf.h system_win32.h rand.h mime.h curl_sha256.h setopt.h \ - curl_path.h curl_ctype.h curl_range.h psl.h doh.h + curl_path.h curl_ctype.h curl_range.h psl.h doh.h urlapi-int.h LIB_RCFILES = libcurl.rc diff --git a/lib/escape.c b/lib/escape.c index 10774f058..afd3899f9 100644 --- a/lib/escape.c +++ b/lib/escape.c @@ -5,7 +5,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2017, Daniel Stenberg, , et al. + * Copyright (C) 1998 - 2018, Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -41,7 +41,7 @@ its behavior is altered by the current locale. See https://tools.ietf.org/html/rfc3986#section-2.3 */ -static bool Curl_isunreserved(unsigned char in) +bool Curl_isunreserved(unsigned char in) { switch(in) { case '0': case '1': case '2': case '3': case '4': @@ -141,6 +141,8 @@ char *curl_easy_escape(struct Curl_easy *data, const char *string, * 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). * + * 'data' can be set to NULL but then this function can't convert network + * data to host for non-ascii. */ CURLcode Curl_urldecode(struct Curl_easy *data, const char *string, size_t length, @@ -151,7 +153,7 @@ CURLcode Curl_urldecode(struct Curl_easy *data, char *ns = malloc(alloc); size_t strindex = 0; unsigned long hex; - CURLcode result; + CURLcode result = CURLE_OK; if(!ns) return CURLE_OUT_OF_MEMORY; @@ -171,11 +173,13 @@ CURLcode Curl_urldecode(struct Curl_easy *data, in = curlx_ultouc(hex); /* this long is never bigger than 255 anyway */ - result = Curl_convert_from_network(data, (char *)&in, 1); - if(result) { - /* Curl_convert_from_network calls failf if unsuccessful */ - free(ns); - return result; + if(data) { + result = Curl_convert_from_network(data, (char *)&in, 1); + if(result) { + /* Curl_convert_from_network calls failf if unsuccessful */ + free(ns); + return result; + } } string += 2; diff --git a/lib/escape.h b/lib/escape.h index 638666f03..666f1ad71 100644 --- a/lib/escape.h +++ b/lib/escape.h @@ -7,7 +7,7 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 1998 - 2011, Daniel Stenberg, , et al. + * Copyright (C) 1998 - 2018, Daniel Stenberg, , et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms @@ -24,6 +24,7 @@ /* 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(struct Curl_easy *data, const char *string, size_t length, char **ostring, size_t *olen, diff --git a/lib/imap.c b/lib/imap.c index 4a9c01ae7..63fcb4d41 100644 --- a/lib/imap.c +++ b/lib/imap.c @@ -159,7 +159,8 @@ const struct Curl_handler Curl_handler_imaps = { ZERO_NULL, /* connection_check */ PORT_IMAPS, /* defport */ CURLPROTO_IMAPS, /* protocol */ - PROTOPT_CLOSEACTION | PROTOPT_SSL /* flags */ + PROTOPT_CLOSEACTION | PROTOPT_SSL | /* flags */ + PROTOPT_URLOPTIONS }; #endif diff --git a/lib/transfer.c b/lib/transfer.c index 63d01efd9..3d8089ee6 100644 --- a/lib/transfer.c +++ b/lib/transfer.c @@ -75,6 +75,7 @@ #include "http2.h" #include "mime.h" #include "strcase.h" +#include "urlapi-int.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" @@ -1454,311 +1455,6 @@ CURLcode Curl_posttransfer(struct Curl_easy *data) return CURLE_OK; } -#ifndef CURL_DISABLE_HTTP -/* - * 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. - */ -static size_t 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. - */ -static void 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) - */ -static bool is_absolute_url(const char *url) -{ - char prot[16]; /* URL protocol string storage */ - char letter; /* used for a silly sscanf */ - - return (2 == sscanf(url, "%15[^?&/:]://%c", prot, &letter)) ? TRUE : 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). - */ -static char *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 = 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 */ - strcpy_url(&newest[urllen], useurl, !host_changed); - - free(url_clone); - - return newest; -} -#endif /* CURL_DISABLE_HTTP */ - /* * Curl_follow() handles the URL redirect magic. Pass in the 'newurl' string * as given by the remote server and set up the new URL to request. @@ -1810,12 +1506,12 @@ CURLcode Curl_follow(struct Curl_easy *data, } } - if(!is_absolute_url(newurl)) { + if(!Curl_is_absolute_url(newurl, NULL, 8)) { /*** *DANG* this is an RFC 2068 violation. The URL is supposed to be absolute and this doesn't seem to be that! */ - char *absolute = concat_url(data->change.url, newurl); + char *absolute = Curl_concat_url(data->change.url, newurl); if(!absolute) return CURLE_OUT_OF_MEMORY; newurl = absolute; @@ -1824,7 +1520,7 @@ CURLcode Curl_follow(struct Curl_easy *data, /* The new URL MAY contain space or high byte values, that means a mighty stupid redirect URL but we still make an effort to do "right". */ char *newest; - size_t newlen = strlen_url(newurl, FALSE); + size_t newlen = Curl_strlen_url(newurl, FALSE); /* This is an absolute URL, don't allow the custom port number */ disallowport = TRUE; @@ -1833,7 +1529,7 @@ CURLcode Curl_follow(struct Curl_easy *data, if(!newest) return CURLE_OUT_OF_MEMORY; - strcpy_url(newest, newurl, FALSE); /* create a space-free URL */ + Curl_strcpy_url(newest, newurl, FALSE); /* create a space-free URL */ newurl = newest; /* use this instead now */ } diff --git a/lib/url.c b/lib/url.c index 977f1d6ad..66faa400d 100644 --- a/lib/url.c +++ b/lib/url.c @@ -1944,30 +1944,37 @@ static struct connectdata *allocate_conn(struct Curl_easy *data) return NULL; } +/* returns the handdler if the given scheme is built-in */ +const struct Curl_handler *Curl_builtin_scheme(const char *scheme) +{ + const struct Curl_handler * const *pp; + const struct Curl_handler *p; + /* Scan protocol handler table and match against 'scheme'. The handler may + be changed later when the protocol specific setup function is called. */ + for(pp = protocols; (p = *pp) != NULL; pp++) + if(strcasecompare(p->scheme, scheme)) + /* Protocol found in table. Check if allowed */ + return p; + return NULL; /* not found */ +} + + static CURLcode findprotocol(struct Curl_easy *data, struct connectdata *conn, const char *protostr) { - const struct Curl_handler * const *pp; - const struct Curl_handler *p; + const struct Curl_handler *p = Curl_builtin_scheme(protostr); - /* Scan protocol handler table and match against 'protostr' to set a few - variables based on the URL. Now that the handler may be changed later - when the protocol specific setup function is called. */ - for(pp = protocols; (p = *pp) != NULL; pp++) { - if(strcasecompare(p->scheme, protostr)) { - /* Protocol found in table. Check if allowed */ - if(!(data->set.allowed_protocols & p->protocol)) - /* nope, get out */ - break; - - /* it is allowed for "normal" request, now do an extra check if this is - the result of a redirect */ - if(data->state.this_is_a_follow && - !(data->set.redir_protocols & p->protocol)) - /* nope, get out */ - break; + if(p && /* Protocol found in table. Check if allowed */ + (data->set.allowed_protocols & p->protocol)) { + /* it is allowed for "normal" request, now do an extra check if this is + the result of a redirect */ + if(data->state.this_is_a_follow && + !(data->set.redir_protocols & p->protocol)) + /* nope, get out */ + ; + else { /* Perform setup complement if some. */ conn->handler = conn->given = p; @@ -1976,7 +1983,6 @@ static CURLcode findprotocol(struct Curl_easy *data, } } - /* The protocol was not found in the table, but we don't have to assign it to anything since it is already assigned to a dummy-struct in the create_conn() function when the connectdata struct is allocated. */ diff --git a/lib/url.h b/lib/url.h index 3eb733a0a..7b9aff5c4 100644 --- a/lib/url.h +++ b/lib/url.h @@ -79,6 +79,8 @@ void Curl_getoff_all_pipelines(struct Curl_easy *data, CURLcode Curl_upkeep(struct conncache *conn_cache, void *data); +const struct Curl_handler *Curl_builtin_scheme(const char *scheme); + #define CURL_DEFAULT_PROXY_PORT 1080 /* default proxy port unless specified */ #define CURL_DEFAULT_HTTPS_PROXY_PORT 443 /* default https proxy port unless specified */ diff --git a/lib/urlapi-int.h b/lib/urlapi-int.h new file mode 100644 index 000000000..7ac09fdb5 --- /dev/null +++ b/lib/urlapi-int.h @@ -0,0 +1,29 @@ +#ifndef HEADER_CURL_URLAPI_INT_H +#define HEADER_CURL_URLAPI_INT_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2018, Daniel Stenberg, , 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. + * + ***************************************************************************/ +#include "curl_setup.h" +bool Curl_is_absolute_url(const char *url, char *scheme, size_t buflen); +char *Curl_concat_url(const char *base, const char *relurl); +size_t Curl_strlen_url(const char *url, bool relative); +void Curl_strcpy_url(char *output, const char *url, bool relative); +#endif diff --git a/lib/urlapi.c b/lib/urlapi.c new file mode 100644 index 000000000..8287861f7 --- /dev/null +++ b/lib/urlapi.c @@ -0,0 +1,1315 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2018, Daniel Stenberg, , 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. + * + ***************************************************************************/ + +#include "curl_setup.h" + +#include "urldata.h" +#include "urlapi-int.h" +#include "strcase.h" +#include "dotdot.h" +#include "url.h" +#include "escape.h" +#include "curl_ctype.h" + +/* The last 3 #include files should be in this order */ +#include "curl_printf.h" +#include "curl_memory.h" +#include "memdebug.h" + +/* 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" + +/* scheme is not URL encoded, the longest libcurl supported ones are 6 + letters */ +#define MAX_SCHEME_LEN 8 + +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 == ':') { + 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, + const struct Curl_handler *h, + 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. */ + ccode = Curl_parse_login_details(login, ptr - login - 1, + &userp, &passwdp, + h->flags & PROTOPT_URLOPTIONS ? + &optionsp: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 != ':') + return CURLUE_MALFORMED_INPUT; + } + 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; /* accepted characters */ + size_t len; + size_t hlen = strlen(hostname); + (void)flags; + + if(hostname[0] == '[') { + hostname++; + l = "0123456789abcdefABCDEF::."; + hlen -= 2; + } + else /* % for URL escaped letters */ + l = "0123456789abcdefghijklimnopqrstuvwxyz-_.ABCDEFGHIJKLIMNOPQRSTUVWXYZ%"; + + len = strspn(hostname, l); + 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, CURLU *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; + size_t schemelen = 0; + size_t urllen; + const struct Curl_handler *h = NULL; + + 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; + + /* MSDOS/Windows style drive prefix, eg c: in c:foo */ +#define STARTS_WITH_DRIVE_PREFIX(str) \ + ((('a' <= str[0] && str[0] <= 'z') || \ + ('A' <= str[0] && str[0] <= 'Z')) && \ + (str[1] == ':')) + + /* MSDOS/Windows style drive prefix, optionally with + * a '|' instead of ':', followed by a slash or NUL */ +#define STARTS_WITH_URL_DRIVE_PREFIX(str) \ + ((('a' <= (str)[0] && (str)[0] <= 'z') || \ + ('A' <= (str)[0] && (str)[0] <= 'Z')) && \ + ((str)[1] == ':' || (str)[1] == '|') && \ + ((str)[2] == '/' || (str)[2] == '\\' || (str)[2] == 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 && strcasecompare(schemebuf, "file")) { + /* path has been allocated large anough 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:/) -- 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]; + + /* + * According to RFC 8089, a file: URL can be reliably dereferenced if: + * + * o it has no/blank hostname, or + * + * o the hostname matches "localhost" (case-insensitively), or + * + * o the hostname is a FQDN that resolves to this machine. + * + * For brevity, we only consider URLs with empty, "localhost", or + * "127.0.0.1" hostnames as local. + * + * Additionally, there is an exception for URLs with a Windows drive + * letter in the authority (which was accidentally omitted from RFC 8089 + * Appendix E, but believe me, it was meant to be there. --MK) + */ + if(ptr[0] != '/' && !STARTS_WITH_URL_DRIVE_PREFIX(ptr)) { + /* the URL includes a host name, it must match "localhost" or + "127.0.0.1" to be valid */ + if(!checkprefix("localhost/", ptr) && + !checkprefix("127.0.0.1/", ptr)) { + /* Invalid file://hostname/, expected localhost or 127.0.0.1 or + none */ + return CURLUE_MALFORMED_INPUT; + } + ptr += 9; /* now points to the slash after the host */ + } + + path = ptr; + } + +#if !defined(MSDOS) && !defined(WIN32) && !defined(__CYGWIN__) + /* Don't allow Windows drive letters when not in Windows. + * This catches both "file:/c:" and "file:c:" */ + if(('/' == path[0] && STARTS_WITH_URL_DRIVE_PREFIX(&path[1])) || + STARTS_WITH_URL_DRIVE_PREFIX(path)) { + /* File drive letters are only accepted in MSDOS/Windows */ + return CURLUE_MALFORMED_INPUT; + } +#else + /* If the path starts with a slash and a drive letter, ditch the slash */ + if('/' == path[0] && STARTS_WITH_URL_DRIVE_PREFIX(&path[1])) { + /* This cannot be done with strcpy, as the memory chunks overlap! */ + memmove(path, &path[1], strlen(&path[1]) + 1); + } +#endif + + } + 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(!Curl_builtin_scheme(schemep) && + !(flags & CURLU_NON_SUPPORT_SCHEME)) + return CURLUE_UNSUPPORTED_SCHEME; + + if(junkscan(schemep)) + return CURLUE_MALFORMED_INPUT; + } + else { + /* no scheme! */ + + if(!(flags & CURLU_DEFAULT_SCHEME)) + return CURLUE_MALFORMED_INPUT; + schemep = (char *) DEFAULT_SCHEME; + + /* + * The URL was badly formatted, let's try without scheme specified. + */ + p = url; + } + 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 this is a known scheme, get some details */ + h = Curl_builtin_scheme(u->scheme); + + 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, h, &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, CURLU *u, unsigned int flags) +{ + CURLUcode result = seturl(url, u, flags); + if(result) { + free_urlhandle(u); + memset(u, 0, sizeof(struct Curl_URL)); + } + return result; +} + +/* + */ +CURLU *curl_url(void) +{ + return calloc(sizeof(struct Curl_URL), 1); +} + +void curl_url_cleanup(CURLU *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; \ + } + +CURLU *curl_url_dup(CURLU *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(CURLU *u, CURLUPart what, + char **part, unsigned int flags) +{ + char *ptr; + CURLUcode ifmissing = CURLUE_UNKNOWN_PART; + char portbuf[7]; + 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 */ + if(!ptr && (flags & CURLU_DEFAULT_PORT) && u->scheme) { + /* there's no stored port number, but asked to deliver + a default one for the scheme */ + const struct Curl_handler *h = + Curl_builtin_scheme(u->scheme); + if(h) { + snprintf(portbuf, sizeof(portbuf), "%ld", h->defport); + ptr = portbuf; + } + } + else if(ptr && u->scheme) { + /* there is a stored port number, but ask to inhibit if + it matches the default one for the scheme */ + const struct Curl_handler *h = + Curl_builtin_scheme(u->scheme); + if(h && (h->defport == u->portnum) && + (flags & CURLU_NO_DEFAULT_PORT)) + ptr = NULL; + } + 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; + urldecode = FALSE; /* not for the whole thing */ + if(u->scheme && strcasecompare("file", u->scheme)) { + url = aprintf("file://%s%s%s", + u->path, + u->fragment? "#": "", + u->fragment? u->fragment : ""); + } + else if(!u->host) + return CURLUE_NO_HOST; + else { + const struct Curl_handler *h = NULL; + if(u->scheme) + scheme = u->scheme; + else if(flags & CURLU_DEFAULT_SCHEME) + scheme = (char *) DEFAULT_SCHEME; + else + return CURLUE_NO_SCHEME; + + if(scheme) { + h = Curl_builtin_scheme(scheme); + if(!port && (flags & CURLU_DEFAULT_PORT)) { + /* there's no stored port number, but asked to deliver + a default one for the scheme */ + if(h) { + snprintf(portbuf, sizeof(portbuf), "%ld", h->defport); + port = portbuf; + } + } + else if(port) { + /* there is a stored port number, but asked to inhibit if it matches + the default one for the scheme */ + if(h && (h->defport == u->portnum) && + (flags & CURLU_NO_DEFAULT_PORT)) + port = NULL; + } + } + if(h && !(h->flags & PROTOPT_URLOPTIONS)) + 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(NULL, *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(CURLU *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; + + 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: + if(!(flags & CURLU_NON_SUPPORT_SCHEME) && + /* verify that it is a fine scheme */ + !Curl_builtin_scheme(part)) + return CURLUE_UNSUPPORTED_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; + 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; + CURLU *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 == '=') && appendquery) || + ((*i == '+') && plusencode)) { + *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; +} diff --git a/tests/data/Makefile.inc b/tests/data/Makefile.inc index aa0b03e3c..04a0c20f6 100644 --- a/tests/data/Makefile.inc +++ b/tests/data/Makefile.inc @@ -178,6 +178,8 @@ test1533 test1534 test1535 test1536 test1537 test1538 \ test1540 \ test1550 test1551 test1552 test1553 test1554 test1555 test1556 test1557 \ \ +test1560 \ +\ test1590 \ test1600 test1601 test1602 test1603 test1604 test1605 test1606 test1607 \ test1608 test1609 test1620 \ diff --git a/tests/data/test1560 b/tests/data/test1560 new file mode 100644 index 000000000..720df036f --- /dev/null +++ b/tests/data/test1560 @@ -0,0 +1,28 @@ + + + +unittest +URL API + + + +# +# Client-side + + +none + + +file +https +http + + +URL API + + +lib1560 + + + + diff --git a/tests/libtest/Makefile.am b/tests/libtest/Makefile.am index d14f37d39..dc97e327e 100644 --- a/tests/libtest/Makefile.am +++ b/tests/libtest/Makefile.am @@ -133,3 +133,8 @@ lib1521.c: $(top_srcdir)/tests/libtest/mk-lib1521.pl $(top_srcdir)/include/curl/ checksrc: @PERL@ $(top_srcdir)/lib/checksrc.pl $(srcdir)/*.c + +if CURLDEBUG +# for debug builds, we scan the sources on all regular make invokes +all-local: checksrc +endif diff --git a/tests/libtest/Makefile.inc b/tests/libtest/Makefile.inc index 238ef97d7..7a3cd1665 100644 --- a/tests/libtest/Makefile.inc +++ b/tests/libtest/Makefile.inc @@ -30,6 +30,7 @@ noinst_PROGRAMS = chkhostname libauthretry libntlmconnect \ lib1534 lib1535 lib1536 lib1537 lib1538 \ lib1540 \ lib1550 lib1551 lib1552 lib1553 lib1554 lib1555 lib1556 lib1557 \ + lib1560 \ lib1900 \ lib2033 @@ -507,6 +508,9 @@ lib1557_SOURCES = lib1557.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS) lib1557_LDADD = $(TESTUTIL_LIBS) lib1557_CPPFLAGS = $(AM_CPPFLAGS) -DLIB1557 +lib1560_SOURCES = lib1560.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS) +lib1560_LDADD = $(TESTUTIL_LIBS) + lib1900_SOURCES = lib1900.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS) lib1900_LDADD = $(TESTUTIL_LIBS) lib1900_CPPFLAGS = $(AM_CPPFLAGS) diff --git a/tests/libtest/lib1560.c b/tests/libtest/lib1560.c new file mode 100644 index 000000000..669ea9ada --- /dev/null +++ b/tests/libtest/lib1560.c @@ -0,0 +1,760 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2018, Daniel Stenberg, , 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. + * + ***************************************************************************/ + +/* + * Note: + * + * Since the URL parser by default only accepts schemes that *this instance* + * of libcurl supports, make sure that the test1560 file lists all the schemes + * that this test will assume to be present! + */ + +#include "test.h" + +#include "testutil.h" +#include "warnless.h" +#include "memdebug.h" /* LAST include file */ + +struct part { + CURLUPart part; + const char *name; +}; + + +static int checkparts(CURLU *u, const char *in, const char *wanted, + unsigned int getflags) +{ + int i; + CURLUcode rc; + char buf[256]; + char *bufp = &buf[0]; + size_t len = sizeof(buf); + struct part parts[] = { + {CURLUPART_SCHEME, "scheme"}, + {CURLUPART_USER, "user"}, + {CURLUPART_PASSWORD, "password"}, + {CURLUPART_OPTIONS, "options"}, + {CURLUPART_HOST, "host"}, + {CURLUPART_PORT, "port"}, + {CURLUPART_PATH, "path"}, + {CURLUPART_QUERY, "query"}, + {CURLUPART_FRAGMENT, "fragment"}, + {0, NULL} + }; + buf[0] = 0; + + for(i = 0; parts[i].name; i++) { + char *p = NULL; + size_t n; + rc = curl_url_get(u, parts[i].part, &p, getflags); + if(!rc && p) { + snprintf(bufp, len, "%s%s", buf[0]?" | ":"", p); + } + else + snprintf(bufp, len, "%s[%d]", buf[0]?" | ":"", (int)rc); + + n = strlen(bufp); + bufp += n; + len -= n; + curl_free(p); + } + if(strcmp(buf, wanted)) { + fprintf(stderr, "in: %s\nwanted: %s\ngot: %s\n", in, wanted, buf); + return 1; + } + return 0; +} + +struct redircase { + const char *in; + const char *set; + const char *out; + unsigned int urlflags; + unsigned int setflags; + CURLUcode ucode; +}; + +struct setcase { + const char *in; + const char *set; + const char *out; + unsigned int urlflags; + unsigned int setflags; + CURLUcode ucode; +}; + +struct testcase { + const char *in; + const char *out; + unsigned int urlflags; + unsigned int getflags; + CURLUcode ucode; +}; + +struct urltestcase { + const char *in; + const char *out; + unsigned int urlflags; /* pass to curl_url() */ + unsigned int getflags; /* pass to curl_url_get() */ + CURLUcode ucode; +}; + +struct querycase { + const char *in; + const char *q; + const char *out; + unsigned int urlflags; /* pass to curl_url() */ + unsigned int qflags; /* pass to curl_url_get() */ + CURLUcode ucode; +}; + +static struct testcase get_parts_list[] ={ + {"https://127.0.0.1:443", + "https | [11] | [12] | [13] | 127.0.0.1 | [15] | / | [17] | [18]", + 0, CURLU_NO_DEFAULT_PORT, CURLUE_OK}, + {"http://%3a:%3a@ex%0ample/%3f+?+%3f+%23#+%23%3f%g7", + "http | : | : | [13] | [6] | [15] | /?+ | ? # | +#?%g7", + 0, CURLU_URLDECODE, CURLUE_OK}, + {"http://%3a:%3a@ex%0ample/%3f?%3f%35#%35%3f%g7", + "http | %3a | %3a | [13] | ex%0ample | [15] | /%3f | %3f%35 | %35%3f%g7", + 0, 0, CURLUE_OK}, + {"http://HO0_-st%41/", + "http | [11] | [12] | [13] | HO0_-st%41 | [15] | / | [17] | [18]", + 0, 0, CURLUE_OK}, + {"file://hello.html", + "", + 0, 0, CURLUE_MALFORMED_INPUT}, + {"http://HO0_-st/", + "http | [11] | [12] | [13] | HO0_-st | [15] | / | [17] | [18]", + 0, 0, CURLUE_OK}, + {"imap://user:pass;option@server/path", + "imap | user | pass | option | server | [15] | /path | [17] | [18]", + 0, 0, CURLUE_OK}, + {"http://user:pass;option@server/path", + "http | user | pass;option | [13] | server | [15] | /path | [17] | [18]", + 0, 0, CURLUE_OK}, + {"file:/hello.html", + "file | [11] | [12] | [13] | [14] | [15] | /hello.html | [17] | [18]", + 0, 0, CURLUE_OK}, + {"file://127.0.0.1/hello.html", + "file | [11] | [12] | [13] | [14] | [15] | /hello.html | [17] | [18]", + 0, 0, CURLUE_OK}, + {"file:////hello.html", + "file | [11] | [12] | [13] | [14] | [15] | //hello.html | [17] | [18]", + 0, 0, CURLUE_OK}, + {"file:///hello.html", + "file | [11] | [12] | [13] | [14] | [15] | /hello.html | [17] | [18]", + 0, 0, CURLUE_OK}, + {"https://127.0.0.1", + "https | [11] | [12] | [13] | 127.0.0.1 | 443 | / | [17] | [18]", + 0, CURLU_DEFAULT_PORT, CURLUE_OK}, + {"https://127.0.0.1", + "https | [11] | [12] | [13] | 127.0.0.1 | [15] | / | [17] | [18]", + CURLU_DEFAULT_SCHEME, 0, CURLUE_OK}, + {"https://[::1]:1234", + "https | [11] | [12] | [13] | [::1] | 1234 | / | [17] | [18]", + CURLU_DEFAULT_SCHEME, 0, CURLUE_OK}, + {"https://127abc.com", + "https | [11] | [12] | [13] | 127abc.com | [15] | / | [17] | [18]", + CURLU_DEFAULT_SCHEME, 0, CURLUE_OK}, + {"https:// example.com?check", + "", + CURLU_DEFAULT_SCHEME, 0, CURLUE_MALFORMED_INPUT}, + {"https://e x a m p l e.com?check", + "", + CURLU_DEFAULT_SCHEME, 0, CURLUE_MALFORMED_INPUT}, + {"https://example.com?check", + "https | [11] | [12] | [13] | example.com | [15] | / | check | [18]", + CURLU_DEFAULT_SCHEME, 0, CURLUE_OK}, + {"https://example.com:65536", + "", + CURLU_DEFAULT_SCHEME, 0, CURLUE_BAD_PORT_NUMBER}, + {"https://example.com:0#moo", + "", + CURLU_DEFAULT_SCHEME, 0, CURLUE_BAD_PORT_NUMBER}, + {"https://example.com:01#moo", + "https | [11] | [12] | [13] | example.com | 1 | / | " + "[17] | moo", + CURLU_DEFAULT_SCHEME, 0, CURLUE_OK}, + {"https://example.com:1#moo", + "https | [11] | [12] | [13] | example.com | 1 | / | " + "[17] | moo", + CURLU_DEFAULT_SCHEME, 0, CURLUE_OK}, + {"http://example.com#moo", + "http | [11] | [12] | [13] | example.com | [15] | / | " + "[17] | moo", + CURLU_DEFAULT_SCHEME, 0, CURLUE_OK}, + {"http://example.com", + "http | [11] | [12] | [13] | example.com | [15] | / | " + "[17] | [18]", + CURLU_DEFAULT_SCHEME, 0, CURLUE_OK}, + {"http://example.com/path/html", + "http | [11] | [12] | [13] | example.com | [15] | /path/html | " + "[17] | [18]", + CURLU_DEFAULT_SCHEME, 0, CURLUE_OK}, + {"http://example.com/path/html?query=name", + "http | [11] | [12] | [13] | example.com | [15] | /path/html | " + "query=name | [18]", + CURLU_DEFAULT_SCHEME, 0, CURLUE_OK}, + {"http://example.com/path/html?query=name#anchor", + "http | [11] | [12] | [13] | example.com | [15] | /path/html | " + "query=name | anchor", + CURLU_DEFAULT_SCHEME, 0, CURLUE_OK}, + {"http://example.com:1234/path/html?query=name#anchor", + "http | [11] | [12] | [13] | example.com | 1234 | /path/html | " + "query=name | anchor", + CURLU_DEFAULT_SCHEME, 0, CURLUE_OK}, + {"http:///user:password@example.com:1234/path/html?query=name#anchor", + "http | user | password | [13] | example.com | 1234 | /path/html | " + "query=name | anchor", + CURLU_DEFAULT_SCHEME, 0, CURLUE_OK}, + {"https://user:password@example.com:1234/path/html?query=name#anchor", + "https | user | password | [13] | example.com | 1234 | /path/html | " + "query=name | anchor", + CURLU_DEFAULT_SCHEME, 0, CURLUE_OK}, + {"http://user:password@example.com:1234/path/html?query=name#anchor", + "http | user | password | [13] | example.com | 1234 | /path/html | " + "query=name | anchor", + CURLU_DEFAULT_SCHEME, 0, CURLUE_OK}, + {"http:/user:password@example.com:1234/path/html?query=name#anchor", + "http | user | password | [13] | example.com | 1234 | /path/html | " + "query=name | anchor", + CURLU_DEFAULT_SCHEME, 0, CURLUE_OK}, + {"http:////user:password@example.com:1234/path/html?query=name#anchor", + "", + CURLU_DEFAULT_SCHEME, 0, CURLUE_MALFORMED_INPUT}, + {NULL, NULL, 0, 0, CURLUE_OK}, +}; + +static struct urltestcase get_url_list[] = { + {"HTTP://test/", "http://test/", 0, 0, CURLUE_OK}, + {"http://HO0_-st..~./", "", 0, 0, CURLUE_MALFORMED_INPUT}, + {"http:/@example.com: 123/", "", 0, 0, CURLUE_BAD_PORT_NUMBER}, + {"http:/@example.com:123 /", "", 0, 0, CURLUE_BAD_PORT_NUMBER}, + {"http:/@example.com:123a/", "", 0, 0, CURLUE_BAD_PORT_NUMBER}, + {"http://host/file\r", "", 0, 0, CURLUE_MALFORMED_INPUT}, + {"http://host/file\n\x03", "", 0, 0, CURLUE_MALFORMED_INPUT}, + {"htt\x02://host/file", "", + CURLU_NON_SUPPORT_SCHEME, 0, CURLUE_MALFORMED_INPUT}, + {" http://host/file", "", 0, 0, CURLUE_MALFORMED_INPUT}, + /* here the password ends at the semicolon and options is 'word' */ + {"imap://user:pass;word@host/file", + "imap://user:pass;word@host/file", + 0, 0, CURLUE_OK}, + /* here the password has the semicolon */ + {"http://user:pass;word@host/file", + "http://user:pass;word@host/file", + 0, 0, CURLUE_OK}, + {"file:///file.txt#moo", + "file:///file.txt#moo", + 0, 0, CURLUE_OK}, + {"file:////file.txt", + "file:////file.txt", + 0, 0, CURLUE_OK}, + {"file:///file.txt", + "file:///file.txt", + 0, 0, CURLUE_OK}, + {"http://example.com/hello/../here", + "http://example.com/hello/../here", + CURLU_PATH_AS_IS, 0, CURLUE_OK}, + {"http://example.com/hello/../here", + "http://example.com/here", + 0, 0, CURLUE_OK}, + {"http://example.com:80", + "http://example.com/", + 0, CURLU_NO_DEFAULT_PORT, CURLUE_OK}, + {"tp://example.com/path/html", + "", + 0, 0, CURLUE_UNSUPPORTED_SCHEME}, + {"http://hello:fool@example.com", + "", + CURLU_DISALLOW_USER, 0, CURLUE_USER_NOT_ALLOWED}, + {"http:/@example.com:123", + "http://example.com:123/", + 0, 0, CURLUE_OK}, + {"http:/:password@example.com", + "http://:password@example.com/", + 0, 0, CURLUE_OK}, + {"http://user@example.com?#", + "http://user@example.com/", + 0, 0, CURLUE_OK}, + {"http://user@example.com?", + "http://user@example.com/", + 0, 0, CURLUE_OK}, + {"http://user@example.com#anchor", + "http://user@example.com/#anchor", + 0, 0, CURLUE_OK}, + {"example.com/path/html", + "https://example.com/path/html", + CURLU_DEFAULT_SCHEME, 0, CURLUE_OK}, + {"example.com/path/html", + "", + 0, 0, CURLUE_MALFORMED_INPUT}, + {"http://user:password@example.com:1234/path/html?query=name#anchor", + "http://user:password@example.com:1234/path/html?query=name#anchor", + 0, 0, CURLUE_OK}, + {"http://example.com:1234/path/html?query=name#anchor", + "http://example.com:1234/path/html?query=name#anchor", + 0, 0, CURLUE_OK}, + {"http://example.com/path/html?query=name#anchor", + "http://example.com/path/html?query=name#anchor", + 0, 0, CURLUE_OK}, + {"http://example.com/path/html?query=name", + "http://example.com/path/html?query=name", + 0, 0, CURLUE_OK}, + {"http://example.com/path/html", + "http://example.com/path/html", + 0, 0, CURLUE_OK}, + {"tp://example.com/path/html", + "tp://example.com/path/html", + CURLU_NON_SUPPORT_SCHEME, 0, CURLUE_OK}, + {NULL, NULL, 0, 0, 0} +}; + +static int checkurl(const char *url, const char *out) +{ + if(strcmp(out, url)) { + fprintf(stderr, "Wanted: %s\nGot : %s\n", + out, url); + return 1; + } + return 0; +} + +/* !checksrc! disable SPACEBEFORECOMMA 1 */ +static struct setcase set_parts_list[] = { + {"https://host/", + "path=%4A%4B%4C,", + "https://host/%4a%4b%4c", + 0, 0, CURLUE_NO_HOST}, + {"https://host/mooo?q#f", + "path=NULL,query=NULL,fragment=NULL,", + "https://host/", + 0, 0, CURLUE_NO_HOST}, + {"https://user:secret@host/", + "user=NULL,password=NULL,", + "https://host/", + 0, 0, CURLUE_NO_HOST}, + {NULL, + "scheme=https,user= @:,host=foobar,", + "https://%20%20%20%40%3a@foobar/", + 0, CURLU_URLENCODE, CURLUE_OK}, + {NULL, + "scheme=https,host= ,path= ,user= ,password= ,query= ,fragment= ,", + "https://%20:%20@%20%20/%20?+#%20", + 0, CURLU_URLENCODE, CURLUE_OK}, + {NULL, + "scheme=https,host=foobar,path=/this /path /is /here,", + "https://foobar/this%20/path%20/is%20/here", + 0, CURLU_URLENCODE, CURLUE_OK}, + {"imap://user:secret;opt@host/", + "options=updated,scheme=imaps,password=p4ssw0rd,", + "imaps://user:p4ssw0rd;updated@host/", + 0, 0, CURLUE_NO_HOST}, + {"imap://user:secret;optit@host/", + "scheme=https,", + "https://user:secret@host/", + 0, 0, CURLUE_NO_HOST}, + {"file:///file#anchor", + "scheme=https,host=example,", + "https://example/file#anchor", + 0, 0, CURLUE_NO_HOST}, + {NULL, /* start fresh! */ + "scheme=file,host=127.0.0.1,path=/no,user=anonymous,", + "file:///no", + 0, 0, CURLUE_OK}, + {NULL, /* start fresh! */ + "scheme=ftp,host=127.0.0.1,path=/no,user=anonymous,", + "ftp://anonymous@127.0.0.1/no", + 0, 0, CURLUE_OK}, + {NULL, /* start fresh! */ + "scheme=https,host=example.com,", + "https://example.com/", + 0, CURLU_NON_SUPPORT_SCHEME, CURLUE_OK}, + {"http://user:foo@example.com/path?query#frag", + "fragment=changed,", + "http://user:foo@example.com/path?query#changed", + 0, CURLU_NON_SUPPORT_SCHEME, CURLUE_OK}, + {"http://example.com/", + "scheme=foo,", /* not accepted */ + "http://example.com/", + 0, 0, CURLUE_OK}, + {"http://example.com/", + "scheme=https,path=/hello,fragment=snippet,", + "https://example.com/hello#snippet", + 0, 0, CURLUE_OK}, + {"http://example.com:80", + "user=foo,port=1922,", + "http://foo@example.com:1922/", + 0, 0, CURLUE_OK}, + {"http://example.com:80", + "user=foo,password=bar,", + "http://foo:bar@example.com:80/", + 0, 0, CURLUE_OK}, + {"http://example.com:80", + "user=foo,", + "http://foo@example.com:80/", + 0, 0, CURLUE_OK}, + {"http://example.com", + "host=www.example.com,", + "http://www.example.com/", + 0, 0, CURLUE_OK}, + {"http://example.com:80", + "scheme=ftp,", + "ftp://example.com:80/", + 0, 0, CURLUE_OK}, + {NULL, NULL, NULL, 0, 0, 0} +}; + +static CURLUPart part2id(char *part) +{ + if(!strcmp("url", part)) + return CURLUPART_URL; + if(!strcmp("scheme", part)) + return CURLUPART_SCHEME; + if(!strcmp("user", part)) + return CURLUPART_USER; + if(!strcmp("password", part)) + return CURLUPART_PASSWORD; + if(!strcmp("options", part)) + return CURLUPART_OPTIONS; + if(!strcmp("host", part)) + return CURLUPART_HOST; + if(!strcmp("port", part)) + return CURLUPART_PORT; + if(!strcmp("path", part)) + return CURLUPART_PATH; + if(!strcmp("query", part)) + return CURLUPART_QUERY; + if(!strcmp("fragment", part)) + return CURLUPART_FRAGMENT; + return 9999; /* bad input => bad output */ +} + +static void updateurl(CURLU *u, const char *cmd, unsigned int setflags) +{ + const char *p = cmd; + + /* make sure the last command ends with a comma too! */ + while(p) { + char *e = strchr(p, ','); + if(e) { + size_t n = e-p; + char buf[80]; + char part[80]; + char value[80]; + memcpy(buf, p, n); + buf[n] = 0; + if(2 == sscanf(buf, "%79[^=]=%79[^,]", part, value)) { + CURLUPart what = part2id(part); +#if 0 + /* for debugging this */ + fprintf(stderr, "%s = %s [%d]\n", part, value, (int)what); +#endif + if(!strcmp("NULL", value)) + curl_url_set(u, what, NULL, setflags); + else + curl_url_set(u, what, value, setflags); + } + p = e + 1; + continue; + } + break; + } + +} + +static struct redircase set_url_list[] = { + {"file://localhost/path?query#frag", + "foo#another", + "file:///foo#another", + 0, 0, 0}, + {"http://example.com/path?query#frag", + "https://two.example.com/bradnew", + "https://two.example.com/bradnew", + 0, 0, 0}, + {"http://example.com/path?query#frag", + "../../newpage#foo", + "http://example.com/newpage#foo", + 0, 0, 0}, + {"http://user:foo@example.com/path?query#frag", + "../../newpage", + "http://user:foo@example.com/newpage", + 0, 0, 0}, + {"http://user:foo@example.com/path?query#frag", + "../newpage", + "http://user:foo@example.com/newpage", + 0, 0, 0}, + {NULL, NULL, NULL, 0, 0, 0} +}; + +static int set_url(void) +{ + int i; + CURLUcode rc; + CURLU *urlp; + int error = 0; + + for(i = 0; set_url_list[i].in && !error; i++) { + char *url = NULL; + urlp = curl_url(); + if(!urlp) + break; + rc = curl_url_set(urlp, CURLUPART_URL, set_url_list[i].in, + set_url_list[i].urlflags); + if(!rc) { + rc = curl_url_set(urlp, CURLUPART_URL, set_url_list[i].set, + set_url_list[i].setflags); + if(rc) { + fprintf(stderr, "%s:%d Set URL %s returned %d\n", + __FILE__, __LINE__, set_url_list[i].set, + (int)rc); + error++; + } + else { + rc = curl_url_get(urlp, CURLUPART_URL, &url, 0); + if(rc) { + fprintf(stderr, "%s:%d Get URL returned %d\n", + __FILE__, __LINE__, (int)rc); + error++; + } + else { + if(checkurl(url, set_url_list[i].out)) { + error++; + } + } + } + curl_free(url); + } + else if(rc != set_url_list[i].ucode) { + fprintf(stderr, "Set URL\nin: %s\nreturned %d (expected %d)\n", + set_url_list[i].in, (int)rc, set_url_list[i].ucode); + error++; + } + curl_url_cleanup(urlp); + } + return error; +} + +static int set_parts(void) +{ + int i; + CURLUcode rc; + int error = 0; + + for(i = 0; set_parts_list[i].set && !error; i++) { + char *url = NULL; + CURLU *urlp = curl_url(); + if(!urlp) { + error++; + break; + } + if(set_parts_list[i].in) + rc = curl_url_set(urlp, CURLUPART_URL, set_parts_list[i].in, + set_parts_list[i].urlflags); + else + rc = CURLUE_OK; + if(!rc) { + updateurl(urlp, set_parts_list[i].set, set_parts_list[i].setflags); + rc = curl_url_get(urlp, CURLUPART_URL, &url, 0); + + if(rc) { + fprintf(stderr, "%s:%d Get URL returned %d\n", + __FILE__, __LINE__, (int)rc); + error++; + } + else if(checkurl(url, set_parts_list[i].out)) { + error++; + } + } + else if(rc != set_parts_list[i].ucode) { + fprintf(stderr, "Set parts\nin: %s\nreturned %d (expected %d)\n", + set_parts_list[i].in, (int)rc, set_parts_list[i].ucode); + error++; + } + curl_free(url); + curl_url_cleanup(urlp); + } + return error; +} + +static int get_url(void) +{ + int i; + CURLUcode rc; + int error = 0; + for(i = 0; get_url_list[i].in && !error; i++) { + char *url = NULL; + CURLU *urlp = curl_url(); + if(!urlp) { + error++; + break; + } + rc = curl_url_set(urlp, CURLUPART_URL, get_url_list[i].in, + get_url_list[i].urlflags); + if(!rc) { + rc = curl_url_get(urlp, CURLUPART_URL, &url, get_url_list[i].getflags); + + if(rc) { + fprintf(stderr, "%s:%d returned %d\n", + __FILE__, __LINE__, (int)rc); + error++; + } + else { + if(checkurl(url, get_url_list[i].out)) { + error++; + } + } + } + else if(rc != get_url_list[i].ucode) { + fprintf(stderr, "Get URL\nin: %s\nreturned %d (expected %d)\n", + get_url_list[i].in, (int)rc, get_url_list[i].ucode); + error++; + } + curl_free(url); + curl_url_cleanup(urlp); + } + return error; +} + +static int get_parts(void) +{ + int i; + CURLUcode rc; + CURLU *urlp; + int error = 0; + for(i = 0; get_parts_list[i].in && !error; i++) { + urlp = curl_url(); + if(!urlp) { + error++; + break; + } + rc = curl_url_set(urlp, CURLUPART_URL, + get_parts_list[i].in, + get_parts_list[i].urlflags); + if(rc != get_parts_list[i].ucode) { + fprintf(stderr, "Get parts\nin: %s\nreturned %d (expected %d)\n", + get_parts_list[i].in, (int)rc, get_parts_list[i].ucode); + error++; + } + else if(get_parts_list[i].ucode) { + /* the expected error happened */ + } + else if(checkparts(urlp, get_parts_list[i].in, get_parts_list[i].out, + get_parts_list[i].getflags)) + error++; + curl_url_cleanup(urlp); + } + return error; +} + +static struct querycase append_list[] = { + {"HTTP://test/?s", "name=joe\x02", "http://test/?s&name=joe%02", + 0, CURLU_URLENCODE, CURLUE_OK}, + {"HTTP://test/?size=2#f", "name=joe=", "http://test/?size=2&name=joe=#f", + 0, CURLU_URLENCODE, CURLUE_OK}, + {"HTTP://test/?size=2#f", "name=joe doe", + "http://test/?size=2&name=joe+doe#f", + 0, CURLU_URLENCODE, CURLUE_OK}, + {"HTTP://test/", "name=joe", "http://test/?name=joe", 0, 0, CURLUE_OK}, + {"HTTP://test/?size=2", "name=joe", "http://test/?size=2&name=joe", + 0, 0, CURLUE_OK}, + {"HTTP://test/?size=2&", "name=joe", "http://test/?size=2&name=joe", + 0, 0, CURLUE_OK}, + {"HTTP://test/?size=2#f", "name=joe", "http://test/?size=2&name=joe#f", + 0, 0, CURLUE_OK}, + {NULL, NULL, NULL, 0, 0, 0} +}; + +static int append(void) +{ + int i; + CURLUcode rc; + CURLU *urlp; + int error = 0; + for(i = 0; append_list[i].in && !error; i++) { + urlp = curl_url(); + if(!urlp) { + error++; + break; + } + rc = curl_url_set(urlp, CURLUPART_URL, + append_list[i].in, + append_list[i].urlflags); + if(rc) + error++; + else + rc = curl_url_set(urlp, CURLUPART_QUERY, + append_list[i].q, + append_list[i].qflags | CURLU_APPENDQUERY); + if(error) + ; + else if(rc != append_list[i].ucode) { + fprintf(stderr, "Append\nin: %s\nreturned %d (expected %d)\n", + append_list[i].in, (int)rc, append_list[i].ucode); + error++; + } + else if(append_list[i].ucode) { + /* the expected error happened */ + } + else { + char *url; + rc = curl_url_get(urlp, CURLUPART_URL, &url, 0); + if(rc) { + fprintf(stderr, "%s:%d Get URL returned %d\n", + __FILE__, __LINE__, (int)rc); + error++; + } + else { + if(checkurl(url, append_list[i].out)) { + error++; + } + curl_free(url); + } + } + curl_url_cleanup(urlp); + } + return error; +} + +int test(char *URL) +{ + (void)URL; /* not used */ + + if(append()) + return 5; + + if(set_url()) + return 1; + + if(set_parts()) + return 2; + + if(get_url()) + return 3; + + if(get_parts()) + return 4; + + printf("success\n"); + return 0; +}