blob: 7d134384bcb03c03af2c0f76cf8173448715a9bb [file] [log] [blame]
/*
* Copyright (c) 2008, 2009, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#define STRICT
#ifndef _WIN32_WINNT
/* REMIND : 0x500 means Windows 2000 .. seems like we can update
* for Windows XP when we move the SDK and build platform
*/
#define _WIN32_WINNT 0x0500
#endif
#define _ATL_APARTMENT_THREADED
#include <atlbase.h>
//You may derive a class from CComModule and use it if you want to override
//something, but do not change the name of _Module
extern CComModule _Module;
#include <atlcom.h>
#include <atlwin.h>
#include <atlhost.h>
#include <commdlg.h>
#include <commctrl.h>
#include <windowsx.h>
#include <urlmon.h>
#include <wininet.h>
#include <shellapi.h>
#include <time.h>
#include <math.h>
#include <stdio.h>
#include <jni.h>
#include "resource.h" // main symbols
#include "DownloadHelper.h"
DownloadHelper::DownloadHelper() {
m_showProgressDialog = TRUE;
m_pszURL = NULL;
m_pszFileName = NULL;
m_pszNameText = NULL;
}
DownloadHelper::~DownloadHelper() {
}
HRESULT DownloadHelper::doDownload() {
return DownloadFile(m_pszURL, m_pszFileName, FALSE, m_showProgressDialog);
}
HRESULT DownloadHelper::DownloadFile(const TCHAR* szURL,
const TCHAR* szLocalFile, BOOL bResumable, BOOL bUIFeedback) {
HINTERNET hOpen = NULL;
HINTERNET hConnect = NULL;
HINTERNET hRequest = NULL;
HANDLE hFile = INVALID_HANDLE_VALUE;
DWORD dwDownloadError = 0;
DWORD nContentLength = 0;
/* Some of error messages use drive letter.
Result is something like "(C:)".
NB: Parentheses are added here because in some other places
we same message but can not provide disk label info */
TCHAR drivePath[5];
/* assuming szLocalFile is not NULL */
_sntprintf(drivePath, 5, "(%c:)", szLocalFile[0]);
WCHAR* wName = CT2CW(drivePath);
__try {
m_csDownload.Lock();
time(&m_startTime);
}
__finally {
m_csDownload.Unlock();
}
__try {
// block potential security hole
if (strstr(szURL, TEXT("file://")) != NULL) {
dwDownloadError = 1;
__leave;
}
HWND hProgressInfo = NULL;
TCHAR szStatus[BUFFER_SIZE];
if (bUIFeedback) {
// init download dialg text
m_dlg->initDialogText(m_pszURL, m_pszNameText);
}
// Open Internet Call
hOpen = ::InternetOpen("deployHelper", INTERNET_OPEN_TYPE_PRECONFIG,
NULL, NULL, NULL);
if (hOpen == NULL) {
dwDownloadError = 1;
__leave;
}
// URL components
URL_COMPONENTS url_components;
::ZeroMemory(&url_components, sizeof(URL_COMPONENTS));
TCHAR szHostName[BUFFER_SIZE], szUrlPath[BUFFER_SIZE],
szExtraInfo[BUFFER_SIZE];
url_components.dwStructSize = sizeof(URL_COMPONENTS);
url_components.lpszHostName = szHostName;
url_components.dwHostNameLength = BUFFER_SIZE;
url_components.nPort = NULL;
url_components.lpszUrlPath = szUrlPath;
url_components.dwUrlPathLength = BUFFER_SIZE;
url_components.lpszExtraInfo = szExtraInfo;
url_components.dwExtraInfoLength = BUFFER_SIZE;
// Crack the URL into pieces
::InternetCrackUrl(szURL, lstrlen(szURL), NULL, &url_components);
// Open Internet Connection
hConnect = ::InternetConnect(hOpen, url_components.lpszHostName,
url_components.nPort, "", "", INTERNET_SERVICE_HTTP, NULL,
NULL);
if (hConnect == NULL) {
dwDownloadError = 1;
__leave;
}
// Determine the relative URL path by combining
// Path and ExtraInfo
char szURL[4096];
if (url_components.dwUrlPathLength != 0)
lstrcpy(szURL, url_components.lpszUrlPath);
else
lstrcpy(szURL, "/");
if (url_components.dwExtraInfoLength != 0)
lstrcat(szURL, url_components.lpszExtraInfo);
BOOL bRetryHttpRequest = FALSE;
int numberOfRetry = 0;
long secondsToWait = 60;
do {
bRetryHttpRequest = FALSE;
// Make a HTTP GET request
hRequest = ::HttpOpenRequest(hConnect, "GET", szURL, "HTTP/1.1",
"", NULL,
INTERNET_FLAG_KEEP_CONNECTION | INTERNET_FLAG_DONT_CACHE,
0);
if (hRequest == NULL) {
dwDownloadError = 1;
__leave;
}
// Create or open existing destination file
hFile = ::CreateFile(szLocalFile, GENERIC_WRITE, 0, NULL,
OPEN_ALWAYS, FILE_ATTRIBUTE_ARCHIVE, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
if (bUIFeedback) {
if (IDRETRY == m_dlg->SafeMessageBox(
IDS_DISK_WRITE_ERROR,
IDS_DISK_WRITE_ERROR_CAPTION,
IDS_ERROR_CAPTION,
DIALOG_ERROR_RETRYCANCEL,
wName)) {
bRetryHttpRequest = TRUE;
continue;
}
}
dwDownloadError = 1;
__leave;
}
DWORD fileSize = GetFileSize(hFile, NULL);
// Check if resumable download is enabled
if (bResumable == FALSE) {
// Start from scratch
fileSize = 0;
}
FILETIME tWrite;
BOOL rangereq = FALSE;
if ((fileSize != 0) && (fileSize != 0xFFFFFFFF) &&
GetFileTime(hFile, NULL, NULL, &tWrite)) {
char szHead[100];
SYSTEMTIME tLocal;
char buf[INTERNET_RFC1123_BUFSIZE];
FileTimeToSystemTime(&tWrite, &tLocal);
InternetTimeFromSystemTime(&tLocal, INTERNET_RFC1123_FORMAT,
buf, INTERNET_RFC1123_BUFSIZE);
sprintf(szHead, "Range: bytes=%d-\r\nIf-Range: %s\r\n",
fileSize, buf);
HttpAddRequestHeaders(hRequest, szHead, lstrlen(szHead),
HTTP_ADDREQ_FLAG_ADD|HTTP_ADDREQ_FLAG_REPLACE);
rangereq = TRUE;
}
// This is a loop to handle various potential error when the
// connection is made
BOOL bCont = TRUE;
while ((FALSE == ::HttpSendRequest(hRequest, NULL, NULL, NULL, NULL))
&& bCont ) {
// We might have an invalid CA.
DWORD dwErrorCode = GetLastError();
switch(dwErrorCode) {
case E_JDHELPER_TIMEOUT:
case E_JDHELPER_NAME_NOT_RESOLVED:
case E_JDHELPER_CANNOT_CONNECT: {
bCont = FALSE;
// Display the information dialog
if (bUIFeedback) {
// decrement download counter to prevent progress
// dialog from popping up while the message box is
// up
m_dlg->bundleInstallComplete();
if (dwErrorCode == E_JDHELPER_TIMEOUT) {
bRetryHttpRequest =
(IDRETRY == m_dlg->SafeMessageBox(
IDS_HTTP_STATUS_REQUEST_TIMEOUT,
IDS_HTTP_INSTRUCTION_REQUEST_TIMEOUT,
IDS_ERROR_CAPTION,
DIALOG_ERROR_RETRYCANCEL));
} else {
bRetryHttpRequest =
(IDRETRY == m_dlg->SafeMessageBox(
IDS_HTTP_STATUS_SERVER_NOT_REACHABLE,
IDS_HTTP_INSTRUCTION_SERVER_NOT_REACHABLE,
IDS_ERROR_CAPTION,
DIALOG_ERROR_RETRYCANCEL));
}
// re-increment counter because it will be decremented
// again upon return
m_dlg->bundleInstallStart();
bCont = bRetryHttpRequest;
}
break;
}
case ERROR_INTERNET_INVALID_CA:
case ERROR_INTERNET_SEC_CERT_CN_INVALID:
case ERROR_INTERNET_SEC_CERT_DATE_INVALID:
case ERROR_INTERNET_HTTP_TO_HTTPS_ON_REDIR:
case ERROR_INTERNET_INCORRECT_PASSWORD:
case ERROR_INTERNET_CLIENT_AUTH_CERT_NEEDED:
default: {
// Unless the user agrees to continue, we just
// abandon now !
bCont = FALSE;
// Make sure to test the return code from
// InternetErrorDlg user may click OK or Cancel. In
// case of Cancel, request should not be resubmitted
if (bUIFeedback) {
if (ERROR_SUCCESS == ::InternetErrorDlg(
NULL, hRequest,
dwErrorCode,
FLAGS_ERROR_UI_FILTER_FOR_ERRORS |
FLAGS_ERROR_UI_FLAGS_GENERATE_DATA |
FLAGS_ERROR_UI_FLAGS_CHANGE_OPTIONS,
NULL))
bCont = TRUE;
}
}
}
}
if (bCont == FALSE) {
// User has denied the request
dwDownloadError = 1;
__leave;
}
//
// Read HTTP status code
//
DWORD dwErrorCode = GetLastError();
DWORD dwStatus=0;
DWORD dwStatusSize = sizeof(DWORD);
if (FALSE == ::HttpQueryInfo(hRequest, HTTP_QUERY_FLAG_NUMBER |
HTTP_QUERY_STATUS_CODE, &dwStatus, &dwStatusSize, NULL)) {
dwErrorCode = GetLastError();
}
bCont = TRUE;
while ((dwStatus == HTTP_STATUS_PROXY_AUTH_REQ ||
dwStatus == HTTP_STATUS_DENIED) &&
bCont) {
int result = ::InternetErrorDlg(GetDesktopWindow(), hRequest, ERROR_INTERNET_INCORRECT_PASSWORD,
FLAGS_ERROR_UI_FILTER_FOR_ERRORS |
FLAGS_ERROR_UI_FLAGS_CHANGE_OPTIONS |
FLAGS_ERROR_UI_FLAGS_GENERATE_DATA,
NULL);
if (ERROR_CANCELLED == result) {
bCont = FALSE;
}
else {
::HttpSendRequest(hRequest, NULL, 0, NULL, 0);
// Reset buffer length
dwStatusSize = sizeof(DWORD);
::HttpQueryInfo(hRequest, HTTP_QUERY_FLAG_NUMBER |
HTTP_QUERY_STATUS_CODE, &dwStatus, &dwStatusSize,
NULL);
}
}
if (dwStatus == HTTP_STATUS_OK ||
dwStatus == HTTP_STATUS_PARTIAL_CONTENT) {
// Determine content length, so we may show the progress bar
// meaningfully
//
nContentLength = 0;
DWORD nLengthSize = sizeof(DWORD);
::HttpQueryInfo(hRequest,
HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER,
&nContentLength, &nLengthSize, NULL);
if (nContentLength <= 0) {
// If can't estimate content length, estimate it
// to be 6MB
nContentLength = 15000000;
}
else if (rangereq && (fileSize != 0) &&
(nContentLength == fileSize)) {
// If the file is already downloaded completely and then
// we send a range request, the whole file is sent instead
// of nothing. So avoid downloading again.
// Some times return value is 206, even when whole file
// is sent. So check if "Content-range:" is present in the
// reply
char buffer[256];
DWORD length = sizeof(buffer);
if(!HttpQueryInfo(hRequest, HTTP_QUERY_CONTENT_RANGE,
buffer, &length, NULL)) {
if(HttpQueryInfo(hRequest, HTTP_QUERY_LAST_MODIFIED,
buffer, &length, NULL)) {
SYSTEMTIME systime;
FILETIME filtime;
InternetTimeToSystemTime(buffer, &systime, NULL);
SystemTimeToFileTime(&systime, &filtime);
if ((CompareFileTime(&tWrite, &filtime)) == 1) {
// no need to download
dwDownloadError = 0;
__leave;
}
}
else {
::SetFilePointer(hFile, 0, 0, FILE_BEGIN);
::SetEndOfFile(hFile); // truncate the file
}
}
}
TCHAR szBuffer[8096];
DWORD dwBufferSize = 8096;
// Read from HTTP connection and write into
// destination file
//
DWORD nRead = 0;
DWORD dwTotalRead = 0;
BOOL bCancel = FALSE;
if (dwStatus == HTTP_STATUS_PARTIAL_CONTENT) {
// If we are using resumable download, fake
// start time so it looks like we have begun
// the download several minutes again.
//
m_startTime = m_startTime - 100;
::SetFilePointer(hFile, 0, 0, FILE_END); // seek to end
}
else {
::SetFilePointer(hFile, 0, 0, FILE_BEGIN);
::SetEndOfFile(hFile); // truncate the file
}
do {
nRead=0;
if (::InternetReadFile(hRequest, szBuffer, dwBufferSize,
&nRead)) {
if (nRead) {
DWORD dwNumberOfBytesWritten = NULL;
BOOL ret = WriteFile(hFile, szBuffer, nRead,
&dwNumberOfBytesWritten, NULL);
if (!ret) {
// WriteFile failed
if (bUIFeedback) {
if (GetLastError() == ERROR_DISK_FULL) {
bRetryHttpRequest =
(IDRETRY == m_dlg->SafeMessageBox(
IDS_DISK_FULL_ERROR,
IDS_DISK_FULL_ERROR_CAPTION,
IDS_ERROR_CAPTION,
DIALOG_ERROR_RETRYCANCEL,
wName));
} else {
bRetryHttpRequest =
(IDRETRY == m_dlg->SafeMessageBox(
IDS_DISK_WRITE_ERROR,
IDS_DISK_WRITE_ERROR_CAPTION,
IDS_ERROR_CAPTION,
DIALOG_ERROR_RETRYCANCEL,
wName));
}
if (!bRetryHttpRequest) {
dwDownloadError = 1;
break;
}
}
continue;
}
}
dwTotalRead += nRead;
// update download progress dialog
m_dlg->OnProgress(nRead);
// Check if download has been cancelled
if (m_dlg->isDownloadCancelled()) {
m_dlg->decrementProgressMax(nContentLength,
dwTotalRead);
bCancel = TRUE;
break;
}
}
else {
bCancel = TRUE;
break;
}
}
while (nRead);
if (bCancel) {
// User has cancelled the operation or InternetRead failed
// don't do return here, we need to cleanup
dwDownloadError = 1;
__leave;
}
}
else if (dwStatus == 416 && (fileSize != 0) &&
(fileSize != 0xFFFFFFFF)) {
// This error could be returned, When the full file exists
// and a range request is sent with range beyond filessize.
// The best way to fix this is in future is, to send HEAD
// request and get filelength before sending range request.
dwDownloadError = 0;
__leave;
}
else if (dwStatus == 403) { // Forbidden from Akamai means we need to get a new download token
JNIEnv *env = m_dlg->getJNIEnv();
jclass exceptionClass = env->FindClass("java/net/HttpRetryException");
if (exceptionClass == NULL) {
/* Unable to find the exception class, give up. */
__leave;
}
jmethodID constructor;
constructor = env->GetMethodID(exceptionClass,
"<init>", "(Ljava/lang/String;I)V");
if (constructor != NULL) {
jobject exception = env->NewObject(exceptionClass,
constructor, env->NewStringUTF("Forbidden"),
403);
env->Throw((jthrowable) exception);
}
__leave;
}
else if(dwStatus >= 400 && dwStatus < 600) {
/* NB: Following case seems to be never used!
HTTP_STATUS_FORBIDDEN is the same as 403 and
403 was specially handled few lines above! */
if (dwStatus == HTTP_STATUS_FORBIDDEN) {
if (bUIFeedback) {
bRetryHttpRequest = (IDRETRY == m_dlg->SafeMessageBox(
IDS_HTTP_STATUS_FORBIDDEN,
IDS_HTTP_INSTRUCTION_FORBIDDEN,
IDS_ERROR_CAPTION,
DIALOG_ERROR_RETRYCANCEL,
L"403"));
}
}
else if (dwStatus == HTTP_STATUS_SERVER_ERROR) {
if (bUIFeedback) {
bRetryHttpRequest = (IDRETRY == m_dlg->SafeMessageBox(
IDS_HTTP_STATUS_SERVER_ERROR,
IDS_HTTP_INSTRUCTION_UNKNOWN_ERROR,
IDS_ERROR_CAPTION,
DIALOG_ERROR_RETRYCANCEL,
L"500"));
}
}
else if (dwStatus == HTTP_STATUS_SERVICE_UNAVAIL) {
if (numberOfRetry < 5) {
// If the server is busy, automatically retry
// We wait couple seconds before retry to avoid
// congestion
for (long i = (long) secondsToWait; i >= 0; i--) {
// Update status
if (bUIFeedback) {
char szBuffer[BUFFER_SIZE];
::LoadString(_Module.GetResourceInstance(),
IDS_DOWNLOAD_STATUS_RETRY, szStatus,
BUFFER_SIZE);
wsprintf(szBuffer, szStatus, i);
::SetWindowText(hProgressInfo, szBuffer);
}
// Sleep 1 second
::Sleep(1000);
}
// We use a semi-binary backoff algorithm to
// determine seconds to wait
numberOfRetry += 1;
secondsToWait = secondsToWait + 30;
bRetryHttpRequest = TRUE;
continue;
}
else {
if (bUIFeedback) {
bRetryHttpRequest = (IDRETRY == m_dlg->SafeMessageBox(
IDS_HTTP_STATUS_SERVICE_UNAVAIL,
IDS_HTTP_INSTRUCTION_SERVICE_UNAVAIL,
IDS_ERROR_CAPTION,
DIALOG_ERROR_RETRYCANCEL,
L"503"));
if (bRetryHttpRequest) {
numberOfRetry = 0;
secondsToWait = 60;
continue;
}
}
}
}
else {
if (bUIFeedback) {
WCHAR szBuffer[10];
_snwprintf(szBuffer, 10, L"%d", dwStatus);
bRetryHttpRequest = (IDRETRY == m_dlg->SafeMessageBox(
IDS_HTTP_STATUS_OTHER,
IDS_HTTP_INSTRUCTION_UNKNOWN_ERROR,
IDS_ERROR_CAPTION,
DIALOG_ERROR_RETRYCANCEL,
szBuffer));
}
}
if (!bRetryHttpRequest) {
dwDownloadError = 1;
}
}
else {
if (bUIFeedback) {
WCHAR szBuffer[10];
_snwprintf(szBuffer, 10, L"%d", dwStatus);
bRetryHttpRequest = (IDRETRY == m_dlg->SafeMessageBox(
IDS_HTTP_STATUS_OTHER,
IDS_HTTP_INSTRUCTION_UNKNOWN_ERROR,
IDS_ERROR_CAPTION,
DIALOG_ERROR_RETRYCANCEL,
szBuffer));
}
if (!bRetryHttpRequest) {
dwDownloadError = 1;
}
}
// Close HTTP request
//
// This is necessary if the HTTP request
// is retried
if (hRequest)
::InternetCloseHandle(hRequest);
if (hFile != INVALID_HANDLE_VALUE) {
::CloseHandle(hFile);
hFile = INVALID_HANDLE_VALUE;
}
}
while (bRetryHttpRequest);
}
__finally {
if (hRequest)
::InternetCloseHandle(hRequest);
if (hConnect)
::InternetCloseHandle(hConnect);
if (hOpen)
::InternetCloseHandle(hOpen);
if (hFile != INVALID_HANDLE_VALUE)
::CloseHandle(hFile);
}
// Exit dialog
if (dwDownloadError == 0) {
return S_OK;
} else {
DeleteFile(szLocalFile);
return E_FAIL;
}
}