ExternalProject: retry download on recoverable errors

In order to shorten the download failure of ExternalProject download
steps, a download retry is only done when a recoverable network
error is encountered.
diff --git a/Modules/ExternalProject-download.cmake.in b/Modules/ExternalProject-download.cmake.in
index 99fb917..587e3cc 100644
--- a/Modules/ExternalProject-download.cmake.in
+++ b/Modules/ExternalProject-download.cmake.in
@@ -107,19 +107,23 @@
    dst='@LOCAL@'
    timeout='@TIMEOUT_MSG@'"
 )
-
+set(download_retry_codes 7 6 8 15)
+set(skip_url_list)
+set(status_code)
 foreach(i RANGE ${retry_number})
-  sleep_before_download(${i})
-
+  if(status_code IN_LIST download_retry_codes)
+    sleep_before_download(${i})
+  endif()
   foreach(url @REMOTE@)
-    message(STATUS "Using src='${url}'")
+    if(NOT url IN_LIST skip_url_list)
+      message(STATUS "Using src='${url}'")
 
-    @TLS_VERIFY_CODE@
-    @TLS_CAINFO_CODE@
-    @NETRC_CODE@
-    @NETRC_FILE_CODE@
+      @TLS_VERIFY_CODE@
+      @TLS_CAINFO_CODE@
+      @NETRC_CODE@
+      @NETRC_FILE_CODE@
 
-    file(
+      file(
         DOWNLOAD
         "${url}" "@LOCAL@"
         @SHOW_PROGRESS@
@@ -128,31 +132,36 @@
         LOG log
         @USERPWD_ARGS@
         @HTTP_HEADERS_ARGS@
-    )
+        )
 
-    list(GET status 0 status_code)
-    list(GET status 1 status_string)
+      list(GET status 0 status_code)
+      list(GET status 1 status_string)
 
-    if(status_code EQUAL 0)
-      check_file_hash(has_hash hash_is_good)
-      if(has_hash AND NOT hash_is_good)
-        message(STATUS "Hash mismatch, removing...")
-        file(REMOVE "@LOCAL@")
+      if(status_code EQUAL 0)
+        check_file_hash(has_hash hash_is_good)
+        if(has_hash AND NOT hash_is_good)
+          message(STATUS "Hash mismatch, removing...")
+          file(REMOVE "@LOCAL@")
+        else()
+          message(STATUS "Downloading... done")
+          return()
+        endif()
       else()
-        message(STATUS "Downloading... done")
-        return()
+        string(APPEND logFailedURLs "error: downloading '${url}' failed
+        status_code: ${status_code}
+        status_string: ${status_string}
+        log:
+        --- LOG BEGIN ---
+        ${log}
+        --- LOG END ---
+        "
+        )
+      if(NOT status_code IN_LIST download_retry_codes)
+        list(APPEND skip_url_list "${url}")
+        break()
       endif()
-    else()
-      string(APPEND logFailedURLs "error: downloading '${url}' failed
-       status_code: ${status_code}
-       status_string: ${status_string}
-       log:
-       --- LOG BEGIN ---
-       ${log}
-       --- LOG END ---
-       "
-      )
     endif()
+  endif()
   endforeach()
 endforeach()
 
diff --git a/Tests/RunCMake/ExternalProject/DownloadTimeout-build-result.txt b/Tests/RunCMake/ExternalProject/DownloadTimeout-build-result.txt
new file mode 100644
index 0000000..c20fd86
--- /dev/null
+++ b/Tests/RunCMake/ExternalProject/DownloadTimeout-build-result.txt
@@ -0,0 +1 @@
+^[^0]
diff --git a/Tests/RunCMake/ExternalProject/DownloadTimeout-build-stderr.txt b/Tests/RunCMake/ExternalProject/DownloadTimeout-build-stderr.txt
new file mode 100644
index 0000000..8d98f9d
--- /dev/null
+++ b/Tests/RunCMake/ExternalProject/DownloadTimeout-build-stderr.txt
@@ -0,0 +1 @@
+.*
diff --git a/Tests/RunCMake/ExternalProject/DownloadTimeout.cmake b/Tests/RunCMake/ExternalProject/DownloadTimeout.cmake
new file mode 100644
index 0000000..c90b4ba
--- /dev/null
+++ b/Tests/RunCMake/ExternalProject/DownloadTimeout.cmake
@@ -0,0 +1,5 @@
+include(ExternalProject)
+set(source_dir "${CMAKE_CURRENT_BINARY_DIR}/DownloadTimeout")
+file(REMOVE_RECURSE "${source_dir}")
+file(MAKE_DIRECTORY "${source_dir}")
+ExternalProject_Add(MyProj URL "http://cmake.org/invalid_file.tar.gz")
diff --git a/Tests/RunCMake/ExternalProject/RunCMakeTest.cmake b/Tests/RunCMake/ExternalProject/RunCMakeTest.cmake
index 0d1da26..4d23bf8 100644
--- a/Tests/RunCMake/ExternalProject/RunCMakeTest.cmake
+++ b/Tests/RunCMake/ExternalProject/RunCMakeTest.cmake
@@ -38,6 +38,7 @@
 if(NOT RunCMake_GENERATOR MATCHES "Visual Studio")
   __ep_test_with_build(LogOutputOnFailure)
   __ep_test_with_build(LogOutputOnFailureMerged)
+  __ep_test_with_build(DownloadTimeout)
 endif()
 
 # We can't test the substitution when using the old MSYS due to