CVE-2020-15121: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection') (CWE-78)
value: In radare2 before version 4.5.0, malformed PDB file names in the PDB server path cause shell injection. To trigger the problem it's required to open the executable in radare2 and run idpd to trigger the download. The shell code will execute, and will create a file called pwned in the current directory.
CWE description: The product constructs all or part of an OS command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended OS command when it is sent to a downstream component.
CVSS v3 base score: 9.6
Published: 2020-07-20T18:15Z
Repository: https://github.com/radareorg/radare2
Language: C
Files changed: libr/bin/pdb/pdb_downloader.c, libr/config.mk.tail, libr/include/r_socket.h, libr/include/r_types.h, libr/include/r_util/r_str.h, libr/socket/socket.c, libr/socket/socket_http.c, libr/util/str.c, libr/util/sys.c, meson.build, shlr/sdb/src/disk.c, test/db/formats/pdb, test/unit/test_str.c
Commit date: 2020-06-10 18:00:00 +0200
Commit message: Fix command injection on PDB download (#16966)

* Fix r_sys_mkdirp with absolute path on Windows
* Fix build with --with-openssl
* Use RBuffer in r_socket_http_answer()
* r_socket_http_answer: Fix read for big responses
* Implement r_str_escape_sh()
* Cleanup r_socket_connect() on Windows
* Fix socket being created without a protocol
* Fix socket connect with SSL ##socket
* Use select() in r_socket_ready()
* Fix read failing if received only protocol answer
* Fix double-free
* r_socket_http_get: Fail if req. SSL with no support
* Follow redirects in r_socket_http_answer()
* Fix r_socket_http_get result length with R2_CURL=1
* Also follow redirects
* Avoid using curl for downloading PDBs
* Use r_socket_http_get() on UNIXs
* Use WinINet API on Windows for r_socket_http_get()
* Fix command injection
* Fix r_sys_cmd_str_full output for binary data
* Validate GUID on PDB download
* Pass depth to socket_http_get_recursive()
* Remove 'r_' and '__' from static function names
* Fix is_valid_guid
* Fix for comments
Diff: diff --git a/libr/bin/pdb/pdb_downloader.c b/libr/bin/pdb/pdb_downloader.c
index f9b0f8699b0..5aa9c89843c 100644
--- a/libr/bin/pdb/pdb_downloader.c
+++ b/libr/bin/pdb/pdb_downloader.c
@@ -16,154 +16,111 @@ static bool checkExtract() {
 	}
 #endif
 	return true;
 }
 
-static bool checkCurl() {
-	const char nul[] = R_SYS_DEVNULL;
-	if (r_sys_cmdf ("curl --version > %s", nul) != 0) {
+static bool download_and_write(SPDBDownloaderOpt *opt, const char *file) {
+	char *dir = r_str_newf ("%s%s%s%s%s",
+		opt->symbol_store_path, R_SYS_DIR,
+		opt->dbg_file, R_SYS_DIR,
+		opt->guid);
+	if (!r_sys_mkdirp (dir)) {
+		free (dir);
 		return false;
 	}
+	char *url = r_str_newf ("%s/%s/%s/%s", opt->symbol_server, opt->dbg_file, opt->guid, file);
+	int len;
+	char *file_buf = r_socket_http_get (url, NULL, &len);
+	free (url);
+	if (!len || R_STR_ISEMPTY (file_buf)) {
+		free (dir);
+		free (file_buf);
+		return false;
+	}
+	char *path = r_str_newf ("%s%s%s", dir, R_SYS_DIR, opt->dbg_file);
+	FILE *f = fopen (path, "wb");
+	if (f) {
+		fwrite (file_buf, sizeof (char), (size_t)len, f);
+		fclose (f);
+	}
+	free (dir);
+	free (path);
+	free (file_buf);
 	return true;
 }
 
 static int download(struct SPDBDownloader *pd) {
 	SPDBDownloaderOpt *opt = pd->opt;
-	char *curl_cmd = NULL;
-	char *extractor_cmd = NULL;
-	char *abspath_to_archive = NULL;
-	char *abspath_to_file = NULL;
-	char *archive_name = NULL;
-	size_t archive_name_len = 0;
-	char *symbol_store_path = NULL;
-	char *dbg_file = NULL;
-	char *guid = NULL;
-	char *archive_name_escaped  = NULL;
-	char *user_agent = NULL;
-	char *symbol_server = NULL;
-
 	int res = 0;
 	int cmd_ret;
+
 	if (!opt->dbg_file || !*opt->dbg_file) {
 		// no pdb debug file
 		return 0;
 	}
-	if (!checkCurl ()) {
-		return 0;
-	}
-	// dbg_file len is > 0
-	archive_name_len = strlen (opt->dbg_file);
-	archive_name = malloc (archive_name_len + 1);
-	if (!archive_name) {
-		return 0;
-	}
-	memcpy (archive_name, opt->dbg_file, archive_name_len + 1);
-	archive_name[archive_name_len - 1] = '_';
-	symbol_store_path = r_str_escape (opt->symbol_store_path);
-	dbg_file = r_str_escape (opt->dbg_file);
-	guid = r_str_escape (opt->guid);
-	archive_name_escaped = r_str_escape (archive_name);
-	user_agent = r_str_escape (opt->user_agent);
-	symbol_server = r_str_escape (opt->symbol_server);
-
-	abspath_to_archive = r_str_newf ("%s%s%s%s%s%s%s",
-			    symbol_store_path, R_SYS_DIR,
-			    dbg_file, R_SYS_DIR,
-			    guid, R_SYS_DIR,
-			    archive_name_escaped);
-
-	abspath_to_file = strdup (abspath_to_archive);
-	abspath_to_file[strlen (abspath_to_file) - 1] = 'b';
+
+	char *abspath_to_file = r_str_newf ("%s%s%s%s%s%s%s",
+		opt->symbol_store_path, R_SYS_DIR,
+		opt->dbg_file, R_SYS_DIR,
+		opt->guid, R_SYS_DIR,
+		opt->dbg_file);
+
 	if (r_file_exists (abspath_to_file)) {
 		eprintf ("File already downloaded.\n");
-		R_FREE (user_agent);
-		R_FREE (abspath_to_archive);
-		R_FREE (archive_name_escaped);
-		R_FREE (symbol_store_path);
-		R_FREE (dbg_file);
-		R_FREE (guid);
-		R_FREE (archive_name);
-		R_FREE (abspath_to_file);
-		R_FREE (symbol_server);
+		free (abspath_to_file);
 		return 1;
 	}
 
 	if (checkExtract () || opt->extract == 0) {
-		res = 1;
-
-		curl_cmd = r_str_newf ("curl -sfLA \"%s\" \"%s/%s/%s/%s\" --create-dirs -o \"%s\"",
-		                       user_agent,
-		                       symbol_server,
-							   dbg_file,
-							   guid,
-		                       archive_name_escaped,
-		                       abspath_to_archive);
+		char *extractor_cmd = NULL;
+		char *archive_name = strdup (opt->dbg_file);
+		archive_name[strlen (archive_name) - 1] = '_';
+		char *abspath_to_archive = r_str_newf ("%s%s%s%s%s%s%s",
+			opt->symbol_store_path, R_SYS_DIR,
+			opt->dbg_file, R_SYS_DIR,
+			opt->guid, R_SYS_DIR,
+			archive_name);
+
+		eprintf ("Attempting to download compressed pdb in %s\n", abspath_to_archive);
+		char *abs_arch_esc = r_str_escape_sh (abspath_to_archive);
 #if __WINDOWS__
-		const char *cabextractor = "expand";
-		const char *format = "%s %s %s";
-
-		// extractor_cmd -> %1 %2 %3
-		// %1 - 'expand'
-		// %2 - absolute path to archive
-		// %3 - absolute path to file that will be dearchive
-		extractor_cmd = r_str_newf (format, cabextractor,
-			abspath_to_archive, abspath_to_file);
+		char *abs_file_esc = r_str_escape_sh (abspath_to_file);
+		// expand %1 %2
+		// %1 - absolute path to archive
+		// %2 - absolute path to file that will be dearchive
+		extractor_cmd = r_str_newf ("expand \"%s\" \"%s\"", abs_arch_esc, abs_file_esc);
+		free (abs_file_esc);
 #else
-		const char *cabextractor = "cabextract";
-		const char *format = "%s -d \"%s\" \"%s\"";
 		char *abspath_to_dir = r_file_dirname (abspath_to_archive);
+		char *abs_dir_esc = r_str_escape_sh (abspath_to_dir);
 		// cabextract -d %1 %2
 		// %1 - path to directory where to extract all files from cab archive
 		// %2 - absolute path to cab archive
-		extractor_cmd = r_str_newf (format, cabextractor, abspath_to_dir, abspath_to_archive);
-		R_FREE (abspath_to_dir);
+		extractor_cmd = r_str_newf ("cabextract -d \"%s\" \"%s\"", abs_arch_esc, abs_dir_esc);
+		free (abs_dir_esc);
+		free (abspath_to_dir);
 #endif
-		eprintf ("Attempting to download compressed pdb in %s\n", abspath_to_archive);
-		if ((cmd_ret = r_sys_cmd (curl_cmd) != 0)) {
-			eprintf("curl exited with error %d\n", cmd_ret);
-			res = 0;
-		}
-		eprintf ("Attempting to decompress pdb\n");
-		if (opt->extract > 0) {
+		free (abs_arch_esc);
+		res = download_and_write (opt, archive_name);
+
+		if (opt->extract > 0 && res) {
+			eprintf ("Attempting to decompress pdb\n");
 			if (res && ((cmd_ret = r_sys_cmd (extractor_cmd)) != 0)) {
 				eprintf ("cab extractor exited with error %d\n", cmd_ret);
 				res = 0;
 			}
 			r_file_rm (abspath_to_archive);
 		}
-		R_FREE (curl_cmd);
+		free (archive_name);
+		free (abspath_to_archive);
 	}
 	if (res == 0) {
 		eprintf ("Falling back to uncompressed pdb\n");
-		res = 1;
-
-		archive_name_escaped[strlen (archive_name_escaped) - 1] = 'b';
-
-		curl_cmd = r_str_newf ("curl -sfLA \"%s\" \"%s/%s/%s/%s\" --create-dirs -o \"%s\"",
-		                       opt->user_agent,
-		                       opt->symbol_server,
-		                       opt->dbg_file,
-		                       opt->guid,
-		                       archive_name_escaped,
-		                       abspath_to_file);
 		eprintf ("Attempting to download uncompressed pdb in %s\n", abspath_to_file);
-		if ((cmd_ret = r_sys_cmd (curl_cmd) != 0)) {
-			eprintf("curl exited with error %d\n", cmd_ret);
-			res = 0;
-		}
-		R_FREE (curl_cmd);
-	}
-	R_FREE (abspath_to_archive);
-	R_FREE (abspath_to_file);
-	R_FREE (archive_name);
-	R_FREE (extractor_cmd);
-	R_FREE (symbol_store_path);
-	R_FREE (dbg_file);
-	R_FREE (guid);
-	R_FREE (archive_name_escaped);
-	R_FREE (user_agent);
-	R_FREE (symbol_server);
+		res = download_and_write (opt, opt->dbg_file);
+	}
+	free (abspath_to_file);
 	return res;
 }
 
 void init_pdb_downloader(SPDBDownloaderOpt *opt, SPDBDownloader *pd) {
 	pd->opt = R_NEW0 (SPDBDownloaderOpt);
@@ -189,10 +146,23 @@ void deinit_pdb_downloader(SPDBDownloader *pd) {
 	R_FREE (pd->opt->symbol_store_path);
 	R_FREE (pd->opt);
 	pd->download = 0;
 }
 
+static bool is_valid_guid(const char *guid) {
+	if (!guid) {
+		return false;
+	}
+	size_t i;
+	for (i = 0; guid[i]; i++) {
+		if (!isxdigit (guid[i])) {
+			return false;
+		}
+	}
+	return i >= 33; // len of GUID and age
+}
+
 int r_bin_pdb_download(RCore *core, int isradjson, int *actions_done, SPDBOptions *options) {
 	int ret;
 	SPDBDownloaderOpt opt;
 	SPDBDownloader pdb_downloader;
 	RBinInfo *info = r_bin_get_info (core->bin);
@@ -200,10 +170,15 @@ int r_bin_pdb_download(RCore *core, int isradjson, int *actions_done, SPDBOption
 	if (!info || !info->debug_file_name) {
 		eprintf ("Can't find debug filename\n");
 		return 1;
 	}
 
+	if (!is_valid_guid (info->guid)) {
+		eprintf ("Invalid GUID for file\n");
+		return 1;
+	}
+
 	if (!options || !options->symbol_server || !options->user_agent) {
 		eprintf ("Can't retrieve pdb configurations\n");
 		return 1;
 	}
 
diff --git a/libr/config.mk.tail b/libr/config.mk.tail
index 7e4569ea6c0..7cda953aec8 100644
--- a/libr/config.mk.tail
+++ b/libr/config.mk.tail
@@ -50,10 +50,15 @@ CFLAGS+=-Wall
 ifeq (${HAVE_LIB_GMP},1)
 CFLAGS+=-DHAVE_LIB_GMP=1
 BN_LIBS=-lgmp
 endif
 
+# open-ssl
+ifeq (${HAVE_LIB_SSL},1)
+BN_LIBS=${SSL_LDFLAGS}
+endif
+
 #both of these need ssl includes
 ifneq (,$(filter r_socket r_util,$(BINDEPS)))
 ifeq (${HAVE_LIB_SSL},1)
 CFLAGS+=${SSL_CFLAGS}
 endif
diff --git a/libr/include/r_socket.h b/libr/include/r_socket.h
index fbda80a31c1..b9fa714032a 100644
--- a/libr/include/r_socket.h
+++ b/libr/include/r_socket.h
@@ -1,13 +1,8 @@
 #ifndef R2_SOCKET_H
 #define R2_SOCKET_H
 
-/* Must be included before windows.h (r_types) */
-#if defined(__WINDOWS__)
-#include <ws2tcpip.h>
-#endif
-
 #include "r_types.h"
 #include "r_bind.h"
 #include "r_list.h"
 
 #ifdef __cplusplus
diff --git a/libr/include/r_types.h b/libr/include/r_types.h
index 78ea8562cb8..27c7ec5a0d7 100644
--- a/libr/include/r_types.h
+++ b/libr/include/r_types.h
@@ -150,10 +150,11 @@
 #endif
 #if __WINDOWS__ || _WIN32
   #ifdef _MSC_VER
   /* Must be included before windows.h */
   #include <winsock2.h>
+  #include <ws2tcpip.h>
   #ifndef WIN32_LEAN_AND_MEAN
   #define WIN32_LEAN_AND_MEAN
   #endif
   #endif
   typedef int socklen_t;
diff --git a/libr/include/r_util/r_str.h b/libr/include/r_util/r_str.h
index c54a0be97cb..28526e9d2c9 100644
--- a/libr/include/r_util/r_str.h
+++ b/libr/include/r_util/r_str.h
@@ -146,10 +146,11 @@ R_API int r_str_re_match(const char *str, const char *reg);
 R_API int r_str_re_replace(const char *str, const char *reg, const char *sub);
 R_API int r_str_path_unescape(char *path);
 R_API char *r_str_path_escape(const char *path);
 R_API int r_str_unescape(char *buf);
 R_API char *r_str_escape(const char *buf);
+R_API char *r_str_escape_sh(const char *buf);
 R_API char *r_str_escape_dot(const char *buf);
 R_API char *r_str_escape_latin1(const char *buf, bool show_asciidot, bool esc_bslash, bool colors);
 R_API char *r_str_escape_utf8(const char *buf, bool show_asciidot, bool esc_bslash);
 R_API char *r_str_escape_utf8_keep_printable(const char *buf, bool show_asciidot, bool esc_bslash); // like escape_utf8 but leaves valid \uXXXX chars directly in utf-8
 R_API char *r_str_escape_utf16le(const char *buf, int buf_size, bool show_asciidot, bool esc_bslash);
diff --git a/libr/socket/socket.c b/libr/socket/socket.c
index 1b24f2f05ad..b3a50e7f26e 100644
--- a/libr/socket/socket.c
+++ b/libr/socket/socket.c
@@ -258,84 +258,34 @@ R_API bool r_socket_spawn(RSocket *s, const char *cmd, unsigned int timeout) {
 }
 
 R_API bool r_socket_connect(RSocket *s, const char *host, const char *port, int proto, unsigned int timeout) {
 	r_return_val_if_fail (s, false);
 #if __WINDOWS__
+#define gai_strerror gai_strerrorA
 	struct sockaddr_in sa;
-	struct hostent *he;
 	WSADATA wsadata;
-	TIMEVAL Timeout;
-	Timeout.tv_sec = timeout;
-	Timeout.tv_usec = 0;
 
 	if (WSAStartup (MAKEWORD (1, 1), &wsadata) == SOCKET_ERROR) {
 		eprintf ("Error creating socket.");
 		return false;
 	}
-	s->fd = socket (AF_INET, SOCK_STREAM, 0);
-	if (s->fd == R_INVALID_SOCKET) {
-		return false;
-	}
-
-	unsigned long iMode = 1;
-	int iResult = ioctlsocket (s->fd, FIONBIO, &iMode);
-	if (iResult != NO_ERROR) {
-		eprintf ("ioctlsocket error: %d\n", iResult);
-	}
-	memset (&sa, 0, sizeof (sa));
-	sa.sin_family = AF_INET;
-	he = (struct hostent *)gethostbyname (host);
-	if (he == (struct hostent*)0) {
-#ifdef _MSC_VER
-		closesocket (s->fd);
-#else
-		close (s->fd);
 #endif
-		return false;
-	}
-	sa.sin_addr = *((struct in_addr *)he->h_addr);
-	s->port = r_socket_port_by_name (port);
-	s->proto = proto;
-	sa.sin_port = htons (s->port);
-	if (!connect (s->fd, (const struct sockaddr*)&sa, sizeof (struct sockaddr))) {
-#ifdef _MSC_VER
-		closesocket (s->fd);
-#else
-		close (s->fd);
-#endif
-		return false;
-	}
-	iMode = 0;
-	iResult = ioctlsocket (s->fd, FIONBIO, &iMode);
-	if (iResult != NO_ERROR) {
-		eprintf ("ioctlsocket error: %d\n", iResult);
-	}
-	if (timeout > 0) {
-		r_socket_block_time (s, 1, timeout, 0);
-	}
-	fd_set Write, Err;
-	FD_ZERO (&Write);
-	FD_ZERO (&Err);
-	FD_SET (s->fd, &Write);
-	FD_SET (s->fd, &Err);
-	select (0, NULL, &Write, &Err, &Timeout);
-	if (FD_ISSET (s->fd, &Write)) {
-		return true;
-	}
-	return false;
-#elif __UNIX__
 	int ret;
-	struct addrinfo hints = {0};
+	struct addrinfo hints = { 0 };
 	struct addrinfo *res, *rp;
 	if (!proto) {
 		proto = R_SOCKET_PROTO_TCP;
 	}
+#if __UNIX__
 	r_sys_signal (SIGPIPE, SIG_IGN);
+#endif
 	if (proto == R_SOCKET_PROTO_UNIX) {
+#if __UNIX__
 		if (!__connect_unix (s, host)) {
 			return false;
 		}
+#endif
 	} else {
 		hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */
 		hints.ai_protocol = proto;
 		int gai = getaddrinfo (host, port, &hints, &res);
 		if (gai != 0) {
@@ -365,21 +315,19 @@ R_API bool r_socket_connect(RSocket *s, const char *host, const char *port, int
 			if (ret == 0) {
 				freeaddrinfo (res);
 				return true;
 			}
 			if (errno == EINPROGRESS) {
-				struct timeval tv;
-				tv.tv_sec = timeout;
-				tv.tv_usec = 0;
+				struct timeval tv = {timeout, 0};
 				fd_set wfds;
-				FD_ZERO(&wfds);
-				FD_SET(s->fd, &wfds);
+				FD_ZERO (&wfds);
+				FD_SET (s->fd, &wfds);
 
 				if ((ret = select (s->fd + 1, NULL, &wfds, NULL, &tv)) != -1) {
 					if (r_socket_is_connected (s)) {
 						freeaddrinfo (res);
-						return true;
+						goto success;
 					}
 				} else {
 					perror ("connect");
 				}
 			}
@@ -389,22 +337,46 @@ R_API bool r_socket_connect(RSocket *s, const char *host, const char *port, int
 		if (!rp) {
 			eprintf ("Could not resolve address '%s' or failed to connect\n", host);
 			return false;
 		}
 	}
-#endif
+success:
 #if HAVE_LIB_SSL
 	if (s->is_ssl) {
 		s->ctx = SSL_CTX_new (SSLv23_client_method ());
 		if (!s->ctx) {
-			r_socket_free (s);
+			r_socket_close (s);
 			return false;
 		}
 		s->sfd = SSL_new (s->ctx);
 		SSL_set_fd (s->sfd, s->fd);
-		if (SSL_connect (s->sfd) != 1) {
-			r_socket_free (s);
+		int ret = SSL_connect (s->sfd);
+		if (ret != 1) {
+			int error = SSL_get_error (s->sfd, ret);
+			int tries = 10;
+			while (tries && ret && (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE)) {
+				struct timeval tv = {1, 0};
+				fd_set rfds, wfds;
+				FD_ZERO (&rfds);
+				FD_ZERO (&wfds);
+				if (error == SSL_ERROR_WANT_READ) {
+					FD_SET (s->fd, &rfds);
+				} else {
+					FD_SET (s->fd, &wfds);
+				}
+				if ((ret = select (s->fd + 1, &rfds, &wfds, NULL, &tv)) < 1) {
+					r_socket_close (s);
+					return false;
+				}
+				ret = SSL_connect (s->sfd);
+				if (ret == 1) {
+					return true;
+				}
+				error = SSL_get_error (s->sfd, ret);
+				tries--;
+			}
+			r_socket_close (s);
 			return false;
 		}
 	}
 #endif
 	return true;
@@ -620,13 +592,11 @@ R_API RSocket *r_socket_accept_timeout(RSocket *s, unsigned int timeout) {
 	FD_SET (s->fd, &read_fds);
 
 	FD_ZERO (&except_fds);
 	FD_SET (s->fd, &except_fds);
 
-	struct timeval t;
-	t.tv_sec = timeout;
-	t.tv_usec = 0;
+	struct timeval t = {timeout, 0};
 
 	int r = select (s->fd + 1, &read_fds, NULL, &except_fds, &t);
 	if(r < 0) {
 		perror ("select");
 	} else if (r > 0 && FD_ISSET (s->fd, &read_fds)) {
@@ -657,13 +627,11 @@ R_API int r_socket_block_time(RSocket *s, int block, int sec, int usec) {
 	}
 #elif __WINDOWS__
 	ioctlsocket (s->fd, FIONBIO, (u_long FAR*)&block);
 #endif
 	if (sec > 0 || usec > 0) {
-		struct timeval tv = {0};
-		tv.tv_sec = sec;
-		tv.tv_usec = usec;
+		struct timeval tv = {sec, usec};
 		if (setsockopt (s->fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof (tv)) < 0) {
 			return false;
 		}
 	}
 	return true;
@@ -676,36 +644,21 @@ R_API int r_socket_flush(RSocket *s) {
 	}
 #endif
 	return true;
 }
 
-// XXX: rewrite it to use select //
 /* waits secs until new data is received.	  */
 /* returns -1 on error, 0 is false, 1 is true */
 R_API int r_socket_ready(RSocket *s, int secs, int usecs) {
-#if __UNIX__
-	//int msecs = (1000 * secs) + (usecs / 1000);
-	int msecs = (usecs / 1000);
-	struct pollfd fds[1];
-	fds[0].fd = s->fd;
-	fds[0].events = POLLIN | POLLPRI;
-	fds[0].revents = POLLNVAL | POLLHUP | POLLERR;
-	return poll ((struct pollfd *)&fds, 1, msecs);
-#elif __WINDOWS__
 	fd_set rfds;
-	struct timeval tv;
+	struct timeval tv = {secs, usecs};
 	if (s->fd == R_INVALID_SOCKET) {
 		return -1;
 	}
 	FD_ZERO (&rfds);
 	FD_SET (s->fd, &rfds);
-	tv.tv_sec = secs;
-	tv.tv_usec = usecs;
 	return select (s->fd + 1, &rfds, NULL, NULL, &tv);
-#else
-	return true; /* always ready if unknown */
-#endif
 }
 
 R_API char *r_socket_to_string(RSocket *s) {
 #if __WINDOWS__
 	return r_str_newf ("fd%d", (int)(size_t)s->fd);
@@ -792,32 +745,28 @@ R_API int r_socket_read(RSocket *s, unsigned char *buf, int len) {
 			return BIO_read (s->bio, buf, len);
 		}
 		return SSL_read (s->sfd, buf, len);
 	}
 #endif
-#if __WINDOWS__
-rep:
-	{
-	int ret = recv (s->fd, (void *)buf, len, 0);
-	if (ret == -1) {
-		goto rep;
-	}
-	return ret;
-	}
-#else
 	// int r = read (s->fd, buf, len);
 	int r = recv (s->fd, buf, len, 0);
 	D { eprintf ("READ "); int i; for (i = 0; i<len; i++) { eprintf ("%02x ", buf[i]); } eprintf ("\n"); }
 	return r;
-#endif
 }
 
 R_API int r_socket_read_block(RSocket *s, ut8 *buf, int len) {
 	int ret = 0;
 	for (ret = 0; ret < len; ) {
 		int r = r_socket_read (s, buf + ret, len - ret);
 		if (r == -1) {
+#if HAVE_LIB_SSL
+			if (SSL_get_error (s->sfd, r) == SSL_ERROR_WANT_READ) {
+				if (r_socket_ready (s, 1, 0) == 1) {
+					continue;
+				}
+			}
+#endif
 			return -1;
 		}
 		if (r < 1) {
 			break;
 		}
diff --git a/libr/socket/socket_http.c b/libr/socket/socket_http.c
index cbc04d6fde9..aea330b70da 100644
--- a/libr/socket/socket_http.c
+++ b/libr/socket/socket_http.c
@@ -1,59 +1,99 @@
 /* radare - LGPL - Copyright 2011-2020 - pancake */
 
 #include <r_socket.h>
 #include <r_util.h>
 
-static int __socket_slurp (RSocket *s, ut8 *buf, int bufsz) {
-	int i;
-	int chsz = 1;
-	// r_socket_block_time (s, 1, 1, 0);
-	if (r_socket_read_block (s, (ut8 *) buf, 1) != 1) {
+#if __WINDOWS__
+#include <WinInet.h>
+#endif
+
+#define SOCKET_HTTP_MAX_HEADER_LENGTH 0x2000
+#define SOCKET_HTTP_MAX_REDIRECTS 5
+
+static size_t socket_slurp(RSocket *s, RBuffer *buf) {
+	size_t i;
+	if (r_socket_ready (s, 1, 0) != 1) {
 		return 0;
 	}
-	for (i = 1; i < bufsz; i += chsz) {
-		buf[i] =0;
-		r_socket_block_time (s, 1, 0, 1000);
-		int olen = r_socket_read_block (s, (ut8 *) buf + i , chsz);
-		if (olen != chsz) {
-			buf[i] = 0;
+	r_socket_block_time (s, 1, 0, 1000);
+	for (i = 0; i < SOCKET_HTTP_MAX_HEADER_LENGTH; i += 1) {
+		ut8 c;
+		int olen = r_socket_read_block (s, &c, 1);
+		if (olen != 1) {
+			r_buf_append_bytes (buf, (ut8 *)"", 1);
 			break;
 		}
+		r_buf_append_bytes (buf, &c, 1);
 	}
 	return i;
 }
 
-static char *r_socket_http_answer (RSocket *s, int *code, int *rlen) {
+static char *socket_http_get_recursive(const char *url, int *code, int *rlen, ut32 redirections);
+
+static char *socket_http_answer(RSocket *s, int *code, int *rlen, ut32 redirections) {
 	r_return_val_if_fail (s, NULL);
 	const char *p;
-	int ret, len = 0, bufsz = 32768, delta = 0;
-	char *dn, *buf = calloc (1, bufsz + 32); // XXX: use r_buffer here
-	if (!buf) {
+	int ret, len = 0, delta = 0;
+	char *dn;
+	RBuffer *b = r_buf_new ();
+	if (!b) {
 		return NULL;
 	}
 	char *res = NULL;
-	int olen = __socket_slurp (s, (ut8*)buf, bufsz);
+	size_t olen = socket_slurp (s, b);
+	char *buf = malloc (olen + 1);
+	if (!buf) {
+		goto exit;
+	}
+	r_buf_read_at (b, 0, (ut8 *)buf, olen);
+	buf[olen] = 0;
 	if ((dn = (char*)r_str_casestr (buf, "\n\n"))) {
 		delta += 2;
 	} else if ((dn = (char*)r_str_casestr (buf, "\r\n\r\n"))) {
 		delta += 4;
 	} else {
-		goto fail;
+		goto exit;
 	}
 
 	olen -= delta;
 	*dn = 0; // chop headers
+
+	/* Follow redirects */
+	p = r_str_casestr (buf, "Location:");
+	if (p) {
+		if (!redirections) {
+			eprintf ("Too many redirects\n");
+			goto exit;
+		}
+		p += strlen ("Location:");
+		char *end_url = strchr (p, '\n');
+		if (end_url) {
+			int url_len = end_url - p;
+			char *url = r_str_ndup (p, url_len);
+			r_str_trim (url);
+			res = socket_http_get_recursive (url, code, rlen, --redirections);
+			free (url);
+			len = *rlen;
+		}
+		goto exit;
+	}
+
 	/* Parse Len */
 	p = r_str_casestr (buf, "Content-Length: ");
 	if (p) {
 		len = atoi (p + 16);
 	} else {
 		len = olen - (dn - buf);
 	}
 	if (len > 0) {
 		if (len > olen) {
 			res = malloc (len + 2);
+			if (!res) {
+				goto exit;
+			}
+			olen -= dn - buf;
 			memcpy (res, dn + delta, olen);
 			do {
 				ret = r_socket_read_block (s, (ut8*) res + olen, len - olen);
 				if (ret < 1) {
 					break;
@@ -69,52 +109,122 @@ static char *r_socket_http_answer (RSocket *s, int *code, int *rlen) {
 			}
 		}
 	} else {
 		res = NULL;
 	}
-fail:
+exit:
 	free (buf);
-// is 's' free'd? isn't this going to cause a double free?
+	r_buf_free (b);
 	r_socket_close (s);
 	if (rlen) {
 		*rlen = len;
 	}
 	return res;
 }
 
-R_API char *r_socket_http_get(const char *url, int *code, int *rlen) {
+#if __WINDOWS__
+static char *http_get_w32(const char *url, int *code, int *rlen) {
+	HINTERNET hInternet = InternetOpenA ("radare2 "R2_VERSION, INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
+	if (!hInternet) {
+		r_sys_perror ("InternetOpenA");
+		return NULL;
+	}
+	HINTERNET hOpenUrl = InternetOpenUrlA (hInternet, url, NULL, 0, 0, 0);
+	if (!hOpenUrl) {
+		r_sys_perror ("InternetOpenUrlA");
+		InternetCloseHandle (hInternet);
+		return NULL;
+	}
+
+	char *ret = NULL;
+	size_t read_sz = 0x100000;
+	DWORD r = 0, w = 0;
+	bool res = true;
+	do {
+		w += r;
+		if (!res && GetLastError () == ERROR_INSUFFICIENT_BUFFER) {
+			read_sz *= 2;
+		}
+		char *tmp = realloc (ret, read_sz + w);
+		if (!tmp) {
+			R_FREE (ret);
+			goto exit;
+		}
+		ret = tmp;
+	} while (!(res = InternetReadFile (hOpenUrl, ret + w, read_sz, &r)) || r);
+
+	if (w) {
+		char *tmp = realloc (ret, (size_t)w + 1);
+		if (tmp) {
+			ret = tmp;
+			ret[w] = 0;
+		} else {
+			R_FREE (ret);
+		}
+	} else {
+		R_FREE (ret);
+	}
+
+exit:
+	if (rlen) {
+		*rlen = w;
+	}
+	if (code && w) {
+		*code = 200;
+	}
+	InternetCloseHandle (hInternet);
+	InternetCloseHandle (hOpenUrl);
+	return ret;
+}
+#endif
+
+static char *socket_http_get_recursive(const char *url, int *code, int *rlen, ut32 redirections) {
+	if (code) {
+		*code = 0;
+	}
+	if (rlen) {
+		*rlen = 0;
+	}
 	char *curl_env = r_sys_getenv ("R2_CURL");
-	if (curl_env && *curl_env) {
-		char *encoded_url = r_str_escape (url);
-		char *res = r_sys_cmd_strf ("curl '%s'", encoded_url);
-		free (encoded_url);
+	if (!R_STR_ISEMPTY (curl_env) && atoi (curl_env)) {
+		int len;
+		char *escaped_url = r_str_escape_sh (url);
+		char *command = r_str_newf ("curl -sfL -o - \"%s\"", escaped_url);
+		char *res = r_sys_cmd_str (command, NULL, &len);
+		free (escaped_url);
+		free (command);
+		free (curl_env);
+		if (!res) {
+			return NULL;
+		}
 		if (res) {
 			if (code) {
 				*code = 200;
 			}
 			if (rlen) {
-				*rlen = strlen (res);
+				*rlen = len;
 			}
 		}
-		free (curl_env);
 		return res;
 	}
 	free (curl_env);
+#if __WINDOWS__
+	return http_get_w32 (url, code, rlen);
+#else
 	RSocket *s;
 	int ssl = r_str_startswith (url, "https://");
+#if !HAVE_LIB_SSL
+	if (ssl) {
+		eprintf ("Tried to get '%s', but SSL support is disabled, set R2_CURL=1 to use curl\n", url);
+		return NULL;
+	}
+#endif
 	char *response, *host, *path, *port = "80";
 	char *uri = strdup (url);
 	if (!uri) {
 		return NULL;
 	}
-
-	if (code) {
-		*code = 0;
-	}
-	if (rlen) {
-		*rlen = 0;
-	}
 	host = strstr (uri, "://");
 	if (!host) {
 		free (uri);
 		eprintf ("r_socket_http_get: Invalid URI");
 		return NULL;
@@ -145,21 +255,26 @@ R_API char *r_socket_http_get(const char *url, int *code, int *rlen) {
 				"GET /%s HTTP/1.1\r\n"
 				"User-Agent: radare2 "R2_VERSION"\r\n"
 				"Accept: */*\r\n"
 				"Host: %s:%s\r\n"
 				"\r\n", path, host, port);
-		response = r_socket_http_answer (s, code, rlen);
+		response = socket_http_answer (s, code, rlen, redirections);
 	} else {
 		eprintf ("Cannot connect to %s:%s\n", host, port);
 		response = NULL;
 	}
 	free (uri);
 	r_socket_free (s);
 	return response;
+#endif
+}
+
+R_API char *r_socket_http_get(const char *url, int *code, int *rlen) {
+	return socket_http_get_recursive (url, code, rlen, SOCKET_HTTP_MAX_REDIRECTS);
 }
 
-R_API char *r_socket_http_post (const char *url, const char *data, int *code, int *rlen) {
+R_API char *r_socket_http_post(const char *url, const char *data, int *code, int *rlen) {
 	RSocket *s;
 	bool ssl = r_str_startswith (url, "https://");
 	char *uri = strdup (url);
 	if (!uri) {
 		return NULL;
@@ -204,11 +319,11 @@ R_API char *r_socket_http_post (const char *url, const char *data, int *code, in
 			"Content-Length: %i\r\n"
 			"Content-Type: application/x-www-form-urlencoded\r\n"
 			"\r\n", path, host, (int)strlen (data));
 	free (uri);
 	r_socket_write (s, (void *)data, strlen (data));
-	return r_socket_http_answer (s, code, rlen);
+	return socket_http_answer (s, code, rlen, 0);
 }
 
 #if TEST
 void main () {
 	int ret;
diff --git a/libr/util/str.c b/libr/util/str.c
index 3956255f590..ba18d9b0fb8 100644
--- a/libr/util/str.c
+++ b/libr/util/str.c
@@ -1349,10 +1349,38 @@ out:
 
 R_API char *r_str_escape(const char *buf) {
 	return r_str_escape_ (buf, false, true, true, false, true);
 }
 
+// Return MUST BE surrounded by double-quotes
+R_API char *r_str_escape_sh(const char *buf) {
+	r_return_val_if_fail (buf, NULL);
+	char *new_buf = malloc (1 + strlen (buf) * 2);
+	if (!new_buf) {
+		return NULL;
+	}
+	const char *p = buf;
+	char *q = new_buf;
+	while (*p) {
+		switch (*p) {
+#if __UNIX__
+		case '$':
+		case '`':
+#endif
+		case '\\':
+		case '"':
+			*q++ = '\\';
+			/* FALLTHRU */
+		default:
+			*q++ = *p++;
+			break;
+		}
+	}
+	*q = '\0';
+	return new_buf;
+}
+
 R_API char *r_str_escape_dot(const char *buf) {
 	return r_str_escape_ (buf, true, true, true, false, true);
 }
 
 R_API char *r_str_escape_latin1(const char *buf, bool show_asciidot, bool esc_bslash, bool colors) {
diff --git a/libr/util/sys.c b/libr/util/sys.c
index 4326a7536aa..e259d409271 100644
--- a/libr/util/sys.c
+++ b/libr/util/sys.c
@@ -658,10 +658,11 @@ R_API int r_sys_cmd_str_full(const char *cmd, const char *input, char **output,
 		if (!inputptr || !*inputptr) {
 			close (sh_in[1]);
 		}
 		// we should handle broken pipes somehow better
 		r_sys_signal (SIGPIPE, SIG_IGN);
+		size_t err_len = 0, out_len = 0;
 		for (;;) {
 			fd_set rfds, wfds;
 			int nfd;
 			FD_ZERO (&rfds);
 			FD_ZERO (&wfds);
@@ -678,24 +679,33 @@ R_API int r_sys_cmd_str_full(const char *cmd, const char *input, char **output,
 			nfd = select (sh_err[0] + 1, &rfds, &wfds, NULL, NULL);
 			if (nfd < 0) {
 				break;
 			}
 			if (output && FD_ISSET (sh_out[0], &rfds)) {
-				if (!(bytes = read (sh_out[0], buffer, sizeof (buffer)-1))) {
+				if ((bytes = read (sh_out[0], buffer, sizeof (buffer))) < 1) {
 					break;
 				}
-				buffer[sizeof (buffer) - 1] = '\0';
-				if (len) {
-					*len += bytes;
+				char *tmp = realloc (outputptr, out_len + bytes + 1);
+				if (!tmp) {
+					R_FREE (outputptr);
+					break;
 				}
-				outputptr = r_str_append (outputptr, buffer);
+				outputptr = tmp;
+				memcpy (outputptr + out_len, buffer, bytes);
+				out_len += bytes;
 			} else if (FD_ISSET (sh_err[0], &rfds) && sterr) {
-				if (!read (sh_err[0], buffer, sizeof (buffer)-1)) {
+				if ((bytes = read (sh_err[0], buffer, sizeof (buffer))) < 1) {
+					break;
+				}
+				char *tmp = realloc (*sterr, err_len + bytes + 1);
+				if (!tmp) {
+					R_FREE (*sterr);
 					break;
 				}
-				buffer[sizeof (buffer) - 1] = '\0';
-				*sterr = r_str_append (*sterr, buffer);
+				*sterr = tmp;
+				memcpy (*sterr + err_len, buffer, bytes);
+				err_len += bytes;
 			} else if (FD_ISSET (sh_in[1], &wfds) && inputptr && *inputptr) {
 				int inputptr_len = strlen (inputptr);
 				bytes = write (sh_in[1], inputptr, inputptr_len);
 				if (bytes != inputptr_len) {
 					break;
@@ -725,10 +735,19 @@ R_API int r_sys_cmd_str_full(const char *cmd, const char *input, char **output,
 			// eprintf ("%s: failed command '%s'\n", __func__, escmd);
 			// free (escmd);
 			ret = false;
 		}
 
+		if (len) {
+			*len = out_len;
+		}
+		if (*sterr) {
+			(*sterr)[err_len] = 0;
+		}
+		if (outputptr) {
+			outputptr[out_len] = 0;
+		}
 		if (output) {
 			*output = outputptr;
 		} else {
 			free (outputptr);
 		}
@@ -827,11 +846,11 @@ R_API bool r_sys_mkdirp(const char *dir) {
 	}
 #if __WINDOWS__
 	{
 		char *p = strstr (ptr, ":\\");
 		if (p) {
-			ptr = p + 2;
+			ptr = p + 3;
 		}
 	}
 #endif
 	for (;;) {
 		// find next slash
diff --git a/meson.build b/meson.build
index 5c3025e1cf8..498ecbc64dd 100644
--- a/meson.build
+++ b/meson.build
@@ -110,11 +110,11 @@ else
 endif
 
 platform_deps = []
 platform_inc = ['.', 'libr/include']
 if host_machine.system() == 'windows'
-  platform_deps = [cc.find_library('ws2_32'), cc.find_library('psapi')]
+  platform_deps = [cc.find_library('ws2_32'), cc.find_library('wininet'), cc.find_library('psapi')]
 endif
 platform_inc = include_directories(platform_inc)
 
 if get_option('static_runtime')
   if cc.get_id() == 'msvc'
diff --git a/shlr/sdb/src/disk.c b/shlr/sdb/src/disk.c
index 9b9d7d0afa8..edba1eff11e 100644
--- a/shlr/sdb/src/disk.c
+++ b/shlr/sdb/src/disk.c
@@ -62,11 +62,11 @@ static inline int r_sys_mkdirp(char *dir) {
 		ptr++;
 	}
 #if __SDB_WINDOWS__
 	char *p = strstr (ptr, ":\\");
 	if (p) {
-		ptr = p + 2;
+		ptr = p + 3;
 	}
 #endif
 	while ((ptr = strchr (ptr, slash))) {
 		*ptr = 0;
 		if (!r_sys_mkdir (path) && r_sys_mkdir_failed ()) {
diff --git a/test/db/formats/pdb b/test/db/formats/pdb
index d230b81992a..d59431b811e 100644
--- a/test/db/formats/pdb
+++ b/test/db/formats/pdb
@@ -1,8 +1,11 @@
 NAME=PDB downloader check
 FILE=bins/pdb/user32.dll
-CMDS=!!rabin2 -PP ${R2_FILE} ~PDB
+CMDS=<<EOF
+%R2_CURL=1
+!!rabin2 -PP ${R2_FILE} ~PDB
+EOF
 EXPECT=<<EOF
 PDB "user32.pdb" download success
 EOF
 RUN
 
diff --git a/test/unit/test_str.c b/test/unit/test_str.c
index ad069232569..a96c9de2431 100644
--- a/test/unit/test_str.c
+++ b/test/unit/test_str.c
@@ -334,10 +334,28 @@ bool test_r_str_sanitize_sdb_key(void) {
 	mu_assert_streq (s, "rada_re2_is_::Cool", "sanitize");
 	free (s);
 	mu_end;
 }
 
+bool test_r_str_escape_sh(void) {
+	char *escaped = r_str_escape_sh ("Hello, \"World\"");
+	mu_assert_streq (escaped, "Hello, \\\"World\\\"", "escaped \"double quotes\"");
+	free (escaped);
+	escaped = r_str_escape_sh ("Hello, \\World\\");
+	mu_assert_streq (escaped, "Hello, \\\\World\\\\", "escaped backspace");
+	free (escaped);
+#if __UNIX__
+	escaped = r_str_escape_sh ("Hello, $(World)");
+	mu_assert_streq (escaped, "Hello, \\$(World)", "escaped $(command)");
+	free (escaped);
+	escaped = r_str_escape_sh ("Hello, `World`");
+	mu_assert_streq (escaped, "Hello, \\`World\\`", "escaped `command`");
+	free (escaped);
+#endif
+	mu_end;
+}
+
 bool test_r_str_unescape(void) {
 	char buf[] = "Hello\\x31World\\n";
 	r_str_unescape (buf);
 	mu_assert_streq (buf, "Hello1World\n", "unescaped");
 	mu_end;
@@ -475,10 +493,11 @@ bool all_tests () {
 	mu_run_test (test_r_str_ansi_len);
 	mu_run_test (test_r_str_len_utf8_ansi);
 	mu_run_test (test_r_str_utf8_charsize);
 	mu_run_test (test_r_str_utf8_charsize_prev);
 	mu_run_test (test_r_str_sanitize_sdb_key);
+	mu_run_test (test_r_str_escape_sh);
 	mu_run_test (test_r_str_unescape);
 	mu_run_test (test_r_str_constpool);
 	mu_run_test (test_r_str_format_msvc_argv);
 	mu_run_test (test_r_str_str_xy);
 	return tests_passed != tests_run;

Vulnerable code: static bool checkCurl() {
	const char nul[] = R_SYS_DEVNULL;
	if (r_sys_cmdf ("curl --version > %s", nul) != 0) {
	char *curl_cmd = NULL;
	char *extractor_cmd = NULL;
	char *abspath_to_archive = NULL;
	char *abspath_to_file = NULL;
	char *archive_name = NULL;
	size_t archive_name_len = 0;
	char *symbol_store_path = NULL;
	char *dbg_file = NULL;
	char *guid = NULL;
	char *archive_name_escaped  = NULL;
	char *user_agent = NULL;
	char *symbol_server = NULL;

	if (!checkCurl ()) {
		return 0;
	}
	// dbg_file len is > 0
	archive_name_len = strlen (opt->dbg_file);
	archive_name = malloc (archive_name_len + 1);
	if (!archive_name) {
		return 0;
	}
	memcpy (archive_name, opt->dbg_file, archive_name_len + 1);
	archive_name[archive_name_len - 1] = '_';
	symbol_store_path = r_str_escape (opt->symbol_store_path);
	dbg_file = r_str_escape (opt->dbg_file);
	guid = r_str_escape (opt->guid);
	archive_name_escaped = r_str_escape (archive_name);
	user_agent = r_str_escape (opt->user_agent);
	symbol_server = r_str_escape (opt->symbol_server);

	abspath_to_archive = r_str_newf ("%s%s%s%s%s%s%s",
			    symbol_store_path, R_SYS_DIR,
			    dbg_file, R_SYS_DIR,
			    guid, R_SYS_DIR,
			    archive_name_escaped);

	abspath_to_file = strdup (abspath_to_archive);
	abspath_to_file[strlen (abspath_to_file) - 1] = 'b';
		R_FREE (user_agent);
		R_FREE (abspath_to_archive);
		R_FREE (archive_name_escaped);
		R_FREE (symbol_store_path);
		R_FREE (dbg_file);
		R_FREE (guid);
		R_FREE (archive_name);
		R_FREE (abspath_to_file);
		R_FREE (symbol_server);
		res = 1;

		curl_cmd = r_str_newf ("curl -sfLA \"%s\" \"%s/%s/%s/%s\" --create-dirs -o \"%s\"",
		                       user_agent,
		                       symbol_server,
							   dbg_file,
							   guid,
		                       archive_name_escaped,
		                       abspath_to_archive);
		const char *cabextractor = "expand";
		const char *format = "%s %s %s";

		// extractor_cmd -> %1 %2 %3
		// %1 - 'expand'
		// %2 - absolute path to archive
		// %3 - absolute path to file that will be dearchive
		extractor_cmd = r_str_newf (format, cabextractor,
			abspath_to_archive, abspath_to_file);
		const char *cabextractor = "cabextract";
		const char *format = "%s -d \"%s\" \"%s\"";
		extractor_cmd = r_str_newf (format, cabextractor, abspath_to_dir, abspath_to_archive);
		R_FREE (abspath_to_dir);
		eprintf ("Attempting to download compressed pdb in %s\n", abspath_to_archive);
		if ((cmd_ret = r_sys_cmd (curl_cmd) != 0)) {
			eprintf("curl exited with error %d\n", cmd_ret);
			res = 0;
		}
		eprintf ("Attempting to decompress pdb\n");
		if (opt->extract > 0) {
		R_FREE (curl_cmd);
		res = 1;

		archive_name_escaped[strlen (archive_name_escaped) - 1] = 'b';

		curl_cmd = r_str_newf ("curl -sfLA \"%s\" \"%s/%s/%s/%s\" --create-dirs -o \"%s\"",
		                       opt->user_agent,
		                       opt->symbol_server,
		                       opt->dbg_file,
		                       opt->guid,
		                       archive_name_escaped,
		                       abspath_to_file);
		if ((cmd_ret = r_sys_cmd (curl_cmd) != 0)) {
			eprintf("curl exited with error %d\n", cmd_ret);
			res = 0;
		}
		R_FREE (curl_cmd);
	}
	R_FREE (abspath_to_archive);
	R_FREE (abspath_to_file);
	R_FREE (archive_name);
	R_FREE (extractor_cmd);
	R_FREE (symbol_store_path);
	R_FREE (dbg_file);
	R_FREE (guid);
	R_FREE (archive_name_escaped);
	R_FREE (user_agent);
	R_FREE (symbol_server);
/* Must be included before windows.h (r_types) */
#if defined(__WINDOWS__)
#include <ws2tcpip.h>
#endif

	struct hostent *he;
	TIMEVAL Timeout;
	Timeout.tv_sec = timeout;
	Timeout.tv_usec = 0;
	s->fd = socket (AF_INET, SOCK_STREAM, 0);
	if (s->fd == R_INVALID_SOCKET) {
		return false;
	}

	unsigned long iMode = 1;
	int iResult = ioctlsocket (s->fd, FIONBIO, &iMode);
	if (iResult != NO_ERROR) {
		eprintf ("ioctlsocket error: %d\n", iResult);
	}
	memset (&sa, 0, sizeof (sa));
	sa.sin_family = AF_INET;
	he = (struct hostent *)gethostbyname (host);
	if (he == (struct hostent*)0) {
#ifdef _MSC_VER
		closesocket (s->fd);
#else
		close (s->fd);
		return false;
	}
	sa.sin_addr = *((struct in_addr *)he->h_addr);
	s->port = r_socket_port_by_name (port);
	s->proto = proto;
	sa.sin_port = htons (s->port);
	if (!connect (s->fd, (const struct sockaddr*)&sa, sizeof (struct sockaddr))) {
#ifdef _MSC_VER
		closesocket (s->fd);
#else
		close (s->fd);
#endif
		return false;
	}
	iMode = 0;
	iResult = ioctlsocket (s->fd, FIONBIO, &iMode);
	if (iResult != NO_ERROR) {
		eprintf ("ioctlsocket error: %d\n", iResult);
	}
	if (timeout > 0) {
		r_socket_block_time (s, 1, timeout, 0);
	}
	fd_set Write, Err;
	FD_ZERO (&Write);
	FD_ZERO (&Err);
	FD_SET (s->fd, &Write);
	FD_SET (s->fd, &Err);
	select (0, NULL, &Write, &Err, &Timeout);
	if (FD_ISSET (s->fd, &Write)) {
		return true;
	}
	return false;
#elif __UNIX__
	struct addrinfo hints = {0};
				struct timeval tv;
				tv.tv_sec = timeout;
				tv.tv_usec = 0;
				FD_ZERO(&wfds);
				FD_SET(s->fd, &wfds);
						return true;
#endif
			r_socket_free (s);
		if (SSL_connect (s->sfd) != 1) {
			r_socket_free (s);
	struct timeval t;
	t.tv_sec = timeout;
	t.tv_usec = 0;
		struct timeval tv = {0};
		tv.tv_sec = sec;
		tv.tv_usec = usec;
// XXX: rewrite it to use select //
#if __UNIX__
	//int msecs = (1000 * secs) + (usecs / 1000);
	int msecs = (usecs / 1000);
	struct pollfd fds[1];
	fds[0].fd = s->fd;
	fds[0].events = POLLIN | POLLPRI;
	fds[0].revents = POLLNVAL | POLLHUP | POLLERR;
	return poll ((struct pollfd *)&fds, 1, msecs);
#elif __WINDOWS__
	struct timeval tv;
	tv.tv_sec = secs;
	tv.tv_usec = usecs;
#else
	return true; /* always ready if unknown */
#endif
#if __WINDOWS__
rep:
	{
	int ret = recv (s->fd, (void *)buf, len, 0);
	if (ret == -1) {
		goto rep;
	}
	return ret;
	}
#else
#endif
static int __socket_slurp (RSocket *s, ut8 *buf, int bufsz) {
	int i;
	int chsz = 1;
	// r_socket_block_time (s, 1, 1, 0);
	if (r_socket_read_block (s, (ut8 *) buf, 1) != 1) {
	for (i = 1; i < bufsz; i += chsz) {
		buf[i] =0;
		r_socket_block_time (s, 1, 0, 1000);
		int olen = r_socket_read_block (s, (ut8 *) buf + i , chsz);
		if (olen != chsz) {
			buf[i] = 0;
static char *r_socket_http_answer (RSocket *s, int *code, int *rlen) {
	int ret, len = 0, bufsz = 32768, delta = 0;
	char *dn, *buf = calloc (1, bufsz + 32); // XXX: use r_buffer here
	if (!buf) {
	int olen = __socket_slurp (s, (ut8*)buf, bufsz);
		goto fail;
fail:
// is 's' free'd? isn't this going to cause a double free?
R_API char *r_socket_http_get(const char *url, int *code, int *rlen) {
	if (curl_env && *curl_env) {
		char *encoded_url = r_str_escape (url);
		char *res = r_sys_cmd_strf ("curl '%s'", encoded_url);
		free (encoded_url);
				*rlen = strlen (res);
		free (curl_env);

	if (code) {
		*code = 0;
	}
	if (rlen) {
		*rlen = 0;
	}
		response = r_socket_http_answer (s, code, rlen);
R_API char *r_socket_http_post (const char *url, const char *data, int *code, int *rlen) {
	return r_socket_http_answer (s, code, rlen);
				if (!(bytes = read (sh_out[0], buffer, sizeof (buffer)-1))) {
				buffer[sizeof (buffer) - 1] = '\0';
				if (len) {
					*len += bytes;
				outputptr = r_str_append (outputptr, buffer);
				if (!read (sh_err[0], buffer, sizeof (buffer)-1)) {
				buffer[sizeof (buffer) - 1] = '\0';
				*sterr = r_str_append (*sterr, buffer);
			ptr = p + 2;
  platform_deps = [cc.find_library('ws2_32'), cc.find_library('psapi')]
		ptr = p + 2;
CMDS=!!rabin2 -PP ${R2_FILE} ~PDB
Fixed code: static bool download_and_write(SPDBDownloaderOpt *opt, const char *file) {
	char *dir = r_str_newf ("%s%s%s%s%s",
		opt->symbol_store_path, R_SYS_DIR,
		opt->dbg_file, R_SYS_DIR,
		opt->guid);
	if (!r_sys_mkdirp (dir)) {
		free (dir);
	char *url = r_str_newf ("%s/%s/%s/%s", opt->symbol_server, opt->dbg_file, opt->guid, file);
	int len;
	char *file_buf = r_socket_http_get (url, NULL, &len);
	free (url);
	if (!len || R_STR_ISEMPTY (file_buf)) {
		free (dir);
		free (file_buf);
		return false;
	}
	char *path = r_str_newf ("%s%s%s", dir, R_SYS_DIR, opt->dbg_file);
	FILE *f = fopen (path, "wb");
	if (f) {
		fwrite (file_buf, sizeof (char), (size_t)len, f);
		fclose (f);
	}
	free (dir);
	free (path);
	free (file_buf);


	char *abspath_to_file = r_str_newf ("%s%s%s%s%s%s%s",
		opt->symbol_store_path, R_SYS_DIR,
		opt->dbg_file, R_SYS_DIR,
		opt->guid, R_SYS_DIR,
		opt->dbg_file);

		free (abspath_to_file);
		char *extractor_cmd = NULL;
		char *archive_name = strdup (opt->dbg_file);
		archive_name[strlen (archive_name) - 1] = '_';
		char *abspath_to_archive = r_str_newf ("%s%s%s%s%s%s%s",
			opt->symbol_store_path, R_SYS_DIR,
			opt->dbg_file, R_SYS_DIR,
			opt->guid, R_SYS_DIR,
			archive_name);

		eprintf ("Attempting to download compressed pdb in %s\n", abspath_to_archive);
		char *abs_arch_esc = r_str_escape_sh (abspath_to_archive);
		char *abs_file_esc = r_str_escape_sh (abspath_to_file);
		// expand %1 %2
		// %1 - absolute path to archive
		// %2 - absolute path to file that will be dearchive
		extractor_cmd = r_str_newf ("expand \"%s\" \"%s\"", abs_arch_esc, abs_file_esc);
		free (abs_file_esc);
		char *abs_dir_esc = r_str_escape_sh (abspath_to_dir);
		extractor_cmd = r_str_newf ("cabextract -d \"%s\" \"%s\"", abs_arch_esc, abs_dir_esc);
		free (abs_dir_esc);
		free (abspath_to_dir);
		free (abs_arch_esc);
		res = download_and_write (opt, archive_name);

		if (opt->extract > 0 && res) {
			eprintf ("Attempting to decompress pdb\n");
		free (archive_name);
		free (abspath_to_archive);
		res = download_and_write (opt, opt->dbg_file);
	}
	free (abspath_to_file);
static bool is_valid_guid(const char *guid) {
	if (!guid) {
		return false;
	}
	size_t i;
	for (i = 0; guid[i]; i++) {
		if (!isxdigit (guid[i])) {
			return false;
		}
	}
	return i >= 33; // len of GUID and age
}

	if (!is_valid_guid (info->guid)) {
		eprintf ("Invalid GUID for file\n");
		return 1;
	}

# open-ssl
ifeq (${HAVE_LIB_SSL},1)
BN_LIBS=${SSL_LDFLAGS}
endif

  #include <ws2tcpip.h>
R_API char *r_str_escape_sh(const char *buf);
#define gai_strerror gai_strerrorA
	struct addrinfo hints = { 0 };
#if __UNIX__
#endif
#if __UNIX__
#endif
				struct timeval tv = {timeout, 0};
				FD_ZERO (&wfds);
				FD_SET (s->fd, &wfds);
						goto success;
success:
			r_socket_close (s);
		int ret = SSL_connect (s->sfd);
		if (ret != 1) {
			int error = SSL_get_error (s->sfd, ret);
			int tries = 10;
			while (tries && ret && (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE)) {
				struct timeval tv = {1, 0};
				fd_set rfds, wfds;
				FD_ZERO (&rfds);
				FD_ZERO (&wfds);
				if (error == SSL_ERROR_WANT_READ) {
					FD_SET (s->fd, &rfds);
				} else {
					FD_SET (s->fd, &wfds);
				}
				if ((ret = select (s->fd + 1, &rfds, &wfds, NULL, &tv)) < 1) {
					r_socket_close (s);
					return false;
				}
				ret = SSL_connect (s->sfd);
				if (ret == 1) {
					return true;
				}
				error = SSL_get_error (s->sfd, ret);
				tries--;
			}
			r_socket_close (s);
	struct timeval t = {timeout, 0};
		struct timeval tv = {sec, usec};
	struct timeval tv = {secs, usecs};
#if HAVE_LIB_SSL
			if (SSL_get_error (s->sfd, r) == SSL_ERROR_WANT_READ) {
				if (r_socket_ready (s, 1, 0) == 1) {
					continue;
				}
			}
#endif
#if __WINDOWS__
#include <WinInet.h>
#endif

#define SOCKET_HTTP_MAX_HEADER_LENGTH 0x2000
#define SOCKET_HTTP_MAX_REDIRECTS 5

static size_t socket_slurp(RSocket *s, RBuffer *buf) {
	size_t i;
	if (r_socket_ready (s, 1, 0) != 1) {
	r_socket_block_time (s, 1, 0, 1000);
	for (i = 0; i < SOCKET_HTTP_MAX_HEADER_LENGTH; i += 1) {
		ut8 c;
		int olen = r_socket_read_block (s, &c, 1);
		if (olen != 1) {
			r_buf_append_bytes (buf, (ut8 *)"", 1);
		r_buf_append_bytes (buf, &c, 1);
static char *socket_http_get_recursive(const char *url, int *code, int *rlen, ut32 redirections);

static char *socket_http_answer(RSocket *s, int *code, int *rlen, ut32 redirections) {
	int ret, len = 0, delta = 0;
	char *dn;
	RBuffer *b = r_buf_new ();
	if (!b) {
	size_t olen = socket_slurp (s, b);
	char *buf = malloc (olen + 1);
	if (!buf) {
		goto exit;
	}
	r_buf_read_at (b, 0, (ut8 *)buf, olen);
	buf[olen] = 0;
		goto exit;

	/* Follow redirects */
	p = r_str_casestr (buf, "Location:");
	if (p) {
		if (!redirections) {
			eprintf ("Too many redirects\n");
			goto exit;
		}
		p += strlen ("Location:");
		char *end_url = strchr (p, '\n');
		if (end_url) {
			int url_len = end_url - p;
			char *url = r_str_ndup (p, url_len);
			r_str_trim (url);
			res = socket_http_get_recursive (url, code, rlen, --redirections);
			free (url);
			len = *rlen;
		}
		goto exit;
	}

			if (!res) {
				goto exit;
			}
			olen -= dn - buf;
exit:
	r_buf_free (b);
#if __WINDOWS__
static char *http_get_w32(const char *url, int *code, int *rlen) {
	HINTERNET hInternet = InternetOpenA ("radare2 "R2_VERSION, INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
	if (!hInternet) {
		r_sys_perror ("InternetOpenA");
		return NULL;
	}
	HINTERNET hOpenUrl = InternetOpenUrlA (hInternet, url, NULL, 0, 0, 0);
	if (!hOpenUrl) {
		r_sys_perror ("InternetOpenUrlA");
		InternetCloseHandle (hInternet);
		return NULL;
	}

	char *ret = NULL;
	size_t read_sz = 0x100000;
	DWORD r = 0, w = 0;
	bool res = true;
	do {
		w += r;
		if (!res && GetLastError () == ERROR_INSUFFICIENT_BUFFER) {
			read_sz *= 2;
		}
		char *tmp = realloc (ret, read_sz + w);
		if (!tmp) {
			R_FREE (ret);
			goto exit;
		}
		ret = tmp;
	} while (!(res = InternetReadFile (hOpenUrl, ret + w, read_sz, &r)) || r);

	if (w) {
		char *tmp = realloc (ret, (size_t)w + 1);
		if (tmp) {
			ret = tmp;
			ret[w] = 0;
		} else {
			R_FREE (ret);
		}
	} else {
		R_FREE (ret);
	}

exit:
	if (rlen) {
		*rlen = w;
	}
	if (code && w) {
		*code = 200;
	}
	InternetCloseHandle (hInternet);
	InternetCloseHandle (hOpenUrl);
	return ret;
}
#endif

static char *socket_http_get_recursive(const char *url, int *code, int *rlen, ut32 redirections) {
	if (code) {
		*code = 0;
	}
	if (rlen) {
		*rlen = 0;
	}
	if (!R_STR_ISEMPTY (curl_env) && atoi (curl_env)) {
		int len;
		char *escaped_url = r_str_escape_sh (url);
		char *command = r_str_newf ("curl -sfL -o - \"%s\"", escaped_url);
		char *res = r_sys_cmd_str (command, NULL, &len);
		free (escaped_url);
		free (command);
		free (curl_env);
		if (!res) {
			return NULL;
		}
				*rlen = len;
#if __WINDOWS__
	return http_get_w32 (url, code, rlen);
#else
#if !HAVE_LIB_SSL
	if (ssl) {
		eprintf ("Tried to get '%s', but SSL support is disabled, set R2_CURL=1 to use curl\n", url);
		return NULL;
	}
#endif
		response = socket_http_answer (s, code, rlen, redirections);
#endif
}

R_API char *r_socket_http_get(const char *url, int *code, int *rlen) {
	return socket_http_get_recursive (url, code, rlen, SOCKET_HTTP_MAX_REDIRECTS);
R_API char *r_socket_http_post(const char *url, const char *data, int *code, int *rlen) {
	return socket_http_answer (s, code, rlen, 0);
// Return MUST BE surrounded by double-quotes
R_API char *r_str_escape_sh(const char *buf) {
	r_return_val_if_fail (buf, NULL);
	char *new_buf = malloc (1 + strlen (buf) * 2);
	if (!new_buf) {
		return NULL;
	}
	const char *p = buf;
	char *q = new_buf;
	while (*p) {
		switch (*p) {
#if __UNIX__
		case '$':
		case '`':
#endif
		case '\\':
		case '"':
			*q++ = '\\';
			/* FALLTHRU */
		default:
			*q++ = *p++;
			break;
		}
	}
	*q = '\0';
	return new_buf;
}

		size_t err_len = 0, out_len = 0;
				if ((bytes = read (sh_out[0], buffer, sizeof (buffer))) < 1) {
				char *tmp = realloc (outputptr, out_len + bytes + 1);
				if (!tmp) {
					R_FREE (outputptr);
					break;
				outputptr = tmp;
				memcpy (outputptr + out_len, buffer, bytes);
				out_len += bytes;
				if ((bytes = read (sh_err[0], buffer, sizeof (buffer))) < 1) {
					break;
				}
				char *tmp = realloc (*sterr, err_len + bytes + 1);
				if (!tmp) {
					R_FREE (*sterr);
				*sterr = tmp;
				memcpy (*sterr + err_len, buffer, bytes);
				err_len += bytes;
		if (len) {
			*len = out_len;
		}
		if (*sterr) {
			(*sterr)[err_len] = 0;
		}
		if (outputptr) {
			outputptr[out_len] = 0;
		}
			ptr = p + 3;
  platform_deps = [cc.find_library('ws2_32'), cc.find_library('wininet'), cc.find_library('psapi')]
		ptr = p + 3;
CMDS=<<EOF
%R2_CURL=1
!!rabin2 -PP ${R2_FILE} ~PDB
EOF
bool test_r_str_escape_sh(void) {
	char *escaped = r_str_escape_sh ("Hello, \"World\"");
	mu_assert_streq (escaped, "Hello, \\\"World\\\"", "escaped \"double quotes\"");
	free (escaped);
	escaped = r_str_escape_sh ("Hello, \\World\\");
	mu_assert_streq (escaped, "Hello, \\\\World\\\\", "escaped backspace");
	free (escaped);
#if __UNIX__
	escaped = r_str_escape_sh ("Hello, $(World)");
	mu_assert_streq (escaped, "Hello, \\$(World)", "escaped $(command)");
	free (escaped);
	escaped = r_str_escape_sh ("Hello, `World`");
	mu_assert_streq (escaped, "Hello, \\`World\\`", "escaped `command`");
	free (escaped);
#endif
	mu_end;
}

	mu_run_test (test_r_str_escape_sh);