+#if ENABLE_FEATURE_WGET_LONG_OPTIONS
+static char *URL_escape(const char *str)
+{
+ /* URL encode, see RFC 2396 */
+ char *dst;
+ char *res = dst = xmalloc(strlen(str) * 3 + 1);
+ unsigned char c;
+
+ while (1) {
+ c = *str++;
+ if (c == '\0'
+ /* || strchr("!&'()*-.=_~", c) - more code */
+ || c == '!'
+ || c == '&'
+ || c == '\''
+ || c == '('
+ || c == ')'
+ || c == '*'
+ || c == '-'
+ || c == '.'
+ || c == '='
+ || c == '_'
+ || c == '~'
+ || (c >= '0' && c <= '9')
+ || ((c|0x20) >= 'a' && (c|0x20) <= 'z')
+ ) {
+ *dst++ = c;
+ if (c == '\0')
+ return res;
+ } else {
+ *dst++ = '%';
+ *dst++ = bb_hexdigits_upcase[c >> 4];
+ *dst++ = bb_hexdigits_upcase[c & 0xf];
+ }
+ }
+}
+#endif
+
+static FILE* prepare_ftp_session(FILE **dfpp, struct host_info *target, len_and_sockaddr *lsa)
+{
+ char buf[512];
+ FILE *sfp;
+ char *str;
+ int port;
+
+ if (!target->user)
+ target->user = xstrdup("anonymous:busybox@");
+
+ sfp = open_socket(lsa);
+ if (ftpcmd(NULL, NULL, sfp, buf) != 220)
+ bb_error_msg_and_die("%s", sanitize_string(buf+4));
+
+ /*
+ * Splitting username:password pair,
+ * trying to log in
+ */
+ str = strchr(target->user, ':');
+ if (str)
+ *str++ = '\0';
+ switch (ftpcmd("USER ", target->user, sfp, buf)) {
+ case 230:
+ break;
+ case 331:
+ if (ftpcmd("PASS ", str, sfp, buf) == 230)
+ break;
+ /* fall through (failed login) */
+ default:
+ bb_error_msg_and_die("ftp login: %s", sanitize_string(buf+4));
+ }
+
+ ftpcmd("TYPE I", NULL, sfp, buf);
+
+ /*
+ * Querying file size
+ */
+ if (ftpcmd("SIZE ", target->path, sfp, buf) == 213) {
+ content_len = BB_STRTOOFF(buf+4, NULL, 10);
+ if (errno || content_len < 0) {
+ bb_error_msg_and_die("SIZE value is garbage");
+ }
+ G.got_clen = 1;
+ }
+
+ /*
+ * Entering passive mode
+ */
+ if (ftpcmd("PASV", NULL, sfp, buf) != 227) {
+ pasv_error:
+ bb_error_msg_and_die("bad response to %s: %s", "PASV", sanitize_string(buf));
+ }
+ // Response is "227 garbageN1,N2,N3,N4,P1,P2[)garbage]
+ // Server's IP is N1.N2.N3.N4 (we ignore it)
+ // Server's port for data connection is P1*256+P2
+ str = strrchr(buf, ')');
+ if (str) str[0] = '\0';
+ str = strrchr(buf, ',');
+ if (!str) goto pasv_error;
+ port = xatou_range(str+1, 0, 255);
+ *str = '\0';
+ str = strrchr(buf, ',');
+ if (!str) goto pasv_error;
+ port += xatou_range(str+1, 0, 255) * 256;
+ set_nport(lsa, htons(port));
+
+ *dfpp = open_socket(lsa);
+
+ if (beg_range) {
+ sprintf(buf, "REST %"OFF_FMT"d", beg_range);
+ if (ftpcmd(buf, NULL, sfp, buf) == 350)
+ content_len -= beg_range;
+ }
+
+ if (ftpcmd("RETR ", target->path, sfp, buf) > 150)
+ bb_error_msg_and_die("bad response to %s: %s", "RETR", sanitize_string(buf));
+
+ return sfp;
+}
+
+/* Must match option string! */
+enum {
+ WGET_OPT_CONTINUE = (1 << 0),
+ WGET_OPT_SPIDER = (1 << 1),
+ WGET_OPT_QUIET = (1 << 2),
+ WGET_OPT_OUTNAME = (1 << 3),
+ WGET_OPT_PREFIX = (1 << 4),
+ WGET_OPT_PROXY = (1 << 5),
+ WGET_OPT_USER_AGENT = (1 << 6),
+ WGET_OPT_RETRIES = (1 << 7),
+ WGET_OPT_NETWORK_READ_TIMEOUT = (1 << 8),
+ WGET_OPT_PASSIVE = (1 << 9),
+ WGET_OPT_HEADER = (1 << 10) * ENABLE_FEATURE_WGET_LONG_OPTIONS,
+ WGET_OPT_POST_DATA = (1 << 11) * ENABLE_FEATURE_WGET_LONG_OPTIONS,
+};
+
+static void NOINLINE retrieve_file_data(FILE *dfp, int output_fd)
+{
+ char buf[512];
+
+ if (!(option_mask32 & WGET_OPT_QUIET))
+ progress_meter(-1);
+
+ if (G.chunked)
+ goto get_clen;
+
+ /* Loops only if chunked */
+ while (1) {
+ while (content_len > 0 || !G.got_clen) {
+ int n;
+ unsigned rdsz = sizeof(buf);
+
+ if (content_len < sizeof(buf) && (G.chunked || G.got_clen))
+ rdsz = (unsigned)content_len;
+ n = safe_fread(buf, rdsz, dfp);
+ if (n <= 0) {
+ if (ferror(dfp)) {
+ /* perror will not work: ferror doesn't set errno */
+ bb_error_msg_and_die(bb_msg_read_error);
+ }
+ break;
+ }
+ xwrite(output_fd, buf, n);
+#if ENABLE_FEATURE_WGET_STATUSBAR
+ transferred += n;
+#endif
+ if (G.got_clen)
+ content_len -= n;
+ }
+
+ if (!G.chunked)
+ break;
+
+ safe_fgets(buf, sizeof(buf), dfp); /* This is a newline */
+ get_clen:
+ safe_fgets(buf, sizeof(buf), dfp);
+ content_len = STRTOOFF(buf, NULL, 16);
+ /* FIXME: error check? */
+ if (content_len == 0)
+ break; /* all done! */
+ }
+
+ if (!(option_mask32 & WGET_OPT_QUIET))
+ progress_meter(0);
+}