From 66b9926b61059f3548f79aa05167c9e208aee0f8 Mon Sep 17 00:00:00 2001 From: wyf <763257820@qq.com> Date: Mon, 31 Oct 2022 20:40:54 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=8E=A5=E5=8F=A3=E6=B5=8B?= =?UTF-8?q?=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- interface_testing/easy/curl_easy_duphandle.c | 13 + interface_testing/easy/curl_easy_escape.c | 11 + interface_testing/easy/curl_easy_getinfo.c | 20 + .../easy/curl_easy_option_by_id.c | 7 + .../easy/curl_easy_option_by_name.c | 7 + .../easy/curl_easy_option_next.c | 10 + interface_testing/easy/curl_easy_pause.c | 16 + interface_testing/easy/curl_easy_perform.c | 10 + interface_testing/easy/curl_easy_recv.c | 135 + interface_testing/easy/curl_easy_strerror.c | 14 + interface_testing/easy/curl_easy_unescape.c | 15 + interface_testing/easy/curl_easy_upkeep.c | 25 + interface_testing/mime/curl_mime_data_cb.c | 56 + .../multi/curl_multi_add_handle.c | 44 + interface_testing/multi/curl_multi_cleanup.c | 23 + interface_testing/multi/curl_multi_fdset.c | 151 + interface_testing/multi/curl_multi_init.c | 7 + interface_testing/multi/curl_multi_perform.c | 23 + interface_testing/multi/curl_multi_poll.c | 90 + interface_testing/share/curl_share_setopt.c | 9 + interface_testing/url/curl_url.c | 15 + interface_testing/url/curl_url_dup.c | 12 + interface_testing/url/curl_url_set.c | 15 + lib/ftp.c | 4401 ----------------- lib/http.c | 4382 ---------------- rust/rust_ffi/lib.rs | 1 - rust/rust_ffi/src/ffi_macros/get_macros.rs | 580 --- rust/rust_ffi/src/ffi_macros/mod.rs | 15 - 28 files changed, 728 insertions(+), 9379 deletions(-) create mode 100644 interface_testing/easy/curl_easy_duphandle.c create mode 100644 interface_testing/easy/curl_easy_escape.c create mode 100644 interface_testing/easy/curl_easy_getinfo.c create mode 100644 interface_testing/easy/curl_easy_option_by_id.c create mode 100644 interface_testing/easy/curl_easy_option_by_name.c create mode 100644 interface_testing/easy/curl_easy_option_next.c create mode 100644 interface_testing/easy/curl_easy_pause.c create mode 100644 interface_testing/easy/curl_easy_perform.c create mode 100644 interface_testing/easy/curl_easy_recv.c create mode 100644 interface_testing/easy/curl_easy_strerror.c create mode 100644 interface_testing/easy/curl_easy_unescape.c create mode 100644 interface_testing/easy/curl_easy_upkeep.c create mode 100644 interface_testing/mime/curl_mime_data_cb.c create mode 100644 interface_testing/multi/curl_multi_add_handle.c create mode 100644 interface_testing/multi/curl_multi_cleanup.c create mode 100644 interface_testing/multi/curl_multi_fdset.c create mode 100644 interface_testing/multi/curl_multi_init.c create mode 100644 interface_testing/multi/curl_multi_perform.c create mode 100644 interface_testing/multi/curl_multi_poll.c create mode 100644 interface_testing/share/curl_share_setopt.c create mode 100644 interface_testing/url/curl_url.c create mode 100644 interface_testing/url/curl_url_dup.c create mode 100644 interface_testing/url/curl_url_set.c delete mode 100644 lib/ftp.c delete mode 100644 lib/http.c delete mode 100644 rust/rust_ffi/src/ffi_macros/get_macros.rs delete mode 100644 rust/rust_ffi/src/ffi_macros/mod.rs diff --git a/interface_testing/easy/curl_easy_duphandle.c b/interface_testing/easy/curl_easy_duphandle.c new file mode 100644 index 0000000..df51759 --- /dev/null +++ b/interface_testing/easy/curl_easy_duphandle.c @@ -0,0 +1,13 @@ +#include +int main(){ + CURL *curl = curl_easy_init(); + CURL *nother; + if(curl) { + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "https://example.com"); + nother = curl_easy_duphandle(curl); + res = curl_easy_perform(nother); + curl_easy_cleanup(nother); + curl_easy_cleanup(curl); + } +} \ No newline at end of file diff --git a/interface_testing/easy/curl_easy_escape.c b/interface_testing/easy/curl_easy_escape.c new file mode 100644 index 0000000..005286f --- /dev/null +++ b/interface_testing/easy/curl_easy_escape.c @@ -0,0 +1,11 @@ +#include +int main(){ + CURL *curl = curl_easy_init(); + if(curl) { + char *output = curl_easy_escape(curl, "data to convert", 15); + if(output) { + printf("Encoded: %s\\n", output); + curl_free(output); + } + } +} \ No newline at end of file diff --git a/interface_testing/easy/curl_easy_getinfo.c b/interface_testing/easy/curl_easy_getinfo.c new file mode 100644 index 0000000..3f6822d --- /dev/null +++ b/interface_testing/easy/curl_easy_getinfo.c @@ -0,0 +1,20 @@ +#include +int main(){ + CURL *curl = curl_easy_init(); + if(curl) { + curl_easy_setopt(curl, CURLOPT_URL, "https://www.example.com/"); + CURLcode res = curl_easy_perform(curl); + + if(CURLE_OK == res) { + char *ct; + /* ask for the content-type */ + res = curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &ct); + + if((CURLE_OK == res) && ct) + printf("We received Content-Type: %s\n", ct); + } + + /* always cleanup */ + curl_easy_cleanup(curl); + } +} \ No newline at end of file diff --git a/interface_testing/easy/curl_easy_option_by_id.c b/interface_testing/easy/curl_easy_option_by_id.c new file mode 100644 index 0000000..68ebb9b --- /dev/null +++ b/interface_testing/easy/curl_easy_option_by_id.c @@ -0,0 +1,7 @@ +#include +int main(){ + const struct curl_easyoption *opt = curl_easy_option_by_id(CURLOPT_URL); + if(opt) { + printf("This option wants type %x\n", opt->type); + } +} \ No newline at end of file diff --git a/interface_testing/easy/curl_easy_option_by_name.c b/interface_testing/easy/curl_easy_option_by_name.c new file mode 100644 index 0000000..efae9a7 --- /dev/null +++ b/interface_testing/easy/curl_easy_option_by_name.c @@ -0,0 +1,7 @@ +#include +int main(){ + const struct curl_easyoption *opt = curl_easy_option_by_name("URL"); + if(opt) { + printf("This option wants CURLoption %x\n", (int)opt->id); + } +} \ No newline at end of file diff --git a/interface_testing/easy/curl_easy_option_next.c b/interface_testing/easy/curl_easy_option_next.c new file mode 100644 index 0000000..797782d --- /dev/null +++ b/interface_testing/easy/curl_easy_option_next.c @@ -0,0 +1,10 @@ +#include +int main(){ + /* iterate over all available options */ + const struct curl_easyoption *opt; + opt = curl_easy_option_next(NULL); + while(opt) { + printf("Name: %s\n", opt->name); + opt = curl_easy_option_next(opt); + } +} \ No newline at end of file diff --git a/interface_testing/easy/curl_easy_pause.c b/interface_testing/easy/curl_easy_pause.c new file mode 100644 index 0000000..c2afa80 --- /dev/null +++ b/interface_testing/easy/curl_easy_pause.c @@ -0,0 +1,16 @@ +#include +int main(){ + CURL *curl = curl_easy_init(); + if(curl) { + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "https://example.com"); + res = curl_easy_pause(curl, CURLPAUSE_ALL); + printf("---- paused the curl handle for perform \n"); + printf("%d\n", res); + if(res == CURLE_OK) { + printf("pause success\n"); + } + res = curl_easy_perform(curl); + curl_easy_cleanup(curl); + } +} \ No newline at end of file diff --git a/interface_testing/easy/curl_easy_perform.c b/interface_testing/easy/curl_easy_perform.c new file mode 100644 index 0000000..83d47d1 --- /dev/null +++ b/interface_testing/easy/curl_easy_perform.c @@ -0,0 +1,10 @@ +#include +int main(){ + CURL *curl = curl_easy_init(); + if(curl) { + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "https://example.com"); + res = curl_easy_perform(curl); + curl_easy_cleanup(curl); + } +} diff --git a/interface_testing/easy/curl_easy_recv.c b/interface_testing/easy/curl_easy_recv.c new file mode 100644 index 0000000..a741003 --- /dev/null +++ b/interface_testing/easy/curl_easy_recv.c @@ -0,0 +1,135 @@ +#include +#include +#include +#include + +/* Auxiliary function that waits on the socket. */ +static int wait_on_socket(curl_socket_t sockfd, int for_recv, long timeout_ms) +{ + struct timeval tv; + fd_set infd, outfd, errfd; + int res; + + tv.tv_sec = timeout_ms / 1000; + tv.tv_usec = (timeout_ms % 1000) * 1000; + + FD_ZERO(&infd); + FD_ZERO(&outfd); + FD_ZERO(&errfd); + + FD_SET(sockfd, &errfd); /* always check for error */ + + if(for_recv) { + FD_SET(sockfd, &infd); + } + else { + FD_SET(sockfd, &outfd); + } + + /* select() returns the number of signalled sockets or -1 */ + res = select((int)sockfd + 1, &infd, &outfd, &errfd, &tv); + return res; +} + +int main(void) +{ + CURL *curl; + /* Minimalistic http request */ + const char *request = "GET / HTTP/1.0\r\nHost: example.com\r\n\r\n"; + size_t request_len = strlen(request); + + /* A general note of caution here: if you are using curl_easy_recv() or + curl_easy_send() to implement HTTP or _any_ other protocol libcurl + supports "natively", you are doing it wrong and you should stop. + + This example uses HTTP only to show how to use this API, it does not + suggest that writing an application doing this is sensible. + */ + + curl = curl_easy_init(); + if(curl) { + CURLcode res; + curl_socket_t sockfd; + size_t nsent_total = 0; + + curl_easy_setopt(curl, CURLOPT_URL, "https://example.com"); + /* Do not do the transfer - only connect to host */ + curl_easy_setopt(curl, CURLOPT_CONNECT_ONLY, 1L); + res = curl_easy_perform(curl); + + if(res != CURLE_OK) { + printf("Error: %s\n", curl_easy_strerror(res)); + return 1; + } + + /* Extract the socket from the curl handle - we will need it for + waiting. */ + res = curl_easy_getinfo(curl, CURLINFO_ACTIVESOCKET, &sockfd); + + if(res != CURLE_OK) { + printf("Error: %s\n", curl_easy_strerror(res)); + return 1; + } + + printf("Sending request.\n"); + + do { + /* Warning: This example program may loop indefinitely. + * A production-quality program must define a timeout and exit this loop + * as soon as the timeout has expired. */ + size_t nsent; + do { + nsent = 0; + res = curl_easy_send(curl, request + nsent_total, + request_len - nsent_total, &nsent); + nsent_total += nsent; + + if(res == CURLE_AGAIN && !wait_on_socket(sockfd, 0, 60000L)) { + printf("Error: timeout.\n"); + return 1; + } + } while(res == CURLE_AGAIN); + + if(res != CURLE_OK) { + printf("Error: %s\n", curl_easy_strerror(res)); + return 1; + } + + printf("Sent %lu bytes.\n", (unsigned long)nsent); + + } while(nsent_total < request_len); + + printf("Reading response.\n"); + + for(;;) { + /* Warning: This example program may loop indefinitely (see above). */ + char buf[1024]; + size_t nread; + do { + nread = 0; + res = curl_easy_recv(curl, buf, sizeof(buf), &nread); + + if(res == CURLE_AGAIN && !wait_on_socket(sockfd, 1, 60000L)) { + printf("Error: timeout.\n"); + return 1; + } + } while(res == CURLE_AGAIN); + + if(res != CURLE_OK) { + printf("Error: %s\n", curl_easy_strerror(res)); + break; + } + + if(nread == 0) { + /* end of the response */ + break; + } + + printf("Received %lu bytes.\n", (unsigned long)nread); + } + + /* always cleanup */ + curl_easy_cleanup(curl); + } + return 0; +} \ No newline at end of file diff --git a/interface_testing/easy/curl_easy_strerror.c b/interface_testing/easy/curl_easy_strerror.c new file mode 100644 index 0000000..d8e915a --- /dev/null +++ b/interface_testing/easy/curl_easy_strerror.c @@ -0,0 +1,14 @@ +#include +int main(){ + CURL *curl = curl_easy_init(); + if(curl) { + CURLcode res; + curl_easy_setopt(curl, CURLOPT_URL, "https://example.c"); + /* Perform the request, res will get the return code */ + res = curl_easy_perform(curl); + /* Check for errors */ + if(res != CURLE_OK) + fprintf(stderr, "curl_easy_perform() failed: %s\n", + curl_easy_strerror(res)); + } +} diff --git a/interface_testing/easy/curl_easy_unescape.c b/interface_testing/easy/curl_easy_unescape.c new file mode 100644 index 0000000..5861cd2 --- /dev/null +++ b/interface_testing/easy/curl_easy_unescape.c @@ -0,0 +1,15 @@ +#include +int main(){ + CURL *curl = curl_easy_init(); + if(curl) { + int decodelen; + char *decoded = curl_easy_unescape(curl, "%63%75%72%6c", 12, &decodelen); + if(decoded) { + /* do not assume printf() works on the decoded data! */ + printf("Decoded: %s\n", decoded); + /* ... */ + curl_free(decoded); + } + curl_easy_cleanup(curl); + } +} diff --git a/interface_testing/easy/curl_easy_upkeep.c b/interface_testing/easy/curl_easy_upkeep.c new file mode 100644 index 0000000..e2fb330 --- /dev/null +++ b/interface_testing/easy/curl_easy_upkeep.c @@ -0,0 +1,25 @@ +#include +int main(){ + CURL *curl = curl_easy_init(); + if(curl) { + /* Make a connection to an HTTP/2 server. */ + curl_easy_setopt(curl, CURLOPT_URL, "https://example.com"); + + /* Set the interval to 30000ms / 30s */ + curl_easy_setopt(curl, CURLOPT_UPKEEP_INTERVAL_MS, 30000L); + + curl_easy_perform(curl); + + /* Perform more work here. */ + + /* While the connection is being held open, curl_easy_upkeep() can be + called. If curl_easy_upkeep() is called and the time since the last + upkeep exceeds the interval, then an HTTP/2 PING is sent. */ + curl_easy_upkeep(curl); + + /* Perform more work here. */ + + /* always cleanup */ + curl_easy_cleanup(curl); + } +} diff --git a/interface_testing/mime/curl_mime_data_cb.c b/interface_testing/mime/curl_mime_data_cb.c new file mode 100644 index 0000000..ca7cc2c --- /dev/null +++ b/interface_testing/mime/curl_mime_data_cb.c @@ -0,0 +1,56 @@ +#include +#include +char hugedata[512000]; + +struct ctl { + char *buffer; + curl_off_t size; + curl_off_t position; +}; + +size_t read_callback(char *buffer, size_t size, size_t nitems, void *arg) +{ + struct ctl *p = (struct ctl *) arg; + curl_off_t sz = p->size - p->position; + + nitems *= size; + if(sz > nitems) + sz = nitems; + if(sz) + memcpy(buffer, p->buffer + p->position, sz); + p->position += sz; + return sz; +} + +int seek_callback(void *arg, curl_off_t offset, int origin) +{ + struct ctl *p = (struct ctl *) arg; + + switch(origin) { + case SEEK_END: + offset += p->size; + break; + case SEEK_CUR: + offset += p->position; + break; + } + + if(offset < 0) + return CURL_SEEKFUNC_FAIL; + p->position = offset; + return CURL_SEEKFUNC_OK; +} + + +int main(){ + CURL *easy = curl_easy_init(); + curl_mime *mime = curl_mime_init(easy); + curl_mimepart *part = curl_mime_addpart(mime); + struct ctl hugectl; + + hugectl.buffer = hugedata; + hugectl.size = sizeof hugedata; + hugectl.position = 0; + curl_mime_data_cb(part, hugectl.size, read_callback, seek_callback, NULL, + &hugectl); +} \ No newline at end of file diff --git a/interface_testing/multi/curl_multi_add_handle.c b/interface_testing/multi/curl_multi_add_handle.c new file mode 100644 index 0000000..3e10aa8 --- /dev/null +++ b/interface_testing/multi/curl_multi_add_handle.c @@ -0,0 +1,44 @@ +#include + +int main() +{ + CURL *easy_handle1 = curl_easy_init(); + CURL *easy_handle2 = curl_easy_init(); + /* init a multi stack */ + CURLM *multi_handle = curl_multi_init(); + int still_running; + + curl_easy_setopt(easy_handle1, CURLOPT_URL, "https://www.example.com/"); + curl_easy_setopt(easy_handle1, CURLOPT_HEADER, 0); + curl_easy_setopt(easy_handle2, CURLOPT_URL, "http://www.php.net/"); + curl_easy_setopt(easy_handle2, CURLOPT_HEADER, 0); + + /* add individual transfers */ + curl_multi_add_handle(multi_handle, easy_handle1); + curl_multi_add_handle(multi_handle, easy_handle2); + + do + { + CURLMcode mc = curl_multi_perform(multi_handle, &still_running); + + if (!mc && still_running) + /* wait for activity, timeout or "nothing" */ + mc = curl_multi_poll(multi_handle, NULL, 0, 1000, NULL); + + if (mc) + { + fprintf(stderr, "curl_multi_poll() failed, code %d.\n", (int)mc); + break; + } + + /* if there are still transfers, loop! */ + } while (still_running); + + curl_multi_remove_handle(multi_handle, easy_handle1); + curl_multi_remove_handle(multi_handle, easy_handle2); + + /* remove all easy handles, then: */ + curl_multi_cleanup(multi_handle); + curl_easy_cleanup(easy_handle1); + curl_easy_cleanup(easy_handle2); +} \ No newline at end of file diff --git a/interface_testing/multi/curl_multi_cleanup.c b/interface_testing/multi/curl_multi_cleanup.c new file mode 100644 index 0000000..74c0c95 --- /dev/null +++ b/interface_testing/multi/curl_multi_cleanup.c @@ -0,0 +1,23 @@ +#include + +// gcc curl_multi_cleanup.c -L/home/hanxj/1020/curl/build/lib -lcurl -lnghttp2 -lgsasl -lpsl -lssl -lcrypto -lssl -lcrypto -lz -ldl -pthread -o curl_multi_cleanup + +int main() +{ + /* init a multi stack */ + CURLM *multi_handle = curl_multi_init(); + /* remove all easy handles, then: */ + CURLMcode res = curl_multi_cleanup(multi_handle); + + // error codes ref: https://curl.se/libcurl/c/libcurl-errors.html + if ((int)res == 0) + { + printf("All fine. Proceed as usual.\n"); + } + else + { + fprintf(stderr, "Something wrong, code %d.\n", (int)res); + } + + return 0; +} \ No newline at end of file diff --git a/interface_testing/multi/curl_multi_fdset.c b/interface_testing/multi/curl_multi_fdset.c new file mode 100644 index 0000000..3bf792a --- /dev/null +++ b/interface_testing/multi/curl_multi_fdset.c @@ -0,0 +1,151 @@ +#include +#include +#include + +/* somewhat unix-specific */ +#include +#include + +/* curl stuff */ +// #include + +/* + * Download a HTTP file and upload an FTP file simultaneously. + */ + +#define HANDLECOUNT 2 /* Number of simultaneous transfers */ +#define HTTP_HANDLE 0 /* Index for the HTTP transfer */ +#define FTP_HANDLE 1 /* Index for the FTP transfer */ + +int main(void) +{ + CURL *handles[HANDLECOUNT]; + CURLM *multi_handle; + + int still_running = 0; /* keep number of running handles */ + int i; + + CURLMsg *msg; /* for picking up messages with the transfer status */ + int msgs_left; /* how many messages are left */ + + /* Allocate one CURL handle per transfer */ + for(i = 0; i= 0) { + timeout.tv_sec = curl_timeo / 1000; + if(timeout.tv_sec > 1) + timeout.tv_sec = 1; + else + timeout.tv_usec = (curl_timeo % 1000) * 1000; + } + + /* get file descriptors from the transfers */ + mc = curl_multi_fdset(multi_handle, &fdread, &fdwrite, &fdexcep, &maxfd); + + if(mc != CURLM_OK) { + fprintf(stderr, "curl_multi_fdset() failed, code %d.\n", mc); + break; + } + + /* On success the value of maxfd is guaranteed to be >= -1. We call + select(maxfd + 1, ...); specially in case of (maxfd == -1) there are + no fds ready yet so we call select(0, ...) --or Sleep() on Windows-- + to sleep 100ms, which is the minimum suggested value in the + curl_multi_fdset() doc. */ + + if(maxfd == -1) { +#ifdef _WIN32 + Sleep(100); + rc = 0; +#else + /* Portable sleep for platforms other than Windows. */ + struct timeval wait = { 0, 100 * 1000 }; /* 100ms */ + rc = select(0, NULL, NULL, NULL, &wait); +#endif + } + else { + /* Note that on some platforms 'timeout' may be modified by select(). + If you need access to the original value save a copy beforehand. */ + rc = select(maxfd + 1, &fdread, &fdwrite, &fdexcep, &timeout); + } + + switch(rc) { + case -1: + /* select error */ + break; + case 0: /* timeout */ + default: /* action */ + curl_multi_perform(multi_handle, &still_running); + break; + } + } + + /* See how the transfers went */ + while((msg = curl_multi_info_read(multi_handle, &msgs_left))) { + if(msg->msg == CURLMSG_DONE) { + int idx; + + /* Find out which handle this message is about */ + for(idx = 0; idxeasy_handle == handles[idx]); + if(found) + break; + } + + switch(idx) { + case HTTP_HANDLE: + printf("HTTP transfer completed with status %d\n", msg->data.result); + break; + case FTP_HANDLE: + printf("FTP transfer completed with status %d\n", msg->data.result); + break; + } + } + } + + curl_multi_cleanup(multi_handle); + + /* Free the CURL handles */ + for(i = 0; i +int main(){ + /* init a multi stack */ + CURLM *multi_handle = curl_multi_init(); + + curl_multi_cleanup(multi_handle); +} \ No newline at end of file diff --git a/interface_testing/multi/curl_multi_perform.c b/interface_testing/multi/curl_multi_perform.c new file mode 100644 index 0000000..be819ca --- /dev/null +++ b/interface_testing/multi/curl_multi_perform.c @@ -0,0 +1,23 @@ +#include +int main() +{ + CURLM *multi_handle = curl_multi_init(); + + int still_running; + do + { + CURLMcode mc = curl_multi_perform(multi_handle, &still_running); + + if (!mc && still_running) + /* wait for activity, timeout or "nothing" */ + mc = curl_multi_poll(multi_handle, NULL, 0, 1000, NULL); + + if (mc) + { + fprintf(stderr, "curl_multi_poll() failed, code %d.\n", (int)mc); + break; + } + + /* if there are still transfers, loop! */ + } while (still_running); +} \ No newline at end of file diff --git a/interface_testing/multi/curl_multi_poll.c b/interface_testing/multi/curl_multi_poll.c new file mode 100644 index 0000000..115520f --- /dev/null +++ b/interface_testing/multi/curl_multi_poll.c @@ -0,0 +1,90 @@ +#include +#include +#include + +/* somewhat unix-specific */ +#include +#include + +/* curl stuff */ +// #include + +/* + * Download a HTTP file and upload an FTP file simultaneously. + */ + +#define HANDLECOUNT 2 /* Number of simultaneous transfers */ +#define HTTP_HANDLE 0 /* Index for the HTTP transfer */ +#define FTP_HANDLE 1 /* Index for the FTP transfer */ + +int main(void) +{ + CURL *handles[HANDLECOUNT]; + CURLM *multi_handle; + + int still_running = 1; /* keep number of running handles */ + int i; + + CURLMsg *msg; /* for picking up messages with the transfer status */ + int msgs_left; /* how many messages are left */ + + /* Allocate one CURL handle per transfer */ + for(i = 0; imsg == CURLMSG_DONE) { + int idx; + + /* Find out which handle this message is about */ + for(idx = 0; idxeasy_handle == handles[idx]); + if(found) + break; + } + + switch(idx) { + case HTTP_HANDLE: + printf("HTTP transfer completed with status %d\n", msg->data.result); + break; + case FTP_HANDLE: + printf("FTP transfer completed with status %d\n", msg->data.result); + break; + } + } + } + + /* remove the transfers and cleanup the handles */ + for(i = 0; i +int main(){ + CURLSHcode sh; + CURLSH *share = curl_share_init(); + sh = curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT); + if(sh) + printf("Error: %s\n", curl_share_strerror(sh)); + curl_share_cleanup(share); +} \ No newline at end of file diff --git a/interface_testing/url/curl_url.c b/interface_testing/url/curl_url.c new file mode 100644 index 0000000..06118cb --- /dev/null +++ b/interface_testing/url/curl_url.c @@ -0,0 +1,15 @@ +#include +int main(){ + 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); + } +} \ No newline at end of file diff --git a/interface_testing/url/curl_url_dup.c b/interface_testing/url/curl_url_dup.c new file mode 100644 index 0000000..cdcfef9 --- /dev/null +++ b/interface_testing/url/curl_url_dup.c @@ -0,0 +1,12 @@ +#include +int main(){ + 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); +} \ No newline at end of file diff --git a/interface_testing/url/curl_url_set.c b/interface_testing/url/curl_url_set.c new file mode 100644 index 0000000..1210881 --- /dev/null +++ b/interface_testing/url/curl_url_set.c @@ -0,0 +1,15 @@ +#include +int main(){ + 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); + rc = curl_url_get(url, CURLUPART_SCHEME, &scheme, 0); + printf("the scheme is %s\n", scheme); + curl_free(scheme); + } + curl_url_cleanup(url); +} \ No newline at end of file diff --git a/lib/ftp.c b/lib/ftp.c deleted file mode 100644 index 0b9c9b7..0000000 --- a/lib/ftp.c +++ /dev/null @@ -1,4401 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2021, 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.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" - -#ifndef CURL_DISABLE_FTP - -#ifdef HAVE_NETINET_IN_H -#include -#endif -#ifdef HAVE_ARPA_INET_H -#include -#endif -#ifdef HAVE_UTSNAME_H -#include -#endif -#ifdef HAVE_NETDB_H -#include -#endif -#ifdef __VMS -#include -#include -#endif - -#if (defined(NETWARE) && defined(__NOVELL_LIBC__)) -#undef in_addr_t -#define in_addr_t unsigned long -#endif - -#include -#include "urldata.h" -#include "sendf.h" -#include "if2ip.h" -#include "hostip.h" -#include "progress.h" -#include "transfer.h" -#include "escape.h" -#include "http.h" /* for HTTP proxy tunnel stuff */ -#include "ftp.h" -#include "fileinfo.h" -#include "ftplistparser.h" -#include "curl_range.h" -#include "curl_krb5.h" -#include "strtoofft.h" -#include "strcase.h" -#include "vtls/vtls.h" -#include "connect.h" -#include "strerror.h" -#include "inet_ntop.h" -#include "inet_pton.h" -#include "select.h" -#include "parsedate.h" /* for the week day and month names */ -#include "sockaddr.h" /* required for Curl_sockaddr_storage */ -#include "multiif.h" -#include "url.h" -#include "strcase.h" -#include "speedcheck.h" -#include "warnless.h" -#include "http_proxy.h" -#include "non-ascii.h" -#include "socks.h" -/* The last 3 #include files should be in this order */ -#include "curl_printf.h" -#include "curl_memory.h" -#include "memdebug.h" - -#ifndef NI_MAXHOST -#define NI_MAXHOST 1025 -#endif -#ifndef INET_ADDRSTRLEN -#define INET_ADDRSTRLEN 16 -#endif - -#ifdef CURL_DISABLE_VERBOSE_STRINGS -#define ftp_pasv_verbose(a,b,c,d) Curl_nop_stmt -#endif - -/* Local API functions */ -#ifndef DEBUGBUILD -static void _state(struct Curl_easy *data, - ftpstate newstate); -#define state(x,y) _state(x,y) -#else -static void _state(struct Curl_easy *data, - ftpstate newstate, - int lineno); -#define state(x,y) _state(x,y,__LINE__) -#endif - -static CURLcode ftp_sendquote(struct Curl_easy *data, - struct connectdata *conn, - struct curl_slist *quote); -static CURLcode ftp_quit(struct Curl_easy *data, struct connectdata *conn); -static CURLcode ftp_parse_url_path(struct Curl_easy *data); -static CURLcode ftp_regular_transfer(struct Curl_easy *data, bool *done); -#ifndef CURL_DISABLE_VERBOSE_STRINGS -static void ftp_pasv_verbose(struct Curl_easy *data, - struct Curl_addrinfo *ai, - char *newhost, /* ascii version */ - int port); -#endif -static CURLcode ftp_state_prepare_transfer(struct Curl_easy *data); -static CURLcode ftp_state_mdtm(struct Curl_easy *data); -static CURLcode ftp_state_quote(struct Curl_easy *data, - bool init, ftpstate instate); -static CURLcode ftp_nb_type(struct Curl_easy *data, - struct connectdata *conn, - bool ascii, ftpstate newstate); -static int ftp_need_type(struct connectdata *conn, - bool ascii); -static CURLcode ftp_do(struct Curl_easy *data, bool *done); -static CURLcode ftp_done(struct Curl_easy *data, - CURLcode, bool premature); -static CURLcode ftp_connect(struct Curl_easy *data, bool *done); -static CURLcode ftp_disconnect(struct Curl_easy *data, - struct connectdata *conn, bool dead_connection); -static CURLcode ftp_do_more(struct Curl_easy *data, int *completed); -static CURLcode ftp_multi_statemach(struct Curl_easy *data, bool *done); -static int ftp_getsock(struct Curl_easy *data, struct connectdata *conn, - curl_socket_t *socks); -static int ftp_domore_getsock(struct Curl_easy *data, - struct connectdata *conn, curl_socket_t *socks); -static CURLcode ftp_doing(struct Curl_easy *data, - bool *dophase_done); -static CURLcode ftp_setup_connection(struct Curl_easy *data, - struct connectdata *conn); -static CURLcode init_wc_data(struct Curl_easy *data); -static CURLcode wc_statemach(struct Curl_easy *data); -static void wc_data_dtor(void *ptr); -static CURLcode ftp_state_retr(struct Curl_easy *data, curl_off_t filesize); -static CURLcode ftp_readresp(struct Curl_easy *data, - curl_socket_t sockfd, - struct pingpong *pp, - int *ftpcode, - size_t *size); -static CURLcode ftp_dophase_done(struct Curl_easy *data, - bool connected); - -/* - * FTP protocol handler. - */ - -const struct Curl_handler Curl_handler_ftp = { - "FTP", /* scheme */ - ftp_setup_connection, /* setup_connection */ - ftp_do, /* do_it */ - ftp_done, /* done */ - ftp_do_more, /* do_more */ - ftp_connect, /* connect_it */ - ftp_multi_statemach, /* connecting */ - ftp_doing, /* doing */ - ftp_getsock, /* proto_getsock */ - ftp_getsock, /* doing_getsock */ - ftp_domore_getsock, /* domore_getsock */ - ZERO_NULL, /* perform_getsock */ - ftp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ - ZERO_NULL, /* connection_check */ - ZERO_NULL, /* attach connection */ - PORT_FTP, /* defport */ - CURLPROTO_FTP, /* protocol */ - CURLPROTO_FTP, /* family */ - PROTOPT_DUAL | PROTOPT_CLOSEACTION | PROTOPT_NEEDSPWD | - PROTOPT_NOURLQUERY | PROTOPT_PROXY_AS_HTTP | - PROTOPT_WILDCARD /* flags */ -}; - - -#ifdef USE_SSL -/* - * FTPS protocol handler. - */ - -const struct Curl_handler Curl_handler_ftps = { - "FTPS", /* scheme */ - ftp_setup_connection, /* setup_connection */ - ftp_do, /* do_it */ - ftp_done, /* done */ - ftp_do_more, /* do_more */ - ftp_connect, /* connect_it */ - ftp_multi_statemach, /* connecting */ - ftp_doing, /* doing */ - ftp_getsock, /* proto_getsock */ - ftp_getsock, /* doing_getsock */ - ftp_domore_getsock, /* domore_getsock */ - ZERO_NULL, /* perform_getsock */ - ftp_disconnect, /* disconnect */ - ZERO_NULL, /* readwrite */ - ZERO_NULL, /* connection_check */ - ZERO_NULL, /* attach connection */ - PORT_FTPS, /* defport */ - CURLPROTO_FTPS, /* protocol */ - CURLPROTO_FTP, /* family */ - PROTOPT_SSL | PROTOPT_DUAL | PROTOPT_CLOSEACTION | - PROTOPT_NEEDSPWD | PROTOPT_NOURLQUERY | PROTOPT_WILDCARD /* flags */ -}; -#endif - -static void close_secondarysocket(struct Curl_easy *data, - struct connectdata *conn) -{ - if(CURL_SOCKET_BAD != conn->sock[SECONDARYSOCKET]) { - Curl_closesocket(data, conn, conn->sock[SECONDARYSOCKET]); - conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD; - } - conn->bits.tcpconnect[SECONDARYSOCKET] = FALSE; -#ifndef CURL_DISABLE_PROXY - conn->bits.proxy_ssl_connected[SECONDARYSOCKET] = FALSE; -#endif -} - -/* - * NOTE: back in the old days, we added code in the FTP code that made NOBODY - * requests on files respond with headers passed to the client/stdout that - * looked like HTTP ones. - * - * This approach is not very elegant, it causes confusion and is error-prone. - * It is subject for removal at the next (or at least a future) soname bump. - * Until then you can test the effects of the removal by undefining the - * following define named CURL_FTP_HTTPSTYLE_HEAD. - */ -#define CURL_FTP_HTTPSTYLE_HEAD 1 - -static void freedirs(struct ftp_conn *ftpc) -{ - if(ftpc->dirs) { - int i; - for(i = 0; i < ftpc->dirdepth; i++) { - free(ftpc->dirs[i]); - ftpc->dirs[i] = NULL; - } - free(ftpc->dirs); - ftpc->dirs = NULL; - ftpc->dirdepth = 0; - } - Curl_safefree(ftpc->file); - - /* no longer of any use */ - Curl_safefree(ftpc->newhost); -} - -/*********************************************************************** - * - * AcceptServerConnect() - * - * After connection request is received from the server this function is - * called to accept the connection and close the listening socket - * - */ -static CURLcode AcceptServerConnect(struct Curl_easy *data) -{ - struct connectdata *conn = data->conn; - curl_socket_t sock = conn->sock[SECONDARYSOCKET]; - curl_socket_t s = CURL_SOCKET_BAD; -#ifdef ENABLE_IPV6 - struct Curl_sockaddr_storage add; -#else - struct sockaddr_in add; -#endif - curl_socklen_t size = (curl_socklen_t) sizeof(add); - - if(0 == getsockname(sock, (struct sockaddr *) &add, &size)) { - size = sizeof(add); - - s = accept(sock, (struct sockaddr *) &add, &size); - } - Curl_closesocket(data, conn, sock); /* close the first socket */ - - if(CURL_SOCKET_BAD == s) { - failf(data, "Error accept()ing server connect"); - return CURLE_FTP_PORT_FAILED; - } - infof(data, "Connection accepted from server"); - /* when this happens within the DO state it is important that we mark us as - not needing DO_MORE anymore */ - conn->bits.do_more = FALSE; - - conn->sock[SECONDARYSOCKET] = s; - (void)curlx_nonblock(s, TRUE); /* enable non-blocking */ - conn->bits.sock_accepted = TRUE; - - if(data->set.fsockopt) { - int error = 0; - - /* activate callback for setting socket options */ - Curl_set_in_callback(data, true); - error = data->set.fsockopt(data->set.sockopt_client, - s, - CURLSOCKTYPE_ACCEPT); - Curl_set_in_callback(data, false); - - if(error) { - close_secondarysocket(data, conn); - return CURLE_ABORTED_BY_CALLBACK; - } - } - - return CURLE_OK; - -} - -/* - * ftp_timeleft_accept() returns the amount of milliseconds left allowed for - * waiting server to connect. If the value is negative, the timeout time has - * already elapsed. - * - * The start time is stored in progress.t_acceptdata - as set with - * Curl_pgrsTime(..., TIMER_STARTACCEPT); - * - */ -static timediff_t ftp_timeleft_accept(struct Curl_easy *data) -{ - timediff_t timeout_ms = DEFAULT_ACCEPT_TIMEOUT; - timediff_t other; - struct curltime now; - - if(data->set.accepttimeout > 0) - timeout_ms = data->set.accepttimeout; - - now = Curl_now(); - - /* check if the generic timeout possibly is set shorter */ - other = Curl_timeleft(data, &now, FALSE); - if(other && (other < timeout_ms)) - /* note that this also works fine for when other happens to be negative - due to it already having elapsed */ - timeout_ms = other; - else { - /* subtract elapsed time */ - timeout_ms -= Curl_timediff(now, data->progress.t_acceptdata); - if(!timeout_ms) - /* avoid returning 0 as that means no timeout! */ - return -1; - } - - return timeout_ms; -} - - -/*********************************************************************** - * - * ReceivedServerConnect() - * - * After allowing server to connect to us from data port, this function - * checks both data connection for connection establishment and ctrl - * connection for a negative response regarding a failure in connecting - * - */ -static CURLcode ReceivedServerConnect(struct Curl_easy *data, bool *received) -{ - struct connectdata *conn = data->conn; - curl_socket_t ctrl_sock = conn->sock[FIRSTSOCKET]; - curl_socket_t data_sock = conn->sock[SECONDARYSOCKET]; - struct ftp_conn *ftpc = &conn->proto.ftpc; - struct pingpong *pp = &ftpc->pp; - int result; - timediff_t timeout_ms; - ssize_t nread; - int ftpcode; - - *received = FALSE; - - timeout_ms = ftp_timeleft_accept(data); - infof(data, "Checking for server connect"); - if(timeout_ms < 0) { - /* if a timeout was already reached, bail out */ - failf(data, "Accept timeout occurred while waiting server connect"); - return CURLE_FTP_ACCEPT_TIMEOUT; - } - - /* First check whether there is a cached response from server */ - if(pp->cache_size && pp->cache && pp->cache[0] > '3') { - /* Data connection could not be established, let's return */ - infof(data, "There is negative response in cache while serv connect"); - (void)Curl_GetFTPResponse(data, &nread, &ftpcode); - return CURLE_FTP_ACCEPT_FAILED; - } - - result = Curl_socket_check(ctrl_sock, data_sock, CURL_SOCKET_BAD, 0); - - /* see if the connection request is already here */ - switch(result) { - case -1: /* error */ - /* let's die here */ - failf(data, "Error while waiting for server connect"); - return CURLE_FTP_ACCEPT_FAILED; - case 0: /* Server connect is not received yet */ - break; /* loop */ - default: - - if(result & CURL_CSELECT_IN2) { - infof(data, "Ready to accept data connection from server"); - *received = TRUE; - } - else if(result & CURL_CSELECT_IN) { - infof(data, "Ctrl conn has data while waiting for data conn"); - (void)Curl_GetFTPResponse(data, &nread, &ftpcode); - - if(ftpcode/100 > 3) - return CURLE_FTP_ACCEPT_FAILED; - - return CURLE_WEIRD_SERVER_REPLY; - } - - break; - } /* switch() */ - - return CURLE_OK; -} - - -/*********************************************************************** - * - * InitiateTransfer() - * - * After connection from server is accepted this function is called to - * setup transfer parameters and initiate the data transfer. - * - */ -static CURLcode InitiateTransfer(struct Curl_easy *data) -{ - CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - - if(conn->bits.ftp_use_data_ssl) { - /* since we only have a plaintext TCP connection here, we must now - * do the TLS stuff */ - infof(data, "Doing the SSL/TLS handshake on the data stream"); - result = Curl_ssl_connect(data, conn, SECONDARYSOCKET); - if(result) - return result; - } - - if(conn->proto.ftpc.state_saved == FTP_STOR) { - /* When we know we're uploading a specified file, we can get the file - size prior to the actual upload. */ - Curl_pgrsSetUploadSize(data, data->state.infilesize); - - /* set the SO_SNDBUF for the secondary socket for those who need it */ - Curl_sndbufset(conn->sock[SECONDARYSOCKET]); - - Curl_setup_transfer(data, -1, -1, FALSE, SECONDARYSOCKET); - } - else { - /* FTP download: */ - Curl_setup_transfer(data, SECONDARYSOCKET, - conn->proto.ftpc.retr_size_saved, FALSE, -1); - } - - conn->proto.ftpc.pp.pending_resp = TRUE; /* expect server response */ - state(data, FTP_STOP); - - return CURLE_OK; -} - -/*********************************************************************** - * - * AllowServerConnect() - * - * When we've issue the PORT command, we have told the server to connect to - * us. This function checks whether data connection is established if so it is - * accepted. - * - */ -static CURLcode AllowServerConnect(struct Curl_easy *data, bool *connected) -{ - timediff_t timeout_ms; - CURLcode result = CURLE_OK; - - *connected = FALSE; - infof(data, "Preparing for accepting server on data port"); - - /* Save the time we start accepting server connect */ - Curl_pgrsTime(data, TIMER_STARTACCEPT); - - timeout_ms = ftp_timeleft_accept(data); - if(timeout_ms < 0) { - /* if a timeout was already reached, bail out */ - failf(data, "Accept timeout occurred while waiting server connect"); - return CURLE_FTP_ACCEPT_TIMEOUT; - } - - /* see if the connection request is already here */ - result = ReceivedServerConnect(data, connected); - if(result) - return result; - - if(*connected) { - result = AcceptServerConnect(data); - if(result) - return result; - - result = InitiateTransfer(data); - if(result) - return result; - } - else { - /* Add timeout to multi handle and break out of the loop */ - if(*connected == FALSE) { - Curl_expire(data, data->set.accepttimeout > 0 ? - data->set.accepttimeout: DEFAULT_ACCEPT_TIMEOUT, 0); - } - } - - return result; -} - -/* macro to check for a three-digit ftp status code at the start of the - given string */ -#define STATUSCODE(line) (ISDIGIT(line[0]) && ISDIGIT(line[1]) && \ - ISDIGIT(line[2])) - -/* macro to check for the last line in an FTP server response */ -#define LASTLINE(line) (STATUSCODE(line) && (' ' == line[3])) - -static bool ftp_endofresp(struct Curl_easy *data, struct connectdata *conn, - char *line, size_t len, int *code) -{ - (void)data; - (void)conn; - - if((len > 3) && LASTLINE(line)) { - *code = curlx_sltosi(strtol(line, NULL, 10)); - return TRUE; - } - - return FALSE; -} - -static CURLcode ftp_readresp(struct Curl_easy *data, - curl_socket_t sockfd, - struct pingpong *pp, - int *ftpcode, /* return the ftp-code if done */ - size_t *size) /* size of the response */ -{ - int code; - CURLcode result = Curl_pp_readresp(data, sockfd, pp, &code, size); - -#ifdef HAVE_GSSAPI - { - struct connectdata *conn = data->conn; - char * const buf = data->state.buffer; - - /* handle the security-oriented responses 6xx ***/ - switch(code) { - case 631: - code = Curl_sec_read_msg(data, conn, buf, PROT_SAFE); - break; - case 632: - code = Curl_sec_read_msg(data, conn, buf, PROT_PRIVATE); - break; - case 633: - code = Curl_sec_read_msg(data, conn, buf, PROT_CONFIDENTIAL); - break; - default: - /* normal ftp stuff we pass through! */ - break; - } - } -#endif - - /* store the latest code for later retrieval */ - data->info.httpcode = code; - - if(ftpcode) - *ftpcode = code; - - if(421 == code) { - /* 421 means "Service not available, closing control connection." and FTP - * servers use it to signal that idle session timeout has been exceeded. - * If we ignored the response, it could end up hanging in some cases. - * - * This response code can come at any point so having it treated - * generically is a good idea. - */ - infof(data, "We got a 421 - timeout!"); - state(data, FTP_STOP); - return CURLE_OPERATION_TIMEDOUT; - } - - return result; -} - -/* --- parse FTP server responses --- */ - -/* - * Curl_GetFTPResponse() is a BLOCKING function to read the full response - * from a server after a command. - * - */ - -CURLcode Curl_GetFTPResponse(struct Curl_easy *data, - ssize_t *nreadp, /* return number of bytes read */ - int *ftpcode) /* return the ftp-code */ -{ - /* - * We cannot read just one byte per read() and then go back to select() as - * the OpenSSL read() doesn't grok that properly. - * - * Alas, read as much as possible, split up into lines, use the ending - * line in a response or continue reading. */ - - struct connectdata *conn = data->conn; - curl_socket_t sockfd = conn->sock[FIRSTSOCKET]; - CURLcode result = CURLE_OK; - struct ftp_conn *ftpc = &conn->proto.ftpc; - struct pingpong *pp = &ftpc->pp; - size_t nread; - int cache_skip = 0; - int value_to_be_ignored = 0; - - if(ftpcode) - *ftpcode = 0; /* 0 for errors */ - else - /* make the pointer point to something for the rest of this function */ - ftpcode = &value_to_be_ignored; - - *nreadp = 0; - - while(!*ftpcode && !result) { - /* check and reset timeout value every lap */ - timediff_t timeout = Curl_pp_state_timeout(data, pp, FALSE); - timediff_t interval_ms; - - if(timeout <= 0) { - failf(data, "FTP response timeout"); - return CURLE_OPERATION_TIMEDOUT; /* already too little time */ - } - - interval_ms = 1000; /* use 1 second timeout intervals */ - if(timeout < interval_ms) - interval_ms = timeout; - - /* - * Since this function is blocking, we need to wait here for input on the - * connection and only then we call the response reading function. We do - * timeout at least every second to make the timeout check run. - * - * A caution here is that the ftp_readresp() function has a cache that may - * contain pieces of a response from the previous invoke and we need to - * make sure we don't just wait for input while there is unhandled data in - * that cache. But also, if the cache is there, we call ftp_readresp() and - * the cache wasn't good enough to continue we must not just busy-loop - * around this function. - * - */ - - if(pp->cache && (cache_skip < 2)) { - /* - * There's a cache left since before. We then skipping the wait for - * socket action, unless this is the same cache like the previous round - * as then the cache was deemed not enough to act on and we then need to - * wait for more data anyway. - */ - } - else if(!Curl_conn_data_pending(conn, FIRSTSOCKET)) { - switch(SOCKET_READABLE(sockfd, interval_ms)) { - case -1: /* select() error, stop reading */ - failf(data, "FTP response aborted due to select/poll error: %d", - SOCKERRNO); - return CURLE_RECV_ERROR; - - case 0: /* timeout */ - if(Curl_pgrsUpdate(data)) - return CURLE_ABORTED_BY_CALLBACK; - continue; /* just continue in our loop for the timeout duration */ - - default: /* for clarity */ - break; - } - } - result = ftp_readresp(data, sockfd, pp, ftpcode, &nread); - if(result) - break; - - if(!nread && pp->cache) - /* bump cache skip counter as on repeated skips we must wait for more - data */ - cache_skip++; - else - /* when we got data or there is no cache left, we reset the cache skip - counter */ - cache_skip = 0; - - *nreadp += nread; - - } /* while there's buffer left and loop is requested */ - - pp->pending_resp = FALSE; - - return result; -} - -#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS) - /* for debug purposes */ -static const char * const ftp_state_names[]={ - "STOP", - "WAIT220", - "AUTH", - "USER", - "PASS", - "ACCT", - "PBSZ", - "PROT", - "CCC", - "PWD", - "SYST", - "NAMEFMT", - "QUOTE", - "RETR_PREQUOTE", - "STOR_PREQUOTE", - "POSTQUOTE", - "CWD", - "MKD", - "MDTM", - "TYPE", - "LIST_TYPE", - "RETR_TYPE", - "STOR_TYPE", - "SIZE", - "RETR_SIZE", - "STOR_SIZE", - "REST", - "RETR_REST", - "PORT", - "PRET", - "PASV", - "LIST", - "RETR", - "STOR", - "QUIT" -}; -#endif - -/* This is the ONLY way to change FTP state! */ -static void _state(struct Curl_easy *data, - ftpstate newstate -#ifdef DEBUGBUILD - , int lineno -#endif - ) -{ - struct connectdata *conn = data->conn; - struct ftp_conn *ftpc = &conn->proto.ftpc; - -#if defined(DEBUGBUILD) - -#if defined(CURL_DISABLE_VERBOSE_STRINGS) - (void) lineno; -#else - if(ftpc->state != newstate) - infof(data, "FTP %p (line %d) state change from %s to %s", - (void *)ftpc, lineno, ftp_state_names[ftpc->state], - ftp_state_names[newstate]); -#endif -#endif - - ftpc->state = newstate; -} - -static CURLcode ftp_state_user(struct Curl_easy *data, - struct connectdata *conn) -{ - CURLcode result = Curl_pp_sendf(data, - &conn->proto.ftpc.pp, "USER %s", - conn->user?conn->user:""); - if(!result) { - state(data, FTP_USER); - data->state.ftp_trying_alternative = FALSE; - } - return result; -} - -static CURLcode ftp_state_pwd(struct Curl_easy *data, - struct connectdata *conn) -{ - CURLcode result = Curl_pp_sendf(data, &conn->proto.ftpc.pp, "%s", "PWD"); - if(!result) - state(data, FTP_PWD); - - return result; -} - -/* For the FTP "protocol connect" and "doing" phases only */ -static int ftp_getsock(struct Curl_easy *data, - struct connectdata *conn, - curl_socket_t *socks) -{ - return Curl_pp_getsock(data, &conn->proto.ftpc.pp, socks); -} - -/* For the FTP "DO_MORE" phase only */ -static int ftp_domore_getsock(struct Curl_easy *data, - struct connectdata *conn, curl_socket_t *socks) -{ - struct ftp_conn *ftpc = &conn->proto.ftpc; - (void)data; - - /* When in DO_MORE state, we could be either waiting for us to connect to a - * remote site, or we could wait for that site to connect to us. Or just - * handle ordinary commands. - */ - - if(SOCKS_STATE(conn->cnnct.state)) - return Curl_SOCKS_getsock(conn, socks, SECONDARYSOCKET); - - if(FTP_STOP == ftpc->state) { - int bits = GETSOCK_READSOCK(0); - bool any = FALSE; - - /* if stopped and still in this state, then we're also waiting for a - connect on the secondary connection */ - socks[0] = conn->sock[FIRSTSOCKET]; - - if(!data->set.ftp_use_port) { - int s; - int i; - /* PORT is used to tell the server to connect to us, and during that we - don't do happy eyeballs, but we do if we connect to the server */ - for(s = 1, i = 0; i<2; i++) { - if(conn->tempsock[i] != CURL_SOCKET_BAD) { - socks[s] = conn->tempsock[i]; - bits |= GETSOCK_WRITESOCK(s++); - any = TRUE; - } - } - } - if(!any) { - socks[1] = conn->sock[SECONDARYSOCKET]; - bits |= GETSOCK_WRITESOCK(1) | GETSOCK_READSOCK(1); - } - - return bits; - } - return Curl_pp_getsock(data, &conn->proto.ftpc.pp, socks); -} - -/* This is called after the FTP_QUOTE state is passed. - - ftp_state_cwd() sends the range of CWD commands to the server to change to - the correct directory. It may also need to send MKD commands to create - missing ones, if that option is enabled. -*/ -static CURLcode ftp_state_cwd(struct Curl_easy *data, - struct connectdata *conn) -{ - CURLcode result = CURLE_OK; - struct ftp_conn *ftpc = &conn->proto.ftpc; - - if(ftpc->cwddone) - /* already done and fine */ - result = ftp_state_mdtm(data); - else { - /* FTPFILE_NOCWD with full path: expect ftpc->cwddone! */ - DEBUGASSERT((data->set.ftp_filemethod != FTPFILE_NOCWD) || - !(ftpc->dirdepth && ftpc->dirs[0][0] == '/')); - - ftpc->count2 = 0; /* count2 counts failed CWDs */ - - /* count3 is set to allow a MKD to fail once. In the case when first CWD - fails and then MKD fails (due to another session raced it to create the - dir) this then allows for a second try to CWD to it */ - ftpc->count3 = (data->set.ftp_create_missing_dirs == 2)?1:0; - - if(conn->bits.reuse && ftpc->entrypath && - /* no need to go to entrypath when we have an absolute path */ - !(ftpc->dirdepth && ftpc->dirs[0][0] == '/')) { - /* This is a re-used connection. Since we change directory to where the - transfer is taking place, we must first get back to the original dir - where we ended up after login: */ - ftpc->cwdcount = 0; /* we count this as the first path, then we add one - for all upcoming ones in the ftp->dirs[] array */ - result = Curl_pp_sendf(data, &ftpc->pp, "CWD %s", ftpc->entrypath); - if(!result) - state(data, FTP_CWD); - } - else { - if(ftpc->dirdepth) { - ftpc->cwdcount = 1; - /* issue the first CWD, the rest is sent when the CWD responses are - received... */ - result = Curl_pp_sendf(data, &ftpc->pp, "CWD %s", - ftpc->dirs[ftpc->cwdcount -1]); - if(!result) - state(data, FTP_CWD); - } - else { - /* No CWD necessary */ - result = ftp_state_mdtm(data); - } - } - } - return result; -} - -typedef enum { - EPRT, - PORT, - DONE -} ftpport; - -static CURLcode ftp_state_use_port(struct Curl_easy *data, - ftpport fcmd) /* start with this */ -{ - CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - struct ftp_conn *ftpc = &conn->proto.ftpc; - curl_socket_t portsock = CURL_SOCKET_BAD; - char myhost[MAX_IPADR_LEN + 1] = ""; - - struct Curl_sockaddr_storage ss; - struct Curl_addrinfo *res, *ai; - curl_socklen_t sslen; - char hbuf[NI_MAXHOST]; - struct sockaddr *sa = (struct sockaddr *)&ss; - struct sockaddr_in * const sa4 = (void *)sa; -#ifdef ENABLE_IPV6 - struct sockaddr_in6 * const sa6 = (void *)sa; -#endif - static const char mode[][5] = { "EPRT", "PORT" }; - enum resolve_t rc; - int error; - char *host = NULL; - char *string_ftpport = data->set.str[STRING_FTPPORT]; - struct Curl_dns_entry *h = NULL; - unsigned short port_min = 0; - unsigned short port_max = 0; - unsigned short port; - bool possibly_non_local = TRUE; - char buffer[STRERROR_LEN]; - char *addr = NULL; - - /* Step 1, figure out what is requested, - * accepted format : - * (ipv4|ipv6|domain|interface)?(:port(-range)?)? - */ - - if(data->set.str[STRING_FTPPORT] && - (strlen(data->set.str[STRING_FTPPORT]) > 1)) { - -#ifdef ENABLE_IPV6 - size_t addrlen = INET6_ADDRSTRLEN > strlen(string_ftpport) ? - INET6_ADDRSTRLEN : strlen(string_ftpport); -#else - size_t addrlen = INET_ADDRSTRLEN > strlen(string_ftpport) ? - INET_ADDRSTRLEN : strlen(string_ftpport); -#endif - char *ip_start = string_ftpport; - char *ip_end = NULL; - char *port_start = NULL; - char *port_sep = NULL; - - addr = calloc(addrlen + 1, 1); - if(!addr) - return CURLE_OUT_OF_MEMORY; - -#ifdef ENABLE_IPV6 - if(*string_ftpport == '[') { - /* [ipv6]:port(-range) */ - ip_start = string_ftpport + 1; - ip_end = strchr(string_ftpport, ']'); - if(ip_end) - strncpy(addr, ip_start, ip_end - ip_start); - } - else -#endif - if(*string_ftpport == ':') { - /* :port */ - ip_end = string_ftpport; - } - else { - ip_end = strchr(string_ftpport, ':'); - if(ip_end) { - /* either ipv6 or (ipv4|domain|interface):port(-range) */ -#ifdef ENABLE_IPV6 - if(Curl_inet_pton(AF_INET6, string_ftpport, sa6) == 1) { - /* ipv6 */ - port_min = port_max = 0; - strcpy(addr, string_ftpport); - ip_end = NULL; /* this got no port ! */ - } - else -#endif - /* (ipv4|domain|interface):port(-range) */ - strncpy(addr, string_ftpport, ip_end - ip_start); - } - else - /* ipv4|interface */ - strcpy(addr, string_ftpport); - } - - /* parse the port */ - if(ip_end != NULL) { - port_start = strchr(ip_end, ':'); - if(port_start) { - port_min = curlx_ultous(strtoul(port_start + 1, NULL, 10)); - port_sep = strchr(port_start, '-'); - if(port_sep) { - port_max = curlx_ultous(strtoul(port_sep + 1, NULL, 10)); - } - else - port_max = port_min; - } - } - - /* correct errors like: - * :1234-1230 - * :-4711, in this case port_min is (unsigned)-1, - * therefore port_min > port_max for all cases - * but port_max = (unsigned)-1 - */ - if(port_min > port_max) - port_min = port_max = 0; - - - if(*addr != '\0') { - /* attempt to get the address of the given interface name */ - switch(Curl_if2ip(conn->ip_addr->ai_family, - Curl_ipv6_scope(conn->ip_addr->ai_addr), - conn->scope_id, addr, hbuf, sizeof(hbuf))) { - case IF2IP_NOT_FOUND: - /* not an interface, use the given string as host name instead */ - host = addr; - break; - case IF2IP_AF_NOT_SUPPORTED: - return CURLE_FTP_PORT_FAILED; - case IF2IP_FOUND: - host = hbuf; /* use the hbuf for host name */ - } - } - else - /* there was only a port(-range) given, default the host */ - host = NULL; - } /* data->set.ftpport */ - - if(!host) { - const char *r; - /* not an interface and not a host name, get default by extracting - the IP from the control connection */ - sslen = sizeof(ss); - if(getsockname(conn->sock[FIRSTSOCKET], sa, &sslen)) { - failf(data, "getsockname() failed: %s", - Curl_strerror(SOCKERRNO, buffer, sizeof(buffer))); - free(addr); - return CURLE_FTP_PORT_FAILED; - } - switch(sa->sa_family) { -#ifdef ENABLE_IPV6 - case AF_INET6: - r = Curl_inet_ntop(sa->sa_family, &sa6->sin6_addr, hbuf, sizeof(hbuf)); - break; -#endif - default: - r = Curl_inet_ntop(sa->sa_family, &sa4->sin_addr, hbuf, sizeof(hbuf)); - break; - } - if(!r) - return CURLE_FTP_PORT_FAILED; - host = hbuf; /* use this host name */ - possibly_non_local = FALSE; /* we know it is local now */ - } - - /* resolv ip/host to ip */ - rc = Curl_resolv(data, host, 0, FALSE, &h); - if(rc == CURLRESOLV_PENDING) - (void)Curl_resolver_wait_resolv(data, &h); - if(h) { - res = h->addr; - /* when we return from this function, we can forget about this entry - to we can unlock it now already */ - Curl_resolv_unlock(data, h); - } /* (h) */ - else - res = NULL; /* failure! */ - - if(!res) { - failf(data, "failed to resolve the address provided to PORT: %s", host); - free(addr); - return CURLE_FTP_PORT_FAILED; - } - - free(addr); - host = NULL; - - /* step 2, create a socket for the requested address */ - - portsock = CURL_SOCKET_BAD; - error = 0; - for(ai = res; ai; ai = ai->ai_next) { - result = Curl_socket(data, ai, NULL, &portsock); - if(result) { - error = SOCKERRNO; - continue; - } - break; - } - if(!ai) { - failf(data, "socket failure: %s", - Curl_strerror(error, buffer, sizeof(buffer))); - return CURLE_FTP_PORT_FAILED; - } - - /* step 3, bind to a suitable local address */ - - memcpy(sa, ai->ai_addr, ai->ai_addrlen); - sslen = ai->ai_addrlen; - - for(port = port_min; port <= port_max;) { - if(sa->sa_family == AF_INET) - sa4->sin_port = htons(port); -#ifdef ENABLE_IPV6 - else - sa6->sin6_port = htons(port); -#endif - /* Try binding the given address. */ - if(bind(portsock, sa, sslen) ) { - /* It failed. */ - error = SOCKERRNO; - if(possibly_non_local && (error == EADDRNOTAVAIL)) { - /* The requested bind address is not local. Use the address used for - * the control connection instead and restart the port loop - */ - infof(data, "bind(port=%hu) on non-local address failed: %s", port, - Curl_strerror(error, buffer, sizeof(buffer))); - - sslen = sizeof(ss); - if(getsockname(conn->sock[FIRSTSOCKET], sa, &sslen)) { - failf(data, "getsockname() failed: %s", - Curl_strerror(SOCKERRNO, buffer, sizeof(buffer))); - Curl_closesocket(data, conn, portsock); - return CURLE_FTP_PORT_FAILED; - } - port = port_min; - possibly_non_local = FALSE; /* don't try this again */ - continue; - } - if(error != EADDRINUSE && error != EACCES) { - failf(data, "bind(port=%hu) failed: %s", port, - Curl_strerror(error, buffer, sizeof(buffer))); - Curl_closesocket(data, conn, portsock); - return CURLE_FTP_PORT_FAILED; - } - } - else - break; - - port++; - } - - /* maybe all ports were in use already*/ - if(port > port_max) { - failf(data, "bind() failed, we ran out of ports!"); - Curl_closesocket(data, conn, portsock); - return CURLE_FTP_PORT_FAILED; - } - - /* get the name again after the bind() so that we can extract the - port number it uses now */ - sslen = sizeof(ss); - if(getsockname(portsock, (struct sockaddr *)sa, &sslen)) { - failf(data, "getsockname() failed: %s", - Curl_strerror(SOCKERRNO, buffer, sizeof(buffer))); - Curl_closesocket(data, conn, portsock); - return CURLE_FTP_PORT_FAILED; - } - - /* step 4, listen on the socket */ - - if(listen(portsock, 1)) { - failf(data, "socket failure: %s", - Curl_strerror(SOCKERRNO, buffer, sizeof(buffer))); - Curl_closesocket(data, conn, portsock); - return CURLE_FTP_PORT_FAILED; - } - - /* step 5, send the proper FTP command */ - - /* get a plain printable version of the numerical address to work with - below */ - Curl_printable_address(ai, myhost, sizeof(myhost)); - -#ifdef ENABLE_IPV6 - if(!conn->bits.ftp_use_eprt && conn->bits.ipv6) - /* EPRT is disabled but we are connected to a IPv6 host, so we ignore the - request and enable EPRT again! */ - conn->bits.ftp_use_eprt = TRUE; -#endif - - for(; fcmd != DONE; fcmd++) { - - if(!conn->bits.ftp_use_eprt && (EPRT == fcmd)) - /* if disabled, goto next */ - continue; - - if((PORT == fcmd) && sa->sa_family != AF_INET) - /* PORT is IPv4 only */ - continue; - - switch(sa->sa_family) { - case AF_INET: - port = ntohs(sa4->sin_port); - break; -#ifdef ENABLE_IPV6 - case AF_INET6: - port = ntohs(sa6->sin6_port); - break; -#endif - default: - continue; /* might as well skip this */ - } - - if(EPRT == fcmd) { - /* - * Two fine examples from RFC2428; - * - * EPRT |1|132.235.1.2|6275| - * - * EPRT |2|1080::8:800:200C:417A|5282| - */ - - result = Curl_pp_sendf(data, &ftpc->pp, "%s |%d|%s|%hu|", mode[fcmd], - sa->sa_family == AF_INET?1:2, - myhost, port); - if(result) { - failf(data, "Failure sending EPRT command: %s", - curl_easy_strerror(result)); - Curl_closesocket(data, conn, portsock); - /* don't retry using PORT */ - ftpc->count1 = PORT; - /* bail out */ - state(data, FTP_STOP); - return result; - } - break; - } - if(PORT == fcmd) { - /* large enough for [IP address],[num],[num] */ - char target[sizeof(myhost) + 20]; - char *source = myhost; - char *dest = target; - - /* translate x.x.x.x to x,x,x,x */ - while(source && *source) { - if(*source == '.') - *dest = ','; - else - *dest = *source; - dest++; - source++; - } - *dest = 0; - msnprintf(dest, 20, ",%d,%d", (int)(port>>8), (int)(port&0xff)); - - result = Curl_pp_sendf(data, &ftpc->pp, "%s %s", mode[fcmd], target); - if(result) { - failf(data, "Failure sending PORT command: %s", - curl_easy_strerror(result)); - Curl_closesocket(data, conn, portsock); - /* bail out */ - state(data, FTP_STOP); - return result; - } - break; - } - } - - /* store which command was sent */ - ftpc->count1 = fcmd; - - close_secondarysocket(data, conn); - - /* we set the secondary socket variable to this for now, it is only so that - the cleanup function will close it in case we fail before the true - secondary stuff is made */ - conn->sock[SECONDARYSOCKET] = portsock; - - /* this tcpconnect assignment below is a hackish work-around to make the - multi interface with active FTP work - as it will not wait for a - (passive) connect in Curl_is_connected(). - - The *proper* fix is to make sure that the active connection from the - server is done in a non-blocking way. Currently, it is still BLOCKING. - */ - conn->bits.tcpconnect[SECONDARYSOCKET] = TRUE; - - state(data, FTP_PORT); - return result; -} - -static CURLcode ftp_state_use_pasv(struct Curl_easy *data, - struct connectdata *conn) -{ - struct ftp_conn *ftpc = &conn->proto.ftpc; - CURLcode result = CURLE_OK; - /* - Here's the executive summary on what to do: - - PASV is RFC959, expect: - 227 Entering Passive Mode (a1,a2,a3,a4,p1,p2) - - LPSV is RFC1639, expect: - 228 Entering Long Passive Mode (4,4,a1,a2,a3,a4,2,p1,p2) - - EPSV is RFC2428, expect: - 229 Entering Extended Passive Mode (|||port|) - - */ - - static const char mode[][5] = { "EPSV", "PASV" }; - int modeoff; - -#ifdef PF_INET6 - if(!conn->bits.ftp_use_epsv && conn->bits.ipv6) - /* EPSV is disabled but we are connected to a IPv6 host, so we ignore the - request and enable EPSV again! */ - conn->bits.ftp_use_epsv = TRUE; -#endif - - modeoff = conn->bits.ftp_use_epsv?0:1; - - result = Curl_pp_sendf(data, &ftpc->pp, "%s", mode[modeoff]); - if(!result) { - ftpc->count1 = modeoff; - state(data, FTP_PASV); - infof(data, "Connect data stream passively"); - } - return result; -} - -/* - * ftp_state_prepare_transfer() starts PORT, PASV or PRET etc. - * - * REST is the last command in the chain of commands when a "head"-like - * request is made. Thus, if an actual transfer is to be made this is where we - * take off for real. - */ -static CURLcode ftp_state_prepare_transfer(struct Curl_easy *data) -{ - CURLcode result = CURLE_OK; - struct FTP *ftp = data->req.p.ftp; - struct connectdata *conn = data->conn; - - if(ftp->transfer != PPTRANSFER_BODY) { - /* doesn't transfer any data */ - - /* still possibly do PRE QUOTE jobs */ - state(data, FTP_RETR_PREQUOTE); - result = ftp_state_quote(data, TRUE, FTP_RETR_PREQUOTE); - } - else if(data->set.ftp_use_port) { - /* We have chosen to use the PORT (or similar) command */ - result = ftp_state_use_port(data, EPRT); - } - else { - /* We have chosen (this is default) to use the PASV (or similar) command */ - if(data->set.ftp_use_pret) { - /* The user has requested that we send a PRET command - to prepare the server for the upcoming PASV */ - struct ftp_conn *ftpc = &conn->proto.ftpc; - if(!conn->proto.ftpc.file) - result = Curl_pp_sendf(data, &ftpc->pp, "PRET %s", - data->set.str[STRING_CUSTOMREQUEST]? - data->set.str[STRING_CUSTOMREQUEST]: - (data->state.list_only?"NLST":"LIST")); - else if(data->set.upload) - result = Curl_pp_sendf(data, &ftpc->pp, "PRET STOR %s", - conn->proto.ftpc.file); - else - result = Curl_pp_sendf(data, &ftpc->pp, "PRET RETR %s", - conn->proto.ftpc.file); - if(!result) - state(data, FTP_PRET); - } - else - result = ftp_state_use_pasv(data, conn); - } - return result; -} - -static CURLcode ftp_state_rest(struct Curl_easy *data, - struct connectdata *conn) -{ - CURLcode result = CURLE_OK; - struct FTP *ftp = data->req.p.ftp; - struct ftp_conn *ftpc = &conn->proto.ftpc; - - if((ftp->transfer != PPTRANSFER_BODY) && ftpc->file) { - /* if a "head"-like request is being made (on a file) */ - - /* Determine if server can respond to REST command and therefore - whether it supports range */ - result = Curl_pp_sendf(data, &ftpc->pp, "REST %d", 0); - if(!result) - state(data, FTP_REST); - } - else - result = ftp_state_prepare_transfer(data); - - return result; -} - -static CURLcode ftp_state_size(struct Curl_easy *data, - struct connectdata *conn) -{ - CURLcode result = CURLE_OK; - struct FTP *ftp = data->req.p.ftp; - struct ftp_conn *ftpc = &conn->proto.ftpc; - - if((ftp->transfer == PPTRANSFER_INFO) && ftpc->file) { - /* if a "head"-like request is being made (on a file) */ - - /* we know ftpc->file is a valid pointer to a file name */ - result = Curl_pp_sendf(data, &ftpc->pp, "SIZE %s", ftpc->file); - if(!result) - state(data, FTP_SIZE); - } - else - result = ftp_state_rest(data, conn); - - return result; -} - -static CURLcode ftp_state_list(struct Curl_easy *data) -{ - CURLcode result = CURLE_OK; - struct FTP *ftp = data->req.p.ftp; - struct connectdata *conn = data->conn; - - /* If this output is to be machine-parsed, the NLST command might be better - to use, since the LIST command output is not specified or standard in any - way. It has turned out that the NLST list output is not the same on all - servers either... */ - - /* - if FTPFILE_NOCWD was specified, we should add the path - as argument for the LIST / NLST / or custom command. - Whether the server will support this, is uncertain. - - The other ftp_filemethods will CWD into dir/dir/ first and - then just do LIST (in that case: nothing to do here) - */ - char *lstArg = NULL; - char *cmd; - - if((data->set.ftp_filemethod == FTPFILE_NOCWD) && ftp->path) { - /* url-decode before evaluation: e.g. paths starting/ending with %2f */ - const char *slashPos = NULL; - char *rawPath = NULL; - result = Curl_urldecode(data, ftp->path, 0, &rawPath, NULL, REJECT_CTRL); - if(result) - return result; - - slashPos = strrchr(rawPath, '/'); - if(slashPos) { - /* chop off the file part if format is dir/file otherwise remove - the trailing slash for dir/dir/ except for absolute path / */ - size_t n = slashPos - rawPath; - if(n == 0) - ++n; - - lstArg = rawPath; - lstArg[n] = '\0'; - } - else - free(rawPath); - } - - cmd = aprintf("%s%s%s", - data->set.str[STRING_CUSTOMREQUEST]? - data->set.str[STRING_CUSTOMREQUEST]: - (data->state.list_only?"NLST":"LIST"), - lstArg? " ": "", - lstArg? lstArg: ""); - free(lstArg); - - if(!cmd) - return CURLE_OUT_OF_MEMORY; - - result = Curl_pp_sendf(data, &conn->proto.ftpc.pp, "%s", cmd); - free(cmd); - - if(!result) - state(data, FTP_LIST); - - return result; -} - -static CURLcode ftp_state_retr_prequote(struct Curl_easy *data) -{ - /* We've sent the TYPE, now we must send the list of prequote strings */ - return ftp_state_quote(data, TRUE, FTP_RETR_PREQUOTE); -} - -static CURLcode ftp_state_stor_prequote(struct Curl_easy *data) -{ - /* We've sent the TYPE, now we must send the list of prequote strings */ - return ftp_state_quote(data, TRUE, FTP_STOR_PREQUOTE); -} - -static CURLcode ftp_state_type(struct Curl_easy *data) -{ - CURLcode result = CURLE_OK; - struct FTP *ftp = data->req.p.ftp; - struct connectdata *conn = data->conn; - struct ftp_conn *ftpc = &conn->proto.ftpc; - - /* If we have selected NOBODY and HEADER, it means that we only want file - information. Which in FTP can't be much more than the file size and - date. */ - if(data->set.opt_no_body && ftpc->file && - ftp_need_type(conn, data->state.prefer_ascii)) { - /* The SIZE command is _not_ RFC 959 specified, and therefore many servers - may not support it! It is however the only way we have to get a file's - size! */ - - ftp->transfer = PPTRANSFER_INFO; - /* this means no actual transfer will be made */ - - /* Some servers return different sizes for different modes, and thus we - must set the proper type before we check the size */ - result = ftp_nb_type(data, conn, data->state.prefer_ascii, FTP_TYPE); - if(result) - return result; - } - else - result = ftp_state_size(data, conn); - - return result; -} - -/* This is called after the CWD commands have been done in the beginning of - the DO phase */ -static CURLcode ftp_state_mdtm(struct Curl_easy *data) -{ - CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - struct ftp_conn *ftpc = &conn->proto.ftpc; - - /* Requested time of file or time-depended transfer? */ - if((data->set.get_filetime || data->set.timecondition) && ftpc->file) { - - /* we have requested to get the modified-time of the file, this is a white - spot as the MDTM is not mentioned in RFC959 */ - result = Curl_pp_sendf(data, &ftpc->pp, "MDTM %s", ftpc->file); - - if(!result) - state(data, FTP_MDTM); - } - else - result = ftp_state_type(data); - - return result; -} - - -/* This is called after the TYPE and possible quote commands have been sent */ -static CURLcode ftp_state_ul_setup(struct Curl_easy *data, - bool sizechecked) -{ - CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - struct FTP *ftp = data->req.p.ftp; - struct ftp_conn *ftpc = &conn->proto.ftpc; - bool append = data->set.remote_append; - - if((data->state.resume_from && !sizechecked) || - ((data->state.resume_from > 0) && sizechecked)) { - /* we're about to continue the uploading of a file */ - /* 1. get already existing file's size. We use the SIZE command for this - which may not exist in the server! The SIZE command is not in - RFC959. */ - - /* 2. This used to set REST. But since we can do append, we - don't another ftp command. We just skip the source file - offset and then we APPEND the rest on the file instead */ - - /* 3. pass file-size number of bytes in the source file */ - /* 4. lower the infilesize counter */ - /* => transfer as usual */ - int seekerr = CURL_SEEKFUNC_OK; - - if(data->state.resume_from < 0) { - /* Got no given size to start from, figure it out */ - result = Curl_pp_sendf(data, &ftpc->pp, "SIZE %s", ftpc->file); - if(!result) - state(data, FTP_STOR_SIZE); - return result; - } - - /* enable append */ - append = TRUE; - - /* Let's read off the proper amount of bytes from the input. */ - if(conn->seek_func) { - Curl_set_in_callback(data, true); - seekerr = conn->seek_func(conn->seek_client, data->state.resume_from, - SEEK_SET); - Curl_set_in_callback(data, false); - } - - if(seekerr != CURL_SEEKFUNC_OK) { - curl_off_t passed = 0; - if(seekerr != CURL_SEEKFUNC_CANTSEEK) { - failf(data, "Could not seek stream"); - return CURLE_FTP_COULDNT_USE_REST; - } - /* seekerr == CURL_SEEKFUNC_CANTSEEK (can't seek to offset) */ - do { - size_t readthisamountnow = - (data->state.resume_from - passed > data->set.buffer_size) ? - (size_t)data->set.buffer_size : - curlx_sotouz(data->state.resume_from - passed); - - size_t actuallyread = - data->state.fread_func(data->state.buffer, 1, readthisamountnow, - data->state.in); - - passed += actuallyread; - if((actuallyread == 0) || (actuallyread > readthisamountnow)) { - /* this checks for greater-than only to make sure that the - CURL_READFUNC_ABORT return code still aborts */ - failf(data, "Failed to read data"); - return CURLE_FTP_COULDNT_USE_REST; - } - } while(passed < data->state.resume_from); - } - /* now, decrease the size of the read */ - if(data->state.infilesize>0) { - data->state.infilesize -= data->state.resume_from; - - if(data->state.infilesize <= 0) { - infof(data, "File already completely uploaded"); - - /* no data to transfer */ - Curl_setup_transfer(data, -1, -1, FALSE, -1); - - /* Set ->transfer so that we won't get any error in - * ftp_done() because we didn't transfer anything! */ - ftp->transfer = PPTRANSFER_NONE; - - state(data, FTP_STOP); - return CURLE_OK; - } - } - /* we've passed, proceed as normal */ - } /* resume_from */ - - result = Curl_pp_sendf(data, &ftpc->pp, append?"APPE %s":"STOR %s", - ftpc->file); - if(!result) - state(data, FTP_STOR); - - return result; -} - -static CURLcode ftp_state_quote(struct Curl_easy *data, - bool init, - ftpstate instate) -{ - CURLcode result = CURLE_OK; - struct FTP *ftp = data->req.p.ftp; - struct connectdata *conn = data->conn; - struct ftp_conn *ftpc = &conn->proto.ftpc; - bool quote = FALSE; - struct curl_slist *item; - - switch(instate) { - case FTP_QUOTE: - default: - item = data->set.quote; - break; - case FTP_RETR_PREQUOTE: - case FTP_STOR_PREQUOTE: - item = data->set.prequote; - break; - case FTP_POSTQUOTE: - item = data->set.postquote; - break; - } - - /* - * This state uses: - * 'count1' to iterate over the commands to send - * 'count2' to store whether to allow commands to fail - */ - - if(init) - ftpc->count1 = 0; - else - ftpc->count1++; - - if(item) { - int i = 0; - - /* Skip count1 items in the linked list */ - while((i< ftpc->count1) && item) { - item = item->next; - i++; - } - if(item) { - char *cmd = item->data; - if(cmd[0] == '*') { - cmd++; - ftpc->count2 = 1; /* the sent command is allowed to fail */ - } - else - ftpc->count2 = 0; /* failure means cancel operation */ - - result = Curl_pp_sendf(data, &ftpc->pp, "%s", cmd); - if(result) - return result; - state(data, instate); - quote = TRUE; - } - } - - if(!quote) { - /* No more quote to send, continue to ... */ - switch(instate) { - case FTP_QUOTE: - default: - result = ftp_state_cwd(data, conn); - break; - case FTP_RETR_PREQUOTE: - if(ftp->transfer != PPTRANSFER_BODY) - state(data, FTP_STOP); - else { - if(ftpc->known_filesize != -1) { - Curl_pgrsSetDownloadSize(data, ftpc->known_filesize); - result = ftp_state_retr(data, ftpc->known_filesize); - } - else { - if(data->set.ignorecl || data->state.prefer_ascii) { - /* 'ignorecl' is used to support download of growing files. It - prevents the state machine from requesting the file size from - the server. With an unknown file size the download continues - until the server terminates it, otherwise the client stops if - the received byte count exceeds the reported file size. Set - option CURLOPT_IGNORE_CONTENT_LENGTH to 1 to enable this - behavior. - - In addition: asking for the size for 'TYPE A' transfers is not - constructive since servers don't report the converted size. So - skip it. - */ - result = Curl_pp_sendf(data, &ftpc->pp, "RETR %s", ftpc->file); - if(!result) - state(data, FTP_RETR); - } - else { - result = Curl_pp_sendf(data, &ftpc->pp, "SIZE %s", ftpc->file); - if(!result) - state(data, FTP_RETR_SIZE); - } - } - } - break; - case FTP_STOR_PREQUOTE: - result = ftp_state_ul_setup(data, FALSE); - break; - case FTP_POSTQUOTE: - break; - } - } - - return result; -} - -/* called from ftp_state_pasv_resp to switch to PASV in case of EPSV - problems */ -static CURLcode ftp_epsv_disable(struct Curl_easy *data, - struct connectdata *conn) -{ - CURLcode result = CURLE_OK; - - if(conn->bits.ipv6 -#ifndef CURL_DISABLE_PROXY - && !(conn->bits.tunnel_proxy || conn->bits.socksproxy) -#endif - ) { - /* We can't disable EPSV when doing IPv6, so this is instead a fail */ - failf(data, "Failed EPSV attempt, exiting"); - return CURLE_WEIRD_SERVER_REPLY; - } - - infof(data, "Failed EPSV attempt. Disabling EPSV"); - /* disable it for next transfer */ - conn->bits.ftp_use_epsv = FALSE; - data->state.errorbuf = FALSE; /* allow error message to get - rewritten */ - result = Curl_pp_sendf(data, &conn->proto.ftpc.pp, "%s", "PASV"); - if(!result) { - conn->proto.ftpc.count1++; - /* remain in/go to the FTP_PASV state */ - state(data, FTP_PASV); - } - return result; -} - - -static char *control_address(struct connectdata *conn) -{ - /* Returns the control connection IP address. - If a proxy tunnel is used, returns the original host name instead, because - the effective control connection address is the proxy address, - not the ftp host. */ -#ifndef CURL_DISABLE_PROXY - if(conn->bits.tunnel_proxy || conn->bits.socksproxy) - return conn->host.name; -#endif - return conn->primary_ip; -} - -static CURLcode ftp_state_pasv_resp(struct Curl_easy *data, - int ftpcode) -{ - struct connectdata *conn = data->conn; - struct ftp_conn *ftpc = &conn->proto.ftpc; - CURLcode result; - struct Curl_dns_entry *addr = NULL; - enum resolve_t rc; - unsigned short connectport; /* the local port connect() should use! */ - char *str = &data->state.buffer[4]; /* start on the first letter */ - - /* if we come here again, make sure the former name is cleared */ - Curl_safefree(ftpc->newhost); - - if((ftpc->count1 == 0) && - (ftpcode == 229)) { - /* positive EPSV response */ - char *ptr = strchr(str, '('); - if(ptr) { - unsigned int num; - char separator[4]; - ptr++; - if(5 == sscanf(ptr, "%c%c%c%u%c", - &separator[0], - &separator[1], - &separator[2], - &num, - &separator[3])) { - const char sep1 = separator[0]; - int i; - - /* The four separators should be identical, or else this is an oddly - formatted reply and we bail out immediately. */ - for(i = 1; i<4; i++) { - if(separator[i] != sep1) { - ptr = NULL; /* set to NULL to signal error */ - break; - } - } - if(num > 0xffff) { - failf(data, "Illegal port number in EPSV reply"); - return CURLE_FTP_WEIRD_PASV_REPLY; - } - if(ptr) { - ftpc->newport = (unsigned short)(num & 0xffff); - ftpc->newhost = strdup(control_address(conn)); - if(!ftpc->newhost) - return CURLE_OUT_OF_MEMORY; - } - } - else - ptr = NULL; - } - if(!ptr) { - failf(data, "Weirdly formatted EPSV reply"); - return CURLE_FTP_WEIRD_PASV_REPLY; - } - } - else if((ftpc->count1 == 1) && - (ftpcode == 227)) { - /* positive PASV response */ - unsigned int ip[4] = {0, 0, 0, 0}; - unsigned int port[2] = {0, 0}; - - /* - * Scan for a sequence of six comma-separated numbers and use them as - * IP+port indicators. - * - * Found reply-strings include: - * "227 Entering Passive Mode (127,0,0,1,4,51)" - * "227 Data transfer will passively listen to 127,0,0,1,4,51" - * "227 Entering passive mode. 127,0,0,1,4,51" - */ - while(*str) { - if(6 == sscanf(str, "%u,%u,%u,%u,%u,%u", - &ip[0], &ip[1], &ip[2], &ip[3], - &port[0], &port[1])) - break; - str++; - } - - if(!*str || (ip[0] > 255) || (ip[1] > 255) || (ip[2] > 255) || - (ip[3] > 255) || (port[0] > 255) || (port[1] > 255) ) { - failf(data, "Couldn't interpret the 227-response"); - return CURLE_FTP_WEIRD_227_FORMAT; - } - - /* we got OK from server */ - if(data->set.ftp_skip_ip) { - /* told to ignore the remotely given IP but instead use the host we used - for the control connection */ - infof(data, "Skip %u.%u.%u.%u for data connection, re-use %s instead", - ip[0], ip[1], ip[2], ip[3], - conn->host.name); - ftpc->newhost = strdup(control_address(conn)); - } - else - ftpc->newhost = aprintf("%u.%u.%u.%u", ip[0], ip[1], ip[2], ip[3]); - - if(!ftpc->newhost) - return CURLE_OUT_OF_MEMORY; - - ftpc->newport = (unsigned short)(((port[0]<<8) + port[1]) & 0xffff); - } - else if(ftpc->count1 == 0) { - /* EPSV failed, move on to PASV */ - return ftp_epsv_disable(data, conn); - } - else { - failf(data, "Bad PASV/EPSV response: %03d", ftpcode); - return CURLE_FTP_WEIRD_PASV_REPLY; - } - -#ifndef CURL_DISABLE_PROXY - if(conn->bits.proxy) { - /* - * This connection uses a proxy and we need to connect to the proxy again - * here. We don't want to rely on a former host lookup that might've - * expired now, instead we remake the lookup here and now! - */ - const char * const host_name = conn->bits.socksproxy ? - conn->socks_proxy.host.name : conn->http_proxy.host.name; - rc = Curl_resolv(data, host_name, (int)conn->port, FALSE, &addr); - if(rc == CURLRESOLV_PENDING) - /* BLOCKING, ignores the return code but 'addr' will be NULL in - case of failure */ - (void)Curl_resolver_wait_resolv(data, &addr); - - connectport = - (unsigned short)conn->port; /* we connect to the proxy's port */ - - if(!addr) { - failf(data, "Can't resolve proxy host %s:%hu", host_name, connectport); - return CURLE_COULDNT_RESOLVE_PROXY; - } - } - else -#endif - { - /* normal, direct, ftp connection */ - DEBUGASSERT(ftpc->newhost); - - /* postponed address resolution in case of tcp fastopen */ - if(conn->bits.tcp_fastopen && !conn->bits.reuse && !ftpc->newhost[0]) { - Curl_conninfo_remote(data, conn, conn->sock[FIRSTSOCKET]); - Curl_safefree(ftpc->newhost); - ftpc->newhost = strdup(control_address(conn)); - if(!ftpc->newhost) - return CURLE_OUT_OF_MEMORY; - } - - rc = Curl_resolv(data, ftpc->newhost, ftpc->newport, FALSE, &addr); - if(rc == CURLRESOLV_PENDING) - /* BLOCKING */ - (void)Curl_resolver_wait_resolv(data, &addr); - - connectport = ftpc->newport; /* we connect to the remote port */ - - if(!addr) { - failf(data, "Can't resolve new host %s:%hu", ftpc->newhost, connectport); - return CURLE_FTP_CANT_GET_HOST; - } - } - - conn->bits.tcpconnect[SECONDARYSOCKET] = FALSE; - result = Curl_connecthost(data, conn, addr); - - if(result) { - Curl_resolv_unlock(data, addr); /* we're done using this address */ - if(ftpc->count1 == 0 && ftpcode == 229) - return ftp_epsv_disable(data, conn); - - return result; - } - - - /* - * When this is used from the multi interface, this might've returned with - * the 'connected' set to FALSE and thus we are now awaiting a non-blocking - * connect to connect. - */ - - if(data->set.verbose) - /* this just dumps information about this second connection */ - ftp_pasv_verbose(data, addr->addr, ftpc->newhost, connectport); - - Curl_resolv_unlock(data, addr); /* we're done using this address */ - - Curl_safefree(conn->secondaryhostname); - conn->secondary_port = ftpc->newport; - conn->secondaryhostname = strdup(ftpc->newhost); - if(!conn->secondaryhostname) - return CURLE_OUT_OF_MEMORY; - - conn->bits.do_more = TRUE; - state(data, FTP_STOP); /* this phase is completed */ - - return result; -} - -static CURLcode ftp_state_port_resp(struct Curl_easy *data, - int ftpcode) -{ - struct connectdata *conn = data->conn; - struct ftp_conn *ftpc = &conn->proto.ftpc; - ftpport fcmd = (ftpport)ftpc->count1; - CURLcode result = CURLE_OK; - - /* The FTP spec tells a positive response should have code 200. - Be more permissive here to tolerate deviant servers. */ - if(ftpcode / 100 != 2) { - /* the command failed */ - - if(EPRT == fcmd) { - infof(data, "disabling EPRT usage"); - conn->bits.ftp_use_eprt = FALSE; - } - fcmd++; - - if(fcmd == DONE) { - failf(data, "Failed to do PORT"); - result = CURLE_FTP_PORT_FAILED; - } - else - /* try next */ - result = ftp_state_use_port(data, fcmd); - } - else { - infof(data, "Connect data stream actively"); - state(data, FTP_STOP); /* end of DO phase */ - result = ftp_dophase_done(data, FALSE); - } - - return result; -} - -static CURLcode ftp_state_mdtm_resp(struct Curl_easy *data, - int ftpcode) -{ - CURLcode result = CURLE_OK; - struct FTP *ftp = data->req.p.ftp; - struct connectdata *conn = data->conn; - struct ftp_conn *ftpc = &conn->proto.ftpc; - - switch(ftpcode) { - case 213: - { - /* we got a time. Format should be: "YYYYMMDDHHMMSS[.sss]" where the - last .sss part is optional and means fractions of a second */ - int year, month, day, hour, minute, second; - if(6 == sscanf(&data->state.buffer[4], "%04d%02d%02d%02d%02d%02d", - &year, &month, &day, &hour, &minute, &second)) { - /* we have a time, reformat it */ - char timebuf[24]; - msnprintf(timebuf, sizeof(timebuf), - "%04d%02d%02d %02d:%02d:%02d GMT", - year, month, day, hour, minute, second); - /* now, convert this into a time() value: */ - data->info.filetime = Curl_getdate_capped(timebuf); - } - -#ifdef CURL_FTP_HTTPSTYLE_HEAD - /* If we asked for a time of the file and we actually got one as well, - we "emulate" a HTTP-style header in our output. */ - - if(data->set.opt_no_body && - ftpc->file && - data->set.get_filetime && - (data->info.filetime >= 0) ) { - char headerbuf[128]; - int headerbuflen; - time_t filetime = data->info.filetime; - struct tm buffer; - const struct tm *tm = &buffer; - - result = Curl_gmtime(filetime, &buffer); - if(result) - return result; - - /* format: "Tue, 15 Nov 1994 12:45:26" */ - headerbuflen = msnprintf(headerbuf, sizeof(headerbuf), - "Last-Modified: %s, %02d %s %4d %02d:%02d:%02d GMT\r\n", - Curl_wkday[tm->tm_wday?tm->tm_wday-1:6], - tm->tm_mday, - Curl_month[tm->tm_mon], - tm->tm_year + 1900, - tm->tm_hour, - tm->tm_min, - tm->tm_sec); - result = Curl_client_write(data, CLIENTWRITE_BOTH, headerbuf, - headerbuflen); - if(result) - return result; - } /* end of a ridiculous amount of conditionals */ -#endif - } - break; - default: - infof(data, "unsupported MDTM reply format"); - break; - case 550: /* "No such file or directory" */ - failf(data, "Given file does not exist"); - result = CURLE_REMOTE_FILE_NOT_FOUND; - break; - } - - if(data->set.timecondition) { - if((data->info.filetime > 0) && (data->set.timevalue > 0)) { - switch(data->set.timecondition) { - case CURL_TIMECOND_IFMODSINCE: - default: - if(data->info.filetime <= data->set.timevalue) { - infof(data, "The requested document is not new enough"); - ftp->transfer = PPTRANSFER_NONE; /* mark to not transfer data */ - data->info.timecond = TRUE; - state(data, FTP_STOP); - return CURLE_OK; - } - break; - case CURL_TIMECOND_IFUNMODSINCE: - if(data->info.filetime > data->set.timevalue) { - infof(data, "The requested document is not old enough"); - ftp->transfer = PPTRANSFER_NONE; /* mark to not transfer data */ - data->info.timecond = TRUE; - state(data, FTP_STOP); - return CURLE_OK; - } - break; - } /* switch */ - } - else { - infof(data, "Skipping time comparison"); - } - } - - if(!result) - result = ftp_state_type(data); - - return result; -} - -static CURLcode ftp_state_type_resp(struct Curl_easy *data, - int ftpcode, - ftpstate instate) -{ - CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - - if(ftpcode/100 != 2) { - /* "sasserftpd" and "(u)r(x)bot ftpd" both responds with 226 after a - successful 'TYPE I'. While that is not as RFC959 says, it is still a - positive response code and we allow that. */ - failf(data, "Couldn't set desired mode"); - return CURLE_FTP_COULDNT_SET_TYPE; - } - if(ftpcode != 200) - infof(data, "Got a %03d response code instead of the assumed 200", - ftpcode); - - if(instate == FTP_TYPE) - result = ftp_state_size(data, conn); - else if(instate == FTP_LIST_TYPE) - result = ftp_state_list(data); - else if(instate == FTP_RETR_TYPE) - result = ftp_state_retr_prequote(data); - else if(instate == FTP_STOR_TYPE) - result = ftp_state_stor_prequote(data); - - return result; -} - -static CURLcode ftp_state_retr(struct Curl_easy *data, - curl_off_t filesize) -{ - CURLcode result = CURLE_OK; - struct FTP *ftp = data->req.p.ftp; - struct connectdata *conn = data->conn; - struct ftp_conn *ftpc = &conn->proto.ftpc; - - if(data->set.max_filesize && (filesize > data->set.max_filesize)) { - failf(data, "Maximum file size exceeded"); - return CURLE_FILESIZE_EXCEEDED; - } - ftp->downloadsize = filesize; - - if(data->state.resume_from) { - /* We always (attempt to) get the size of downloads, so it is done before - this even when not doing resumes. */ - if(filesize == -1) { - infof(data, "ftp server doesn't support SIZE"); - /* We couldn't get the size and therefore we can't know if there really - is a part of the file left to get, although the server will just - close the connection when we start the connection so it won't cause - us any harm, just not make us exit as nicely. */ - } - else { - /* We got a file size report, so we check that there actually is a - part of the file left to get, or else we go home. */ - if(data->state.resume_from< 0) { - /* We're supposed to download the last abs(from) bytes */ - if(filesize < -data->state.resume_from) { - failf(data, "Offset (%" CURL_FORMAT_CURL_OFF_T - ") was beyond file size (%" CURL_FORMAT_CURL_OFF_T ")", - data->state.resume_from, filesize); - return CURLE_BAD_DOWNLOAD_RESUME; - } - /* convert to size to download */ - ftp->downloadsize = -data->state.resume_from; - /* download from where? */ - data->state.resume_from = filesize - ftp->downloadsize; - } - else { - if(filesize < data->state.resume_from) { - failf(data, "Offset (%" CURL_FORMAT_CURL_OFF_T - ") was beyond file size (%" CURL_FORMAT_CURL_OFF_T ")", - data->state.resume_from, filesize); - return CURLE_BAD_DOWNLOAD_RESUME; - } - /* Now store the number of bytes we are expected to download */ - ftp->downloadsize = filesize-data->state.resume_from; - } - } - - if(ftp->downloadsize == 0) { - /* no data to transfer */ - Curl_setup_transfer(data, -1, -1, FALSE, -1); - infof(data, "File already completely downloaded"); - - /* Set ->transfer so that we won't get any error in ftp_done() - * because we didn't transfer the any file */ - ftp->transfer = PPTRANSFER_NONE; - state(data, FTP_STOP); - return CURLE_OK; - } - - /* Set resume file transfer offset */ - infof(data, "Instructs server to resume from offset %" - CURL_FORMAT_CURL_OFF_T, data->state.resume_from); - - result = Curl_pp_sendf(data, &ftpc->pp, "REST %" CURL_FORMAT_CURL_OFF_T, - data->state.resume_from); - if(!result) - state(data, FTP_RETR_REST); - } - else { - /* no resume */ - result = Curl_pp_sendf(data, &ftpc->pp, "RETR %s", ftpc->file); - if(!result) - state(data, FTP_RETR); - } - - return result; -} - -static CURLcode ftp_state_size_resp(struct Curl_easy *data, - int ftpcode, - ftpstate instate) -{ - CURLcode result = CURLE_OK; - curl_off_t filesize = -1; - char *buf = data->state.buffer; - - /* get the size from the ascii string: */ - if(ftpcode == 213) { - /* To allow servers to prepend "rubbish" in the response string, we scan - for all the digits at the end of the response and parse only those as a - number. */ - char *start = &buf[4]; - char *fdigit = strchr(start, '\r'); - if(fdigit) { - do - fdigit--; - while(ISDIGIT(*fdigit) && (fdigit > start)); - if(!ISDIGIT(*fdigit)) - fdigit++; - } - else - fdigit = start; - /* ignores parsing errors, which will make the size remain unknown */ - (void)curlx_strtoofft(fdigit, NULL, 0, &filesize); - - } - else if(ftpcode == 550) { /* "No such file or directory" */ - /* allow a SIZE failure for (resumed) uploads, when probing what command - to use */ - if(instate != FTP_STOR_SIZE) { - failf(data, "The file does not exist"); - return CURLE_REMOTE_FILE_NOT_FOUND; - } - } - - if(instate == FTP_SIZE) { -#ifdef CURL_FTP_HTTPSTYLE_HEAD - if(-1 != filesize) { - char clbuf[128]; - int clbuflen = msnprintf(clbuf, sizeof(clbuf), - "Content-Length: %" CURL_FORMAT_CURL_OFF_T "\r\n", filesize); - result = Curl_client_write(data, CLIENTWRITE_BOTH, clbuf, clbuflen); - if(result) - return result; - } -#endif - Curl_pgrsSetDownloadSize(data, filesize); - result = ftp_state_rest(data, data->conn); - } - else if(instate == FTP_RETR_SIZE) { - Curl_pgrsSetDownloadSize(data, filesize); - result = ftp_state_retr(data, filesize); - } - else if(instate == FTP_STOR_SIZE) { - data->state.resume_from = filesize; - result = ftp_state_ul_setup(data, TRUE); - } - - return result; -} - -static CURLcode ftp_state_rest_resp(struct Curl_easy *data, - struct connectdata *conn, - int ftpcode, - ftpstate instate) -{ - CURLcode result = CURLE_OK; - struct ftp_conn *ftpc = &conn->proto.ftpc; - - switch(instate) { - case FTP_REST: - default: -#ifdef CURL_FTP_HTTPSTYLE_HEAD - if(ftpcode == 350) { - char buffer[24]= { "Accept-ranges: bytes\r\n" }; - result = Curl_client_write(data, CLIENTWRITE_BOTH, buffer, - strlen(buffer)); - if(result) - return result; - } -#endif - result = ftp_state_prepare_transfer(data); - break; - - case FTP_RETR_REST: - if(ftpcode != 350) { - failf(data, "Couldn't use REST"); - result = CURLE_FTP_COULDNT_USE_REST; - } - else { - result = Curl_pp_sendf(data, &ftpc->pp, "RETR %s", ftpc->file); - if(!result) - state(data, FTP_RETR); - } - break; - } - - return result; -} - -static CURLcode ftp_state_stor_resp(struct Curl_easy *data, - int ftpcode, ftpstate instate) -{ - CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - - if(ftpcode >= 400) { - failf(data, "Failed FTP upload: %0d", ftpcode); - state(data, FTP_STOP); - /* oops, we never close the sockets! */ - return CURLE_UPLOAD_FAILED; - } - - conn->proto.ftpc.state_saved = instate; - - /* PORT means we are now awaiting the server to connect to us. */ - if(data->set.ftp_use_port) { - bool connected; - - state(data, FTP_STOP); /* no longer in STOR state */ - - result = AllowServerConnect(data, &connected); - if(result) - return result; - - if(!connected) { - struct ftp_conn *ftpc = &conn->proto.ftpc; - infof(data, "Data conn was not available immediately"); - ftpc->wait_data_conn = TRUE; - } - - return CURLE_OK; - } - return InitiateTransfer(data); -} - -/* for LIST and RETR responses */ -static CURLcode ftp_state_get_resp(struct Curl_easy *data, - int ftpcode, - ftpstate instate) -{ - CURLcode result = CURLE_OK; - struct FTP *ftp = data->req.p.ftp; - struct connectdata *conn = data->conn; - - if((ftpcode == 150) || (ftpcode == 125)) { - - /* - A; - 150 Opening BINARY mode data connection for /etc/passwd (2241 - bytes). (ok, the file is being transferred) - - B: - 150 Opening ASCII mode data connection for /bin/ls - - C: - 150 ASCII data connection for /bin/ls (137.167.104.91,37445) (0 bytes). - - D: - 150 Opening ASCII mode data connection for [file] (0.0.0.0,0) (545 bytes) - - E: - 125 Data connection already open; Transfer starting. */ - - curl_off_t size = -1; /* default unknown size */ - - - /* - * It appears that there are FTP-servers that return size 0 for files when - * SIZE is used on the file while being in BINARY mode. To work around - * that (stupid) behavior, we attempt to parse the RETR response even if - * the SIZE returned size zero. - * - * Debugging help from Salvatore Sorrentino on February 26, 2003. - */ - - if((instate != FTP_LIST) && - !data->state.prefer_ascii && - (ftp->downloadsize < 1)) { - /* - * It seems directory listings either don't show the size or very - * often uses size 0 anyway. ASCII transfers may very well turn out - * that the transferred amount of data is not the same as this line - * tells, why using this number in those cases only confuses us. - * - * Example D above makes this parsing a little tricky */ - char *bytes; - char *buf = data->state.buffer; - bytes = strstr(buf, " bytes"); - if(bytes) { - long in = (long)(--bytes-buf); - /* this is a hint there is size information in there! ;-) */ - while(--in) { - /* scan for the left parenthesis and break there */ - if('(' == *bytes) - break; - /* skip only digits */ - if(!ISDIGIT(*bytes)) { - bytes = NULL; - break; - } - /* one more estep backwards */ - bytes--; - } - /* if we have nothing but digits: */ - if(bytes) { - ++bytes; - /* get the number! */ - (void)curlx_strtoofft(bytes, NULL, 0, &size); - } - } - } - else if(ftp->downloadsize > -1) - size = ftp->downloadsize; - - if(size > data->req.maxdownload && data->req.maxdownload > 0) - size = data->req.size = data->req.maxdownload; - else if((instate != FTP_LIST) && (data->state.prefer_ascii)) - size = -1; /* kludge for servers that understate ASCII mode file size */ - - infof(data, "Maxdownload = %" CURL_FORMAT_CURL_OFF_T, - data->req.maxdownload); - - if(instate != FTP_LIST) - infof(data, "Getting file with size: %" CURL_FORMAT_CURL_OFF_T, - size); - - /* FTP download: */ - conn->proto.ftpc.state_saved = instate; - conn->proto.ftpc.retr_size_saved = size; - - if(data->set.ftp_use_port) { - bool connected; - - result = AllowServerConnect(data, &connected); - if(result) - return result; - - if(!connected) { - struct ftp_conn *ftpc = &conn->proto.ftpc; - infof(data, "Data conn was not available immediately"); - state(data, FTP_STOP); - ftpc->wait_data_conn = TRUE; - } - } - else - return InitiateTransfer(data); - } - else { - if((instate == FTP_LIST) && (ftpcode == 450)) { - /* simply no matching files in the dir listing */ - ftp->transfer = PPTRANSFER_NONE; /* don't download anything */ - state(data, FTP_STOP); /* this phase is over */ - } - else { - failf(data, "RETR response: %03d", ftpcode); - return instate == FTP_RETR && ftpcode == 550? - CURLE_REMOTE_FILE_NOT_FOUND: - CURLE_FTP_COULDNT_RETR_FILE; - } - } - - return result; -} - -/* after USER, PASS and ACCT */ -static CURLcode ftp_state_loggedin(struct Curl_easy *data) -{ - CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - - if(conn->bits.ftp_use_control_ssl) { - /* PBSZ = PROTECTION BUFFER SIZE. - - The 'draft-murray-auth-ftp-ssl' (draft 12, page 7) says: - - Specifically, the PROT command MUST be preceded by a PBSZ - command and a PBSZ command MUST be preceded by a successful - security data exchange (the TLS negotiation in this case) - - ... (and on page 8): - - Thus the PBSZ command must still be issued, but must have a - parameter of '0' to indicate that no buffering is taking place - and the data connection should not be encapsulated. - */ - result = Curl_pp_sendf(data, &conn->proto.ftpc.pp, "PBSZ %d", 0); - if(!result) - state(data, FTP_PBSZ); - } - else { - result = ftp_state_pwd(data, conn); - } - return result; -} - -/* for USER and PASS responses */ -static CURLcode ftp_state_user_resp(struct Curl_easy *data, - int ftpcode, - ftpstate instate) -{ - CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - struct ftp_conn *ftpc = &conn->proto.ftpc; - (void)instate; /* no use for this yet */ - - /* some need password anyway, and others just return 2xx ignored */ - if((ftpcode == 331) && (ftpc->state == FTP_USER)) { - /* 331 Password required for ... - (the server requires to send the user's password too) */ - result = Curl_pp_sendf(data, &ftpc->pp, "PASS %s", - conn->passwd?conn->passwd:""); - if(!result) - state(data, FTP_PASS); - } - else if(ftpcode/100 == 2) { - /* 230 User ... logged in. - (the user logged in with or without password) */ - result = ftp_state_loggedin(data); - } - else if(ftpcode == 332) { - if(data->set.str[STRING_FTP_ACCOUNT]) { - result = Curl_pp_sendf(data, &ftpc->pp, "ACCT %s", - data->set.str[STRING_FTP_ACCOUNT]); - if(!result) - state(data, FTP_ACCT); - } - else { - failf(data, "ACCT requested but none available"); - result = CURLE_LOGIN_DENIED; - } - } - else { - /* All other response codes, like: - - 530 User ... access denied - (the server denies to log the specified user) */ - - if(data->set.str[STRING_FTP_ALTERNATIVE_TO_USER] && - !data->state.ftp_trying_alternative) { - /* Ok, USER failed. Let's try the supplied command. */ - result = - Curl_pp_sendf(data, &ftpc->pp, "%s", - data->set.str[STRING_FTP_ALTERNATIVE_TO_USER]); - if(!result) { - data->state.ftp_trying_alternative = TRUE; - state(data, FTP_USER); - } - } - else { - failf(data, "Access denied: %03d", ftpcode); - result = CURLE_LOGIN_DENIED; - } - } - return result; -} - -/* for ACCT response */ -static CURLcode ftp_state_acct_resp(struct Curl_easy *data, - int ftpcode) -{ - CURLcode result = CURLE_OK; - if(ftpcode != 230) { - failf(data, "ACCT rejected by server: %03d", ftpcode); - result = CURLE_FTP_WEIRD_PASS_REPLY; /* FIX */ - } - else - result = ftp_state_loggedin(data); - - return result; -} - - -static CURLcode ftp_statemachine(struct Curl_easy *data, - struct connectdata *conn) -{ - CURLcode result; - curl_socket_t sock = conn->sock[FIRSTSOCKET]; - int ftpcode; - struct ftp_conn *ftpc = &conn->proto.ftpc; - struct pingpong *pp = &ftpc->pp; - static const char ftpauth[][4] = { "SSL", "TLS" }; - size_t nread = 0; - - if(pp->sendleft) - return Curl_pp_flushsend(data, pp); - - result = ftp_readresp(data, sock, pp, &ftpcode, &nread); - if(result) - return result; - - if(ftpcode) { - /* we have now received a full FTP server response */ - switch(ftpc->state) { - case FTP_WAIT220: - if(ftpcode == 230) { - /* 230 User logged in - already! Take as 220 if TLS required. */ - if(data->set.use_ssl <= CURLUSESSL_TRY || - conn->bits.ftp_use_control_ssl) - return ftp_state_user_resp(data, ftpcode, ftpc->state); - } - else if(ftpcode != 220) { - failf(data, "Got a %03d ftp-server response when 220 was expected", - ftpcode); - return CURLE_WEIRD_SERVER_REPLY; - } - - /* We have received a 220 response fine, now we proceed. */ -#ifdef HAVE_GSSAPI - if(data->set.krb) { - /* If not anonymous login, try a secure login. Note that this - procedure is still BLOCKING. */ - - Curl_sec_request_prot(conn, "private"); - /* We set private first as default, in case the line below fails to - set a valid level */ - Curl_sec_request_prot(conn, data->set.str[STRING_KRB_LEVEL]); - - if(Curl_sec_login(data, conn)) - infof(data, "Logging in with password in cleartext!"); - else - infof(data, "Authentication successful"); - } -#endif - - if(data->set.use_ssl && !conn->bits.ftp_use_control_ssl) { - /* We don't have a SSL/TLS control connection yet, but FTPS is - requested. Try a FTPS connection now */ - - ftpc->count3 = 0; - switch(data->set.ftpsslauth) { - case CURLFTPAUTH_DEFAULT: - case CURLFTPAUTH_SSL: - ftpc->count2 = 1; /* add one to get next */ - ftpc->count1 = 0; - break; - case CURLFTPAUTH_TLS: - ftpc->count2 = -1; /* subtract one to get next */ - ftpc->count1 = 1; - break; - default: - failf(data, "unsupported parameter to CURLOPT_FTPSSLAUTH: %d", - (int)data->set.ftpsslauth); - return CURLE_UNKNOWN_OPTION; /* we don't know what to do */ - } - result = Curl_pp_sendf(data, &ftpc->pp, "AUTH %s", - ftpauth[ftpc->count1]); - if(!result) - state(data, FTP_AUTH); - } - else - result = ftp_state_user(data, conn); - break; - - case FTP_AUTH: - /* we have gotten the response to a previous AUTH command */ - - if(pp->cache_size) - return CURLE_WEIRD_SERVER_REPLY; /* Forbid pipelining in response. */ - - /* RFC2228 (page 5) says: - * - * If the server is willing to accept the named security mechanism, - * and does not require any security data, it must respond with - * reply code 234/334. - */ - - if((ftpcode == 234) || (ftpcode == 334)) { - /* Curl_ssl_connect is BLOCKING */ - result = Curl_ssl_connect(data, conn, FIRSTSOCKET); - if(!result) { - conn->bits.ftp_use_data_ssl = FALSE; /* clear-text data */ - conn->bits.ftp_use_control_ssl = TRUE; /* SSL on control */ - result = ftp_state_user(data, conn); - } - } - else if(ftpc->count3 < 1) { - ftpc->count3++; - ftpc->count1 += ftpc->count2; /* get next attempt */ - result = Curl_pp_sendf(data, &ftpc->pp, "AUTH %s", - ftpauth[ftpc->count1]); - /* remain in this same state */ - } - else { - if(data->set.use_ssl > CURLUSESSL_TRY) - /* we failed and CURLUSESSL_CONTROL or CURLUSESSL_ALL is set */ - result = CURLE_USE_SSL_FAILED; - else - /* ignore the failure and continue */ - result = ftp_state_user(data, conn); - } - break; - - case FTP_USER: - case FTP_PASS: - result = ftp_state_user_resp(data, ftpcode, ftpc->state); - break; - - case FTP_ACCT: - result = ftp_state_acct_resp(data, ftpcode); - break; - - case FTP_PBSZ: - result = - Curl_pp_sendf(data, &ftpc->pp, "PROT %c", - data->set.use_ssl == CURLUSESSL_CONTROL ? 'C' : 'P'); - if(!result) - state(data, FTP_PROT); - break; - - case FTP_PROT: - if(ftpcode/100 == 2) - /* We have enabled SSL for the data connection! */ - conn->bits.ftp_use_data_ssl = - (data->set.use_ssl != CURLUSESSL_CONTROL) ? TRUE : FALSE; - /* FTP servers typically responds with 500 if they decide to reject - our 'P' request */ - else if(data->set.use_ssl > CURLUSESSL_CONTROL) - /* we failed and bails out */ - return CURLE_USE_SSL_FAILED; - - if(data->set.ftp_ccc) { - /* CCC - Clear Command Channel - */ - result = Curl_pp_sendf(data, &ftpc->pp, "%s", "CCC"); - if(!result) - state(data, FTP_CCC); - } - else - result = ftp_state_pwd(data, conn); - break; - - case FTP_CCC: - if(ftpcode < 500) { - /* First shut down the SSL layer (note: this call will block) */ - result = Curl_ssl_shutdown(data, conn, FIRSTSOCKET); - - if(result) - failf(data, "Failed to clear the command channel (CCC)"); - } - if(!result) - /* Then continue as normal */ - result = ftp_state_pwd(data, conn); - break; - - case FTP_PWD: - if(ftpcode == 257) { - char *ptr = &data->state.buffer[4]; /* start on the first letter */ - const size_t buf_size = data->set.buffer_size; - char *dir; - bool entry_extracted = FALSE; - - dir = malloc(nread + 1); - if(!dir) - return CURLE_OUT_OF_MEMORY; - - /* Reply format is like - 257[rubbish]"" and the - RFC959 says - - The directory name can contain any character; embedded - double-quotes should be escaped by double-quotes (the - "quote-doubling" convention). - */ - - /* scan for the first double-quote for non-standard responses */ - while(ptr < &data->state.buffer[buf_size] - && *ptr != '\n' && *ptr != '\0' && *ptr != '"') - ptr++; - - if('\"' == *ptr) { - /* it started good */ - char *store; - ptr++; - for(store = dir; *ptr;) { - if('\"' == *ptr) { - if('\"' == ptr[1]) { - /* "quote-doubling" */ - *store = ptr[1]; - ptr++; - } - else { - /* end of path */ - entry_extracted = TRUE; - break; /* get out of this loop */ - } - } - else - *store = *ptr; - store++; - ptr++; - } - *store = '\0'; /* null-terminate */ - } - if(entry_extracted) { - /* If the path name does not look like an absolute path (i.e.: it - does not start with a '/'), we probably need some server-dependent - adjustments. For example, this is the case when connecting to - an OS400 FTP server: this server supports two name syntaxes, - the default one being incompatible with standard paths. In - addition, this server switches automatically to the regular path - syntax when one is encountered in a command: this results in - having an entrypath in the wrong syntax when later used in CWD. - The method used here is to check the server OS: we do it only - if the path name looks strange to minimize overhead on other - systems. */ - - if(!ftpc->server_os && dir[0] != '/') { - result = Curl_pp_sendf(data, &ftpc->pp, "%s", "SYST"); - if(result) { - free(dir); - return result; - } - Curl_safefree(ftpc->entrypath); - ftpc->entrypath = dir; /* remember this */ - infof(data, "Entry path is '%s'", ftpc->entrypath); - /* also save it where getinfo can access it: */ - data->state.most_recent_ftp_entrypath = ftpc->entrypath; - state(data, FTP_SYST); - break; - } - - Curl_safefree(ftpc->entrypath); - ftpc->entrypath = dir; /* remember this */ - infof(data, "Entry path is '%s'", ftpc->entrypath); - /* also save it where getinfo can access it: */ - data->state.most_recent_ftp_entrypath = ftpc->entrypath; - } - else { - /* couldn't get the path */ - free(dir); - infof(data, "Failed to figure out path"); - } - } - state(data, FTP_STOP); /* we are done with the CONNECT phase! */ - DEBUGF(infof(data, "protocol connect phase DONE")); - break; - - case FTP_SYST: - if(ftpcode == 215) { - char *ptr = &data->state.buffer[4]; /* start on the first letter */ - char *os; - char *store; - - os = malloc(nread + 1); - if(!os) - return CURLE_OUT_OF_MEMORY; - - /* Reply format is like - 215 - */ - while(*ptr == ' ') - ptr++; - for(store = os; *ptr && *ptr != ' ';) - *store++ = *ptr++; - *store = '\0'; /* null-terminate */ - - /* Check for special servers here. */ - - if(strcasecompare(os, "OS/400")) { - /* Force OS400 name format 1. */ - result = Curl_pp_sendf(data, &ftpc->pp, "%s", "SITE NAMEFMT 1"); - if(result) { - free(os); - return result; - } - /* remember target server OS */ - Curl_safefree(ftpc->server_os); - ftpc->server_os = os; - state(data, FTP_NAMEFMT); - break; - } - /* Nothing special for the target server. */ - /* remember target server OS */ - Curl_safefree(ftpc->server_os); - ftpc->server_os = os; - } - else { - /* Cannot identify server OS. Continue anyway and cross fingers. */ - } - - state(data, FTP_STOP); /* we are done with the CONNECT phase! */ - DEBUGF(infof(data, "protocol connect phase DONE")); - break; - - case FTP_NAMEFMT: - if(ftpcode == 250) { - /* Name format change successful: reload initial path. */ - ftp_state_pwd(data, conn); - break; - } - - state(data, FTP_STOP); /* we are done with the CONNECT phase! */ - DEBUGF(infof(data, "protocol connect phase DONE")); - break; - - case FTP_QUOTE: - case FTP_POSTQUOTE: - case FTP_RETR_PREQUOTE: - case FTP_STOR_PREQUOTE: - if((ftpcode >= 400) && !ftpc->count2) { - /* failure response code, and not allowed to fail */ - failf(data, "QUOT command failed with %03d", ftpcode); - result = CURLE_QUOTE_ERROR; - } - else - result = ftp_state_quote(data, FALSE, ftpc->state); - break; - - case FTP_CWD: - if(ftpcode/100 != 2) { - /* failure to CWD there */ - if(data->set.ftp_create_missing_dirs && - ftpc->cwdcount && !ftpc->count2) { - /* try making it */ - ftpc->count2++; /* counter to prevent CWD-MKD loops */ - result = Curl_pp_sendf(data, &ftpc->pp, "MKD %s", - ftpc->dirs[ftpc->cwdcount - 1]); - if(!result) - state(data, FTP_MKD); - } - else { - /* return failure */ - failf(data, "Server denied you to change to the given directory"); - ftpc->cwdfail = TRUE; /* don't remember this path as we failed - to enter it */ - result = CURLE_REMOTE_ACCESS_DENIED; - } - } - else { - /* success */ - ftpc->count2 = 0; - if(++ftpc->cwdcount <= ftpc->dirdepth) - /* send next CWD */ - result = Curl_pp_sendf(data, &ftpc->pp, "CWD %s", - ftpc->dirs[ftpc->cwdcount - 1]); - else - result = ftp_state_mdtm(data); - } - break; - - case FTP_MKD: - if((ftpcode/100 != 2) && !ftpc->count3--) { - /* failure to MKD the dir */ - failf(data, "Failed to MKD dir: %03d", ftpcode); - result = CURLE_REMOTE_ACCESS_DENIED; - } - else { - state(data, FTP_CWD); - /* send CWD */ - result = Curl_pp_sendf(data, &ftpc->pp, "CWD %s", - ftpc->dirs[ftpc->cwdcount - 1]); - } - break; - - case FTP_MDTM: - result = ftp_state_mdtm_resp(data, ftpcode); - break; - - case FTP_TYPE: - case FTP_LIST_TYPE: - case FTP_RETR_TYPE: - case FTP_STOR_TYPE: - result = ftp_state_type_resp(data, ftpcode, ftpc->state); - break; - - case FTP_SIZE: - case FTP_RETR_SIZE: - case FTP_STOR_SIZE: - result = ftp_state_size_resp(data, ftpcode, ftpc->state); - break; - - case FTP_REST: - case FTP_RETR_REST: - result = ftp_state_rest_resp(data, conn, ftpcode, ftpc->state); - break; - - case FTP_PRET: - if(ftpcode != 200) { - /* there only is this one standard OK return code. */ - failf(data, "PRET command not accepted: %03d", ftpcode); - return CURLE_FTP_PRET_FAILED; - } - result = ftp_state_use_pasv(data, conn); - break; - - case FTP_PASV: - result = ftp_state_pasv_resp(data, ftpcode); - break; - - case FTP_PORT: - result = ftp_state_port_resp(data, ftpcode); - break; - - case FTP_LIST: - case FTP_RETR: - result = ftp_state_get_resp(data, ftpcode, ftpc->state); - break; - - case FTP_STOR: - result = ftp_state_stor_resp(data, ftpcode, ftpc->state); - break; - - case FTP_QUIT: - /* fallthrough, just stop! */ - default: - /* internal error */ - state(data, FTP_STOP); - break; - } - } /* if(ftpcode) */ - - return result; -} - - -/* called repeatedly until done from multi.c */ -static CURLcode ftp_multi_statemach(struct Curl_easy *data, - bool *done) -{ - struct connectdata *conn = data->conn; - struct ftp_conn *ftpc = &conn->proto.ftpc; - CURLcode result = Curl_pp_statemach(data, &ftpc->pp, FALSE, FALSE); - - /* Check for the state outside of the Curl_socket_check() return code checks - since at times we are in fact already in this state when this function - gets called. */ - *done = (ftpc->state == FTP_STOP) ? TRUE : FALSE; - - return result; -} - -static CURLcode ftp_block_statemach(struct Curl_easy *data, - struct connectdata *conn) -{ - struct ftp_conn *ftpc = &conn->proto.ftpc; - struct pingpong *pp = &ftpc->pp; - CURLcode result = CURLE_OK; - - while(ftpc->state != FTP_STOP) { - result = Curl_pp_statemach(data, pp, TRUE, TRUE /* disconnecting */); - if(result) - break; - } - - return result; -} - -/* - * ftp_connect() should do everything that is to be considered a part of - * the connection phase. - * - * The variable 'done' points to will be TRUE if the protocol-layer connect - * phase is done when this function returns, or FALSE if not. - * - */ -static CURLcode ftp_connect(struct Curl_easy *data, - bool *done) /* see description above */ -{ - CURLcode result; - struct connectdata *conn = data->conn; - struct ftp_conn *ftpc = &conn->proto.ftpc; - struct pingpong *pp = &ftpc->pp; - - *done = FALSE; /* default to not done yet */ - - /* We always support persistent connections on ftp */ - connkeep(conn, "FTP default"); - - PINGPONG_SETUP(pp, ftp_statemachine, ftp_endofresp); - - if(conn->handler->flags & PROTOPT_SSL) { - /* BLOCKING */ - result = Curl_ssl_connect(data, conn, FIRSTSOCKET); - if(result) - return result; - conn->bits.ftp_use_control_ssl = TRUE; - } - - Curl_pp_setup(pp); /* once per transfer */ - Curl_pp_init(data, pp); /* init the generic pingpong data */ - - /* When we connect, we start in the state where we await the 220 - response */ - state(data, FTP_WAIT220); - - result = ftp_multi_statemach(data, done); - - return result; -} - -/*********************************************************************** - * - * ftp_done() - * - * The DONE function. This does what needs to be done after a single DO has - * performed. - * - * Input argument is already checked for validity. - */ -static CURLcode ftp_done(struct Curl_easy *data, CURLcode status, - bool premature) -{ - struct connectdata *conn = data->conn; - struct FTP *ftp = data->req.p.ftp; - struct ftp_conn *ftpc = &conn->proto.ftpc; - struct pingpong *pp = &ftpc->pp; - ssize_t nread; - int ftpcode; - CURLcode result = CURLE_OK; - char *rawPath = NULL; - size_t pathLen = 0; - - if(!ftp) - return CURLE_OK; - - switch(status) { - case CURLE_BAD_DOWNLOAD_RESUME: - case CURLE_FTP_WEIRD_PASV_REPLY: - case CURLE_FTP_PORT_FAILED: - case CURLE_FTP_ACCEPT_FAILED: - case CURLE_FTP_ACCEPT_TIMEOUT: - case CURLE_FTP_COULDNT_SET_TYPE: - case CURLE_FTP_COULDNT_RETR_FILE: - case CURLE_PARTIAL_FILE: - case CURLE_UPLOAD_FAILED: - case CURLE_REMOTE_ACCESS_DENIED: - case CURLE_FILESIZE_EXCEEDED: - case CURLE_REMOTE_FILE_NOT_FOUND: - case CURLE_WRITE_ERROR: - /* the connection stays alive fine even though this happened */ - /* fall-through */ - case CURLE_OK: /* doesn't affect the control connection's status */ - if(!premature) - break; - - /* until we cope better with prematurely ended requests, let them - * fallback as if in complete failure */ - /* FALLTHROUGH */ - default: /* by default, an error means the control connection is - wedged and should not be used anymore */ - ftpc->ctl_valid = FALSE; - ftpc->cwdfail = TRUE; /* set this TRUE to prevent us to remember the - current path, as this connection is going */ - connclose(conn, "FTP ended with bad error code"); - result = status; /* use the already set error code */ - break; - } - - if(data->state.wildcardmatch) { - if(data->set.chunk_end && ftpc->file) { - Curl_set_in_callback(data, true); - data->set.chunk_end(data->wildcard.customptr); - Curl_set_in_callback(data, false); - } - ftpc->known_filesize = -1; - } - - if(!result) - /* get the url-decoded "raw" path */ - result = Curl_urldecode(data, ftp->path, 0, &rawPath, &pathLen, - REJECT_CTRL); - if(result) { - /* We can limp along anyway (and should try to since we may already be in - * the error path) */ - ftpc->ctl_valid = FALSE; /* mark control connection as bad */ - connclose(conn, "FTP: out of memory!"); /* mark for connection closure */ - free(ftpc->prevpath); - ftpc->prevpath = NULL; /* no path remembering */ - } - else { /* remember working directory for connection reuse */ - if((data->set.ftp_filemethod == FTPFILE_NOCWD) && (rawPath[0] == '/')) - free(rawPath); /* full path => no CWDs happened => keep ftpc->prevpath */ - else { - free(ftpc->prevpath); - - if(!ftpc->cwdfail) { - if(data->set.ftp_filemethod == FTPFILE_NOCWD) - pathLen = 0; /* relative path => working directory is FTP home */ - else - pathLen -= ftpc->file?strlen(ftpc->file):0; /* file is url-decoded */ - - rawPath[pathLen] = '\0'; - ftpc->prevpath = rawPath; - } - else { - free(rawPath); - ftpc->prevpath = NULL; /* no path */ - } - } - - if(ftpc->prevpath) - infof(data, "Remembering we are in dir \"%s\"", ftpc->prevpath); - } - - /* free the dir tree and file parts */ - freedirs(ftpc); - - /* shut down the socket to inform the server we're done */ - -#ifdef _WIN32_WCE - shutdown(conn->sock[SECONDARYSOCKET], 2); /* SD_BOTH */ -#endif - - if(conn->sock[SECONDARYSOCKET] != CURL_SOCKET_BAD) { - if(!result && ftpc->dont_check && data->req.maxdownload > 0) { - /* partial download completed */ - result = Curl_pp_sendf(data, pp, "%s", "ABOR"); - if(result) { - failf(data, "Failure sending ABOR command: %s", - curl_easy_strerror(result)); - ftpc->ctl_valid = FALSE; /* mark control connection as bad */ - connclose(conn, "ABOR command failed"); /* connection closure */ - } - } - - if(conn->ssl[SECONDARYSOCKET].use) { - /* The secondary socket is using SSL so we must close down that part - first before we close the socket for real */ - Curl_ssl_close(data, conn, SECONDARYSOCKET); - - /* Note that we keep "use" set to TRUE since that (next) connection is - still requested to use SSL */ - } - close_secondarysocket(data, conn); - } - - if(!result && (ftp->transfer == PPTRANSFER_BODY) && ftpc->ctl_valid && - pp->pending_resp && !premature) { - /* - * Let's see what the server says about the transfer we just performed, - * but lower the timeout as sometimes this connection has died while the - * data has been transferred. This happens when doing through NATs etc that - * abandon old silent connections. - */ - timediff_t old_time = pp->response_time; - - pp->response_time = 60*1000; /* give it only a minute for now */ - pp->response = Curl_now(); /* timeout relative now */ - - result = Curl_GetFTPResponse(data, &nread, &ftpcode); - - pp->response_time = old_time; /* set this back to previous value */ - - if(!nread && (CURLE_OPERATION_TIMEDOUT == result)) { - failf(data, "control connection looks dead"); - ftpc->ctl_valid = FALSE; /* mark control connection as bad */ - connclose(conn, "Timeout or similar in FTP DONE operation"); /* close */ - } - - if(result) { - Curl_safefree(ftp->pathalloc); - return result; - } - - if(ftpc->dont_check && data->req.maxdownload > 0) { - /* we have just sent ABOR and there is no reliable way to check if it was - * successful or not; we have to close the connection now */ - infof(data, "partial download completed, closing connection"); - connclose(conn, "Partial download with no ability to check"); - return result; - } - - if(!ftpc->dont_check) { - /* 226 Transfer complete, 250 Requested file action okay, completed. */ - switch(ftpcode) { - case 226: - case 250: - break; - case 552: - failf(data, "Exceeded storage allocation"); - result = CURLE_REMOTE_DISK_FULL; - break; - default: - failf(data, "server did not report OK, got %d", ftpcode); - result = CURLE_PARTIAL_FILE; - break; - } - } - } - - if(result || premature) - /* the response code from the transfer showed an error already so no - use checking further */ - ; - else if(data->set.upload) { - if((-1 != data->state.infilesize) && - (data->state.infilesize != data->req.writebytecount) && - !data->set.crlf && - (ftp->transfer == PPTRANSFER_BODY)) { - failf(data, "Uploaded unaligned file size (%" CURL_FORMAT_CURL_OFF_T - " out of %" CURL_FORMAT_CURL_OFF_T " bytes)", - data->req.bytecount, data->state.infilesize); - result = CURLE_PARTIAL_FILE; - } - } - else { - if((-1 != data->req.size) && - (data->req.size != data->req.bytecount) && -#ifdef CURL_DO_LINEEND_CONV - /* Most FTP servers don't adjust their file SIZE response for CRLFs, so - * we'll check to see if the discrepancy can be explained by the number - * of CRLFs we've changed to LFs. - */ - ((data->req.size + data->state.crlf_conversions) != - data->req.bytecount) && -#endif /* CURL_DO_LINEEND_CONV */ - (data->req.maxdownload != data->req.bytecount)) { - failf(data, "Received only partial file: %" CURL_FORMAT_CURL_OFF_T - " bytes", data->req.bytecount); - result = CURLE_PARTIAL_FILE; - } - else if(!ftpc->dont_check && - !data->req.bytecount && - (data->req.size>0)) { - failf(data, "No data was received!"); - result = CURLE_FTP_COULDNT_RETR_FILE; - } - } - - /* clear these for next connection */ - ftp->transfer = PPTRANSFER_BODY; - ftpc->dont_check = FALSE; - - /* Send any post-transfer QUOTE strings? */ - if(!status && !result && !premature && data->set.postquote) - result = ftp_sendquote(data, conn, data->set.postquote); - Curl_safefree(ftp->pathalloc); - return result; -} - -/*********************************************************************** - * - * ftp_sendquote() - * - * Where a 'quote' means a list of custom commands to send to the server. - * The quote list is passed as an argument. - * - * BLOCKING - */ - -static -CURLcode ftp_sendquote(struct Curl_easy *data, - struct connectdata *conn, struct curl_slist *quote) -{ - struct curl_slist *item; - struct ftp_conn *ftpc = &conn->proto.ftpc; - struct pingpong *pp = &ftpc->pp; - - item = quote; - while(item) { - if(item->data) { - ssize_t nread; - char *cmd = item->data; - bool acceptfail = FALSE; - CURLcode result; - int ftpcode = 0; - - /* if a command starts with an asterisk, which a legal FTP command never - can, the command will be allowed to fail without it causing any - aborts or cancels etc. It will cause libcurl to act as if the command - is successful, whatever the server reponds. */ - - if(cmd[0] == '*') { - cmd++; - acceptfail = TRUE; - } - - result = Curl_pp_sendf(data, &ftpc->pp, "%s", cmd); - if(!result) { - pp->response = Curl_now(); /* timeout relative now */ - result = Curl_GetFTPResponse(data, &nread, &ftpcode); - } - if(result) - return result; - - if(!acceptfail && (ftpcode >= 400)) { - failf(data, "QUOT string not accepted: %s", cmd); - return CURLE_QUOTE_ERROR; - } - } - - item = item->next; - } - - return CURLE_OK; -} - -/*********************************************************************** - * - * ftp_need_type() - * - * Returns TRUE if we in the current situation should send TYPE - */ -static int ftp_need_type(struct connectdata *conn, - bool ascii_wanted) -{ - return conn->proto.ftpc.transfertype != (ascii_wanted?'A':'I'); -} - -/*********************************************************************** - * - * ftp_nb_type() - * - * Set TYPE. We only deal with ASCII or BINARY so this function - * sets one of them. - * If the transfer type is not sent, simulate on OK response in newstate - */ -static CURLcode ftp_nb_type(struct Curl_easy *data, - struct connectdata *conn, - bool ascii, ftpstate newstate) -{ - struct ftp_conn *ftpc = &conn->proto.ftpc; - CURLcode result; - char want = (char)(ascii?'A':'I'); - - if(ftpc->transfertype == want) { - state(data, newstate); - return ftp_state_type_resp(data, 200, newstate); - } - - result = Curl_pp_sendf(data, &ftpc->pp, "TYPE %c", want); - if(!result) { - state(data, newstate); - - /* keep track of our current transfer type */ - ftpc->transfertype = want; - } - return result; -} - -/*************************************************************************** - * - * ftp_pasv_verbose() - * - * This function only outputs some informationals about this second connection - * when we've issued a PASV command before and thus we have connected to a - * possibly new IP address. - * - */ -#ifndef CURL_DISABLE_VERBOSE_STRINGS -static void -ftp_pasv_verbose(struct Curl_easy *data, - struct Curl_addrinfo *ai, - char *newhost, /* ascii version */ - int port) -{ - char buf[256]; - Curl_printable_address(ai, buf, sizeof(buf)); - infof(data, "Connecting to %s (%s) port %d", newhost, buf, port); -} -#endif - -/* - * ftp_do_more() - * - * This function shall be called when the second FTP (data) connection is - * connected. - * - * 'complete' can return 0 for incomplete, 1 for done and -1 for go back - * (which basically is only for when PASV is being sent to retry a failed - * EPSV). - */ - -static CURLcode ftp_do_more(struct Curl_easy *data, int *completep) -{ - struct connectdata *conn = data->conn; - struct ftp_conn *ftpc = &conn->proto.ftpc; - CURLcode result = CURLE_OK; - bool connected = FALSE; - bool complete = FALSE; - - /* the ftp struct is inited in ftp_connect() */ - struct FTP *ftp = data->req.p.ftp; - - /* if the second connection isn't done yet, wait for it */ - if(!conn->bits.tcpconnect[SECONDARYSOCKET]) { - if(Curl_connect_ongoing(conn)) { - /* As we're in TUNNEL_CONNECT state now, we know the proxy name and port - aren't used so we blank their arguments. */ - result = Curl_proxyCONNECT(data, SECONDARYSOCKET, NULL, 0); - - return result; - } - - result = Curl_is_connected(data, conn, SECONDARYSOCKET, &connected); - - /* Ready to do more? */ - if(connected) { - DEBUGF(infof(data, "DO-MORE connected phase starts")); - } - else { - if(result && (ftpc->count1 == 0)) { - *completep = -1; /* go back to DOING please */ - /* this is a EPSV connect failing, try PASV instead */ - return ftp_epsv_disable(data, conn); - } - return result; - } - } - -#ifndef CURL_DISABLE_PROXY - result = Curl_proxy_connect(data, SECONDARYSOCKET); - if(result) - return result; - - if(CONNECT_SECONDARYSOCKET_PROXY_SSL()) - return result; - - if(conn->bits.tunnel_proxy && conn->bits.httpproxy && - Curl_connect_ongoing(conn)) - return result; -#endif - - if(ftpc->state) { - /* already in a state so skip the initial commands. - They are only done to kickstart the do_more state */ - result = ftp_multi_statemach(data, &complete); - - *completep = (int)complete; - - /* if we got an error or if we don't wait for a data connection return - immediately */ - if(result || !ftpc->wait_data_conn) - return result; - - /* if we reach the end of the FTP state machine here, *complete will be - TRUE but so is ftpc->wait_data_conn, which says we need to wait for the - data connection and therefore we're not actually complete */ - *completep = 0; - } - - if(ftp->transfer <= PPTRANSFER_INFO) { - /* a transfer is about to take place, or if not a file name was given - so we'll do a SIZE on it later and then we need the right TYPE first */ - - if(ftpc->wait_data_conn == TRUE) { - bool serv_conned; - - result = ReceivedServerConnect(data, &serv_conned); - if(result) - return result; /* Failed to accept data connection */ - - if(serv_conned) { - /* It looks data connection is established */ - result = AcceptServerConnect(data); - ftpc->wait_data_conn = FALSE; - if(!result) - result = InitiateTransfer(data); - - if(result) - return result; - - *completep = 1; /* this state is now complete when the server has - connected back to us */ - } - } - else if(data->set.upload) { - result = ftp_nb_type(data, conn, data->state.prefer_ascii, - FTP_STOR_TYPE); - if(result) - return result; - - result = ftp_multi_statemach(data, &complete); - if(ftpc->wait_data_conn) - /* if we reach the end of the FTP state machine here, *complete will be - TRUE but so is ftpc->wait_data_conn, which says we need to wait for - the data connection and therefore we're not actually complete */ - *completep = 0; - else - *completep = (int)complete; - } - else { - /* download */ - ftp->downloadsize = -1; /* unknown as of yet */ - - result = Curl_range(data); - - if(result == CURLE_OK && data->req.maxdownload >= 0) { - /* Don't check for successful transfer */ - ftpc->dont_check = TRUE; - } - - if(result) - ; - else if(data->state.list_only || !ftpc->file) { - /* The specified path ends with a slash, and therefore we think this - is a directory that is requested, use LIST. But before that we - need to set ASCII transfer mode. */ - - /* But only if a body transfer was requested. */ - if(ftp->transfer == PPTRANSFER_BODY) { - result = ftp_nb_type(data, conn, TRUE, FTP_LIST_TYPE); - if(result) - return result; - } - /* otherwise just fall through */ - } - else { - result = ftp_nb_type(data, conn, data->state.prefer_ascii, - FTP_RETR_TYPE); - if(result) - return result; - } - - result = ftp_multi_statemach(data, &complete); - *completep = (int)complete; - } - return result; - } - - /* no data to transfer */ - Curl_setup_transfer(data, -1, -1, FALSE, -1); - - if(!ftpc->wait_data_conn) { - /* no waiting for the data connection so this is now complete */ - *completep = 1; - DEBUGF(infof(data, "DO-MORE phase ends with %d", (int)result)); - } - - return result; -} - - - -/*********************************************************************** - * - * ftp_perform() - * - * This is the actual DO function for FTP. Get a file/directory according to - * the options previously setup. - */ - -static -CURLcode ftp_perform(struct Curl_easy *data, - bool *connected, /* connect status after PASV / PORT */ - bool *dophase_done) -{ - /* this is FTP and no proxy */ - CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - - DEBUGF(infof(data, "DO phase starts")); - - if(data->set.opt_no_body) { - /* requested no body means no transfer... */ - struct FTP *ftp = data->req.p.ftp; - ftp->transfer = PPTRANSFER_INFO; - } - - *dophase_done = FALSE; /* not done yet */ - - /* start the first command in the DO phase */ - result = ftp_state_quote(data, TRUE, FTP_QUOTE); - if(result) - return result; - - /* run the state-machine */ - result = ftp_multi_statemach(data, dophase_done); - - *connected = conn->bits.tcpconnect[SECONDARYSOCKET]; - - infof(data, "ftp_perform ends with SECONDARY: %d", *connected); - - if(*dophase_done) - DEBUGF(infof(data, "DO phase is complete1")); - - return result; -} - -static void wc_data_dtor(void *ptr) -{ - struct ftp_wc *ftpwc = ptr; - if(ftpwc && ftpwc->parser) - Curl_ftp_parselist_data_free(&ftpwc->parser); - free(ftpwc); -} - -static CURLcode init_wc_data(struct Curl_easy *data) -{ - char *last_slash; - struct FTP *ftp = data->req.p.ftp; - char *path = ftp->path; - struct WildcardData *wildcard = &(data->wildcard); - CURLcode result = CURLE_OK; - struct ftp_wc *ftpwc = NULL; - - last_slash = strrchr(ftp->path, '/'); - if(last_slash) { - last_slash++; - if(last_slash[0] == '\0') { - wildcard->state = CURLWC_CLEAN; - result = ftp_parse_url_path(data); - return result; - } - wildcard->pattern = strdup(last_slash); - if(!wildcard->pattern) - return CURLE_OUT_OF_MEMORY; - last_slash[0] = '\0'; /* cut file from path */ - } - else { /* there is only 'wildcard pattern' or nothing */ - if(path[0]) { - wildcard->pattern = strdup(path); - if(!wildcard->pattern) - return CURLE_OUT_OF_MEMORY; - path[0] = '\0'; - } - else { /* only list */ - wildcard->state = CURLWC_CLEAN; - result = ftp_parse_url_path(data); - return result; - } - } - - /* program continues only if URL is not ending with slash, allocate needed - resources for wildcard transfer */ - - /* allocate ftp protocol specific wildcard data */ - ftpwc = calloc(1, sizeof(struct ftp_wc)); - if(!ftpwc) { - result = CURLE_OUT_OF_MEMORY; - goto fail; - } - - /* INITIALIZE parselist structure */ - ftpwc->parser = Curl_ftp_parselist_data_alloc(); - if(!ftpwc->parser) { - result = CURLE_OUT_OF_MEMORY; - goto fail; - } - - wildcard->protdata = ftpwc; /* put it to the WildcardData tmp pointer */ - wildcard->dtor = wc_data_dtor; - - /* wildcard does not support NOCWD option (assert it?) */ - if(data->set.ftp_filemethod == FTPFILE_NOCWD) - data->set.ftp_filemethod = FTPFILE_MULTICWD; - - /* try to parse ftp url */ - result = ftp_parse_url_path(data); - if(result) { - goto fail; - } - - wildcard->path = strdup(ftp->path); - if(!wildcard->path) { - result = CURLE_OUT_OF_MEMORY; - goto fail; - } - - /* backup old write_function */ - ftpwc->backup.write_function = data->set.fwrite_func; - /* parsing write function */ - data->set.fwrite_func = Curl_ftp_parselist; - /* backup old file descriptor */ - ftpwc->backup.file_descriptor = data->set.out; - /* let the writefunc callback know the transfer */ - data->set.out = data; - - infof(data, "Wildcard - Parsing started"); - return CURLE_OK; - - fail: - if(ftpwc) { - Curl_ftp_parselist_data_free(&ftpwc->parser); - free(ftpwc); - } - Curl_safefree(wildcard->pattern); - wildcard->dtor = ZERO_NULL; - wildcard->protdata = NULL; - return result; -} - -static CURLcode wc_statemach(struct Curl_easy *data) -{ - struct WildcardData * const wildcard = &(data->wildcard); - struct connectdata *conn = data->conn; - CURLcode result = CURLE_OK; - - for(;;) { - switch(wildcard->state) { - case CURLWC_INIT: - result = init_wc_data(data); - if(wildcard->state == CURLWC_CLEAN) - /* only listing! */ - return result; - wildcard->state = result ? CURLWC_ERROR : CURLWC_MATCHING; - return result; - - case CURLWC_MATCHING: { - /* In this state is LIST response successfully parsed, so lets restore - previous WRITEFUNCTION callback and WRITEDATA pointer */ - struct ftp_wc *ftpwc = wildcard->protdata; - data->set.fwrite_func = ftpwc->backup.write_function; - data->set.out = ftpwc->backup.file_descriptor; - ftpwc->backup.write_function = ZERO_NULL; - ftpwc->backup.file_descriptor = NULL; - wildcard->state = CURLWC_DOWNLOADING; - - if(Curl_ftp_parselist_geterror(ftpwc->parser)) { - /* error found in LIST parsing */ - wildcard->state = CURLWC_CLEAN; - continue; - } - if(wildcard->filelist.size == 0) { - /* no corresponding file */ - wildcard->state = CURLWC_CLEAN; - return CURLE_REMOTE_FILE_NOT_FOUND; - } - continue; - } - - case CURLWC_DOWNLOADING: { - /* filelist has at least one file, lets get first one */ - struct ftp_conn *ftpc = &conn->proto.ftpc; - struct curl_fileinfo *finfo = wildcard->filelist.head->ptr; - struct FTP *ftp = data->req.p.ftp; - - char *tmp_path = aprintf("%s%s", wildcard->path, finfo->filename); - if(!tmp_path) - return CURLE_OUT_OF_MEMORY; - - /* switch default ftp->path and tmp_path */ - free(ftp->pathalloc); - ftp->pathalloc = ftp->path = tmp_path; - - infof(data, "Wildcard - START of \"%s\"", finfo->filename); - if(data->set.chunk_bgn) { - long userresponse; - Curl_set_in_callback(data, true); - userresponse = data->set.chunk_bgn( - finfo, wildcard->customptr, (int)wildcard->filelist.size); - Curl_set_in_callback(data, false); - switch(userresponse) { - case CURL_CHUNK_BGN_FUNC_SKIP: - infof(data, "Wildcard - \"%s\" skipped by user", - finfo->filename); - wildcard->state = CURLWC_SKIP; - continue; - case CURL_CHUNK_BGN_FUNC_FAIL: - return CURLE_CHUNK_FAILED; - } - } - - if(finfo->filetype != CURLFILETYPE_FILE) { - wildcard->state = CURLWC_SKIP; - continue; - } - - if(finfo->flags & CURLFINFOFLAG_KNOWN_SIZE) - ftpc->known_filesize = finfo->size; - - result = ftp_parse_url_path(data); - if(result) - return result; - - /* we don't need the Curl_fileinfo of first file anymore */ - Curl_llist_remove(&wildcard->filelist, wildcard->filelist.head, NULL); - - if(wildcard->filelist.size == 0) { /* remains only one file to down. */ - wildcard->state = CURLWC_CLEAN; - /* after that will be ftp_do called once again and no transfer - will be done because of CURLWC_CLEAN state */ - return CURLE_OK; - } - return result; - } - - case CURLWC_SKIP: { - if(data->set.chunk_end) { - Curl_set_in_callback(data, true); - data->set.chunk_end(data->wildcard.customptr); - Curl_set_in_callback(data, false); - } - Curl_llist_remove(&wildcard->filelist, wildcard->filelist.head, NULL); - wildcard->state = (wildcard->filelist.size == 0) ? - CURLWC_CLEAN : CURLWC_DOWNLOADING; - continue; - } - - case CURLWC_CLEAN: { - struct ftp_wc *ftpwc = wildcard->protdata; - result = CURLE_OK; - if(ftpwc) - result = Curl_ftp_parselist_geterror(ftpwc->parser); - - wildcard->state = result ? CURLWC_ERROR : CURLWC_DONE; - return result; - } - - case CURLWC_DONE: - case CURLWC_ERROR: - case CURLWC_CLEAR: - if(wildcard->dtor) - wildcard->dtor(wildcard->protdata); - return result; - } - } - /* UNREACHABLE */ -} - -/*********************************************************************** - * - * ftp_do() - * - * This function is registered as 'curl_do' function. It decodes the path - * parts etc as a wrapper to the actual DO function (ftp_perform). - * - * The input argument is already checked for validity. - */ -static CURLcode ftp_do(struct Curl_easy *data, bool *done) -{ - CURLcode result = CURLE_OK; - struct connectdata *conn = data->conn; - struct ftp_conn *ftpc = &conn->proto.ftpc; - - *done = FALSE; /* default to false */ - ftpc->wait_data_conn = FALSE; /* default to no such wait */ - - if(data->state.wildcardmatch) { - result = wc_statemach(data); - if(data->wildcard.state == CURLWC_SKIP || - data->wildcard.state == CURLWC_DONE) { - /* do not call ftp_regular_transfer */ - return CURLE_OK; - } - if(result) /* error, loop or skipping the file */ - return result; - } - else { /* no wildcard FSM needed */ - result = ftp_parse_url_path(data); - if(result) - return result; - } - - result = ftp_regular_transfer(data, done); - - return result; -} - -/*********************************************************************** - * - * ftp_quit() - * - * This should be called before calling sclose() on an ftp control connection - * (not data connections). We should then wait for the response from the - * server before returning. The calling code should then try to close the - * connection. - * - */ -static CURLcode ftp_quit(struct Curl_easy *data, struct connectdata *conn) -{ - CURLcode result = CURLE_OK; - - if(conn->proto.ftpc.ctl_valid) { - result = Curl_pp_sendf(data, &conn->proto.ftpc.pp, "%s", "QUIT"); - if(result) { - failf(data, "Failure sending QUIT command: %s", - curl_easy_strerror(result)); - conn->proto.ftpc.ctl_valid = FALSE; /* mark control connection as bad */ - connclose(conn, "QUIT command failed"); /* mark for connection closure */ - state(data, FTP_STOP); - return result; - } - - state(data, FTP_QUIT); - - result = ftp_block_statemach(data, conn); - } - - return result; -} - -/*********************************************************************** - * - * ftp_disconnect() - * - * Disconnect from an FTP server. Cleanup protocol-specific per-connection - * resources. BLOCKING. - */ -static CURLcode ftp_disconnect(struct Curl_easy *data, - struct connectdata *conn, - bool dead_connection) -{ - struct ftp_conn *ftpc = &conn->proto.ftpc; - struct pingpong *pp = &ftpc->pp; - - /* We cannot send quit unconditionally. If this connection is stale or - bad in any way, sending quit and waiting around here will make the - disconnect wait in vain and cause more problems than we need to. - - ftp_quit() will check the state of ftp->ctl_valid. If it's ok it - will try to send the QUIT command, otherwise it will just return. - */ - if(dead_connection) - ftpc->ctl_valid = FALSE; - - /* The FTP session may or may not have been allocated/setup at this point! */ - (void)ftp_quit(data, conn); /* ignore errors on the QUIT */ - - if(ftpc->entrypath) { - if(data->state.most_recent_ftp_entrypath == ftpc->entrypath) { - data->state.most_recent_ftp_entrypath = NULL; - } - Curl_safefree(ftpc->entrypath); - } - - freedirs(ftpc); - Curl_safefree(ftpc->prevpath); - Curl_safefree(ftpc->server_os); - Curl_pp_disconnect(pp); - Curl_sec_end(conn); - return CURLE_OK; -} - -/*********************************************************************** - * - * ftp_parse_url_path() - * - * Parse the URL path into separate path components. - * - */ -static -CURLcode ftp_parse_url_path(struct Curl_easy *data) -{ - /* the ftp struct is already inited in ftp_connect() */ - struct FTP *ftp = data->req.p.ftp; - struct connectdata *conn = data->conn; - struct ftp_conn *ftpc = &conn->proto.ftpc; - const char *slashPos = NULL; - const char *fileName = NULL; - CURLcode result = CURLE_OK; - char *rawPath = NULL; /* url-decoded "raw" path */ - size_t pathLen = 0; - - ftpc->ctl_valid = FALSE; - ftpc->cwdfail = FALSE; - - /* url-decode ftp path before further evaluation */ - result = Curl_urldecode(data, ftp->path, 0, &rawPath, &pathLen, REJECT_CTRL); - if(result) - return result; - - switch(data->set.ftp_filemethod) { - case FTPFILE_NOCWD: /* fastest, but less standard-compliant */ - - if((pathLen > 0) && (rawPath[pathLen - 1] != '/')) - fileName = rawPath; /* this is a full file path */ - /* - else: ftpc->file is not used anywhere other than for operations on - a file. In other words, never for directory operations. - So we can safely leave filename as NULL here and use it as a - argument in dir/file decisions. - */ - break; - - case FTPFILE_SINGLECWD: - slashPos = strrchr(rawPath, '/'); - if(slashPos) { - /* get path before last slash, except for / */ - size_t dirlen = slashPos - rawPath; - if(dirlen == 0) - dirlen++; - - ftpc->dirs = calloc(1, sizeof(ftpc->dirs[0])); - if(!ftpc->dirs) { - free(rawPath); - return CURLE_OUT_OF_MEMORY; - } - - ftpc->dirs[0] = calloc(1, dirlen + 1); - if(!ftpc->dirs[0]) { - free(rawPath); - return CURLE_OUT_OF_MEMORY; - } - - strncpy(ftpc->dirs[0], rawPath, dirlen); - ftpc->dirdepth = 1; /* we consider it to be a single dir */ - fileName = slashPos + 1; /* rest is file name */ - } - else - fileName = rawPath; /* file name only (or empty) */ - break; - - default: /* allow pretty much anything */ - case FTPFILE_MULTICWD: { - /* current position: begin of next path component */ - const char *curPos = rawPath; - - int dirAlloc = 0; /* number of entries allocated for the 'dirs' array */ - const char *str = rawPath; - for(; *str != 0; ++str) - if (*str == '/') - ++dirAlloc; - - if(dirAlloc > 0) { - ftpc->dirs = calloc(dirAlloc, sizeof(ftpc->dirs[0])); - if(!ftpc->dirs) { - free(rawPath); - return CURLE_OUT_OF_MEMORY; - } - - /* parse the URL path into separate path components */ - while((slashPos = strchr(curPos, '/')) != NULL) { - size_t compLen = slashPos - curPos; - - /* path starts with a slash: add that as a directory */ - if((compLen == 0) && (ftpc->dirdepth == 0)) - ++compLen; - - /* we skip empty path components, like "x//y" since the FTP command - CWD requires a parameter and a non-existent parameter a) doesn't - work on many servers and b) has no effect on the others. */ - if(compLen > 0) { - char *comp = calloc(1, compLen + 1); - if(!comp) { - free(rawPath); - return CURLE_OUT_OF_MEMORY; - } - strncpy(comp, curPos, compLen); - ftpc->dirs[ftpc->dirdepth++] = comp; - } - curPos = slashPos + 1; - } - } - DEBUGASSERT(ftpc->dirdepth <= dirAlloc); - fileName = curPos; /* the rest is the file name (or empty) */ - } - break; - } /* switch */ - - if(fileName && *fileName) - ftpc->file = strdup(fileName); - else - ftpc->file = NULL; /* instead of point to a zero byte, - we make it a NULL pointer */ - - if(data->set.upload && !ftpc->file && (ftp->transfer == PPTRANSFER_BODY)) { - /* We need a file name when uploading. Return error! */ - failf(data, "Uploading to a URL without a file name!"); - free(rawPath); - return CURLE_URL_MALFORMAT; - } - - ftpc->cwddone = FALSE; /* default to not done */ - - if((data->set.ftp_filemethod == FTPFILE_NOCWD) && (rawPath[0] == '/')) - ftpc->cwddone = TRUE; /* skip CWD for absolute paths */ - else { /* newly created FTP connections are already in entry path */ - const char *oldPath = conn->bits.reuse ? ftpc->prevpath : ""; - if(oldPath) { - size_t n = pathLen; - if(data->set.ftp_filemethod == FTPFILE_NOCWD) - n = 0; /* CWD to entry for relative paths */ - else - n -= ftpc->file?strlen(ftpc->file):0; - - if((strlen(oldPath) == n) && !strncmp(rawPath, oldPath, n)) { - infof(data, "Request has same path as previous transfer"); - ftpc->cwddone = TRUE; - } - } - } - - free(rawPath); - return CURLE_OK; -} - -/* call this when the DO phase has completed */ -static CURLcode ftp_dophase_done(struct Curl_easy *data, bool connected) -{ - struct connectdata *conn = data->conn; - struct FTP *ftp = data->req.p.ftp; - struct ftp_conn *ftpc = &conn->proto.ftpc; - - if(connected) { - int completed; - CURLcode result = ftp_do_more(data, &completed); - - if(result) { - close_secondarysocket(data, conn); - return result; - } - } - - if(ftp->transfer != PPTRANSFER_BODY) - /* no data to transfer */ - Curl_setup_transfer(data, -1, -1, FALSE, -1); - else if(!connected) - /* since we didn't connect now, we want do_more to get called */ - conn->bits.do_more = TRUE; - - ftpc->ctl_valid = TRUE; /* seems good */ - - return CURLE_OK; -} - -/* called from multi.c while DOing */ -static CURLcode ftp_doing(struct Curl_easy *data, - bool *dophase_done) -{ - CURLcode result = ftp_multi_statemach(data, dophase_done); - - if(result) - DEBUGF(infof(data, "DO phase failed")); - else if(*dophase_done) { - result = ftp_dophase_done(data, FALSE /* not connected */); - - DEBUGF(infof(data, "DO phase is complete2")); - } - return result; -} - -/*********************************************************************** - * - * ftp_regular_transfer() - * - * The input argument is already checked for validity. - * - * Performs all commands done before a regular transfer between a local and a - * remote host. - * - * ftp->ctl_valid starts out as FALSE, and gets set to TRUE if we reach the - * ftp_done() function without finding any major problem. - */ -static -CURLcode ftp_regular_transfer(struct Curl_easy *data, - bool *dophase_done) -{ - CURLcode result = CURLE_OK; - bool connected = FALSE; - struct connectdata *conn = data->conn; - struct ftp_conn *ftpc = &conn->proto.ftpc; - data->req.size = -1; /* make sure this is unknown at this point */ - - Curl_pgrsSetUploadCounter(data, 0); - Curl_pgrsSetDownloadCounter(data, 0); - Curl_pgrsSetUploadSize(data, -1); - Curl_pgrsSetDownloadSize(data, -1); - - ftpc->ctl_valid = TRUE; /* starts good */ - - result = ftp_perform(data, - &connected, /* have we connected after PASV/PORT */ - dophase_done); /* all commands in the DO-phase done? */ - - if(!result) { - - if(!*dophase_done) - /* the DO phase has not completed yet */ - return CURLE_OK; - - result = ftp_dophase_done(data, connected); - - if(result) - return result; - } - else - freedirs(ftpc); - - return result; -} - -static CURLcode ftp_setup_connection(struct Curl_easy *data, - struct connectdata *conn) -{ - char *type; - struct FTP *ftp; - - data->req.p.ftp = ftp = calloc(sizeof(struct FTP), 1); - if(NULL == ftp) - return CURLE_OUT_OF_MEMORY; - - ftp->path = &data->state.up.path[1]; /* don't include the initial slash */ - - /* FTP URLs support an extension like ";type=" that - * we'll try to get now! */ - type = strstr(ftp->path, ";type="); - - if(!type) - type = strstr(conn->host.rawalloc, ";type="); - - if(type) { - char command; - *type = 0; /* it was in the middle of the hostname */ - command = Curl_raw_toupper(type[6]); - - switch(command) { - case 'A': /* ASCII mode */ - data->state.prefer_ascii = TRUE; - break; - - case 'D': /* directory mode */ - data->state.list_only = TRUE; - break; - - case 'I': /* binary mode */ - default: - /* switch off ASCII */ - data->state.prefer_ascii = FALSE; - break; - } - } - - /* get some initial data into the ftp struct */ - ftp->transfer = PPTRANSFER_BODY; - ftp->downloadsize = 0; - conn->proto.ftpc.known_filesize = -1; /* unknown size for now */ - - return CURLE_OK; -} - -#endif /* CURL_DISABLE_FTP */ diff --git a/lib/http.c b/lib/http.c deleted file mode 100644 index 648583c..0000000 --- a/lib/http.c +++ /dev/null @@ -1,4382 +0,0 @@ -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2021, 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.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" - -#ifndef CURL_DISABLE_HTTP - -#ifdef HAVE_NETINET_IN_H -#include -#endif - -#ifdef HAVE_NETDB_H -#include -#endif -#ifdef HAVE_ARPA_INET_H -#include -#endif -#ifdef HAVE_NET_IF_H -#include -#endif -#ifdef HAVE_SYS_IOCTL_H -#include -#endif - -#ifdef HAVE_SYS_PARAM_H -#include -#endif - -#ifdef USE_HYPER -#include -#endif - -#include "urldata.h" -#include -#include "transfer.h" -#include "sendf.h" -#include "formdata.h" -#include "mime.h" -#include "progress.h" -#include "curl_base64.h" -#include "cookie.h" -#include "vauth/vauth.h" -#include "vtls/vtls.h" -#include "http_digest.h" -#include "http_ntlm.h" -#include "curl_ntlm_wb.h" -#include "http_negotiate.h" -#include "http_aws_sigv4.h" -#include "url.h" -#include "share.h" -#include "hostip.h" -#include "http.h" -#include "select.h" -#include "parsedate.h" /* for the week day and month names */ -#include "strtoofft.h" -#include "multiif.h" -#include "strcase.h" -#include "content_encoding.h" -#include "http_proxy.h" -#include "warnless.h" -#include "non-ascii.h" -#include "http2.h" -#include "connect.h" -#include "strdup.h" -#include "altsvc.h" -#include "hsts.h" -#include "c-hyper.h" - -/* The last 3 #include files should be in this order */ -#include "curl_printf.h" -#include "curl_memory.h" -#include "memdebug.h" - -/* - * Forward declarations. - */ - -static int http_getsock_do(struct Curl_easy *data, - struct connectdata *conn, - curl_socket_t *socks); -static bool http_should_fail(struct Curl_easy *data); - -#ifndef CURL_DISABLE_PROXY -static CURLcode add_haproxy_protocol_header(struct Curl_easy *data); -#endif - -#ifdef USE_SSL -static CURLcode https_connecting(struct Curl_easy *data, bool *done); -static int https_getsock(struct Curl_easy *data, - struct connectdata *conn, - curl_socket_t *socks); -#else -#define https_connecting(x,y) CURLE_COULDNT_CONNECT -#endif -static CURLcode http_setup_conn(struct Curl_easy *data, - struct connectdata *conn); - -/* - * HTTP handler interface. - */ -const struct Curl_handler Curl_handler_http = { - "HTTP", /* scheme */ - http_setup_conn, /* setup_connection */ - Curl_http, /* do_it */ - Curl_http_done, /* done */ - ZERO_NULL, /* do_more */ - Curl_http_connect, /* connect_it */ - ZERO_NULL, /* connecting */ - ZERO_NULL, /* doing */ - ZERO_NULL, /* proto_getsock */ - http_getsock_do, /* doing_getsock */ - ZERO_NULL, /* domore_getsock */ - ZERO_NULL, /* perform_getsock */ - ZERO_NULL, /* disconnect */ - ZERO_NULL, /* readwrite */ - ZERO_NULL, /* connection_check */ - ZERO_NULL, /* attach connection */ - PORT_HTTP, /* defport */ - CURLPROTO_HTTP, /* protocol */ - CURLPROTO_HTTP, /* family */ - PROTOPT_CREDSPERREQUEST | /* flags */ - PROTOPT_USERPWDCTRL -}; - -#ifdef USE_SSL -/* - * HTTPS handler interface. - */ -const struct Curl_handler Curl_handler_https = { - "HTTPS", /* scheme */ - http_setup_conn, /* setup_connection */ - Curl_http, /* do_it */ - Curl_http_done, /* done */ - ZERO_NULL, /* do_more */ - Curl_http_connect, /* connect_it */ - https_connecting, /* connecting */ - ZERO_NULL, /* doing */ - https_getsock, /* proto_getsock */ - http_getsock_do, /* doing_getsock */ - ZERO_NULL, /* domore_getsock */ - ZERO_NULL, /* perform_getsock */ - ZERO_NULL, /* disconnect */ - ZERO_NULL, /* readwrite */ - ZERO_NULL, /* connection_check */ - ZERO_NULL, /* attach connection */ - PORT_HTTPS, /* defport */ - CURLPROTO_HTTPS, /* protocol */ - CURLPROTO_HTTP, /* family */ - PROTOPT_SSL | PROTOPT_CREDSPERREQUEST | PROTOPT_ALPN_NPN | /* flags */ - PROTOPT_USERPWDCTRL -}; -#endif - -static CURLcode http_setup_conn(struct Curl_easy *data, - struct connectdata *conn) -{ - /* allocate the HTTP-specific struct for the Curl_easy, only to survive - during this request */ - struct HTTP *http; - DEBUGASSERT(data->req.p.http == NULL); - - http = calloc(1, sizeof(struct HTTP)); - if(!http) - return CURLE_OUT_OF_MEMORY; - - Curl_mime_initpart(&http->form, data); - data->req.p.http = http; - - if(data->state.httpwant == CURL_HTTP_VERSION_3) { - if(conn->handler->flags & PROTOPT_SSL) - /* Only go HTTP/3 directly on HTTPS URLs. It needs a UDP socket and does - the QUIC dance. */ - conn->transport = TRNSPRT_QUIC; - else { - failf(data, "HTTP/3 requested for non-HTTPS URL"); - return CURLE_URL_MALFORMAT; - } - } - else { - if(!CONN_INUSE(conn)) - /* if not already multi-using, setup connection details */ - Curl_http2_setup_conn(conn); - Curl_http2_setup_req(data); - } - return CURLE_OK; -} - -#ifndef CURL_DISABLE_PROXY -/* - * checkProxyHeaders() checks the linked list of custom proxy headers - * if proxy headers are not available, then it will lookup into http header - * link list - * - * It takes a connectdata struct as input to see if this is a proxy request or - * not, as it then might check a different header list. Provide the header - * prefix without colon! - */ -char *Curl_checkProxyheaders(struct Curl_easy *data, - const struct connectdata *conn, - const char *thisheader) -{ - struct curl_slist *head; - size_t thislen = strlen(thisheader); - - for(head = (conn->bits.proxy && data->set.sep_headers) ? - data->set.proxyheaders : data->set.headers; - head; head = head->next) { - if(strncasecompare(head->data, thisheader, thislen) && - Curl_headersep(head->data[thislen])) - return head->data; - } - - return NULL; -} -#else -/* disabled */ -#define Curl_checkProxyheaders(x,y,z) NULL -#endif - -/* - * Strip off leading and trailing whitespace from the value in the - * given HTTP header line and return a strdupped copy. Returns NULL in - * case of allocation failure. Returns an empty string if the header value - * consists entirely of whitespace. - */ -char *Curl_copy_header_value(const char *header) -{ - const char *start; - const char *end; - char *value; - size_t len; - - /* Find the end of the header name */ - while(*header && (*header != ':')) - ++header; - - if(*header) - /* Skip over colon */ - ++header; - - /* Find the first non-space letter */ - start = header; - while(*start && ISSPACE(*start)) - start++; - - /* data is in the host encoding so - use '\r' and '\n' instead of 0x0d and 0x0a */ - end = strchr(start, '\r'); - if(!end) - end = strchr(start, '\n'); - if(!end) - end = strchr(start, '\0'); - if(!end) - return NULL; - - /* skip all trailing space letters */ - while((end > start) && ISSPACE(*end)) - end--; - - /* get length of the type */ - len = end - start + 1; - - value = malloc(len + 1); - if(!value) - return NULL; - - memcpy(value, start, len); - value[len] = 0; /* null-terminate */ - - return value; -} - -#ifndef CURL_DISABLE_HTTP_AUTH -/* - * http_output_basic() sets up an Authorization: header (or the proxy version) - * for HTTP Basic authentication. - * - * Returns CURLcode. - */ -static CURLcode http_output_basic(struct Curl_easy *data, bool proxy) -{ - size_t size = 0; - char *authorization = NULL; - char **userp; - const char *user; - const char *pwd; - CURLcode result; - char *out; - - /* credentials are unique per transfer for HTTP, do not use the ones for the - connection */ - if(proxy) { -#ifndef CURL_DISABLE_PROXY - userp = &data->state.aptr.proxyuserpwd; - user = data->state.aptr.proxyuser; - pwd = data->state.aptr.proxypasswd; -#else - return CURLE_NOT_BUILT_IN; -#endif - } - else { - userp = &data->state.aptr.userpwd; - user = data->state.aptr.user; - pwd = data->state.aptr.passwd; - } - - out = aprintf("%s:%s", user, pwd ? pwd : ""); - if(!out) - return CURLE_OUT_OF_MEMORY; - - result = Curl_base64_encode(data, out, strlen(out), &authorization, &size); - if(result) - goto fail; - - if(!authorization) { - result = CURLE_REMOTE_ACCESS_DENIED; - goto fail; - } - - free(*userp); - *userp = aprintf("%sAuthorization: Basic %s\r\n", - proxy ? "Proxy-" : "", - authorization); - free(authorization); - if(!*userp) { - result = CURLE_OUT_OF_MEMORY; - goto fail; - } - - fail: - free(out); - return result; -} - -/* - * http_output_bearer() sets up an Authorization: header - * for HTTP Bearer authentication. - * - * Returns CURLcode. - */ -static CURLcode http_output_bearer(struct Curl_easy *data) -{ - char **userp; - CURLcode result = CURLE_OK; - - userp = &data->state.aptr.userpwd; - free(*userp); - *userp = aprintf("Authorization: Bearer %s\r\n", - data->set.str[STRING_BEARER]); - - if(!*userp) { - result = CURLE_OUT_OF_MEMORY; - goto fail; - } - - fail: - return result; -} - -#endif - -/* pickoneauth() selects the most favourable authentication method from the - * ones available and the ones we want. - * - * return TRUE if one was picked - */ -static bool pickoneauth(struct auth *pick, unsigned long mask) -{ - bool picked; - /* only deal with authentication we want */ - unsigned long avail = pick->avail & pick->want & mask; - picked = TRUE; - - /* The order of these checks is highly relevant, as this will be the order - of preference in case of the existence of multiple accepted types. */ - if(avail & CURLAUTH_NEGOTIATE) - pick->picked = CURLAUTH_NEGOTIATE; - else if(avail & CURLAUTH_BEARER) - pick->picked = CURLAUTH_BEARER; - else if(avail & CURLAUTH_DIGEST) - pick->picked = CURLAUTH_DIGEST; - else if(avail & CURLAUTH_NTLM) - pick->picked = CURLAUTH_NTLM; - else if(avail & CURLAUTH_NTLM_WB) - pick->picked = CURLAUTH_NTLM_WB; - else if(avail & CURLAUTH_BASIC) - pick->picked = CURLAUTH_BASIC; - else if(avail & CURLAUTH_AWS_SIGV4) - pick->picked = CURLAUTH_AWS_SIGV4; - else { - pick->picked = CURLAUTH_PICKNONE; /* we select to use nothing */ - picked = FALSE; - } - pick->avail = CURLAUTH_NONE; /* clear it here */ - - return picked; -} - -/* - * http_perhapsrewind() - * - * If we are doing POST or PUT { - * If we have more data to send { - * If we are doing NTLM { - * Keep sending since we must not disconnect - * } - * else { - * If there is more than just a little data left to send, close - * the current connection by force. - * } - * } - * If we have sent any data { - * If we don't have track of all the data { - * call app to tell it to rewind - * } - * else { - * rewind internally so that the operation can restart fine - * } - * } - * } - */ -static CURLcode http_perhapsrewind(struct Curl_easy *data, - struct connectdata *conn) -{ - struct HTTP *http = data->req.p.http; - curl_off_t bytessent; - curl_off_t expectsend = -1; /* default is unknown */ - - if(!http) - /* If this is still NULL, we have not reach very far and we can safely - skip this rewinding stuff */ - return CURLE_OK; - - switch(data->state.httpreq) { - case HTTPREQ_GET: - case HTTPREQ_HEAD: - return CURLE_OK; - default: - break; - } - - bytessent = data->req.writebytecount; - - if(conn->bits.authneg) { - /* This is a state where we are known to be negotiating and we don't send - any data then. */ - expectsend = 0; - } - else if(!conn->bits.protoconnstart) { - /* HTTP CONNECT in progress: there is no body */ - expectsend = 0; - } - else { - /* figure out how much data we are expected to send */ - switch(data->state.httpreq) { - case HTTPREQ_POST: - case HTTPREQ_PUT: - if(data->state.infilesize != -1) - expectsend = data->state.infilesize; - break; - case HTTPREQ_POST_FORM: - case HTTPREQ_POST_MIME: - expectsend = http->postsize; - break; - default: - break; - } - } - - conn->bits.rewindaftersend = FALSE; /* default */ - - if((expectsend == -1) || (expectsend > bytessent)) { -#if defined(USE_NTLM) - /* There is still data left to send */ - if((data->state.authproxy.picked == CURLAUTH_NTLM) || - (data->state.authhost.picked == CURLAUTH_NTLM) || - (data->state.authproxy.picked == CURLAUTH_NTLM_WB) || - (data->state.authhost.picked == CURLAUTH_NTLM_WB)) { - if(((expectsend - bytessent) < 2000) || - (conn->http_ntlm_state != NTLMSTATE_NONE) || - (conn->proxy_ntlm_state != NTLMSTATE_NONE)) { - /* The NTLM-negotiation has started *OR* there is just a little (<2K) - data left to send, keep on sending. */ - - /* rewind data when completely done sending! */ - if(!conn->bits.authneg && (conn->writesockfd != CURL_SOCKET_BAD)) { - conn->bits.rewindaftersend = TRUE; - infof(data, "Rewind stream after send"); - } - - return CURLE_OK; - } - - if(conn->bits.close) - /* this is already marked to get closed */ - return CURLE_OK; - - infof(data, "NTLM send, close instead of sending %" - CURL_FORMAT_CURL_OFF_T " bytes", - (curl_off_t)(expectsend - bytessent)); - } -#endif -#if defined(USE_SPNEGO) - /* There is still data left to send */ - if((data->state.authproxy.picked == CURLAUTH_NEGOTIATE) || - (data->state.authhost.picked == CURLAUTH_NEGOTIATE)) { - if(((expectsend - bytessent) < 2000) || - (conn->http_negotiate_state != GSS_AUTHNONE) || - (conn->proxy_negotiate_state != GSS_AUTHNONE)) { - /* The NEGOTIATE-negotiation has started *OR* - there is just a little (<2K) data left to send, keep on sending. */ - - /* rewind data when completely done sending! */ - if(!conn->bits.authneg && (conn->writesockfd != CURL_SOCKET_BAD)) { - conn->bits.rewindaftersend = TRUE; - infof(data, "Rewind stream after send"); - } - - return CURLE_OK; - } - - if(conn->bits.close) - /* this is already marked to get closed */ - return CURLE_OK; - - infof(data, "NEGOTIATE send, close instead of sending %" - CURL_FORMAT_CURL_OFF_T " bytes", - (curl_off_t)(expectsend - bytessent)); - } -#endif - - /* This is not NEGOTIATE/NTLM or many bytes left to send: close */ - streamclose(conn, "Mid-auth HTTP and much data left to send"); - data->req.size = 0; /* don't download any more than 0 bytes */ - - /* There still is data left to send, but this connection is marked for - closure so we can safely do the rewind right now */ - } - - if(bytessent) - /* we rewind now at once since if we already sent something */ - return Curl_readrewind(data); - - return CURLE_OK; -} - -/* - * Curl_http_auth_act() gets called when all HTTP headers have been received - * and it checks what authentication methods that are available and decides - * which one (if any) to use. It will set 'newurl' if an auth method was - * picked. - */ - -CURLcode Curl_http_auth_act(struct Curl_easy *data) -{ - struct connectdata *conn = data->conn; - bool pickhost = FALSE; - bool pickproxy = FALSE; - CURLcode result = CURLE_OK; - unsigned long authmask = ~0ul; - - if(!data->set.str[STRING_BEARER]) - authmask &= (unsigned long)~CURLAUTH_BEARER; - - if(100 <= data->req.httpcode && 199 >= data->req.httpcode) - /* this is a transient response code, ignore */ - return CURLE_OK; - - if(data->state.authproblem) - return data->set.http_fail_on_error?CURLE_HTTP_RETURNED_ERROR:CURLE_OK; - - if((conn->bits.user_passwd || data->set.str[STRING_BEARER]) && - ((data->req.httpcode == 401) || - (conn->bits.authneg && data->req.httpcode < 300))) { - pickhost = pickoneauth(&data->state.authhost, authmask); - if(!pickhost) - data->state.authproblem = TRUE; - if(data->state.authhost.picked == CURLAUTH_NTLM && - conn->httpversion > 11) { - infof(data, "Forcing HTTP/1.1 for NTLM"); - connclose(conn, "Force HTTP/1.1 connection"); - data->state.httpwant = CURL_HTTP_VERSION_1_1; - } - } -#ifndef CURL_DISABLE_PROXY - if(conn->bits.proxy_user_passwd && - ((data->req.httpcode == 407) || - (conn->bits.authneg && data->req.httpcode < 300))) { - pickproxy = pickoneauth(&data->state.authproxy, - authmask & ~CURLAUTH_BEARER); - if(!pickproxy) - data->state.authproblem = TRUE; - } -#endif - - if(pickhost || pickproxy) { - if((data->state.httpreq != HTTPREQ_GET) && - (data->state.httpreq != HTTPREQ_HEAD) && - !conn->bits.rewindaftersend) { - result = http_perhapsrewind(data, conn); - if(result) - return result; - } - /* In case this is GSS auth, the newurl field is already allocated so - we must make sure to free it before allocating a new one. As figured - out in bug #2284386 */ - Curl_safefree(data->req.newurl); - data->req.newurl = strdup(data->state.url); /* clone URL */ - if(!data->req.newurl) - return CURLE_OUT_OF_MEMORY; - } - else if((data->req.httpcode < 300) && - (!data->state.authhost.done) && - conn->bits.authneg) { - /* no (known) authentication available, - authentication is not "done" yet and - no authentication seems to be required and - we didn't try HEAD or GET */ - if((data->state.httpreq != HTTPREQ_GET) && - (data->state.httpreq != HTTPREQ_HEAD)) { - data->req.newurl = strdup(data->state.url); /* clone URL */ - if(!data->req.newurl) - return CURLE_OUT_OF_MEMORY; - data->state.authhost.done = TRUE; - } - } - if(http_should_fail(data)) { - failf(data, "The requested URL returned error: %d", - data->req.httpcode); - result = CURLE_HTTP_RETURNED_ERROR; - } - - return result; -} - -#ifndef CURL_DISABLE_HTTP_AUTH -/* - * Output the correct authentication header depending on the auth type - * and whether or not it is to a proxy. - */ -static CURLcode -output_auth_headers(struct Curl_easy *data, - struct connectdata *conn, - struct auth *authstatus, - const char *request, - const char *path, - bool proxy) -{ - const char *auth = NULL; - CURLcode result = CURLE_OK; - -#ifdef CURL_DISABLE_CRYPTO_AUTH - (void)request; - (void)path; -#endif -#ifndef CURL_DISABLE_CRYPTO_AUTH - if(authstatus->picked == CURLAUTH_AWS_SIGV4) { - auth = "AWS_SIGV4"; - result = Curl_output_aws_sigv4(data, proxy); - if(result) - return result; - } - else -#endif -#ifdef USE_SPNEGO - if(authstatus->picked == CURLAUTH_NEGOTIATE) { - auth = "Negotiate"; - result = Curl_output_negotiate(data, conn, proxy); - if(result) - return result; - } - else -#endif -#ifdef USE_NTLM - if(authstatus->picked == CURLAUTH_NTLM) { - auth = "NTLM"; - result = Curl_output_ntlm(data, proxy); - if(result) - return result; - } - else -#endif -#if defined(USE_NTLM) && defined(NTLM_WB_ENABLED) - if(authstatus->picked == CURLAUTH_NTLM_WB) { - auth = "NTLM_WB"; - result = Curl_output_ntlm_wb(data, conn, proxy); - if(result) - return result; - } - else -#endif -#ifndef CURL_DISABLE_CRYPTO_AUTH - if(authstatus->picked == CURLAUTH_DIGEST) { - auth = "Digest"; - result = Curl_output_digest(data, - proxy, - (const unsigned char *)request, - (const unsigned char *)path); - if(result) - return result; - } - else -#endif - if(authstatus->picked == CURLAUTH_BASIC) { - /* Basic */ - if( -#ifndef CURL_DISABLE_PROXY - (proxy && conn->bits.proxy_user_passwd && - !Curl_checkProxyheaders(data, conn, "Proxy-authorization")) || -#endif - (!proxy && conn->bits.user_passwd && - !Curl_checkheaders(data, "Authorization"))) { - auth = "Basic"; - result = http_output_basic(data, proxy); - if(result) - return result; - } - - /* NOTE: this function should set 'done' TRUE, as the other auth - functions work that way */ - authstatus->done = TRUE; - } - if(authstatus->picked == CURLAUTH_BEARER) { - /* Bearer */ - if((!proxy && data->set.str[STRING_BEARER] && - !Curl_checkheaders(data, "Authorization"))) { - auth = "Bearer"; - result = http_output_bearer(data); - if(result) - return result; - } - - /* NOTE: this function should set 'done' TRUE, as the other auth - functions work that way */ - authstatus->done = TRUE; - } - - if(auth) { -#ifndef CURL_DISABLE_PROXY - infof(data, "%s auth using %s with user '%s'", - proxy ? "Proxy" : "Server", auth, - proxy ? (data->state.aptr.proxyuser ? - data->state.aptr.proxyuser : "") : - (data->state.aptr.user ? - data->state.aptr.user : "")); -#else - infof(data, "Server auth using %s with user '%s'", - auth, data->state.aptr.user ? - data->state.aptr.user : ""); -#endif - authstatus->multipass = (!authstatus->done) ? TRUE : FALSE; - } - else - authstatus->multipass = FALSE; - - return CURLE_OK; -} - -/** - * Curl_http_output_auth() setups the authentication headers for the - * host/proxy and the correct authentication - * method. data->state.authdone is set to TRUE when authentication is - * done. - * - * @param conn all information about the current connection - * @param request pointer to the request keyword - * @param path pointer to the requested path; should include query part - * @param proxytunnel boolean if this is the request setting up a "proxy - * tunnel" - * - * @returns CURLcode - */ -CURLcode -Curl_http_output_auth(struct Curl_easy *data, - struct connectdata *conn, - const char *request, - Curl_HttpReq httpreq, - const char *path, - bool proxytunnel) /* TRUE if this is the request setting - up the proxy tunnel */ -{ - CURLcode result = CURLE_OK; - struct auth *authhost; - struct auth *authproxy; - - DEBUGASSERT(data); - - authhost = &data->state.authhost; - authproxy = &data->state.authproxy; - - if( -#ifndef CURL_DISABLE_PROXY - (conn->bits.httpproxy && conn->bits.proxy_user_passwd) || -#endif - conn->bits.user_passwd || data->set.str[STRING_BEARER]) - /* continue please */; - else { - authhost->done = TRUE; - authproxy->done = TRUE; - return CURLE_OK; /* no authentication with no user or password */ - } - - if(authhost->want && !authhost->picked) - /* The app has selected one or more methods, but none has been picked - so far by a server round-trip. Then we set the picked one to the - want one, and if this is one single bit it'll be used instantly. */ - authhost->picked = authhost->want; - - if(authproxy->want && !authproxy->picked) - /* The app has selected one or more methods, but none has been picked so - far by a proxy round-trip. Then we set the picked one to the want one, - and if this is one single bit it'll be used instantly. */ - authproxy->picked = authproxy->want; - -#ifndef CURL_DISABLE_PROXY - /* Send proxy authentication header if needed */ - if(conn->bits.httpproxy && - (conn->bits.tunnel_proxy == (bit)proxytunnel)) { - result = output_auth_headers(data, conn, authproxy, request, path, TRUE); - if(result) - return result; - } - else -#else - (void)proxytunnel; -#endif /* CURL_DISABLE_PROXY */ - /* we have no proxy so let's pretend we're done authenticating - with it */ - authproxy->done = TRUE; - - /* To prevent the user+password to get sent to other than the original - host due to a location-follow, we do some weirdo checks here */ - if(!data->state.this_is_a_follow || -#ifndef CURL_DISABLE_NETRC - conn->bits.netrc || -#endif - !data->state.first_host || - data->set.allow_auth_to_other_hosts || - strcasecompare(data->state.first_host, conn->host.name)) { - result = output_auth_headers(data, conn, authhost, request, path, FALSE); - } - else - authhost->done = TRUE; - - if(((authhost->multipass && !authhost->done) || - (authproxy->multipass && !authproxy->done)) && - (httpreq != HTTPREQ_GET) && - (httpreq != HTTPREQ_HEAD)) { - /* Auth is required and we are not authenticated yet. Make a PUT or POST - with content-length zero as a "probe". */ - conn->bits.authneg = TRUE; - } - else - conn->bits.authneg = FALSE; - - return result; -} - -#else -/* when disabled */ -CURLcode -Curl_http_output_auth(struct Curl_easy *data, - struct connectdata *conn, - const char *request, - Curl_HttpReq httpreq, - const char *path, - bool proxytunnel) -{ - (void)data; - (void)conn; - (void)request; - (void)httpreq; - (void)path; - (void)proxytunnel; - return CURLE_OK; -} -#endif - -/* - * Curl_http_input_auth() deals with Proxy-Authenticate: and WWW-Authenticate: - * headers. They are dealt with both in the transfer.c main loop and in the - * proxy CONNECT loop. - */ - -static int is_valid_auth_separator(char ch) -{ - return ch == '\0' || ch == ',' || ISSPACE(ch); -} - -CURLcode Curl_http_input_auth(struct Curl_easy *data, bool proxy, - const char *auth) /* the first non-space */ -{ - /* - * This resource requires authentication - */ - struct connectdata *conn = data->conn; -#ifdef USE_SPNEGO - curlnegotiate *negstate = proxy ? &conn->proxy_negotiate_state : - &conn->http_negotiate_state; -#endif - unsigned long *availp; - struct auth *authp; - - (void) conn; /* In case conditionals make it unused. */ - - if(proxy) { - availp = &data->info.proxyauthavail; - authp = &data->state.authproxy; - } - else { - availp = &data->info.httpauthavail; - authp = &data->state.authhost; - } - - /* - * Here we check if we want the specific single authentication (using ==) and - * if we do, we initiate usage of it. - * - * If the provided authentication is wanted as one out of several accepted - * types (using &), we OR this authentication type to the authavail - * variable. - * - * Note: - * - * ->picked is first set to the 'want' value (one or more bits) before the - * request is sent, and then it is again set _after_ all response 401/407 - * headers have been received but then only to a single preferred method - * (bit). - */ - - while(*auth) { -#ifdef USE_SPNEGO - if(checkprefix("Negotiate", auth) && is_valid_auth_separator(auth[9])) { - if((authp->avail & CURLAUTH_NEGOTIATE) || - Curl_auth_is_spnego_supported()) { - *availp |= CURLAUTH_NEGOTIATE; - authp->avail |= CURLAUTH_NEGOTIATE; - - if(authp->picked == CURLAUTH_NEGOTIATE) { - CURLcode result = Curl_input_negotiate(data, conn, proxy, auth); - if(!result) { - DEBUGASSERT(!data->req.newurl); - data->req.newurl = strdup(data->state.url); - if(!data->req.newurl) - return CURLE_OUT_OF_MEMORY; - data->state.authproblem = FALSE; - /* we received a GSS auth token and we dealt with it fine */ - *negstate = GSS_AUTHRECV; - } - else - data->state.authproblem = TRUE; - } - } - } - else -#endif -#ifdef USE_NTLM - /* NTLM support requires the SSL crypto libs */ - if(checkprefix("NTLM", auth) && is_valid_auth_separator(auth[4])) { - if((authp->avail & CURLAUTH_NTLM) || - (authp->avail & CURLAUTH_NTLM_WB) || - Curl_auth_is_ntlm_supported()) { - *availp |= CURLAUTH_NTLM; - authp->avail |= CURLAUTH_NTLM; - - if(authp->picked == CURLAUTH_NTLM || - authp->picked == CURLAUTH_NTLM_WB) { - /* NTLM authentication is picked and activated */ - CURLcode result = Curl_input_ntlm(data, proxy, auth); - if(!result) { - data->state.authproblem = FALSE; -#ifdef NTLM_WB_ENABLED - if(authp->picked == CURLAUTH_NTLM_WB) { - *availp &= ~CURLAUTH_NTLM; - authp->avail &= ~CURLAUTH_NTLM; - *availp |= CURLAUTH_NTLM_WB; - authp->avail |= CURLAUTH_NTLM_WB; - - result = Curl_input_ntlm_wb(data, conn, proxy, auth); - if(result) { - infof(data, "Authentication problem. Ignoring this."); - data->state.authproblem = TRUE; - } - } -#endif - } - else { - infof(data, "Authentication problem. Ignoring this."); - data->state.authproblem = TRUE; - } - } - } - } - else -#endif -#ifndef CURL_DISABLE_CRYPTO_AUTH - if(checkprefix("Digest", auth) && is_valid_auth_separator(auth[6])) { - if((authp->avail & CURLAUTH_DIGEST) != 0) - infof(data, "Ignoring duplicate digest auth header."); - else if(Curl_auth_is_digest_supported()) { - CURLcode result; - - *availp |= CURLAUTH_DIGEST; - authp->avail |= CURLAUTH_DIGEST; - - /* We call this function on input Digest headers even if Digest - * authentication isn't activated yet, as we need to store the - * incoming data from this header in case we are going to use - * Digest */ - result = Curl_input_digest(data, proxy, auth); - if(result) { - infof(data, "Authentication problem. Ignoring this."); - data->state.authproblem = TRUE; - } - } - } - else -#endif - if(checkprefix("Basic", auth) && - is_valid_auth_separator(auth[5])) { - *availp |= CURLAUTH_BASIC; - authp->avail |= CURLAUTH_BASIC; - if(authp->picked == CURLAUTH_BASIC) { - /* We asked for Basic authentication but got a 40X back - anyway, which basically means our name+password isn't - valid. */ - authp->avail = CURLAUTH_NONE; - infof(data, "Authentication problem. Ignoring this."); - data->state.authproblem = TRUE; - } - } - else - if(checkprefix("Bearer", auth) && - is_valid_auth_separator(auth[6])) { - *availp |= CURLAUTH_BEARER; - authp->avail |= CURLAUTH_BEARER; - if(authp->picked == CURLAUTH_BEARER) { - /* We asked for Bearer authentication but got a 40X back - anyway, which basically means our token isn't valid. */ - authp->avail = CURLAUTH_NONE; - infof(data, "Authentication problem. Ignoring this."); - data->state.authproblem = TRUE; - } - } - - /* there may be multiple methods on one line, so keep reading */ - while(*auth && *auth != ',') /* read up to the next comma */ - auth++; - if(*auth == ',') /* if we're on a comma, skip it */ - auth++; - while(*auth && ISSPACE(*auth)) - auth++; - } - - return CURLE_OK; -} - -/** - * http_should_fail() determines whether an HTTP response has gotten us - * into an error state or not. - * - * @param conn all information about the current connection - * - * @retval FALSE communications should continue - * - * @retval TRUE communications should not continue - */ -static bool http_should_fail(struct Curl_easy *data) -{ - int httpcode; - DEBUGASSERT(data); - DEBUGASSERT(data->conn); - - httpcode = data->req.httpcode; - - /* - ** If we haven't been asked to fail on error, - ** don't fail. - */ - if(!data->set.http_fail_on_error) - return FALSE; - - /* - ** Any code < 400 is never terminal. - */ - if(httpcode < 400) - return FALSE; - - /* - ** A 416 response to a resume request is presumably because the file is - ** already completely downloaded and thus not actually a fail. - */ - if(data->state.resume_from && data->state.httpreq == HTTPREQ_GET && - httpcode == 416) - return FALSE; - - /* - ** Any code >= 400 that's not 401 or 407 is always - ** a terminal error - */ - if((httpcode != 401) && (httpcode != 407)) - return TRUE; - - /* - ** All we have left to deal with is 401 and 407 - */ - DEBUGASSERT((httpcode == 401) || (httpcode == 407)); - - /* - ** Examine the current authentication state to see if this - ** is an error. The idea is for this function to get - ** called after processing all the headers in a response - ** message. So, if we've been to asked to authenticate a - ** particular stage, and we've done it, we're OK. But, if - ** we're already completely authenticated, it's not OK to - ** get another 401 or 407. - ** - ** It is possible for authentication to go stale such that - ** the client needs to reauthenticate. Once that info is - ** available, use it here. - */ - - /* - ** Either we're not authenticating, or we're supposed to - ** be authenticating something else. This is an error. - */ - if((httpcode == 401) && !data->conn->bits.user_passwd) - return TRUE; -#ifndef CURL_DISABLE_PROXY - if((httpcode == 407) && !data->conn->bits.proxy_user_passwd) - return TRUE; -#endif - - return data->state.authproblem; -} - -#ifndef USE_HYPER -/* - * readmoredata() is a "fread() emulation" to provide POST and/or request - * data. It is used when a huge POST is to be made and the entire chunk wasn't - * sent in the first send(). This function will then be called from the - * transfer.c loop when more data is to be sent to the peer. - * - * Returns the amount of bytes it filled the buffer with. - */ -static size_t readmoredata(char *buffer, - size_t size, - size_t nitems, - void *userp) -{ - struct Curl_easy *data = (struct Curl_easy *)userp; - struct HTTP *http = data->req.p.http; - size_t fullsize = size * nitems; - - if(!http->postsize) - /* nothing to return */ - return 0; - - /* make sure that a HTTP request is never sent away chunked! */ - data->req.forbidchunk = (http->sending == HTTPSEND_REQUEST)?TRUE:FALSE; - - if(data->set.max_send_speed && - (data->set.max_send_speed < (curl_off_t)fullsize) && - (data->set.max_send_speed < http->postsize)) - /* speed limit */ - fullsize = (size_t)data->set.max_send_speed; - - else if(http->postsize <= (curl_off_t)fullsize) { - memcpy(buffer, http->postdata, (size_t)http->postsize); - fullsize = (size_t)http->postsize; - - if(http->backup.postsize) { - /* move backup data into focus and continue on that */ - http->postdata = http->backup.postdata; - http->postsize = http->backup.postsize; - data->state.fread_func = http->backup.fread_func; - data->state.in = http->backup.fread_in; - - http->sending++; /* move one step up */ - - http->backup.postsize = 0; - } - else - http->postsize = 0; - - return fullsize; - } - - memcpy(buffer, http->postdata, fullsize); - http->postdata += fullsize; - http->postsize -= fullsize; - - return fullsize; -} - -/* - * Curl_buffer_send() sends a header buffer and frees all associated - * memory. Body data may be appended to the header data if desired. - * - * Returns CURLcode - */ -CURLcode Curl_buffer_send(struct dynbuf *in, - struct Curl_easy *data, - /* add the number of sent bytes to this - counter */ - curl_off_t *bytes_written, - /* how much of the buffer contains body data */ - curl_off_t included_body_bytes, - int socketindex) -{ - ssize_t amount; - CURLcode result; - char *ptr; - size_t size; - struct connectdata *conn = data->conn; - struct HTTP *http = data->req.p.http; - size_t sendsize; - curl_socket_t sockfd; - size_t headersize; - - DEBUGASSERT(socketindex <= SECONDARYSOCKET); - - sockfd = conn->sock[socketindex]; - - /* The looping below is required since we use non-blocking sockets, but due - to the circumstances we will just loop and try again and again etc */ - - ptr = Curl_dyn_ptr(in); - size = Curl_dyn_len(in); - - headersize = size - (size_t)included_body_bytes; /* the initial part that - isn't body is header */ - - DEBUGASSERT(size > (size_t)included_body_bytes); - - result = Curl_convert_to_network(data, ptr, headersize); - /* Curl_convert_to_network calls failf if unsuccessful */ - if(result) { - /* conversion failed, free memory and return to the caller */ - Curl_dyn_free(in); - return result; - } - - if((conn->handler->flags & PROTOPT_SSL -#ifndef CURL_DISABLE_PROXY - || conn->http_proxy.proxytype == CURLPROXY_HTTPS -#endif - ) - && conn->httpversion != 20) { - /* Make sure this doesn't send more body bytes than what the max send - speed says. The request bytes do not count to the max speed. - */ - if(data->set.max_send_speed && - (included_body_bytes > data->set.max_send_speed)) { - curl_off_t overflow = included_body_bytes - data->set.max_send_speed; - DEBUGASSERT((size_t)overflow < size); - sendsize = size - (size_t)overflow; - } - else - sendsize = size; - - /* OpenSSL is very picky and we must send the SAME buffer pointer to the - library when we attempt to re-send this buffer. Sending the same data - is not enough, we must use the exact same address. For this reason, we - must copy the data to the uploadbuffer first, since that is the buffer - we will be using if this send is retried later. - */ - result = Curl_get_upload_buffer(data); - if(result) { - /* malloc failed, free memory and return to the caller */ - Curl_dyn_free(in); - return result; - } - /* We never send more than upload_buffer_size bytes in one single chunk - when we speak HTTPS, as if only a fraction of it is sent now, this data - needs to fit into the normal read-callback buffer later on and that - buffer is using this size. - */ - if(sendsize > (size_t)data->set.upload_buffer_size) - sendsize = (size_t)data->set.upload_buffer_size; - - memcpy(data->state.ulbuf, ptr, sendsize); - ptr = data->state.ulbuf; - } - else { -#ifdef CURLDEBUG - /* Allow debug builds to override this logic to force short initial - sends - */ - char *p = getenv("CURL_SMALLREQSEND"); - if(p) { - size_t altsize = (size_t)strtoul(p, NULL, 10); - if(altsize) - sendsize = CURLMIN(size, altsize); - else - sendsize = size; - } - else -#endif - { - /* Make sure this doesn't send more body bytes than what the max send - speed says. The request bytes do not count to the max speed. - */ - if(data->set.max_send_speed && - (included_body_bytes > data->set.max_send_speed)) { - curl_off_t overflow = included_body_bytes - data->set.max_send_speed; - DEBUGASSERT((size_t)overflow < size); - sendsize = size - (size_t)overflow; - } - else - sendsize = size; - } - } - - result = Curl_write(data, sockfd, ptr, sendsize, &amount); - - if(!result) { - /* - * Note that we may not send the entire chunk at once, and we have a set - * number of data bytes at the end of the big buffer (out of which we may - * only send away a part). - */ - /* how much of the header that was sent */ - size_t headlen = (size_t)amount>headersize ? headersize : (size_t)amount; - size_t bodylen = amount - headlen; - - /* this data _may_ contain binary stuff */ - Curl_debug(data, CURLINFO_HEADER_OUT, ptr, headlen); - if(bodylen) - /* there was body data sent beyond the initial header part, pass that on - to the debug callback too */ - Curl_debug(data, CURLINFO_DATA_OUT, ptr + headlen, bodylen); - - /* 'amount' can never be a very large value here so typecasting it so a - signed 31 bit value should not cause problems even if ssize_t is - 64bit */ - *bytes_written += (long)amount; - - if(http) { - /* if we sent a piece of the body here, up the byte counter for it - accordingly */ - data->req.writebytecount += bodylen; - Curl_pgrsSetUploadCounter(data, data->req.writebytecount); - - if((size_t)amount != size) { - /* The whole request could not be sent in one system call. We must - queue it up and send it later when we get the chance. We must not - loop here and wait until it might work again. */ - - size -= amount; - - ptr = Curl_dyn_ptr(in) + amount; - - /* backup the currently set pointers */ - http->backup.fread_func = data->state.fread_func; - http->backup.fread_in = data->state.in; - http->backup.postdata = http->postdata; - http->backup.postsize = http->postsize; - - /* set the new pointers for the request-sending */ - data->state.fread_func = (curl_read_callback)readmoredata; - data->state.in = (void *)data; - http->postdata = ptr; - http->postsize = (curl_off_t)size; - - /* this much data is remaining header: */ - data->req.pendingheader = headersize - headlen; - - http->send_buffer = *in; /* copy the whole struct */ - http->sending = HTTPSEND_REQUEST; - - return CURLE_OK; - } - http->sending = HTTPSEND_BODY; - /* the full buffer was sent, clean up and return */ - } - else { - if((size_t)amount != size) - /* We have no continue-send mechanism now, fail. This can only happen - when this function is used from the CONNECT sending function. We - currently (stupidly) assume that the whole request is always sent - away in the first single chunk. - - This needs FIXing. - */ - return CURLE_SEND_ERROR; - } - } - Curl_dyn_free(in); - - /* no remaining header data */ - data->req.pendingheader = 0; - return result; -} - -#endif - -/* end of the add_buffer functions */ -/* ------------------------------------------------------------------------- */ - - - -/* - * Curl_compareheader() - * - * Returns TRUE if 'headerline' contains the 'header' with given 'content'. - * Pass headers WITH the colon. - */ -bool -Curl_compareheader(const char *headerline, /* line to check */ - const char *header, /* header keyword _with_ colon */ - const char *content) /* content string to find */ -{ - /* RFC2616, section 4.2 says: "Each header field consists of a name followed - * by a colon (":") and the field value. Field names are case-insensitive. - * The field value MAY be preceded by any amount of LWS, though a single SP - * is preferred." */ - - size_t hlen = strlen(header); - size_t clen; - size_t len; - const char *start; - const char *end; - - if(!strncasecompare(headerline, header, hlen)) - return FALSE; /* doesn't start with header */ - - /* pass the header */ - start = &headerline[hlen]; - - /* pass all whitespace */ - while(*start && ISSPACE(*start)) - start++; - - /* find the end of the header line */ - end = strchr(start, '\r'); /* lines end with CRLF */ - if(!end) { - /* in case there's a non-standard compliant line here */ - end = strchr(start, '\n'); - - if(!end) - /* hm, there's no line ending here, use the zero byte! */ - end = strchr(start, '\0'); - } - - len = end-start; /* length of the content part of the input line */ - clen = strlen(content); /* length of the word to find */ - - /* find the content string in the rest of the line */ - for(; len >= clen; len--, start++) { - if(strncasecompare(start, content, clen)) - return TRUE; /* match! */ - } - - return FALSE; /* no match */ -} - -/* - * Curl_http_connect() performs HTTP stuff to do at connect-time, called from - * the generic Curl_connect(). - */ -CURLcode Curl_http_connect(struct Curl_easy *data, bool *done) -{ - CURLcode result; - struct connectdata *conn = data->conn; - - /* We default to persistent connections. We set this already in this connect - function to make the re-use checks properly be able to check this bit. */ - connkeep(conn, "HTTP default"); - -#ifndef CURL_DISABLE_PROXY - /* the CONNECT procedure might not have been completed */ - result = Curl_proxy_connect(data, FIRSTSOCKET); - if(result) - return result; - - if(conn->bits.proxy_connect_closed) - /* this is not an error, just part of the connection negotiation */ - return CURLE_OK; - - if(CONNECT_FIRSTSOCKET_PROXY_SSL()) - return CURLE_OK; /* wait for HTTPS proxy SSL initialization to complete */ - - if(Curl_connect_ongoing(conn)) - /* nothing else to do except wait right now - we're not done here. */ - return CURLE_OK; - - if(data->set.haproxyprotocol) { - /* add HAProxy PROXY protocol header */ - result = add_haproxy_protocol_header(data); - if(result) - return result; - } -#endif - - if(conn->given->protocol & CURLPROTO_HTTPS) { - /* perform SSL initialization */ - result = https_connecting(data, done); - if(result) - return result; - } - else - *done = TRUE; - - return CURLE_OK; -} - -/* this returns the socket to wait for in the DO and DOING state for the multi - interface and then we're always _sending_ a request and thus we wait for - the single socket to become writable only */ -static int http_getsock_do(struct Curl_easy *data, - struct connectdata *conn, - curl_socket_t *socks) -{ - /* write mode */ - (void)data; - socks[0] = conn->sock[FIRSTSOCKET]; - return GETSOCK_WRITESOCK(0); -} - -#ifndef CURL_DISABLE_PROXY -static CURLcode add_haproxy_protocol_header(struct Curl_easy *data) -{ - struct dynbuf req; - CURLcode result; - const char *tcp_version; - DEBUGASSERT(data->conn); - Curl_dyn_init(&req, DYN_HAXPROXY); - -#ifdef USE_UNIX_SOCKETS - if(data->conn->unix_domain_socket) - /* the buffer is large enough to hold this! */ - result = Curl_dyn_add(&req, "PROXY UNKNOWN\r\n"); - else { -#endif - /* Emit the correct prefix for IPv6 */ - tcp_version = data->conn->bits.ipv6 ? "TCP6" : "TCP4"; - - result = Curl_dyn_addf(&req, "PROXY %s %s %s %i %i\r\n", - tcp_version, - data->info.conn_local_ip, - data->info.conn_primary_ip, - data->info.conn_local_port, - data->info.conn_primary_port); - -#ifdef USE_UNIX_SOCKETS - } -#endif - - if(!result) - result = Curl_buffer_send(&req, data, &data->info.request_size, - 0, FIRSTSOCKET); - return result; -} -#endif - -#ifdef USE_SSL -static CURLcode https_connecting(struct Curl_easy *data, bool *done) -{ - CURLcode result; - struct connectdata *conn = data->conn; - DEBUGASSERT((data) && (data->conn->handler->flags & PROTOPT_SSL)); - -#ifdef ENABLE_QUIC - if(conn->transport == TRNSPRT_QUIC) { - *done = TRUE; - return CURLE_OK; - } -#endif - - /* perform SSL initialization for this socket */ - result = Curl_ssl_connect_nonblocking(data, conn, FALSE, FIRSTSOCKET, done); - if(result) - connclose(conn, "Failed HTTPS connection"); - - return result; -} - -static int https_getsock(struct Curl_easy *data, - struct connectdata *conn, - curl_socket_t *socks) -{ - (void)data; - if(conn->handler->flags & PROTOPT_SSL) - return Curl_ssl->getsock(conn, socks); - return GETSOCK_BLANK; -} -#endif /* USE_SSL */ - -/* - * Curl_http_done() gets called after a single HTTP request has been - * performed. - */ - -CURLcode Curl_http_done(struct Curl_easy *data, - CURLcode status, bool premature) -{ - struct connectdata *conn = data->conn; - struct HTTP *http = data->req.p.http; - - /* Clear multipass flag. If authentication isn't done yet, then it will get - * a chance to be set back to true when we output the next auth header */ - data->state.authhost.multipass = FALSE; - data->state.authproxy.multipass = FALSE; - - Curl_unencode_cleanup(data); - - /* set the proper values (possibly modified on POST) */ - conn->seek_func = data->set.seek_func; /* restore */ - conn->seek_client = data->set.seek_client; /* restore */ - - if(!http) - return CURLE_OK; - - Curl_dyn_free(&http->send_buffer); - Curl_http2_done(data, premature); - Curl_quic_done(data, premature); - Curl_mime_cleanpart(&http->form); - Curl_dyn_reset(&data->state.headerb); - Curl_hyper_done(data); - - if(status) - return status; - - if(!premature && /* this check is pointless when DONE is called before the - entire operation is complete */ - !conn->bits.retry && - !data->set.connect_only && - (data->req.bytecount + - data->req.headerbytecount - - data->req.deductheadercount) <= 0) { - /* If this connection isn't simply closed to be retried, AND nothing was - read from the HTTP server (that counts), this can't be right so we - return an error here */ - failf(data, "Empty reply from server"); - /* Mark it as closed to avoid the "left intact" message */ - streamclose(conn, "Empty reply from server"); - return CURLE_GOT_NOTHING; - } - - return CURLE_OK; -} - -/* - * Determine if we should use HTTP 1.1 (OR BETTER) for this request. Reasons - * to avoid it include: - * - * - if the user specifically requested HTTP 1.0 - * - if the server we are connected to only supports 1.0 - * - if any server previously contacted to handle this request only supports - * 1.0. - */ -bool Curl_use_http_1_1plus(const struct Curl_easy *data, - const struct connectdata *conn) -{ - if((data->state.httpversion == 10) || (conn->httpversion == 10)) - return FALSE; - if((data->state.httpwant == CURL_HTTP_VERSION_1_0) && - (conn->httpversion <= 10)) - return FALSE; - return ((data->state.httpwant == CURL_HTTP_VERSION_NONE) || - (data->state.httpwant >= CURL_HTTP_VERSION_1_1)); -} - -#ifndef USE_HYPER -static const char *get_http_string(const struct Curl_easy *data, - const struct connectdata *conn) -{ -#ifdef ENABLE_QUIC - if((data->state.httpwant == CURL_HTTP_VERSION_3) || - (conn->httpversion == 30)) - return "3"; -#endif - -#ifdef USE_NGHTTP2 - if(conn->proto.httpc.h2) - return "2"; -#endif - - if(Curl_use_http_1_1plus(data, conn)) - return "1.1"; - - return "1.0"; -} -#endif - -/* check and possibly add an Expect: header */ -static CURLcode expect100(struct Curl_easy *data, - struct connectdata *conn, - struct dynbuf *req) -{ - CURLcode result = CURLE_OK; - data->state.expect100header = FALSE; /* default to false unless it is set - to TRUE below */ - if(!data->state.disableexpect && Curl_use_http_1_1plus(data, conn) && - (conn->httpversion < 20)) { - /* if not doing HTTP 1.0 or version 2, or disabled explicitly, we add an - Expect: 100-continue to the headers which actually speeds up post - operations (as there is one packet coming back from the web server) */ - const char *ptr = Curl_checkheaders(data, "Expect"); - if(ptr) { - data->state.expect100header = - Curl_compareheader(ptr, "Expect:", "100-continue"); - } - else { - result = Curl_dyn_add(req, "Expect: 100-continue\r\n"); - if(!result) - data->state.expect100header = TRUE; - } - } - - return result; -} - -enum proxy_use { - HEADER_SERVER, /* direct to server */ - HEADER_PROXY, /* regular request to proxy */ - HEADER_CONNECT /* sending CONNECT to a proxy */ -}; - -/* used to compile the provided trailers into one buffer - will return an error code if one of the headers is - not formatted correctly */ -CURLcode Curl_http_compile_trailers(struct curl_slist *trailers, - struct dynbuf *b, - struct Curl_easy *handle) -{ - char *ptr = NULL; - CURLcode result = CURLE_OK; - const char *endofline_native = NULL; - const char *endofline_network = NULL; - - if( -#ifdef CURL_DO_LINEEND_CONV - (handle->state.prefer_ascii) || -#endif - (handle->set.crlf)) { - /* \n will become \r\n later on */ - endofline_native = "\n"; - endofline_network = "\x0a"; - } - else { - endofline_native = "\r\n"; - endofline_network = "\x0d\x0a"; - } - - while(trailers) { - /* only add correctly formatted trailers */ - ptr = strchr(trailers->data, ':'); - if(ptr && *(ptr + 1) == ' ') { - result = Curl_dyn_add(b, trailers->data); - if(result) - return result; - result = Curl_dyn_add(b, endofline_native); - if(result) - return result; - } - else - infof(handle, "Malformatted trailing header ! Skipping trailer."); - trailers = trailers->next; - } - result = Curl_dyn_add(b, endofline_network); - return result; -} - -CURLcode Curl_add_custom_headers(struct Curl_easy *data, - bool is_connect, -#ifndef USE_HYPER - struct dynbuf *req -#else - void *req -#endif - ) -{ - struct connectdata *conn = data->conn; - char *ptr; - struct curl_slist *h[2]; - struct curl_slist *headers; - int numlists = 1; /* by default */ - int i; - -#ifndef CURL_DISABLE_PROXY - enum proxy_use proxy; - - if(is_connect) - proxy = HEADER_CONNECT; - else - proxy = conn->bits.httpproxy && !conn->bits.tunnel_proxy? - HEADER_PROXY:HEADER_SERVER; - - switch(proxy) { - case HEADER_SERVER: - h[0] = data->set.headers; - break; - case HEADER_PROXY: - h[0] = data->set.headers; - if(data->set.sep_headers) { - h[1] = data->set.proxyheaders; - numlists++; - } - break; - case HEADER_CONNECT: - if(data->set.sep_headers) - h[0] = data->set.proxyheaders; - else - h[0] = data->set.headers; - break; - } -#else - (void)is_connect; - h[0] = data->set.headers; -#endif - - /* loop through one or two lists */ - for(i = 0; i < numlists; i++) { - headers = h[i]; - - while(headers) { - char *semicolonp = NULL; - ptr = strchr(headers->data, ':'); - if(!ptr) { - char *optr; - /* no colon, semicolon? */ - ptr = strchr(headers->data, ';'); - if(ptr) { - optr = ptr; - ptr++; /* pass the semicolon */ - while(*ptr && ISSPACE(*ptr)) - ptr++; - - if(*ptr) { - /* this may be used for something else in the future */ - optr = NULL; - } - else { - if(*(--ptr) == ';') { - /* copy the source */ - semicolonp = strdup(headers->data); - if(!semicolonp) { -#ifndef USE_HYPER - Curl_dyn_free(req); -#endif - return CURLE_OUT_OF_MEMORY; - } - /* put a colon where the semicolon is */ - semicolonp[ptr - headers->data] = ':'; - /* point at the colon */ - optr = &semicolonp [ptr - headers->data]; - } - } - ptr = optr; - } - } - if(ptr) { - /* we require a colon for this to be a true header */ - - ptr++; /* pass the colon */ - while(*ptr && ISSPACE(*ptr)) - ptr++; - - if(*ptr || semicolonp) { - /* only send this if the contents was non-blank or done special */ - CURLcode result = CURLE_OK; - char *compare = semicolonp ? semicolonp : headers->data; - - if(data->state.aptr.host && - /* a Host: header was sent already, don't pass on any custom Host: - header as that will produce *two* in the same request! */ - checkprefix("Host:", compare)) - ; - else if(data->state.httpreq == HTTPREQ_POST_FORM && - /* this header (extended by formdata.c) is sent later */ - checkprefix("Content-Type:", compare)) - ; - else if(data->state.httpreq == HTTPREQ_POST_MIME && - /* this header is sent later */ - checkprefix("Content-Type:", compare)) - ; - else if(conn->bits.authneg && - /* while doing auth neg, don't allow the custom length since - we will force length zero then */ - checkprefix("Content-Length:", compare)) - ; - else if(data->state.aptr.te && - /* when asking for Transfer-Encoding, don't pass on a custom - Connection: */ - checkprefix("Connection:", compare)) - ; - else if((conn->httpversion >= 20) && - checkprefix("Transfer-Encoding:", compare)) - /* HTTP/2 doesn't support chunked requests */ - ; - else if((checkprefix("Authorization:", compare) || - checkprefix("Cookie:", compare)) && - /* be careful of sending this potentially sensitive header to - other hosts */ - (data->state.this_is_a_follow && - data->state.first_host && - !data->set.allow_auth_to_other_hosts && - !strcasecompare(data->state.first_host, conn->host.name))) - ; - else { -#ifdef USE_HYPER - result = Curl_hyper_header(data, req, compare); -#else - result = Curl_dyn_addf(req, "%s\r\n", compare); -#endif - } - if(semicolonp) - free(semicolonp); - if(result) - return result; - } - } - headers = headers->next; - } - } - - return CURLE_OK; -} - -#ifndef CURL_DISABLE_PARSEDATE -CURLcode Curl_add_timecondition(struct Curl_easy *data, -#ifndef USE_HYPER - struct dynbuf *req -#else - void *req -#endif - ) -{ - const struct tm *tm; - struct tm keeptime; - CURLcode result; - char datestr[80]; - const char *condp; - - if(data->set.timecondition == CURL_TIMECOND_NONE) - /* no condition was asked for */ - return CURLE_OK; - - result = Curl_gmtime(data->set.timevalue, &keeptime); - if(result) { - failf(data, "Invalid TIMEVALUE"); - return result; - } - tm = &keeptime; - - switch(data->set.timecondition) { - default: - return CURLE_BAD_FUNCTION_ARGUMENT; - - case CURL_TIMECOND_IFMODSINCE: - condp = "If-Modified-Since"; - break; - case CURL_TIMECOND_IFUNMODSINCE: - condp = "If-Unmodified-Since"; - break; - case CURL_TIMECOND_LASTMOD: - condp = "Last-Modified"; - break; - } - - if(Curl_checkheaders(data, condp)) { - /* A custom header was specified; it will be sent instead. */ - return CURLE_OK; - } - - /* The If-Modified-Since header family should have their times set in - * GMT as RFC2616 defines: "All HTTP date/time stamps MUST be - * represented in Greenwich Mean Time (GMT), without exception. For the - * purposes of HTTP, GMT is exactly equal to UTC (Coordinated Universal - * Time)." (see page 20 of RFC2616). - */ - - /* format: "Tue, 15 Nov 1994 12:45:26 GMT" */ - msnprintf(datestr, sizeof(datestr), - "%s: %s, %02d %s %4d %02d:%02d:%02d GMT\r\n", - condp, - Curl_wkday[tm->tm_wday?tm->tm_wday-1:6], - tm->tm_mday, - Curl_month[tm->tm_mon], - tm->tm_year + 1900, - tm->tm_hour, - tm->tm_min, - tm->tm_sec); - -#ifndef USE_HYPER - result = Curl_dyn_add(req, datestr); -#else - result = Curl_hyper_header(data, req, datestr); -#endif - - return result; -} -#else -/* disabled */ -CURLcode Curl_add_timecondition(struct Curl_easy *data, - struct dynbuf *req) -{ - (void)data; - (void)req; - return CURLE_OK; -} -#endif - -void Curl_http_method(struct Curl_easy *data, struct connectdata *conn, - const char **method, Curl_HttpReq *reqp) -{ - Curl_HttpReq httpreq = data->state.httpreq; - const char *request; - if((conn->handler->protocol&(PROTO_FAMILY_HTTP|CURLPROTO_FTP)) && - data->set.upload) - httpreq = HTTPREQ_PUT; - - /* Now set the 'request' pointer to the proper request string */ - if(data->set.str[STRING_CUSTOMREQUEST]) - request = data->set.str[STRING_CUSTOMREQUEST]; - else { - if(data->set.opt_no_body) - request = "HEAD"; - else { - DEBUGASSERT((httpreq >= HTTPREQ_GET) && (httpreq <= HTTPREQ_HEAD)); - switch(httpreq) { - case HTTPREQ_POST: - case HTTPREQ_POST_FORM: - case HTTPREQ_POST_MIME: - request = "POST"; - break; - case HTTPREQ_PUT: - request = "PUT"; - break; - default: /* this should never happen */ - case HTTPREQ_GET: - request = "GET"; - break; - case HTTPREQ_HEAD: - request = "HEAD"; - break; - } - } - } - *method = request; - *reqp = httpreq; -} - -CURLcode Curl_http_useragent(struct Curl_easy *data) -{ - /* The User-Agent string might have been allocated in url.c already, because - it might have been used in the proxy connect, but if we have got a header - with the user-agent string specified, we erase the previously made string - here. */ - if(Curl_checkheaders(data, "User-Agent")) { - free(data->state.aptr.uagent); - data->state.aptr.uagent = NULL; - } - return CURLE_OK; -} - - -CURLcode Curl_http_host(struct Curl_easy *data, struct connectdata *conn) -{ - const char *ptr; - if(!data->state.this_is_a_follow) { - /* Free to avoid leaking memory on multiple requests*/ - free(data->state.first_host); - - data->state.first_host = strdup(conn->host.name); - if(!data->state.first_host) - return CURLE_OUT_OF_MEMORY; - - data->state.first_remote_port = conn->remote_port; - } - Curl_safefree(data->state.aptr.host); - - ptr = Curl_checkheaders(data, "Host"); - if(ptr && (!data->state.this_is_a_follow || - strcasecompare(data->state.first_host, conn->host.name))) { -#if !defined(CURL_DISABLE_COOKIES) - /* If we have a given custom Host: header, we extract the host name in - order to possibly use it for cookie reasons later on. We only allow the - custom Host: header if this is NOT a redirect, as setting Host: in the - redirected request is being out on thin ice. Except if the host name - is the same as the first one! */ - char *cookiehost = Curl_copy_header_value(ptr); - if(!cookiehost) - return CURLE_OUT_OF_MEMORY; - if(!*cookiehost) - /* ignore empty data */ - free(cookiehost); - else { - /* If the host begins with '[', we start searching for the port after - the bracket has been closed */ - if(*cookiehost == '[') { - char *closingbracket; - /* since the 'cookiehost' is an allocated memory area that will be - freed later we cannot simply increment the pointer */ - memmove(cookiehost, cookiehost + 1, strlen(cookiehost) - 1); - closingbracket = strchr(cookiehost, ']'); - if(closingbracket) - *closingbracket = 0; - } - else { - int startsearch = 0; - char *colon = strchr(cookiehost + startsearch, ':'); - if(colon) - *colon = 0; /* The host must not include an embedded port number */ - } - Curl_safefree(data->state.aptr.cookiehost); - data->state.aptr.cookiehost = cookiehost; - } -#endif - - if(strcmp("Host:", ptr)) { - data->state.aptr.host = aprintf("Host:%s\r\n", &ptr[5]); - if(!data->state.aptr.host) - return CURLE_OUT_OF_MEMORY; - } - else - /* when clearing the header */ - data->state.aptr.host = NULL; - } - else { - /* When building Host: headers, we must put the host name within - [brackets] if the host name is a plain IPv6-address. RFC2732-style. */ - const char *host = conn->host.name; - - if(((conn->given->protocol&CURLPROTO_HTTPS) && - (conn->remote_port == PORT_HTTPS)) || - ((conn->given->protocol&CURLPROTO_HTTP) && - (conn->remote_port == PORT_HTTP)) ) - /* if(HTTPS on port 443) OR (HTTP on port 80) then don't include - the port number in the host string */ - data->state.aptr.host = aprintf("Host: %s%s%s\r\n", - conn->bits.ipv6_ip?"[":"", - host, - conn->bits.ipv6_ip?"]":""); - else - data->state.aptr.host = aprintf("Host: %s%s%s:%d\r\n", - conn->bits.ipv6_ip?"[":"", - host, - conn->bits.ipv6_ip?"]":"", - conn->remote_port); - - if(!data->state.aptr.host) - /* without Host: we can't make a nice request */ - return CURLE_OUT_OF_MEMORY; - } - return CURLE_OK; -} - -/* - * Append the request-target to the HTTP request - */ -CURLcode Curl_http_target(struct Curl_easy *data, - struct connectdata *conn, - struct dynbuf *r) -{ - CURLcode result = CURLE_OK; - const char *path = data->state.up.path; - const char *query = data->state.up.query; - - if(data->set.str[STRING_TARGET]) { - path = data->set.str[STRING_TARGET]; - query = NULL; - } - -#ifndef CURL_DISABLE_PROXY - if(conn->bits.httpproxy && !conn->bits.tunnel_proxy) { - /* Using a proxy but does not tunnel through it */ - - /* The path sent to the proxy is in fact the entire URL. But if the remote - host is a IDN-name, we must make sure that the request we produce only - uses the encoded host name! */ - - /* and no fragment part */ - CURLUcode uc; - char *url; - CURLU *h = curl_url_dup(data->state.uh); - if(!h) - return CURLE_OUT_OF_MEMORY; - - if(conn->host.dispname != conn->host.name) { - uc = curl_url_set(h, CURLUPART_HOST, conn->host.name, 0); - if(uc) { - curl_url_cleanup(h); - return CURLE_OUT_OF_MEMORY; - } - } - uc = curl_url_set(h, CURLUPART_FRAGMENT, NULL, 0); - if(uc) { - curl_url_cleanup(h); - return CURLE_OUT_OF_MEMORY; - } - - if(strcasecompare("http", data->state.up.scheme)) { - /* when getting HTTP, we don't want the userinfo the URL */ - uc = curl_url_set(h, CURLUPART_USER, NULL, 0); - if(uc) { - curl_url_cleanup(h); - return CURLE_OUT_OF_MEMORY; - } - uc = curl_url_set(h, CURLUPART_PASSWORD, NULL, 0); - if(uc) { - curl_url_cleanup(h); - return CURLE_OUT_OF_MEMORY; - } - } - /* Extract the URL to use in the request. Store in STRING_TEMP_URL for - clean-up reasons if the function returns before the free() further - down. */ - uc = curl_url_get(h, CURLUPART_URL, &url, CURLU_NO_DEFAULT_PORT); - if(uc) { - curl_url_cleanup(h); - return CURLE_OUT_OF_MEMORY; - } - - curl_url_cleanup(h); - - /* target or url */ - result = Curl_dyn_add(r, data->set.str[STRING_TARGET]? - data->set.str[STRING_TARGET]:url); - free(url); - if(result) - return (result); - - if(strcasecompare("ftp", data->state.up.scheme)) { - if(data->set.proxy_transfer_mode) { - /* when doing ftp, append ;type= if not present */ - char *type = strstr(path, ";type="); - if(type && type[6] && type[7] == 0) { - switch(Curl_raw_toupper(type[6])) { - case 'A': - case 'D': - case 'I': - break; - default: - type = NULL; - } - } - if(!type) { - result = Curl_dyn_addf(r, ";type=%c", - data->state.prefer_ascii ? 'a' : 'i'); - if(result) - return result; - } - } - } - } - - else -#else - (void)conn; /* not used in disabled-proxy builds */ -#endif - { - result = Curl_dyn_add(r, path); - if(result) - return result; - if(query) - result = Curl_dyn_addf(r, "?%s", query); - } - - return result; -} - -CURLcode Curl_http_body(struct Curl_easy *data, struct connectdata *conn, - Curl_HttpReq httpreq, const char **tep) -{ - CURLcode result = CURLE_OK; - const char *ptr; - struct HTTP *http = data->req.p.http; - http->postsize = 0; - - switch(httpreq) { - case HTTPREQ_POST_MIME: - http->sendit = &data->set.mimepost; - break; - case HTTPREQ_POST_FORM: - /* Convert the form structure into a mime structure. */ - Curl_mime_cleanpart(&http->form); - result = Curl_getformdata(data, &http->form, data->set.httppost, - data->state.fread_func); - if(result) - return result; - http->sendit = &http->form; - break; - default: - http->sendit = NULL; - } - -#ifndef CURL_DISABLE_MIME - if(http->sendit) { - const char *cthdr = Curl_checkheaders(data, "Content-Type"); - - /* Read and seek body only. */ - http->sendit->flags |= MIME_BODY_ONLY; - - /* Prepare the mime structure headers & set content type. */ - - if(cthdr) - for(cthdr += 13; *cthdr == ' '; cthdr++) - ; - else if(http->sendit->kind == MIMEKIND_MULTIPART) - cthdr = "multipart/form-data"; - - curl_mime_headers(http->sendit, data->set.headers, 0); - result = Curl_mime_prepare_headers(http->sendit, cthdr, - NULL, MIMESTRATEGY_FORM); - curl_mime_headers(http->sendit, NULL, 0); - if(!result) - result = Curl_mime_rewind(http->sendit); - if(result) - return result; - http->postsize = Curl_mime_size(http->sendit); - } -#endif - - ptr = Curl_checkheaders(data, "Transfer-Encoding"); - if(ptr) { - /* Some kind of TE is requested, check if 'chunked' is chosen */ - data->req.upload_chunky = - Curl_compareheader(ptr, "Transfer-Encoding:", "chunked"); - } - else { - if((conn->handler->protocol & PROTO_FAMILY_HTTP) && - (((httpreq == HTTPREQ_POST_MIME || httpreq == HTTPREQ_POST_FORM) && - http->postsize < 0) || - ((data->set.upload || httpreq == HTTPREQ_POST) && - data->state.infilesize == -1))) { - if(conn->bits.authneg) - /* don't enable chunked during auth neg */ - ; - else if(Curl_use_http_1_1plus(data, conn)) { - if(conn->httpversion < 20) - /* HTTP, upload, unknown file size and not HTTP 1.0 */ - data->req.upload_chunky = TRUE; - } - else { - failf(data, "Chunky upload is not supported by HTTP 1.0"); - return CURLE_UPLOAD_FAILED; - } - } - else { - /* else, no chunky upload */ - data->req.upload_chunky = FALSE; - } - - if(data->req.upload_chunky) - *tep = "Transfer-Encoding: chunked\r\n"; - } - return result; -} - -CURLcode Curl_http_bodysend(struct Curl_easy *data, struct connectdata *conn, - struct dynbuf *r, Curl_HttpReq httpreq) -{ -#ifndef USE_HYPER - /* Hyper always handles the body separately */ - curl_off_t included_body = 0; -#endif - CURLcode result = CURLE_OK; - struct HTTP *http = data->req.p.http; - const char *ptr; - - /* If 'authdone' is FALSE, we must not set the write socket index to the - Curl_transfer() call below, as we're not ready to actually upload any - data yet. */ - - switch(httpreq) { - - case HTTPREQ_PUT: /* Let's PUT the data to the server! */ - - if(conn->bits.authneg) - http->postsize = 0; - else - http->postsize = data->state.infilesize; - - if((http->postsize != -1) && !data->req.upload_chunky && - (conn->bits.authneg || !Curl_checkheaders(data, "Content-Length"))) { - /* only add Content-Length if not uploading chunked */ - result = Curl_dyn_addf(r, "Content-Length: %" CURL_FORMAT_CURL_OFF_T - "\r\n", http->postsize); - if(result) - return result; - } - - if(http->postsize) { - result = expect100(data, conn, r); - if(result) - return result; - } - - /* end of headers */ - result = Curl_dyn_add(r, "\r\n"); - if(result) - return result; - - /* set the upload size to the progress meter */ - Curl_pgrsSetUploadSize(data, http->postsize); - - /* this sends the buffer and frees all the buffer resources */ - result = Curl_buffer_send(r, data, &data->info.request_size, 0, - FIRSTSOCKET); - if(result) - failf(data, "Failed sending PUT request"); - else - /* prepare for transfer */ - Curl_setup_transfer(data, FIRSTSOCKET, -1, TRUE, - http->postsize?FIRSTSOCKET:-1); - if(result) - return result; - break; - - case HTTPREQ_POST_FORM: - case HTTPREQ_POST_MIME: - /* This is form posting using mime data. */ - if(conn->bits.authneg) { - /* nothing to post! */ - result = Curl_dyn_add(r, "Content-Length: 0\r\n\r\n"); - if(result) - return result; - - result = Curl_buffer_send(r, data, &data->info.request_size, 0, - FIRSTSOCKET); - if(result) - failf(data, "Failed sending POST request"); - else - /* setup variables for the upcoming transfer */ - Curl_setup_transfer(data, FIRSTSOCKET, -1, TRUE, -1); - break; - } - - data->state.infilesize = http->postsize; - - /* We only set Content-Length and allow a custom Content-Length if - we don't upload data chunked, as RFC2616 forbids us to set both - kinds of headers (Transfer-Encoding: chunked and Content-Length) */ - if(http->postsize != -1 && !data->req.upload_chunky && - (conn->bits.authneg || !Curl_checkheaders(data, "Content-Length"))) { - /* we allow replacing this header if not during auth negotiation, - although it isn't very wise to actually set your own */ - result = Curl_dyn_addf(r, - "Content-Length: %" CURL_FORMAT_CURL_OFF_T - "\r\n", http->postsize); - if(result) - return result; - } - -#ifndef CURL_DISABLE_MIME - /* Output mime-generated headers. */ - { - struct curl_slist *hdr; - - for(hdr = http->sendit->curlheaders; hdr; hdr = hdr->next) { - result = Curl_dyn_addf(r, "%s\r\n", hdr->data); - if(result) - return result; - } - } -#endif - - /* For really small posts we don't use Expect: headers at all, and for - the somewhat bigger ones we allow the app to disable it. Just make - sure that the expect100header is always set to the preferred value - here. */ - ptr = Curl_checkheaders(data, "Expect"); - if(ptr) { - data->state.expect100header = - Curl_compareheader(ptr, "Expect:", "100-continue"); - } - else if(http->postsize > EXPECT_100_THRESHOLD || http->postsize < 0) { - result = expect100(data, conn, r); - if(result) - return result; - } - else - data->state.expect100header = FALSE; - - /* make the request end in a true CRLF */ - result = Curl_dyn_add(r, "\r\n"); - if(result) - return result; - - /* set the upload size to the progress meter */ - Curl_pgrsSetUploadSize(data, http->postsize); - - /* Read from mime structure. */ - data->state.fread_func = (curl_read_callback) Curl_mime_read; - data->state.in = (void *) http->sendit; - http->sending = HTTPSEND_BODY; - - /* this sends the buffer and frees all the buffer resources */ - result = Curl_buffer_send(r, data, &data->info.request_size, 0, - FIRSTSOCKET); - if(result) - failf(data, "Failed sending POST request"); - else - /* prepare for transfer */ - Curl_setup_transfer(data, FIRSTSOCKET, -1, TRUE, - http->postsize?FIRSTSOCKET:-1); - if(result) - return result; - - break; - - case HTTPREQ_POST: - /* this is the simple POST, using x-www-form-urlencoded style */ - - if(conn->bits.authneg) - http->postsize = 0; - else - /* the size of the post body */ - http->postsize = data->state.infilesize; - - /* We only set Content-Length and allow a custom Content-Length if - we don't upload data chunked, as RFC2616 forbids us to set both - kinds of headers (Transfer-Encoding: chunked and Content-Length) */ - if((http->postsize != -1) && !data->req.upload_chunky && - (conn->bits.authneg || !Curl_checkheaders(data, "Content-Length"))) { - /* we allow replacing this header if not during auth negotiation, - although it isn't very wise to actually set your own */ - result = Curl_dyn_addf(r, "Content-Length: %" CURL_FORMAT_CURL_OFF_T - "\r\n", http->postsize); - if(result) - return result; - } - - if(!Curl_checkheaders(data, "Content-Type")) { - result = Curl_dyn_add(r, "Content-Type: application/" - "x-www-form-urlencoded\r\n"); - if(result) - return result; - } - - /* For really small posts we don't use Expect: headers at all, and for - the somewhat bigger ones we allow the app to disable it. Just make - sure that the expect100header is always set to the preferred value - here. */ - ptr = Curl_checkheaders(data, "Expect"); - if(ptr) { - data->state.expect100header = - Curl_compareheader(ptr, "Expect:", "100-continue"); - } - else if(http->postsize > EXPECT_100_THRESHOLD || http->postsize < 0) { - result = expect100(data, conn, r); - if(result) - return result; - } - else - data->state.expect100header = FALSE; - -#ifndef USE_HYPER - /* With Hyper the body is always passed on separately */ - if(data->set.postfields) { - - /* In HTTP2, we send request body in DATA frame regardless of - its size. */ - if(conn->httpversion != 20 && - !data->state.expect100header && - (http->postsize < MAX_INITIAL_POST_SIZE)) { - /* if we don't use expect: 100 AND - postsize is less than MAX_INITIAL_POST_SIZE - - then append the post data to the HTTP request header. This limit - is no magic limit but only set to prevent really huge POSTs to - get the data duplicated with malloc() and family. */ - - /* end of headers! */ - result = Curl_dyn_add(r, "\r\n"); - if(result) - return result; - - if(!data->req.upload_chunky) { - /* We're not sending it 'chunked', append it to the request - already now to reduce the number if send() calls */ - result = Curl_dyn_addn(r, data->set.postfields, - (size_t)http->postsize); - included_body = http->postsize; - } - else { - if(http->postsize) { - char chunk[16]; - /* Append the POST data chunky-style */ - msnprintf(chunk, sizeof(chunk), "%x\r\n", (int)http->postsize); - result = Curl_dyn_add(r, chunk); - if(!result) { - included_body = http->postsize + strlen(chunk); - result = Curl_dyn_addn(r, data->set.postfields, - (size_t)http->postsize); - if(!result) - result = Curl_dyn_add(r, "\r\n"); - included_body += 2; - } - } - if(!result) { - result = Curl_dyn_add(r, "\x30\x0d\x0a\x0d\x0a"); - /* 0 CR LF CR LF */ - included_body += 5; - } - } - if(result) - return result; - /* Make sure the progress information is accurate */ - Curl_pgrsSetUploadSize(data, http->postsize); - } - else { - /* A huge POST coming up, do data separate from the request */ - http->postdata = data->set.postfields; - - http->sending = HTTPSEND_BODY; - - data->state.fread_func = (curl_read_callback)readmoredata; - data->state.in = (void *)data; - - /* set the upload size to the progress meter */ - Curl_pgrsSetUploadSize(data, http->postsize); - - /* end of headers! */ - result = Curl_dyn_add(r, "\r\n"); - if(result) - return result; - } - } - else -#endif - { - /* end of headers! */ - result = Curl_dyn_add(r, "\r\n"); - if(result) - return result; - - if(data->req.upload_chunky && conn->bits.authneg) { - /* Chunky upload is selected and we're negotiating auth still, send - end-of-data only */ - result = Curl_dyn_add(r, (char *)"\x30\x0d\x0a\x0d\x0a"); - /* 0 CR LF CR LF */ - if(result) - return result; - } - - else if(data->state.infilesize) { - /* set the upload size to the progress meter */ - Curl_pgrsSetUploadSize(data, http->postsize?http->postsize:-1); - - /* set the pointer to mark that we will send the post body using the - read callback, but only if we're not in authenticate negotiation */ - if(!conn->bits.authneg) - http->postdata = (char *)&http->postdata; - } - } - /* issue the request */ - result = Curl_buffer_send(r, data, &data->info.request_size, included_body, - FIRSTSOCKET); - - if(result) - failf(data, "Failed sending HTTP POST request"); - else - Curl_setup_transfer(data, FIRSTSOCKET, -1, TRUE, - http->postdata?FIRSTSOCKET:-1); - break; - - default: - result = Curl_dyn_add(r, "\r\n"); - if(result) - return result; - - /* issue the request */ - result = Curl_buffer_send(r, data, &data->info.request_size, 0, - FIRSTSOCKET); - - if(result) - failf(data, "Failed sending HTTP request"); - else - /* HTTP GET/HEAD download: */ - Curl_setup_transfer(data, FIRSTSOCKET, -1, TRUE, -1); - } - - return result; -} - -#if !defined(CURL_DISABLE_COOKIES) -CURLcode Curl_http_cookies(struct Curl_easy *data, - struct connectdata *conn, - struct dynbuf *r) -{ - CURLcode result = CURLE_OK; - char *addcookies = NULL; - if(data->set.str[STRING_COOKIE] && !Curl_checkheaders(data, "Cookie")) - addcookies = data->set.str[STRING_COOKIE]; - - if(data->cookies || addcookies) { - struct Cookie *co = NULL; /* no cookies from start */ - int count = 0; - - if(data->cookies && data->state.cookie_engine) { - const char *host = data->state.aptr.cookiehost ? - data->state.aptr.cookiehost : conn->host.name; - const bool secure_context = - conn->handler->protocol&CURLPROTO_HTTPS || - strcasecompare("localhost", host) || - !strcmp(host, "127.0.0.1") || - !strcmp(host, "[::1]") ? TRUE : FALSE; - Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE); - co = Curl_cookie_getlist(data->cookies, host, data->state.up.path, - secure_context); - Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); - } - if(co) { - struct Cookie *store = co; - /* now loop through all cookies that matched */ - while(co) { - if(co->value) { - if(0 == count) { - result = Curl_dyn_add(r, "Cookie: "); - if(result) - break; - } - result = Curl_dyn_addf(r, "%s%s=%s", count?"; ":"", - co->name, co->value); - if(result) - break; - count++; - } - co = co->next; /* next cookie please */ - } - Curl_cookie_freelist(store); - } - if(addcookies && !result) { - if(!count) - result = Curl_dyn_add(r, "Cookie: "); - if(!result) { - result = Curl_dyn_addf(r, "%s%s", count?"; ":"", addcookies); - count++; - } - } - if(count && !result) - result = Curl_dyn_add(r, "\r\n"); - - if(result) - return result; - } - return result; -} -#endif - -CURLcode Curl_http_range(struct Curl_easy *data, - Curl_HttpReq httpreq) -{ - if(data->state.use_range) { - /* - * A range is selected. We use different headers whether we're downloading - * or uploading and we always let customized headers override our internal - * ones if any such are specified. - */ - if(((httpreq == HTTPREQ_GET) || (httpreq == HTTPREQ_HEAD)) && - !Curl_checkheaders(data, "Range")) { - /* if a line like this was already allocated, free the previous one */ - free(data->state.aptr.rangeline); - data->state.aptr.rangeline = aprintf("Range: bytes=%s\r\n", - data->state.range); - } - else if((httpreq == HTTPREQ_POST || httpreq == HTTPREQ_PUT) && - !Curl_checkheaders(data, "Content-Range")) { - - /* if a line like this was already allocated, free the previous one */ - free(data->state.aptr.rangeline); - - if(data->set.set_resume_from < 0) { - /* Upload resume was asked for, but we don't know the size of the - remote part so we tell the server (and act accordingly) that we - upload the whole file (again) */ - data->state.aptr.rangeline = - aprintf("Content-Range: bytes 0-%" CURL_FORMAT_CURL_OFF_T - "/%" CURL_FORMAT_CURL_OFF_T "\r\n", - data->state.infilesize - 1, data->state.infilesize); - - } - else if(data->state.resume_from) { - /* This is because "resume" was selected */ - curl_off_t total_expected_size = - data->state.resume_from + data->state.infilesize; - data->state.aptr.rangeline = - aprintf("Content-Range: bytes %s%" CURL_FORMAT_CURL_OFF_T - "/%" CURL_FORMAT_CURL_OFF_T "\r\n", - data->state.range, total_expected_size-1, - total_expected_size); - } - else { - /* Range was selected and then we just pass the incoming range and - append total size */ - data->state.aptr.rangeline = - aprintf("Content-Range: bytes %s/%" CURL_FORMAT_CURL_OFF_T "\r\n", - data->state.range, data->state.infilesize); - } - if(!data->state.aptr.rangeline) - return CURLE_OUT_OF_MEMORY; - } - } - return CURLE_OK; -} - -CURLcode Curl_http_resume(struct Curl_easy *data, - struct connectdata *conn, - Curl_HttpReq httpreq) -{ - if((HTTPREQ_POST == httpreq || HTTPREQ_PUT == httpreq) && - data->state.resume_from) { - /********************************************************************** - * Resuming upload in HTTP means that we PUT or POST and that we have - * got a resume_from value set. The resume value has already created - * a Range: header that will be passed along. We need to "fast forward" - * the file the given number of bytes and decrease the assume upload - * file size before we continue this venture in the dark lands of HTTP. - * Resuming mime/form posting at an offset > 0 has no sense and is ignored. - *********************************************************************/ - - if(data->state.resume_from < 0) { - /* - * This is meant to get the size of the present remote-file by itself. - * We don't support this now. Bail out! - */ - data->state.resume_from = 0; - } - - if(data->state.resume_from && !data->state.this_is_a_follow) { - /* do we still game? */ - - /* Now, let's read off the proper amount of bytes from the - input. */ - int seekerr = CURL_SEEKFUNC_CANTSEEK; - if(conn->seek_func) { - Curl_set_in_callback(data, true); - seekerr = conn->seek_func(conn->seek_client, data->state.resume_from, - SEEK_SET); - Curl_set_in_callback(data, false); - } - - if(seekerr != CURL_SEEKFUNC_OK) { - curl_off_t passed = 0; - - if(seekerr != CURL_SEEKFUNC_CANTSEEK) { - failf(data, "Could not seek stream"); - return CURLE_READ_ERROR; - } - /* when seekerr == CURL_SEEKFUNC_CANTSEEK (can't seek to offset) */ - do { - size_t readthisamountnow = - (data->state.resume_from - passed > data->set.buffer_size) ? - (size_t)data->set.buffer_size : - curlx_sotouz(data->state.resume_from - passed); - - size_t actuallyread = - data->state.fread_func(data->state.buffer, 1, readthisamountnow, - data->state.in); - - passed += actuallyread; - if((actuallyread == 0) || (actuallyread > readthisamountnow)) { - /* this checks for greater-than only to make sure that the - CURL_READFUNC_ABORT return code still aborts */ - failf(data, "Could only read %" CURL_FORMAT_CURL_OFF_T - " bytes from the input", passed); - return CURLE_READ_ERROR; - } - } while(passed < data->state.resume_from); - } - - /* now, decrease the size of the read */ - if(data->state.infilesize>0) { - data->state.infilesize -= data->state.resume_from; - - if(data->state.infilesize <= 0) { - failf(data, "File already completely uploaded"); - return CURLE_PARTIAL_FILE; - } - } - /* we've passed, proceed as normal */ - } - } - return CURLE_OK; -} - -CURLcode Curl_http_firstwrite(struct Curl_easy *data, - struct connectdata *conn, - bool *done) -{ - struct SingleRequest *k = &data->req; - DEBUGASSERT(conn->handler->protocol&(PROTO_FAMILY_HTTP|CURLPROTO_RTSP)); - if(data->req.ignore_cl) { - k->size = k->maxdownload = -1; - } - else if(k->size != -1) { - /* We wait until after all headers have been received to set this so that - we know for sure Content-Length is valid. */ - if(data->set.max_filesize && - k->size > data->set.max_filesize) { - failf(data, "Maximum file size exceeded"); - return CURLE_FILESIZE_EXCEEDED; - } - Curl_pgrsSetDownloadSize(data, k->size); - } - - if(data->req.newurl) { - if(conn->bits.close) { - /* Abort after the headers if "follow Location" is set - and we're set to close anyway. */ - k->keepon &= ~KEEP_RECV; - *done = TRUE; - return CURLE_OK; - } - /* We have a new url to load, but since we want to be able to re-use this - connection properly, we read the full response in "ignore more" */ - k->ignorebody = TRUE; - infof(data, "Ignoring the response-body"); - } - if(data->state.resume_from && !k->content_range && - (data->state.httpreq == HTTPREQ_GET) && - !k->ignorebody) { - - if(k->size == data->state.resume_from) { - /* The resume point is at the end of file, consider this fine even if it - doesn't allow resume from here. */ - infof(data, "The entire document is already downloaded"); - connclose(conn, "already downloaded"); - /* Abort download */ - k->keepon &= ~KEEP_RECV; - *done = TRUE; - return CURLE_OK; - } - - /* we wanted to resume a download, although the server doesn't seem to - * support this and we did this with a GET (if it wasn't a GET we did a - * POST or PUT resume) */ - failf(data, "HTTP server doesn't seem to support " - "byte ranges. Cannot resume."); - return CURLE_RANGE_ERROR; - } - - if(data->set.timecondition && !data->state.range) { - /* A time condition has been set AND no ranges have been requested. This - seems to be what chapter 13.3.4 of RFC 2616 defines to be the correct - action for a HTTP/1.1 client */ - - if(!Curl_meets_timecondition(data, k->timeofdoc)) { - *done = TRUE; - /* We're simulating a http 304 from server so we return - what should have been returned from the server */ - data->info.httpcode = 304; - infof(data, "Simulate a HTTP 304 response!"); - /* we abort the transfer before it is completed == we ruin the - re-use ability. Close the connection */ - connclose(conn, "Simulated 304 handling"); - return CURLE_OK; - } - } /* we have a time condition */ - - return CURLE_OK; -} - -#ifdef HAVE_LIBZ -CURLcode Curl_transferencode(struct Curl_easy *data) -{ - if(!Curl_checkheaders(data, "TE") && - data->set.http_transfer_encoding) { - /* When we are to insert a TE: header in the request, we must also insert - TE in a Connection: header, so we need to merge the custom provided - Connection: header and prevent the original to get sent. Note that if - the user has inserted his/her own TE: header we don't do this magic - but then assume that the user will handle it all! */ - char *cptr = Curl_checkheaders(data, "Connection"); -#define TE_HEADER "TE: gzip\r\n" - - Curl_safefree(data->state.aptr.te); - - if(cptr) { - cptr = Curl_copy_header_value(cptr); - if(!cptr) - return CURLE_OUT_OF_MEMORY; - } - - /* Create the (updated) Connection: header */ - data->state.aptr.te = aprintf("Connection: %s%sTE\r\n" TE_HEADER, - cptr ? cptr : "", (cptr && *cptr) ? ", ":""); - - free(cptr); - if(!data->state.aptr.te) - return CURLE_OUT_OF_MEMORY; - } - return CURLE_OK; -} -#endif - -#ifndef USE_HYPER -/* - * Curl_http() gets called from the generic multi_do() function when a HTTP - * request is to be performed. This creates and sends a properly constructed - * HTTP request. - */ -CURLcode Curl_http(struct Curl_easy *data, bool *done) -{ - struct connectdata *conn = data->conn; - CURLcode result = CURLE_OK; - struct HTTP *http; - Curl_HttpReq httpreq; - const char *te = ""; /* transfer-encoding */ - const char *request; - const char *httpstring; - struct dynbuf req; - char *altused = NULL; - const char *p_accept; /* Accept: string */ - - /* Always consider the DO phase done after this function call, even if there - may be parts of the request that are not yet sent, since we can deal with - the rest of the request in the PERFORM phase. */ - *done = TRUE; - - if(conn->transport != TRNSPRT_QUIC) { - if(conn->httpversion < 20) { /* unless the connection is re-used and - already http2 */ - switch(conn->negnpn) { - case CURL_HTTP_VERSION_2: - conn->httpversion = 20; /* we know we're on HTTP/2 now */ - - result = Curl_http2_switched(data, NULL, 0); - if(result) - return result; - break; - case CURL_HTTP_VERSION_1_1: - /* continue with HTTP/1.1 when explicitly requested */ - break; - default: - /* Check if user wants to use HTTP/2 with clear TCP*/ -#ifdef USE_NGHTTP2 - if(data->state.httpwant == CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE) { -#ifndef CURL_DISABLE_PROXY - if(conn->bits.httpproxy && !conn->bits.tunnel_proxy) { - /* We don't support HTTP/2 proxies yet. Also it's debatable - whether or not this setting should apply to HTTP/2 proxies. */ - infof(data, "Ignoring HTTP/2 prior knowledge due to proxy"); - break; - } -#endif - DEBUGF(infof(data, "HTTP/2 over clean TCP")); - conn->httpversion = 20; - - result = Curl_http2_switched(data, NULL, 0); - if(result) - return result; - } -#endif - break; - } - } - else { - /* prepare for a http2 request */ - result = Curl_http2_setup(data, conn); - if(result) - return result; - } - } - http = data->req.p.http; - DEBUGASSERT(http); - - result = Curl_http_host(data, conn); - if(result) - return result; - - result = Curl_http_useragent(data); - if(result) - return result; - - Curl_http_method(data, conn, &request, &httpreq); - - /* setup the authentication headers */ - { - char *pq = NULL; - if(data->state.up.query) { - pq = aprintf("%s?%s", data->state.up.path, data->state.up.query); - if(!pq) - return CURLE_OUT_OF_MEMORY; - } - result = Curl_http_output_auth(data, conn, request, httpreq, - (pq ? pq : data->state.up.path), FALSE); - free(pq); - if(result) - return result; - } - - Curl_safefree(data->state.aptr.ref); - if(data->state.referer && !Curl_checkheaders(data, "Referer")) { - data->state.aptr.ref = aprintf("Referer: %s\r\n", data->state.referer); - if(!data->state.aptr.ref) - return CURLE_OUT_OF_MEMORY; - } - - if(!Curl_checkheaders(data, "Accept-Encoding") && - data->set.str[STRING_ENCODING]) { - Curl_safefree(data->state.aptr.accept_encoding); - data->state.aptr.accept_encoding = - aprintf("Accept-Encoding: %s\r\n", data->set.str[STRING_ENCODING]); - if(!data->state.aptr.accept_encoding) - return CURLE_OUT_OF_MEMORY; - } - else - Curl_safefree(data->state.aptr.accept_encoding); - -#ifdef HAVE_LIBZ - /* we only consider transfer-encoding magic if libz support is built-in */ - result = Curl_transferencode(data); - if(result) - return result; -#endif - - result = Curl_http_body(data, conn, httpreq, &te); - if(result) - return result; - - p_accept = Curl_checkheaders(data, "Accept")?NULL:"Accept: */*\r\n"; - - result = Curl_http_resume(data, conn, httpreq); - if(result) - return result; - - result = Curl_http_range(data, httpreq); - if(result) - return result; - - httpstring = get_http_string(data, conn); - - /* initialize a dynamic send-buffer */ - Curl_dyn_init(&req, DYN_HTTP_REQUEST); - - /* make sure the header buffer is reset - if there are leftovers from a - previous transfer */ - Curl_dyn_reset(&data->state.headerb); - - /* add the main request stuff */ - /* GET/HEAD/POST/PUT */ - result = Curl_dyn_addf(&req, "%s ", request); - if(!result) - result = Curl_http_target(data, conn, &req); - if(result) { - Curl_dyn_free(&req); - return result; - } - -#ifndef CURL_DISABLE_ALTSVC - if(conn->bits.altused && !Curl_checkheaders(data, "Alt-Used")) { - altused = aprintf("Alt-Used: %s:%d\r\n", - conn->conn_to_host.name, conn->conn_to_port); - if(!altused) { - Curl_dyn_free(&req); - return CURLE_OUT_OF_MEMORY; - } - } -#endif - result = - Curl_dyn_addf(&req, - " HTTP/%s\r\n" /* HTTP version */ - "%s" /* host */ - "%s" /* proxyuserpwd */ - "%s" /* userpwd */ - "%s" /* range */ - "%s" /* user agent */ - "%s" /* accept */ - "%s" /* TE: */ - "%s" /* accept-encoding */ - "%s" /* referer */ - "%s" /* Proxy-Connection */ - "%s" /* transfer-encoding */ - "%s",/* Alt-Used */ - - httpstring, - (data->state.aptr.host?data->state.aptr.host:""), - data->state.aptr.proxyuserpwd? - data->state.aptr.proxyuserpwd:"", - data->state.aptr.userpwd?data->state.aptr.userpwd:"", - (data->state.use_range && data->state.aptr.rangeline)? - data->state.aptr.rangeline:"", - (data->set.str[STRING_USERAGENT] && - *data->set.str[STRING_USERAGENT] && - data->state.aptr.uagent)? - data->state.aptr.uagent:"", - p_accept?p_accept:"", - data->state.aptr.te?data->state.aptr.te:"", - (data->set.str[STRING_ENCODING] && - *data->set.str[STRING_ENCODING] && - data->state.aptr.accept_encoding)? - data->state.aptr.accept_encoding:"", - (data->state.referer && data->state.aptr.ref)? - data->state.aptr.ref:"" /* Referer: */, -#ifndef CURL_DISABLE_PROXY - (conn->bits.httpproxy && - !conn->bits.tunnel_proxy && - !Curl_checkheaders(data, "Proxy-Connection") && - !Curl_checkProxyheaders(data, conn, "Proxy-Connection"))? - "Proxy-Connection: Keep-Alive\r\n":"", -#else - "", -#endif - te, - altused ? altused : "" - ); - - /* clear userpwd and proxyuserpwd to avoid re-using old credentials - * from re-used connections */ - Curl_safefree(data->state.aptr.userpwd); - Curl_safefree(data->state.aptr.proxyuserpwd); - free(altused); - - if(result) { - Curl_dyn_free(&req); - return result; - } - - if(!(conn->handler->flags&PROTOPT_SSL) && - conn->httpversion != 20 && - (data->state.httpwant == CURL_HTTP_VERSION_2)) { - /* append HTTP2 upgrade magic stuff to the HTTP request if it isn't done - over SSL */ - result = Curl_http2_request_upgrade(&req, data); - if(result) { - Curl_dyn_free(&req); - return result; - } - } - - result = Curl_http_cookies(data, conn, &req); - if(!result) - result = Curl_add_timecondition(data, &req); - if(!result) - result = Curl_add_custom_headers(data, FALSE, &req); - - if(!result) { - http->postdata = NULL; /* nothing to post at this point */ - if((httpreq == HTTPREQ_GET) || - (httpreq == HTTPREQ_HEAD)) - Curl_pgrsSetUploadSize(data, 0); /* nothing */ - - /* bodysend takes ownership of the 'req' memory on success */ - result = Curl_http_bodysend(data, conn, &req, httpreq); - } - if(result) { - Curl_dyn_free(&req); - return result; - } - - if((http->postsize > -1) && - (http->postsize <= data->req.writebytecount) && - (http->sending != HTTPSEND_REQUEST)) - data->req.upload_done = TRUE; - - if(data->req.writebytecount) { - /* if a request-body has been sent off, we make sure this progress is noted - properly */ - Curl_pgrsSetUploadCounter(data, data->req.writebytecount); - if(Curl_pgrsUpdate(data)) - result = CURLE_ABORTED_BY_CALLBACK; - - if(!http->postsize) { - /* already sent the entire request body, mark the "upload" as - complete */ - infof(data, "upload completely sent off: %" CURL_FORMAT_CURL_OFF_T - " out of %" CURL_FORMAT_CURL_OFF_T " bytes", - data->req.writebytecount, http->postsize); - data->req.upload_done = TRUE; - data->req.keepon &= ~KEEP_SEND; /* we're done writing */ - data->req.exp100 = EXP100_SEND_DATA; /* already sent */ - Curl_expire_done(data, EXPIRE_100_TIMEOUT); - } - } - - if((conn->httpversion == 20) && data->req.upload_chunky) - /* upload_chunky was set above to set up the request in a chunky fashion, - but is disabled here again to avoid that the chunked encoded version is - actually used when sending the request body over h2 */ - data->req.upload_chunky = FALSE; - return result; -} - -#endif /* USE_HYPER */ - -typedef enum { - STATUS_UNKNOWN, /* not enough data to tell yet */ - STATUS_DONE, /* a status line was read */ - STATUS_BAD /* not a status line */ -} statusline; - - -/* Check a string for a prefix. Check no more than 'len' bytes */ -static bool checkprefixmax(const char *prefix, const char *buffer, size_t len) -{ - size_t ch = CURLMIN(strlen(prefix), len); - return curl_strnequal(prefix, buffer, ch); -} - -/* - * checkhttpprefix() - * - * Returns TRUE if member of the list matches prefix of string - */ -static statusline -checkhttpprefix(struct Curl_easy *data, - const char *s, size_t len) -{ - struct curl_slist *head = data->set.http200aliases; - statusline rc = STATUS_BAD; - statusline onmatch = len >= 5? STATUS_DONE : STATUS_UNKNOWN; -#ifdef CURL_DOES_CONVERSIONS - /* convert from the network encoding using a scratch area */ - char *scratch = strdup(s); - if(NULL == scratch) { - failf(data, "Failed to allocate memory for conversion!"); - return FALSE; /* can't return CURLE_OUT_OF_MEMORY so return FALSE */ - } - if(CURLE_OK != Curl_convert_from_network(data, scratch, strlen(s) + 1)) { - /* Curl_convert_from_network calls failf if unsuccessful */ - free(scratch); - return FALSE; /* can't return CURLE_foobar so return FALSE */ - } - s = scratch; -#endif /* CURL_DOES_CONVERSIONS */ - - while(head) { - if(checkprefixmax(head->data, s, len)) { - rc = onmatch; - break; - } - head = head->next; - } - - if((rc != STATUS_DONE) && (checkprefixmax("HTTP/", s, len))) - rc = onmatch; - -#ifdef CURL_DOES_CONVERSIONS - free(scratch); -#endif /* CURL_DOES_CONVERSIONS */ - return rc; -} - -#ifndef CURL_DISABLE_RTSP -static statusline -checkrtspprefix(struct Curl_easy *data, - const char *s, size_t len) -{ - statusline result = STATUS_BAD; - statusline onmatch = len >= 5? STATUS_DONE : STATUS_UNKNOWN; - -#ifdef CURL_DOES_CONVERSIONS - /* convert from the network encoding using a scratch area */ - char *scratch = strdup(s); - if(NULL == scratch) { - failf(data, "Failed to allocate memory for conversion!"); - return FALSE; /* can't return CURLE_OUT_OF_MEMORY so return FALSE */ - } - if(CURLE_OK != Curl_convert_from_network(data, scratch, strlen(s) + 1)) { - /* Curl_convert_from_network calls failf if unsuccessful */ - result = FALSE; /* can't return CURLE_foobar so return FALSE */ - } - else if(checkprefixmax("RTSP/", scratch, len)) - result = onmatch; - free(scratch); -#else - (void)data; /* unused */ - if(checkprefixmax("RTSP/", s, len)) - result = onmatch; -#endif /* CURL_DOES_CONVERSIONS */ - - return result; -} -#endif /* CURL_DISABLE_RTSP */ - -static statusline -checkprotoprefix(struct Curl_easy *data, struct connectdata *conn, - const char *s, size_t len) -{ -#ifndef CURL_DISABLE_RTSP - if(conn->handler->protocol & CURLPROTO_RTSP) - return checkrtspprefix(data, s, len); -#else - (void)conn; -#endif /* CURL_DISABLE_RTSP */ - - return checkhttpprefix(data, s, len); -} - -/* - * Curl_http_header() parses a single response header. - */ -CURLcode Curl_http_header(struct Curl_easy *data, struct connectdata *conn, - char *headp) -{ - CURLcode result; - struct SingleRequest *k = &data->req; - /* Check for Content-Length: header lines to get size */ - if(!k->http_bodyless && - !data->set.ignorecl && checkprefix("Content-Length:", headp)) { - curl_off_t contentlength; - CURLofft offt = curlx_strtoofft(headp + strlen("Content-Length:"), - NULL, 10, &contentlength); - - if(offt == CURL_OFFT_OK) { - k->size = contentlength; - k->maxdownload = k->size; - } - else if(offt == CURL_OFFT_FLOW) { - /* out of range */ - if(data->set.max_filesize) { - failf(data, "Maximum file size exceeded"); - return CURLE_FILESIZE_EXCEEDED; - } - streamclose(conn, "overflow content-length"); - infof(data, "Overflow Content-Length: value!"); - } - else { - /* negative or just rubbish - bad HTTP */ - failf(data, "Invalid Content-Length: value"); - return CURLE_WEIRD_SERVER_REPLY; - } - } - /* check for Content-Type: header lines to get the MIME-type */ - else if(checkprefix("Content-Type:", headp)) { - char *contenttype = Curl_copy_header_value(headp); - if(!contenttype) - return CURLE_OUT_OF_MEMORY; - if(!*contenttype) - /* ignore empty data */ - free(contenttype); - else { - Curl_safefree(data->info.contenttype); - data->info.contenttype = contenttype; - } - } -#ifndef CURL_DISABLE_PROXY - else if((conn->httpversion == 10) && - conn->bits.httpproxy && - Curl_compareheader(headp, "Proxy-Connection:", "keep-alive")) { - /* - * When a HTTP/1.0 reply comes when using a proxy, the - * 'Proxy-Connection: keep-alive' line tells us the - * connection will be kept alive for our pleasure. - * Default action for 1.0 is to close. - */ - connkeep(conn, "Proxy-Connection keep-alive"); /* don't close */ - infof(data, "HTTP/1.0 proxy connection set to keep alive!"); - } - else if((conn->httpversion == 11) && - conn->bits.httpproxy && - Curl_compareheader(headp, "Proxy-Connection:", "close")) { - /* - * We get a HTTP/1.1 response from a proxy and it says it'll - * close down after this transfer. - */ - connclose(conn, "Proxy-Connection: asked to close after done"); - infof(data, "HTTP/1.1 proxy connection set close!"); - } -#endif - else if((conn->httpversion == 10) && - Curl_compareheader(headp, "Connection:", "keep-alive")) { - /* - * A HTTP/1.0 reply with the 'Connection: keep-alive' line - * tells us the connection will be kept alive for our - * pleasure. Default action for 1.0 is to close. - * - * [RFC2068, section 19.7.1] */ - connkeep(conn, "Connection keep-alive"); - infof(data, "HTTP/1.0 connection set to keep alive!"); - } - else if(Curl_compareheader(headp, "Connection:", "close")) { - /* - * [RFC 2616, section 8.1.2.1] - * "Connection: close" is HTTP/1.1 language and means that - * the connection will close when this request has been - * served. - */ - streamclose(conn, "Connection: close used"); - } - else if(!k->http_bodyless && checkprefix("Transfer-Encoding:", headp)) { - /* One or more encodings. We check for chunked and/or a compression - algorithm. */ - /* - * [RFC 2616, section 3.6.1] A 'chunked' transfer encoding - * means that the server will send a series of "chunks". Each - * chunk starts with line with info (including size of the - * coming block) (terminated with CRLF), then a block of data - * with the previously mentioned size. There can be any amount - * of chunks, and a chunk-data set to zero signals the - * end-of-chunks. */ - - result = Curl_build_unencoding_stack(data, - headp + strlen("Transfer-Encoding:"), - TRUE); - if(result) - return result; - if(!k->chunk) { - /* if this isn't chunked, only close can signal the end of this transfer - as Content-Length is said not to be trusted for transfer-encoding! */ - connclose(conn, "HTTP/1.1 transfer-encoding without chunks"); - k->ignore_cl = TRUE; - } - } - else if(!k->http_bodyless && checkprefix("Content-Encoding:", headp) && - data->set.str[STRING_ENCODING]) { - /* - * Process Content-Encoding. Look for the values: identity, - * gzip, deflate, compress, x-gzip and x-compress. x-gzip and - * x-compress are the same as gzip and compress. (Sec 3.5 RFC - * 2616). zlib cannot handle compress. However, errors are - * handled further down when the response body is processed - */ - result = Curl_build_unencoding_stack(data, - headp + strlen("Content-Encoding:"), - FALSE); - if(result) - return result; - } - else if(checkprefix("Retry-After:", headp)) { - /* Retry-After = HTTP-date / delay-seconds */ - curl_off_t retry_after = 0; /* zero for unknown or "now" */ - time_t date = Curl_getdate_capped(headp + strlen("Retry-After:")); - if(-1 == date) { - /* not a date, try it as a decimal number */ - (void)curlx_strtoofft(headp + strlen("Retry-After:"), - NULL, 10, &retry_after); - } - else - /* convert date to number of seconds into the future */ - retry_after = date - time(NULL); - data->info.retry_after = retry_after; /* store it */ - } - else if(!k->http_bodyless && checkprefix("Content-Range:", headp)) { - /* Content-Range: bytes [num]- - Content-Range: bytes: [num]- - Content-Range: [num]- - Content-Range: [asterisk]/[total] - - The second format was added since Sun's webserver - JavaWebServer/1.1.1 obviously sends the header this way! - The third added since some servers use that! - The forth means the requested range was unsatisfied. - */ - - char *ptr = headp + strlen("Content-Range:"); - - /* Move forward until first digit or asterisk */ - while(*ptr && !ISDIGIT(*ptr) && *ptr != '*') - ptr++; - - /* if it truly stopped on a digit */ - if(ISDIGIT(*ptr)) { - if(!curlx_strtoofft(ptr, NULL, 10, &k->offset)) { - if(data->state.resume_from == k->offset) - /* we asked for a resume and we got it */ - k->content_range = TRUE; - } - } - else - data->state.resume_from = 0; /* get everything */ - } -#if !defined(CURL_DISABLE_COOKIES) - else if(data->cookies && data->state.cookie_engine && - checkprefix("Set-Cookie:", headp)) { - /* If there is a custom-set Host: name, use it here, or else use real peer - host name. */ - const char *host = data->state.aptr.cookiehost? - data->state.aptr.cookiehost:conn->host.name; - const bool secure_context = - conn->handler->protocol&CURLPROTO_HTTPS || - strcasecompare("localhost", host) || - !strcmp(host, "127.0.0.1") || - !strcmp(host, "[::1]") ? TRUE : FALSE; - - Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, - CURL_LOCK_ACCESS_SINGLE); - Curl_cookie_add(data, data->cookies, TRUE, FALSE, - headp + strlen("Set-Cookie:"), host, - data->state.up.path, secure_context); - Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE); - } -#endif - else if(!k->http_bodyless && checkprefix("Last-Modified:", headp) && - (data->set.timecondition || data->set.get_filetime) ) { - k->timeofdoc = Curl_getdate_capped(headp + strlen("Last-Modified:")); - if(data->set.get_filetime) - data->info.filetime = k->timeofdoc; - } - else if((checkprefix("WWW-Authenticate:", headp) && - (401 == k->httpcode)) || - (checkprefix("Proxy-authenticate:", headp) && - (407 == k->httpcode))) { - - bool proxy = (k->httpcode == 407) ? TRUE : FALSE; - char *auth = Curl_copy_header_value(headp); - if(!auth) - return CURLE_OUT_OF_MEMORY; - - result = Curl_http_input_auth(data, proxy, auth); - - free(auth); - - if(result) - return result; - } -#ifdef USE_SPNEGO - else if(checkprefix("Persistent-Auth:", headp)) { - struct negotiatedata *negdata = &conn->negotiate; - struct auth *authp = &data->state.authhost; - if(authp->picked == CURLAUTH_NEGOTIATE) { - char *persistentauth = Curl_copy_header_value(headp); - if(!persistentauth) - return CURLE_OUT_OF_MEMORY; - negdata->noauthpersist = checkprefix("false", persistentauth)? - TRUE:FALSE; - negdata->havenoauthpersist = TRUE; - infof(data, "Negotiate: noauthpersist -> %d, header part: %s", - negdata->noauthpersist, persistentauth); - free(persistentauth); - } - } -#endif - else if((k->httpcode >= 300 && k->httpcode < 400) && - checkprefix("Location:", headp) && - !data->req.location) { - /* this is the URL that the server advises us to use instead */ - char *location = Curl_copy_header_value(headp); - if(!location) - return CURLE_OUT_OF_MEMORY; - if(!*location) - /* ignore empty data */ - free(location); - else { - data->req.location = location; - - if(data->set.http_follow_location) { - DEBUGASSERT(!data->req.newurl); - data->req.newurl = strdup(data->req.location); /* clone */ - if(!data->req.newurl) - return CURLE_OUT_OF_MEMORY; - - /* some cases of POST and PUT etc needs to rewind the data - stream at this point */ - result = http_perhapsrewind(data, conn); - if(result) - return result; - } - } - } - -#ifndef CURL_DISABLE_HSTS - /* If enabled, the header is incoming and this is over HTTPS */ - else if(data->hsts && checkprefix("Strict-Transport-Security:", headp) && - (conn->handler->flags & PROTOPT_SSL)) { - CURLcode check = - Curl_hsts_parse(data->hsts, data->state.up.hostname, - headp + strlen("Strict-Transport-Security:")); - if(check) - infof(data, "Illegal STS header skipped"); -#ifdef DEBUGBUILD - else - infof(data, "Parsed STS header fine (%zu entries)", - data->hsts->list.size); -#endif - } -#endif -#ifndef CURL_DISABLE_ALTSVC - /* If enabled, the header is incoming and this is over HTTPS */ - else if(data->asi && checkprefix("Alt-Svc:", headp) && - ((conn->handler->flags & PROTOPT_SSL) || -#ifdef CURLDEBUG - /* allow debug builds to circumvent the HTTPS restriction */ - getenv("CURL_ALTSVC_HTTP") -#else - 0 -#endif - )) { - /* the ALPN of the current request */ - enum alpnid id = (conn->httpversion == 20) ? ALPN_h2 : ALPN_h1; - result = Curl_altsvc_parse(data, data->asi, - headp + strlen("Alt-Svc:"), - id, conn->host.name, - curlx_uitous(conn->remote_port)); - if(result) - return result; - } -#endif - else if(conn->handler->protocol & CURLPROTO_RTSP) { - result = Curl_rtsp_parseheader(data, headp); - if(result) - return result; - } - return CURLE_OK; -} - -/* - * Called after the first HTTP response line (the status line) has been - * received and parsed. - */ - -CURLcode Curl_http_statusline(struct Curl_easy *data, - struct connectdata *conn) -{ - struct SingleRequest *k = &data->req; - data->info.httpcode = k->httpcode; - - data->info.httpversion = conn->httpversion; - if(!data->state.httpversion || - data->state.httpversion > conn->httpversion) - /* store the lowest server version we encounter */ - data->state.httpversion = conn->httpversion; - - /* - * This code executes as part of processing the header. As a - * result, it's not totally clear how to interpret the - * response code yet as that depends on what other headers may - * be present. 401 and 407 may be errors, but may be OK - * depending on how authentication is working. Other codes - * are definitely errors, so give up here. - */ - if(data->state.resume_from && data->state.httpreq == HTTPREQ_GET && - k->httpcode == 416) { - /* "Requested Range Not Satisfiable", just proceed and - pretend this is no error */ - k->ignorebody = TRUE; /* Avoid appending error msg to good data. */ - } - - if(conn->httpversion == 10) { - /* Default action for HTTP/1.0 must be to close, unless - we get one of those fancy headers that tell us the - server keeps it open for us! */ - infof(data, "HTTP 1.0, assume close after body"); - connclose(conn, "HTTP/1.0 close after body"); - } - else if(conn->httpversion == 20 || - (k->upgr101 == UPGR101_REQUESTED && k->httpcode == 101)) { - DEBUGF(infof(data, "HTTP/2 found, allow multiplexing")); - /* HTTP/2 cannot avoid multiplexing since it is a core functionality - of the protocol */ - conn->bundle->multiuse = BUNDLE_MULTIPLEX; - } - else if(conn->httpversion >= 11 && - !conn->bits.close) { - /* If HTTP version is >= 1.1 and connection is persistent */ - DEBUGF(infof(data, - "HTTP 1.1 or later with persistent connection")); - } - - k->http_bodyless = k->httpcode >= 100 && k->httpcode < 200; - switch(k->httpcode) { - case 304: - /* (quote from RFC2616, section 10.3.5): The 304 response - * MUST NOT contain a message-body, and thus is always - * terminated by the first empty line after the header - * fields. */ - if(data->set.timecondition) - data->info.timecond = TRUE; - /* FALLTHROUGH */ - case 204: - /* (quote from RFC2616, section 10.2.5): The server has - * fulfilled the request but does not need to return an - * entity-body ... The 204 response MUST NOT include a - * message-body, and thus is always terminated by the first - * empty line after the header fields. */ - k->size = 0; - k->maxdownload = 0; - k->http_bodyless = TRUE; - break; - default: - break; - } - return CURLE_OK; -} - -/* - * Read any HTTP header lines from the server and pass them to the client app. - */ -CURLcode Curl_http_readwrite_headers(struct Curl_easy *data, - struct connectdata *conn, - ssize_t *nread, - bool *stop_reading) -{ - CURLcode result; - struct SingleRequest *k = &data->req; - ssize_t onread = *nread; - char *ostr = k->str; - char *headp; - char *str_start; - char *end_ptr; - - /* header line within buffer loop */ - do { - size_t rest_length; - size_t full_length; - int writetype; - - /* str_start is start of line within buf */ - str_start = k->str; - - /* data is in network encoding so use 0x0a instead of '\n' */ - end_ptr = memchr(str_start, 0x0a, *nread); - - if(!end_ptr) { - /* Not a complete header line within buffer, append the data to - the end of the headerbuff. */ - result = Curl_dyn_addn(&data->state.headerb, str_start, *nread); - if(result) - return result; - - if(!k->headerline) { - /* check if this looks like a protocol header */ - statusline st = - checkprotoprefix(data, conn, - Curl_dyn_ptr(&data->state.headerb), - Curl_dyn_len(&data->state.headerb)); - - if(st == STATUS_BAD) { - /* this is not the beginning of a protocol first header line */ - k->header = FALSE; - k->badheader = HEADER_ALLBAD; - streamclose(conn, "bad HTTP: No end-of-message indicator"); - if(!data->set.http09_allowed) { - failf(data, "Received HTTP/0.9 when not allowed"); - return CURLE_UNSUPPORTED_PROTOCOL; - } - break; - } - } - - break; /* read more and try again */ - } - - /* decrease the size of the remaining (supposed) header line */ - rest_length = (end_ptr - k->str) + 1; - *nread -= (ssize_t)rest_length; - - k->str = end_ptr + 1; /* move past new line */ - - full_length = k->str - str_start; - - result = Curl_dyn_addn(&data->state.headerb, str_start, full_length); - if(result) - return result; - - /**** - * We now have a FULL header line in 'headerb'. - *****/ - - if(!k->headerline) { - /* the first read header */ - statusline st = checkprotoprefix(data, conn, - Curl_dyn_ptr(&data->state.headerb), - Curl_dyn_len(&data->state.headerb)); - if(st == STATUS_BAD) { - streamclose(conn, "bad HTTP: No end-of-message indicator"); - /* this is not the beginning of a protocol first header line */ - if(!data->set.http09_allowed) { - failf(data, "Received HTTP/0.9 when not allowed"); - return CURLE_UNSUPPORTED_PROTOCOL; - } - k->header = FALSE; - if(*nread) - /* since there's more, this is a partial bad header */ - k->badheader = HEADER_PARTHEADER; - else { - /* this was all we read so it's all a bad header */ - k->badheader = HEADER_ALLBAD; - *nread = onread; - k->str = ostr; - return CURLE_OK; - } - break; - } - } - - /* headers are in network encoding so use 0x0a and 0x0d instead of '\n' - and '\r' */ - headp = Curl_dyn_ptr(&data->state.headerb); - if((0x0a == *headp) || (0x0d == *headp)) { - size_t headerlen; - /* Zero-length header line means end of headers! */ - -#ifdef CURL_DOES_CONVERSIONS - if(0x0d == *headp) { - *headp = '\r'; /* replace with CR in host encoding */ - headp++; /* pass the CR byte */ - } - if(0x0a == *headp) { - *headp = '\n'; /* replace with LF in host encoding */ - headp++; /* pass the LF byte */ - } -#else - if('\r' == *headp) - headp++; /* pass the \r byte */ - if('\n' == *headp) - headp++; /* pass the \n byte */ -#endif /* CURL_DOES_CONVERSIONS */ - - if(100 <= k->httpcode && 199 >= k->httpcode) { - /* "A user agent MAY ignore unexpected 1xx status responses." */ - switch(k->httpcode) { - case 100: - /* - * We have made a HTTP PUT or POST and this is 1.1-lingo - * that tells us that the server is OK with this and ready - * to receive the data. - * However, we'll get more headers now so we must get - * back into the header-parsing state! - */ - k->header = TRUE; - k->headerline = 0; /* restart the header line counter */ - - /* if we did wait for this do enable write now! */ - if(k->exp100 > EXP100_SEND_DATA) { - k->exp100 = EXP100_SEND_DATA; - k->keepon |= KEEP_SEND; - Curl_expire_done(data, EXPIRE_100_TIMEOUT); - } - break; - case 101: - /* Switching Protocols */ - if(k->upgr101 == UPGR101_REQUESTED) { - /* Switching to HTTP/2 */ - infof(data, "Received 101"); - k->upgr101 = UPGR101_RECEIVED; - - /* we'll get more headers (HTTP/2 response) */ - k->header = TRUE; - k->headerline = 0; /* restart the header line counter */ - - /* switch to http2 now. The bytes after response headers - are also processed here, otherwise they are lost. */ - result = Curl_http2_switched(data, k->str, *nread); - if(result) - return result; - *nread = 0; - } - else { - /* Switching to another protocol (e.g. WebSocket) */ - k->header = FALSE; /* no more header to parse! */ - } - break; - default: - /* the status code 1xx indicates a provisional response, so - we'll get another set of headers */ - k->header = TRUE; - k->headerline = 0; /* restart the header line counter */ - break; - } - } - else { - k->header = FALSE; /* no more header to parse! */ - - if((k->size == -1) && !k->chunk && !conn->bits.close && - (conn->httpversion == 11) && - !(conn->handler->protocol & CURLPROTO_RTSP) && - data->state.httpreq != HTTPREQ_HEAD) { - /* On HTTP 1.1, when connection is not to get closed, but no - Content-Length nor Transfer-Encoding chunked have been - received, according to RFC2616 section 4.4 point 5, we - assume that the server will close the connection to - signal the end of the document. */ - infof(data, "no chunk, no close, no size. Assume close to " - "signal end"); - streamclose(conn, "HTTP: No end-of-message indicator"); - } - } - - /* At this point we have some idea about the fate of the connection. - If we are closing the connection it may result auth failure. */ -#if defined(USE_NTLM) - if(conn->bits.close && - (((data->req.httpcode == 401) && - (conn->http_ntlm_state == NTLMSTATE_TYPE2)) || - ((data->req.httpcode == 407) && - (conn->proxy_ntlm_state == NTLMSTATE_TYPE2)))) { - infof(data, "Connection closure while negotiating auth (HTTP 1.0?)"); - data->state.authproblem = TRUE; - } -#endif -#if defined(USE_SPNEGO) - if(conn->bits.close && - (((data->req.httpcode == 401) && - (conn->http_negotiate_state == GSS_AUTHRECV)) || - ((data->req.httpcode == 407) && - (conn->proxy_negotiate_state == GSS_AUTHRECV)))) { - infof(data, "Connection closure while negotiating auth (HTTP 1.0?)"); - data->state.authproblem = TRUE; - } - if((conn->http_negotiate_state == GSS_AUTHDONE) && - (data->req.httpcode != 401)) { - conn->http_negotiate_state = GSS_AUTHSUCC; - } - if((conn->proxy_negotiate_state == GSS_AUTHDONE) && - (data->req.httpcode != 407)) { - conn->proxy_negotiate_state = GSS_AUTHSUCC; - } -#endif - - /* now, only output this if the header AND body are requested: - */ - writetype = CLIENTWRITE_HEADER; - if(data->set.include_header) - writetype |= CLIENTWRITE_BODY; - - headerlen = Curl_dyn_len(&data->state.headerb); - result = Curl_client_write(data, writetype, - Curl_dyn_ptr(&data->state.headerb), - headerlen); - if(result) - return result; - - data->info.header_size += (long)headerlen; - data->req.headerbytecount += (long)headerlen; - - /* - * When all the headers have been parsed, see if we should give - * up and return an error. - */ - if(http_should_fail(data)) { - failf(data, "The requested URL returned error: %d", - k->httpcode); - return CURLE_HTTP_RETURNED_ERROR; - } - - data->req.deductheadercount = - (100 <= k->httpcode && 199 >= k->httpcode)?data->req.headerbytecount:0; - - /* Curl_http_auth_act() checks what authentication methods - * that are available and decides which one (if any) to - * use. It will set 'newurl' if an auth method was picked. */ - result = Curl_http_auth_act(data); - - if(result) - return result; - - if(k->httpcode >= 300) { - if((!conn->bits.authneg) && !conn->bits.close && - !conn->bits.rewindaftersend) { - /* - * General treatment of errors when about to send data. Including : - * "417 Expectation Failed", while waiting for 100-continue. - * - * The check for close above is done simply because of something - * else has already deemed the connection to get closed then - * something else should've considered the big picture and we - * avoid this check. - * - * rewindaftersend indicates that something has told libcurl to - * continue sending even if it gets discarded - */ - - switch(data->state.httpreq) { - case HTTPREQ_PUT: - case HTTPREQ_POST: - case HTTPREQ_POST_FORM: - case HTTPREQ_POST_MIME: - /* We got an error response. If this happened before the whole - * request body has been sent we stop sending and mark the - * connection for closure after we've read the entire response. - */ - Curl_expire_done(data, EXPIRE_100_TIMEOUT); - if(!k->upload_done) { - if((k->httpcode == 417) && data->state.expect100header) { - /* 417 Expectation Failed - try again without the Expect - header */ - infof(data, "Got 417 while waiting for a 100"); - data->state.disableexpect = TRUE; - DEBUGASSERT(!data->req.newurl); - data->req.newurl = strdup(data->state.url); - Curl_done_sending(data, k); - } - else if(data->set.http_keep_sending_on_error) { - infof(data, "HTTP error before end of send, keep sending"); - if(k->exp100 > EXP100_SEND_DATA) { - k->exp100 = EXP100_SEND_DATA; - k->keepon |= KEEP_SEND; - } - } - else { - infof(data, "HTTP error before end of send, stop sending"); - streamclose(conn, "Stop sending data before everything sent"); - result = Curl_done_sending(data, k); - if(result) - return result; - k->upload_done = TRUE; - if(data->state.expect100header) - k->exp100 = EXP100_FAILED; - } - } - break; - - default: /* default label present to avoid compiler warnings */ - break; - } - } - - if(conn->bits.rewindaftersend) { - /* We rewind after a complete send, so thus we continue - sending now */ - infof(data, "Keep sending data to get tossed away!"); - k->keepon |= KEEP_SEND; - } - } - - if(!k->header) { - /* - * really end-of-headers. - * - * If we requested a "no body", this is a good time to get - * out and return home. - */ - if(data->set.opt_no_body) - *stop_reading = TRUE; -#ifndef CURL_DISABLE_RTSP - else if((conn->handler->protocol & CURLPROTO_RTSP) && - (data->set.rtspreq == RTSPREQ_DESCRIBE) && - (k->size <= -1)) - /* Respect section 4.4 of rfc2326: If the Content-Length header is - absent, a length 0 must be assumed. It will prevent libcurl from - hanging on DESCRIBE request that got refused for whatever - reason */ - *stop_reading = TRUE; -#endif - else { - /* If we know the expected size of this document, we set the - maximum download size to the size of the expected - document or else, we won't know when to stop reading! - - Note that we set the download maximum even if we read a - "Connection: close" header, to make sure that - "Content-Length: 0" still prevents us from attempting to - read the (missing) response-body. - */ - /* According to RFC2616 section 4.4, we MUST ignore - Content-Length: headers if we are now receiving data - using chunked Transfer-Encoding. - */ - if(k->chunk) - k->maxdownload = k->size = -1; - } - if(-1 != k->size) { - /* We do this operation even if no_body is true, since this - data might be retrieved later with curl_easy_getinfo() - and its CURLINFO_CONTENT_LENGTH_DOWNLOAD option. */ - - Curl_pgrsSetDownloadSize(data, k->size); - k->maxdownload = k->size; - } - - /* If max download size is *zero* (nothing) we already have - nothing and can safely return ok now! But for HTTP/2, we'd - like to call http2_handle_stream_close to properly close a - stream. In order to do this, we keep reading until we - close the stream. */ - if(0 == k->maxdownload -#if defined(USE_NGHTTP2) - && !((conn->handler->protocol & PROTO_FAMILY_HTTP) && - conn->httpversion == 20) -#endif - ) - *stop_reading = TRUE; - - if(*stop_reading) { - /* we make sure that this socket isn't read more now */ - k->keepon &= ~KEEP_RECV; - } - - Curl_debug(data, CURLINFO_HEADER_IN, str_start, headerlen); - break; /* exit header line loop */ - } - - /* We continue reading headers, reset the line-based header */ - Curl_dyn_reset(&data->state.headerb); - continue; - } - - /* - * Checks for special headers coming up. - */ - - if(!k->headerline++) { - /* This is the first header, it MUST be the error code line - or else we consider this to be the body right away! */ - int httpversion_major; - int rtspversion_major; - int nc = 0; -#ifdef CURL_DOES_CONVERSIONS -#define HEADER1 scratch -#define SCRATCHSIZE 21 - CURLcode res; - char scratch[SCRATCHSIZE + 1]; /* "HTTP/major.minor 123" */ - /* We can't really convert this yet because we don't know if it's the - 1st header line or the body. So we do a partial conversion into a - scratch area, leaving the data at 'headp' as-is. - */ - strncpy(&scratch[0], headp, SCRATCHSIZE); - scratch[SCRATCHSIZE] = 0; /* null terminate */ - res = Curl_convert_from_network(data, - &scratch[0], - SCRATCHSIZE); - if(res) - /* Curl_convert_from_network calls failf if unsuccessful */ - return res; -#else -#define HEADER1 headp /* no conversion needed, just use headp */ -#endif /* CURL_DOES_CONVERSIONS */ - - if(conn->handler->protocol & PROTO_FAMILY_HTTP) { - /* - * https://tools.ietf.org/html/rfc7230#section-3.1.2 - * - * The response code is always a three-digit number in HTTP as the spec - * says. We allow any three-digit number here, but we cannot make - * guarantees on future behaviors since it isn't within the protocol. - */ - char separator; - char twoorthree[2]; - int httpversion = 0; - char digit4 = 0; - nc = sscanf(HEADER1, - " HTTP/%1d.%1d%c%3d%c", - &httpversion_major, - &httpversion, - &separator, - &k->httpcode, - &digit4); - - if(nc == 1 && httpversion_major >= 2 && - 2 == sscanf(HEADER1, " HTTP/%1[23] %d", twoorthree, &k->httpcode)) { - conn->httpversion = 0; - nc = 4; - separator = ' '; - } - - /* There can only be a 4th response code digit stored in 'digit4' if - all the other fields were parsed and stored first, so nc is 5 when - digit4 a digit */ - else if(ISDIGIT(digit4)) { - failf(data, "Unsupported response code in HTTP response"); - return CURLE_UNSUPPORTED_PROTOCOL; - } - - if((nc >= 4) && (' ' == separator)) { - httpversion += 10 * httpversion_major; - switch(httpversion) { - case 10: - case 11: -#if defined(USE_NGHTTP2) || defined(USE_HYPER) - case 20: -#endif -#if defined(ENABLE_QUIC) - case 30: -#endif - conn->httpversion = (unsigned char)httpversion; - break; - default: - failf(data, "Unsupported HTTP version (%u.%d) in response", - httpversion/10, httpversion%10); - return CURLE_UNSUPPORTED_PROTOCOL; - } - - if(k->upgr101 == UPGR101_RECEIVED) { - /* supposedly upgraded to http2 now */ - if(conn->httpversion != 20) - infof(data, "Lying server, not serving HTTP/2"); - } - if(conn->httpversion < 20) { - conn->bundle->multiuse = BUNDLE_NO_MULTIUSE; - infof(data, "Mark bundle as not supporting multiuse"); - } - } - else if(!nc) { - /* this is the real world, not a Nirvana - NCSA 1.5.x returns this crap when asked for HTTP/1.1 - */ - nc = sscanf(HEADER1, " HTTP %3d", &k->httpcode); - conn->httpversion = 10; - - /* If user has set option HTTP200ALIASES, - compare header line against list of aliases - */ - if(!nc) { - statusline check = - checkhttpprefix(data, - Curl_dyn_ptr(&data->state.headerb), - Curl_dyn_len(&data->state.headerb)); - if(check == STATUS_DONE) { - nc = 1; - k->httpcode = 200; - conn->httpversion = 10; - } - } - } - else { - failf(data, "Unsupported HTTP version in response"); - return CURLE_UNSUPPORTED_PROTOCOL; - } - } - else if(conn->handler->protocol & CURLPROTO_RTSP) { - char separator; - int rtspversion; - nc = sscanf(HEADER1, - " RTSP/%1d.%1d%c%3d", - &rtspversion_major, - &rtspversion, - &separator, - &k->httpcode); - if((nc == 4) && (' ' == separator)) { - conn->httpversion = 11; /* For us, RTSP acts like HTTP 1.1 */ - } - else { - nc = 0; - } - } - - if(nc) { - result = Curl_http_statusline(data, conn); - if(result) - return result; - } - else { - k->header = FALSE; /* this is not a header line */ - break; - } - } - - result = Curl_convert_from_network(data, headp, strlen(headp)); - /* Curl_convert_from_network calls failf if unsuccessful */ - if(result) - return result; - - result = Curl_http_header(data, conn, headp); - if(result) - return result; - - /* - * End of header-checks. Write them to the client. - */ - - writetype = CLIENTWRITE_HEADER; - if(data->set.include_header) - writetype |= CLIENTWRITE_BODY; - - Curl_debug(data, CURLINFO_HEADER_IN, headp, - Curl_dyn_len(&data->state.headerb)); - - result = Curl_client_write(data, writetype, headp, - Curl_dyn_len(&data->state.headerb)); - if(result) - return result; - - data->info.header_size += Curl_dyn_len(&data->state.headerb); - data->req.headerbytecount += Curl_dyn_len(&data->state.headerb); - - Curl_dyn_reset(&data->state.headerb); - } - while(*k->str); /* header line within buffer */ - - /* We might have reached the end of the header part here, but - there might be a non-header part left in the end of the read - buffer. */ - - return CURLE_OK; -} - -#endif /* CURL_DISABLE_HTTP */ diff --git a/rust/rust_ffi/lib.rs b/rust/rust_ffi/lib.rs index d2255b8..d4d1549 100644 --- a/rust/rust_ffi/lib.rs +++ b/rust/rust_ffi/lib.rs @@ -17,6 +17,5 @@ extern crate c2rust_bitfields; pub mod src { pub mod ffi_alias; pub mod ffi_fun; - // pub mod ffi_macros; pub mod ffi_struct; } diff --git a/rust/rust_ffi/src/ffi_macros/get_macros.rs b/rust/rust_ffi/src/ffi_macros/get_macros.rs deleted file mode 100644 index 5236ae7..0000000 --- a/rust/rust_ffi/src/ffi_macros/get_macros.rs +++ /dev/null @@ -1,580 +0,0 @@ -/****************************************************************************** - * Copyright (c) USTC(Suzhou) & Huawei Technologies Co., Ltd. 2022. All rights reserved. - * curl-rust licensed under the Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * http://license.coscl.org.cn/MulanPSL2 - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR - * PURPOSE. - * See the Mulan PSL v2 for more details. - * Author: wyf, - * Create: 2022-10-31 - * Description: get values of macro that the build of Rust code relies on from C side - ******************************************************************************/ -extern "C" { - // http2 - fn get_USE_NGHTTP2() -> i32; - fn get_CURL_DISABLE_VERBOSE_STRINGS() -> i32; - fn get_DEBUG_HTTP2() -> i32; - fn get_NGHTTP2_HAS_SET_LOCAL_WINDOW_SIZE() -> i32; - fn get_DEBUGBUILD() -> i32; - - fn get_USE_RECV_BEFORE_SEND_WORKAROUND() -> i32; - fn get_USE_KERBEROS5() -> i32; - // http_proxy - fn get_CURL_DISABLE_PROXY() -> i32; - fn get_CURL_DISABLE_HTTP() -> i32; - fn get_USE_HYPER() -> i32; - fn get_USE_SSL() -> i32; - // http_ntlm - fn get_USE_NTLM() -> i32; - fn get_USE_WINDOWS_SSPI() -> i32; - fn get_NTLM_WB_ENABLED() -> i32; - fn get_SECPKG_ATTR_ENDPOINT_BINDINGS() -> i32; - // http_negotiate - - // http_digest - fn get_CURL_DISABLE_CRYPTO_AUTH() -> i32; - // http_chunks - fn get_CURL_DOES_CONVERSIONS() -> i32; - // http_aws_sigv4 - // no macro - // http - fn get_USE_SPNEGO() -> i32; - fn get_CURLDEBUG() -> i32; - fn get_USE_UNIX_SOCKETS() -> i32; - fn get_ENABLE_QUIC() -> i32; - fn get_CURL_DO_LINEEND_CONV() -> i32; - fn get_HAVE_LIBZ() -> i32; - fn get_CURL_DISABLE_HTTP_AUTH() -> i32; - fn get_CURL_DISABLE_NETRC() -> i32; - fn get_CURL_DISABLE_PARSEDATE() -> i32; - fn get_CURL_DISABLE_MIME() -> i32; - fn get_CURL_DISABLE_ALTSVC() -> i32; - fn get_CURL_DISABLE_RTSP() -> i32; - fn get_CURL_DISABLE_HSTS() -> i32; - fn get_CURL_DISABLE_COOKIES() -> i32; - - fn get_HAVE_NET_IF_H() -> i32; - fn get_HAVE_SYS_IOCTL_H() -> i32; - fn get_HAVE_SYS_PARAM_H() -> i32; - - // ftplistparser - - // ftp - fn get_CURL_DISABLE_FTP() -> i32; - fn get_NI_MAXHOST() -> i32; - fn get_INET_ADDRSTRLEN() -> i32; - fn get_HAVE_NETINET_IN_H() -> i32; - fn get_HAVE_ARPA_INET_H() -> i32; - fn get_HAVE_UTSNAME_H() -> i32; - fn get_HAVE_NETDB_H() -> i32; - fn get___VMS() -> i32; - fn get_ENABLE_IPV6() -> i32; - fn get_HAVE_GSSAPI() -> i32; - fn get_PF_INET6() -> i32; - fn get_CURL_FTP_HTTPSTYLE_HEAD() -> i32; - fn get__WIN32_WCE() -> i32; - fn get_NETWARE() -> i32; - fn get___NOVELL_LIBC__() -> i32; - - // bearssl - - // gskit - - // gtls - - // keylog - fn get_WIN32() -> i32; - - // mbedtls - - // mbedtls_threadlock - - // nss - - // mesalink - - // openssl - - // rustls - - // vtls - - // wolfssl - - // struct - fn get_USE_LIBPSL() -> i32; - fn get_HAVE_SIGNAL() -> i32; - fn get_USE_CURL_ASYNC() -> i32; - fn get_USE_OPENSSL() -> i32; - fn get_MSDOS() -> i32; - fn get___EMX__() -> i32; - fn get_USE_TLS_SRP() -> i32; - fn get_CURL_DISABLE_DOH() -> i32; - fn get_USE_NGHTTP3() -> i32; - fn get_ENABLE_WAKEUP() -> i32; - fn get_USE_GSASL() -> i32; -} -pub fn get_all_cfg() { - // http2 - get_USE_NGHTTP2_add_cfg(); - get_CURL_DISABLE_VERBOSE_STRINGS_add_cfg(); - get_DEBUG_HTTP2_add_cfg(); - get_NGHTTP2_HAS_SET_LOCAL_WINDOW_SIZE_add_cfg(); - get_DEBUGBUILD_add_cfg(); - - get_USE_RECV_BEFORE_SEND_WORKAROUND_add_cfg(); - get_USE_KERBEROS5_add_cfg(); - // http_proxy - get_CURL_DISABLE_PROXY_add_cfg(); - get_CURL_DISABLE_HTTP_add_cfg(); - get_USE_HYPER_add_cfg(); - get_USE_SSL_add_cfg(); - // http_ntlm - get_USE_NTLM_add_cfg(); - get_USE_WINDOWS_SSPI_add_cfg(); - get_NTLM_WB_ENABLED_add_cfg(); - get_SECPKG_ATTR_ENDPOINT_BINDINGS_add_cfg(); - // http_negotiate - - // http_digest - get_CURL_DISABLE_CRYPTO_AUTH_add_cfg(); - // http_chunks - get_CURL_DOES_CONVERSIONS_add_cfg(); - // http_aws_sigv4 - - // http - get_USE_SPNEGO_add_cfg(); - get_CURLDEBUG_add_cfg(); - get_USE_UNIX_SOCKETS_add_cfg(); - get_ENABLE_QUIC_add_cfg(); - get_CURL_DO_LINEEND_CONV_add_cfg(); - get_HAVE_LIBZ_add_cfg(); - get_CURL_DISABLE_HTTP_AUTH_add_cfg(); - get_CURL_DISABLE_NETRC_add_cfg(); - get_CURL_DISABLE_PARSEDATE_add_cfg(); - get_CURL_DISABLE_MIME_add_cfg(); - get_CURL_DISABLE_ALTSVC_add_cfg(); - get_CURL_DISABLE_RTSP_add_cfg(); - get_CURL_DISABLE_HSTS_add_cfg(); - get_CURL_DISABLE_COOKIES_add_cfg(); - - get_HAVE_NET_IF_H_add_cfg(); - get_HAVE_SYS_IOCTL_H_add_cfg(); - get_HAVE_SYS_PARAM_H_add_cfg(); - - // ftplistparser - - // ftp - get_CURL_DISABLE_FTP_add_cfg(); - get_NI_MAXHOST_add_cfg(); - get_INET_ADDRSTRLEN_add_cfg(); - get_HAVE_NETINET_IN_H_add_cfg(); - get_HAVE_ARPA_INET_H_add_cfg(); - get_HAVE_UTSNAME_H_add_cfg(); - get_HAVE_NETDB_H_add_cfg(); - get___VMS_add_cfg(); - get_ENABLE_IPV6_add_cfg(); - get_HAVE_GSSAPI_add_cfg(); - get_PF_INET6_add_cfg(); - get_CURL_FTP_HTTPSTYLE_HEAD_add_cfg(); - get__WIN32_WCE_add_cfg(); - get_NETWARE_add_cfg(); - get___NOVELL_LIBC___add_cfg(); - // bearssl - - // gskit - - // gtls - - // keylog - get_WIN32_add_cfg(); - - // mbedtls - - // mbedtls_threadlock - - // nss - - // mesalink - - // openssl - - // rustls - - // vtls - - // wolfssl - - // struct - get_USE_LIBPSL_add_cfg(); - get_HAVE_SIGNAL_add_cfg(); - get_USE_CURL_ASYNC_add_cfg(); - get_USE_OPENSSL_add_cfg(); - get_MSDOS_add_cfg(); - get___EMX___add_cfg(); - get_USE_TLS_SRP_add_cfg(); - get_CURL_DISABLE_DOH_add_cfg(); - get_USE_NGHTTP3_add_cfg(); - get_ENABLE_WAKEUP_add_cfg(); - get_USE_GSASL_add_cfg(); -} - -// http2 -fn get_USE_NGHTTP2_add_cfg() { - if unsafe { get_USE_NGHTTP2() } == 1 { - println!("cargo:rustc-cfg=USE_NGHTTP2"); - } -} -fn get_CURL_DISABLE_VERBOSE_STRINGS_add_cfg() { - if unsafe { get_CURL_DISABLE_VERBOSE_STRINGS() } == 1 { - println!("cargo:rustc-cfg=CURL_DISABLE_VERBOSE_STRINGS"); - } -} -fn get_DEBUG_HTTP2_add_cfg() { - if unsafe { get_DEBUG_HTTP2() } == 1 { - println!("cargo:rustc-cfg=DEBUG_HTTP2"); - } -} -fn get_NGHTTP2_HAS_SET_LOCAL_WINDOW_SIZE_add_cfg() { - if unsafe { get_NGHTTP2_HAS_SET_LOCAL_WINDOW_SIZE() } == 1 { - println!("cargo:rustc-cfg=NGHTTP2_HAS_SET_LOCAL_WINDOW_SIZE"); - } -} -fn get_DEBUGBUILD_add_cfg() { - if unsafe { get_DEBUGBUILD() } == 1 { - println!("cargo:rustc-cfg=DEBUGBUILD"); - } -} -fn get_USE_RECV_BEFORE_SEND_WORKAROUND_add_cfg() { - if unsafe { get_USE_RECV_BEFORE_SEND_WORKAROUND() } == 1 { - println!("cargo:rustc-cfg=USE_RECV_BEFORE_SEND_WORKAROUND"); - } -} -fn get_USE_KERBEROS5_add_cfg() { - if unsafe { get_USE_KERBEROS5() } == 1 { - println!("cargo:rustc-cfg=USE_KERBEROS5"); - } -} -// http_proxy -fn get_CURL_DISABLE_PROXY_add_cfg() { - if unsafe { get_CURL_DISABLE_PROXY() } == 1 { - println!("cargo:rustc-cfg=CURL_DISABLE_PROXY"); - } -} -fn get_CURL_DISABLE_HTTP_add_cfg() { - if unsafe { get_CURL_DISABLE_HTTP() } == 1 { - println!("cargo:rustc-cfg=CURL_DISABLE_HTTP"); - } -} -fn get_USE_HYPER_add_cfg() { - if unsafe { get_USE_HYPER() } == 1 { - println!("cargo:rustc-cfg=USE_HYPER"); - } -} -fn get_USE_SSL_add_cfg() { - if unsafe { get_USE_SSL() } == 1 { - println!("cargo:rustc-cfg=USE_SSL"); - } -} -// http_ntlm -fn get_USE_NTLM_add_cfg() { - if unsafe { get_USE_NTLM() } == 1 { - println!("cargo:rustc-cfg=USE_NTLM"); - } -} -fn get_USE_WINDOWS_SSPI_add_cfg() { - if unsafe { get_USE_WINDOWS_SSPI() } == 1 { - println!("cargo:rustc-cfg=USE_WINDOWS_SSPI"); - } -} -fn get_NTLM_WB_ENABLED_add_cfg() { - if unsafe { get_NTLM_WB_ENABLED() } == 1 { - println!("cargo:rustc-cfg=NTLM_WB_ENABLED"); - } -} -fn get_SECPKG_ATTR_ENDPOINT_BINDINGS_add_cfg() { - if unsafe { get_SECPKG_ATTR_ENDPOINT_BINDINGS() } == 1 { - println!("cargo:rustc-cfg=SECPKG_ATTR_ENDPOINT_BINDINGS"); - } -} -// http_negotiate - -// http_digest -fn get_CURL_DISABLE_CRYPTO_AUTH_add_cfg() { - if unsafe { get_CURL_DISABLE_CRYPTO_AUTH() } == 1 { - println!("cargo:rustc-cfg=CURL_DISABLE_CRYPTO_AUTH"); - } -} -// http_chunks -fn get_CURL_DOES_CONVERSIONS_add_cfg() { - if unsafe { get_CURL_DOES_CONVERSIONS() } == 1 { - println!("cargo:rustc-cfg=CURL_DOES_CONVERSIONS"); - } -} -// http_aws_sigv4 - -// http -fn get_USE_SPNEGO_add_cfg() { - if unsafe { get_USE_SPNEGO() } == 1 { - println!("cargo:rustc-cfg=USE_SPNEGO"); - } -} -fn get_CURLDEBUG_add_cfg() { - if unsafe { get_CURLDEBUG() } == 1 { - println!("cargo:rustc-cfg=CURLDEBUG"); - } -} -fn get_USE_UNIX_SOCKETS_add_cfg() { - if unsafe { get_USE_UNIX_SOCKETS() } == 1 { - println!("cargo:rustc-cfg=USE_UNIX_SOCKETS"); - } -} -fn get_ENABLE_QUIC_add_cfg() { - if unsafe { get_ENABLE_QUIC() } == 1 { - println!("cargo:rustc-cfg=ENABLE_QUIC"); - } -} -fn get_CURL_DO_LINEEND_CONV_add_cfg() { - if unsafe { get_CURL_DO_LINEEND_CONV() } == 1 { - println!("cargo:rustc-cfg=CURL_DO_LINEEND_CONV"); - } -} -fn get_HAVE_LIBZ_add_cfg() { - if unsafe { get_HAVE_LIBZ() } == 1 { - println!("cargo:rustc-cfg=HAVE_LIBZ"); - } -} -fn get_CURL_DISABLE_HTTP_AUTH_add_cfg() { - if unsafe { get_CURL_DISABLE_HTTP_AUTH() } == 1 { - println!("cargo:rustc-cfg=CURL_DISABLE_HTTP_AUTH"); - } -} -fn get_CURL_DISABLE_NETRC_add_cfg() { - if unsafe { get_CURL_DISABLE_NETRC() } == 1 { - println!("cargo:rustc-cfg=CURL_DISABLE_NETRC"); - } -} -fn get_CURL_DISABLE_PARSEDATE_add_cfg() { - if unsafe { get_CURL_DISABLE_PARSEDATE() } == 1 { - println!("cargo:rustc-cfg=CURL_DISABLE_PARSEDATE"); - } -} -fn get_CURL_DISABLE_MIME_add_cfg() { - if unsafe { get_CURL_DISABLE_MIME() } == 1 { - println!("cargo:rustc-cfg=CURL_DISABLE_MIME"); - } -} -fn get_CURL_DISABLE_ALTSVC_add_cfg() { - if unsafe { get_CURL_DISABLE_ALTSVC() } == 1 { - println!("cargo:rustc-cfg=CURL_DISABLE_ALTSVC"); - } -} -fn get_CURL_DISABLE_RTSP_add_cfg() { - if unsafe { get_CURL_DISABLE_RTSP() } == 1 { - println!("cargo:rustc-cfg=CURL_DISABLE_RTSP"); - } -} -fn get_CURL_DISABLE_HSTS_add_cfg() { - if unsafe { get_CURL_DISABLE_HSTS() } == 1 { - println!("cargo:rustc-cfg=CURL_DISABLE_HSTS"); - } -} -fn get_CURL_DISABLE_COOKIES_add_cfg() { - if unsafe { get_CURL_DISABLE_COOKIES() } == 1 { - println!("cargo:rustc-cfg=CURL_DISABLE_COOKIES"); - } -} -fn get_HAVE_NET_IF_H_add_cfg() { - if unsafe { get_HAVE_NET_IF_H() } == 1 { - println!("cargo:rustc-cfg=HAVE_NET_IF_H"); - } -} -fn get_HAVE_SYS_IOCTL_H_add_cfg() { - if unsafe { get_HAVE_SYS_IOCTL_H() } == 1 { - println!("cargo:rustc-cfg=HAVE_SYS_IOCTL_H"); - } -} -fn get_HAVE_SYS_PARAM_H_add_cfg() { - if unsafe { get_HAVE_SYS_PARAM_H() } == 1 { - println!("cargo:rustc-cfg=HAVE_SYS_PARAM_H"); - } -} - -// ftplistparser -// repeated - -// ftp -fn get_CURL_DISABLE_FTP_add_cfg() { - if unsafe { get_CURL_DISABLE_FTP() } == 1 { - println!("cargo:rustc-cfg=CURL_DISABLE_FTP"); - } -} - -fn get_NI_MAXHOST_add_cfg() { - if unsafe { get_NI_MAXHOST() } == 1 { - println!("cargo:rustc-cfg=NI_MAXHOST"); - } -} - -fn get_INET_ADDRSTRLEN_add_cfg() { - if unsafe { get_INET_ADDRSTRLEN() } == 1 { - println!("cargo:rustc-cfg=INET_ADDRSTRLEN"); - } -} -fn get_HAVE_NETINET_IN_H_add_cfg() { - if unsafe { get_HAVE_NETINET_IN_H() } == 1 { - println!("cargo:rustc-cfg=HAVE_NETINET_IN_H"); - } -} -fn get_HAVE_ARPA_INET_H_add_cfg() { - if unsafe { get_HAVE_ARPA_INET_H() } == 1 { - println!("cargo:rustc-cfg=HAVE_ARPA_INET_H"); - } -} -fn get_HAVE_UTSNAME_H_add_cfg() { - if unsafe { get_HAVE_UTSNAME_H() } == 1 { - println!("cargo:rustc-cfg=HAVE_UTSNAME_H"); - } -} -fn get_HAVE_NETDB_H_add_cfg() { - if unsafe { get_HAVE_NETDB_H() } == 1 { - println!("cargo:rustc-cfg=HAVE_NETDB_H"); - } -} -fn get___VMS_add_cfg() { - if unsafe { get___VMS() } == 1 { - println!("cargo:rustc-cfg=__VMS"); - } -} -fn get_ENABLE_IPV6_add_cfg() { - if unsafe { get_ENABLE_IPV6() } == 1 { - println!("cargo:rustc-cfg=ENABLE_IPV6"); - } -} -fn get_HAVE_GSSAPI_add_cfg() { - if unsafe { get_HAVE_GSSAPI() } == 1 { - println!("cargo:rustc-cfg=HAVE_GSSAPI"); - } -} -fn get_PF_INET6_add_cfg() { - if unsafe { get_PF_INET6() } == 1 { - println!("cargo:rustc-cfg=PF_INET6"); - } -} -fn get_CURL_FTP_HTTPSTYLE_HEAD_add_cfg() { - if unsafe { get_CURL_FTP_HTTPSTYLE_HEAD() } == 1 { - println!("cargo:rustc-cfg=CURL_FTP_HTTPSTYLE_HEAD"); - } -} -fn get__WIN32_WCE_add_cfg() { - if unsafe { get__WIN32_WCE() } == 1 { - println!("cargo:rustc-cfg=_WIN32_WCE"); - } -} -fn get_NETWARE_add_cfg() { - if unsafe { get_NETWARE() } == 1 { - println!("cargo:rustc-cfg=NETWARE"); - } -} -fn get___NOVELL_LIBC___add_cfg() { - if unsafe { get___NOVELL_LIBC__() } == 1 { - println!("cargo:rustc-cfg=__NOVELL_LIBC__"); - } -} -// bearssl - -// gskit - -// gtls - -// keylog -fn get_WIN32_add_cfg() { - if unsafe { get_WIN32() } == 1 { - println!("cargo:rustc-cfg=WIN32"); - } -} -// mbedtls - -// mbedtls_threadlock - -// nss - -// mesalink - -// openssl - -// rustls - -// vtls - -// wolfssl - -//struct -fn get_USE_LIBPSL_add_cfg() { - if unsafe { get_USE_LIBPSL() } == 1 { - println!("cargo:rustc-cfg=USE_LIBPSL"); - } -} - -fn get_HAVE_SIGNAL_add_cfg() { - if unsafe { get_HAVE_SIGNAL() } == 1 { - println!("cargo:rustc-cfg=HAVE_SIGNAL"); - } -} - -fn get_USE_CURL_ASYNC_add_cfg() { - if unsafe { get_USE_CURL_ASYNC() } == 1 { - println!("cargo:rustc-cfg=USE_CURL_ASYNC"); - } -} - -fn get_USE_OPENSSL_add_cfg() { - if unsafe { get_USE_OPENSSL() } == 1 { - println!("cargo:rustc-cfg=USE_OPENSSL"); - } -} - -fn get_MSDOS_add_cfg() { - if unsafe { get_MSDOS() } == 1 { - println!("cargo:rustc-cfg=MSDOS"); - } -} - -fn get___EMX___add_cfg() { - if unsafe { get___EMX__() } == 1 { - println!("cargo:rustc-cfg=__EMX__"); - } -} - -fn get_USE_TLS_SRP_add_cfg() { - if unsafe { get_USE_TLS_SRP() } == 1 { - println!("cargo:rustc-cfg=USE_TLS_SRP"); - } -} - -fn get_CURL_DISABLE_DOH_add_cfg() { - if unsafe { get_CURL_DISABLE_DOH() } == 1 { - println!("cargo:rustc-cfg=CURL_DISABLE_DOH"); - } -} - -fn get_USE_NGHTTP3_add_cfg() { - if unsafe { get_USE_NGHTTP3() } == 1 { - println!("cargo:rustc-cfg=USE_NGHTTP3"); - } -} - -fn get_ENABLE_WAKEUP_add_cfg() { - if unsafe { get_ENABLE_WAKEUP() } == 1 { - println!("cargo:rustc-cfg=ENABLE_WAKEUP"); - } -} - -fn get_USE_GSASL_add_cfg() { - if unsafe { get_USE_GSASL() } == 1 { - println!("cargo:rustc-cfg=USE_GSASL"); - } -} diff --git a/rust/rust_ffi/src/ffi_macros/mod.rs b/rust/rust_ffi/src/ffi_macros/mod.rs deleted file mode 100644 index c9f360b..0000000 --- a/rust/rust_ffi/src/ffi_macros/mod.rs +++ /dev/null @@ -1,15 +0,0 @@ -/****************************************************************************** - * Copyright (c) USTC(Suzhou) & Huawei Technologies Co., Ltd. 2022. All rights reserved. - * curl-rust licensed under the Mulan PSL v2. - * You can use this software according to the terms and conditions of the Mulan PSL v2. - * You may obtain a copy of Mulan PSL v2 at: - * http://license.coscl.org.cn/MulanPSL2 - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR - * PURPOSE. - * See the Mulan PSL v2 for more details. - * Author: wyf, - * Create: 2022-10-31 - * Description: Rust module declaration - ******************************************************************************/ -pub mod get_macros; -- Gitee