Skip to content

Commit

Permalink
support https connections for CRAN and other downloads
Browse files Browse the repository at this point in the history
  • Loading branch information
jjallaire committed Jul 3, 2015
1 parent fd76c61 commit da2bd52
Show file tree
Hide file tree
Showing 22 changed files with 515 additions and 139 deletions.
14 changes: 11 additions & 3 deletions package/linux/CMakeLists.txt
Expand Up @@ -46,7 +46,7 @@ if(RSTUDIO_SERVER)
# automatic dependency resolution use e.g.
# sudo apt-get install gdebi-core
# sudo gdebi rstudio-server-0.97.151-amd64.deb
set(RSTUDIO_DEBIAN_DEPENDS "psmisc, libapparmor1, libedit2, ")
set(RSTUDIO_DEBIAN_DEPENDS "psmisc, libapparmor1, libedit2, ca-certificates, ")

# rpm dependencies
set(RSTUDIO_RPM_DEPENDS "psmisc, ")
Expand All @@ -55,6 +55,11 @@ if(RSTUDIO_SERVER)
if(IS_RHEL_5 AND NOT RSTUDIO_PACKAGE_BUILD_SLES)
set(RSTUDIO_RPM_DEPENDS "${RSTUDIO_RPM_DEPENDS}libffi, ")
endif()

# on RHEL > 5 we depend on ca-certificates (didn't exist prior to that)
if(NOT IS_RHEL_5)
set(RSTUDIO_RPM_DEPENDS "${RSTUDIO_RPM_DEPENDS}ca-certificates, ")
endif()

# don't auto-resolve other dependencies
set(CPACK_RPM_PACKAGE_AUTOREQPROV " no")
Expand All @@ -69,8 +74,11 @@ elseif(RSTUDIO_DESKTOP)
set(RPM_POSTINST postinst-desktop.sh.in)
set(RPM_POSTRM postrm-desktop.sh.in)

# depend on libjpeg62 (for Qt 4.8 jpeg plugin)
set(RSTUDIO_DEBIAN_DEPENDS "libjpeg62, libedit2, ")
# Debian/Ubuntu desktop dependencies
set(RSTUDIO_DEBIAN_DEPENDS "libjpeg62, libedit2, ca-certificates, ")

# RPM desktop dependencies
set(RSTUDIO_RPM_DEPENDS "ca-certificates, ")

endif()

Expand Down
10 changes: 8 additions & 2 deletions src/cpp/r/R/Tools.R
Expand Up @@ -456,12 +456,19 @@ assign(envir = .rs.Env, ".rs.getVar", function(name)
.rs.setCRANRepos(reposUrl)
})


.rs.addFunction( "isCRANReposFromSettings", function()
{
!is.null(attr(getOption("repos"), "RStudio"))
})


.rs.addFunction( "setCRANReposFromSettings", function(reposUrl)
{
# only set the repository if the repository was set by us
# in the first place (it wouldn't be if the user defined a
# repository in .Rprofile or called setRepositories directly)
if (!is.null(attr(getOption("repos"), "RStudio")))
if (.rs.isCRANReposFromSettings())
.rs.setCRANRepos(reposUrl)
})

Expand Down Expand Up @@ -631,4 +638,3 @@ assign(envir = .rs.Env, ".rs.getVar", function(name)
.rs.addFunction("rVersionString", function() {
as.character(getRversion())
})

32 changes: 32 additions & 0 deletions src/cpp/session/SessionModuleContext.cpp
Expand Up @@ -1719,6 +1719,38 @@ std::string CRANReposURL()
return url;
}

std::string downloadFileMethod(const std::string& defaultMethod)
{
std::string method;
Error error = r::exec::evaluateString(
"getOption('download.file.method', '" +
defaultMethod + "')", &method);
if (error)
LOG_ERROR(error);
return method;
}

std::string CRANDownloadOptions()
{
std::string options("options(repos = c(CRAN='" +
module_context::CRANReposURL() + "')");
std::string method = module_context::downloadFileMethod();
if (!method.empty())
options += ", download.file.method = '" + method + "'";
options += ")";
return options;
}

bool haveSecureDownloadFileMethod()
{
bool secure = false;
Error error = r::exec::RFunction(".rs.haveSecureDownloadFileMethod").call(
&secure);
if (error)
LOG_ERROR(error);
return secure;
}

shell_utils::ShellCommand RCommand::buildRCmd(const core::FilePath& rBinDir)
{
#if defined(_WIN32)
Expand Down
19 changes: 17 additions & 2 deletions src/cpp/session/SessionUserSettings.cpp
Expand Up @@ -447,7 +447,11 @@ CRANMirror UserSettings::cranMirror() const

// re-map cran.rstudio.org to cran.rstudio.com
if (mirror.url == "http://cran.rstudio.org")
mirror.url = "http://cran.rstudio.com";
mirror.url = "https://cran.rstudio.com";

// re-map http to https
if (mirror.url == "http://cran.rstudio.com")
mirror.url = "https://cran.rstudio.com";

mirror.country = settings_.get(kCRANMirrorCountry);

Expand All @@ -456,7 +460,7 @@ CRANMirror UserSettings::cranMirror() const
{
mirror.name = "Global (CDN)";
mirror.host = "RStudio";
mirror.url = "http://cran.rstudio.com";
mirror.url = "https://cran.rstudio.com";
mirror.country = "us";
}

Expand Down Expand Up @@ -500,6 +504,17 @@ void UserSettings::setBioconductorMirror(
setBioconductorReposOption(bioconductorMirror.url);
}

bool UserSettings::securePackageDownload() const
{
return settings_.getBool("securePackageDownload", true);
}

void UserSettings::setSecurePackageDownload(bool secureDownload)
{
settings_.set("securePackageDownload", secureDownload);
}


bool UserSettings::vcsEnabled() const
{
return settings_.getBool("vcsEnabled", true);
Expand Down
8 changes: 8 additions & 0 deletions src/cpp/session/include/session/SessionModuleContext.hpp
Expand Up @@ -533,6 +533,14 @@ std::string previousRpubsUploadId(const core::FilePath& filePath);

std::string CRANReposURL();

std::string downloadFileMethod(const std::string& defaultMethod = "");

std::string CRANDownloadOptions();

bool haveSecureDownloadFileMethod();

void verifyCRANMirrorSecurity();

struct UserPrompt
{
enum Type { Info = 0, Warning = 1, Error = 2, Question = 3 };
Expand Down
3 changes: 3 additions & 0 deletions src/cpp/session/include/session/SessionUserSettings.hpp
Expand Up @@ -126,6 +126,9 @@ class UserSettings : boost::noncopyable
BioconductorMirror bioconductorMirror() const;
void setBioconductorMirror(const BioconductorMirror& bioconductorMirror);

bool securePackageDownload() const;
void setSecurePackageDownload(bool secureDownload);

bool vcsEnabled() const;
void setVcsEnabled(bool enabled);

Expand Down
13 changes: 10 additions & 3 deletions src/cpp/session/modules/SessionDependencies.cpp
Expand Up @@ -348,14 +348,21 @@ Error installDependencies(const json::JsonRpcRequest& request,
{
std::string pkgList = boost::algorithm::join(cranPackages, ",");
cmd += "utils::install.packages(c(" + pkgList + "), " +
"repos = '"+ module_context::CRANReposURL() + "');";
"repos = '"+ module_context::CRANReposURL() + "'";
std::string method = module_context::downloadFileMethod();
if (!method.empty())
cmd += ", method = '" + module_context::downloadFileMethod() + "'";
cmd += ");";
}
if (!cranSourcePackages.empty())
{
std::string pkgList = boost::algorithm::join(cranSourcePackages, ",");
cmd += "utils::install.packages(c(" + pkgList + "), " +
"repos = '"+ module_context::CRANReposURL() +
"', type = 'source');";
"repos = '"+ module_context::CRANReposURL() + "', ";
std::string method = module_context::downloadFileMethod();
if (!method.empty())
cmd += "method = '" + module_context::downloadFileMethod() + "', ";
cmd += "type = 'source');";
}
BOOST_FOREACH(const std::string& pkg, embeddedPackages)
{
Expand Down
140 changes: 138 additions & 2 deletions src/cpp/session/modules/SessionPackages.R
Expand Up @@ -351,12 +351,12 @@ if (identical(as.character(Sys.info()["sysname"]), "Darwin") &&
# RStudio mirror
rstudioDF <- data.frame(name = "Global (CDN)",
host = "RStudio",
url = "http://cran.rstudio.com",
url = "https://cran.rstudio.com",
country = "us",
stringsAsFactors = FALSE)

# CRAN mirrors
cranMirrors <- utils::getCRANmirrors()
cranMirrors <- utils::getCRANmirrors(local.only = TRUE)
cranDF <- data.frame(name = cranMirrors$Name,
host = cranMirrors$Host,
url = cranMirrors$URL,
Expand Down Expand Up @@ -943,3 +943,139 @@ if (identical(as.character(Sys.info()["sysname"]), "Darwin") &&
.rs.success()

})


.rs.addFunction("secureDownloadMethod", function()
{
# Check whether we are running R 3.2 and whether we have libcurl
isR32 <- getRversion() >= "3.2"
haveLibcurl <- isR32 && capabilities("libcurl")

# Utility function to bind to libcurl or a fallback utility (e.g. wget)
posixMethod <- function(utility) {
if (haveLibcurl)
"libcurl"
else if (nzchar(Sys.which(utility)))
utility
else
""
}

# Determine the right secure download method per-system
sysName <- Sys.info()[['sysname']]

# For windows we prefer binding directly to wininet if we can (since
# that doesn't rely on the value of setInternet2). If it's R <= 3.1
# then we can use "internal" for https so long as internet2 is enabled
# (we don't use libcurl on Windows because it doesn't check certs).
if (identical(sysName, "Windows")) {
if (isR32)
"wininet"
else if (setInternet2(NA))
"internal"
else
""
}

# For Darwin and Linux we use libcurl if we can and then fall back
# to curl or wget as appropriate. We prefer libcurl because it honors
# the same proxy configuration that "internal" does so it less likely
# to break downloads for users behind proxy servers.

else if (identical(sysName, "Darwin")) {
posixMethod("curl")
}

else if (identical(sysName, "Linux")) {
method <- posixMethod("wget")
if (!nzchar(method))
method <- posixMethod("curl")
method
}

# Another OS, don't even attempt detection since RStudio currently
# only runs on Windows, Linux, and Mac
else {
""
}
})

.rs.addFunction("autoDownloadMethod", function() {
if (capabilities("http/ftp"))
"internal"
else if (nzchar(Sys.which("wget")))
"wget"
else if (nzchar(Sys.which("curl")))
"curl"
else
""
})

.rs.addFunction("isDownloadMethodSecure", function(method) {

# resolve auto if needed
if (identical(method, "auto"))
method <- .rs.autoDownloadMethod()

# check for methods known to work securely
if (method %in% c("wininet", "libcurl", "wget", "curl")) {
TRUE
}

# if internal then see if were using windows internal with inet2
else if (identical(method, "internal")) {
identical(Sys.info()[['sysname']], "Windows") && setInternet2(NA)
}

# method with unknown properties (e.g. "lynx") or unresolved auto
else {
FALSE
}
})

.rs.addFunction("haveSecureDownloadFileMethod", function() {
.rs.isDownloadMethodSecure(getOption("download.file.method", "auto"))
})


.rs.addFunction("insecureReposWarning", function(msg) {
message("WARNING: ", msg, " To learn more and/or disable this warning ",
"message see the \"Use secure download method for HTTP\" option ",
"in Tools -> Global Options -> Packages.")
})

.rs.addFunction("initSecureDownload", function() {

# constant for the 'learn more' part of the message
learnMore <- paste("To learn more and/or disable this warning",
"message see the \"Use secure download method for HTTP\" option",
"in Tools -> Global Options -> Packages.")

# check if the user has already established a download.file.method and
# if so verify that it is secure
method <- getOption("download.file.method")
if (!is.null(method)) {
if (!.rs.isDownloadMethodSecure(method)) {
.rs.insecureReposWarning(
paste("The download.file.method option is \"", method, "\" ",
"however that method cannot provide secure (https) downloads ",
"on this platform.", sep = "")
)
}
}

# no user specified method, automatically set a secure one if we can
else {
secureMethod <- .rs.secureDownloadMethod()
if (nzchar(secureMethod)) {
options(download.file.method = secureMethod)
}
else {
.rs.insecureReposWarning(
"Unable to set a secure (https) download.file.method (no ",
"compatible method available in this installation of R)."
)
}
}
})

0 comments on commit da2bd52

Please sign in to comment.