2 // This file is part of the aMule Project.
4 // Copyright (c) 2003-2011 aMule Team ( admin@amule.org / http://www.amule.org )
5 // Copyright (c) 2002-2011 Timo Kujala ( tiku@users.sourceforge.net )
7 // Any parts of this program derived from the xMule, lMule or eMule project,
8 // or contributed by third-party developers are copyrighted by their
11 // This program is free software; you can redistribute it and/or modify
12 // it under the terms of the GNU General Public License as published by
13 // the Free Software Foundation; either version 2 of the License, or
14 // (at your option) any later version.
16 // This program is distributed in the hope that it will be useful,
17 // but WITHOUT ANY WARRANTY; without even the implied warranty of
18 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 // GNU General Public License for more details.
21 // You should have received a copy of the GNU General Public License
22 // along with this program; if not, write to the Free Software
23 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
27 #include <wx/wfstream.h>
28 #include <wx/protocol/http.h>
31 #include "HTTPDownload.h" // Interface declarations
32 #include <common/StringFunctions.h> // Needed for unicode2char
33 #include "OtherFunctions.h" // Needed for CastChild
34 #include "Logger.h" // Needed for AddLogLine*
35 #include <common/Format.h> // Needed for CFormat
36 #include "InternalEvents.h" // Needed for CMuleInternalEvent
37 #include "Preferences.h"
38 #include "ScopedPtr.h"
39 #include <wx/filename.h> // Needed for wxFileName
43 #include "inetdownload.h" // Needed for inetDownload
44 #include "muuli_wdr.h" // Needed for ID_CANCEL: Let it here or will fail on win32
45 #include "MuleGifCtrl.h"
47 typedef wxGauge wxGaugeControl
;
49 DECLARE_LOCAL_EVENT_TYPE(wxEVT_HTTP_PROGRESS
, wxANY_ID
)
50 DECLARE_LOCAL_EVENT_TYPE(wxEVT_HTTP_SHUTDOWN
, wxANY_ID
)
53 class CHTTPDownloadDialog
: public wxDialog
56 CHTTPDownloadDialog(CHTTPDownloadThread
* thread
)
57 : wxDialog(wxTheApp
->GetTopWindow(), -1, _("Downloading..."),
58 wxDefaultPosition
, wxDefaultSize
, wxDEFAULT_DIALOG_STYLE
| wxSYSTEM_MENU
)
60 downloadDlg(this, true)->Show(this, true);
62 m_progressbar
= CastChild(ID_HTTPDOWNLOADPROGRESS
, wxGaugeControl
);
63 m_progressbar
->SetRange(100);
65 m_ani
= CastChild(ID_ANIMATE
, MuleGifCtrl
);
66 m_ani
->LoadData((const char*)inetDownload
, sizeof(inetDownload
));
72 ~CHTTPDownloadDialog() {
76 void UpdateGauge(int total
, int current
) {
77 CFormat
label( wxT("( %s / %s )") );
79 label
% CastItoXBytes(current
);
81 label
% CastItoXBytes(total
);
86 CastChild(IDC_DOWNLOADSIZE
, wxStaticText
)->SetLabel(label
.GetString());
88 if (total
&& (total
!= m_progressbar
->GetRange())) {
89 m_progressbar
->SetRange(total
);
92 if (current
&& (current
<= total
)) {
93 m_progressbar
->SetValue(current
);
108 void OnBtnCancel(wxCommandEvent
& WXUNUSED(evt
)) {
109 AddLogLineN(_("HTTP download cancelled"));
114 void OnProgress(CMuleInternalEvent
& evt
) {
115 UpdateGauge(evt
.GetExtraLong(), evt
.GetInt());
118 void OnShutdown(CMuleInternalEvent
& WXUNUSED(evt
)) {
123 CMuleThread
* m_thread
;
125 wxGaugeControl
* m_progressbar
;
127 DECLARE_EVENT_TABLE()
131 BEGIN_EVENT_TABLE(CHTTPDownloadDialog
, wxDialog
)
132 EVT_BUTTON(ID_HTTPCANCEL
, CHTTPDownloadDialog::OnBtnCancel
)
133 EVT_MULE_INTERNAL(wxEVT_HTTP_PROGRESS
, -1, CHTTPDownloadDialog::OnProgress
)
134 EVT_MULE_INTERNAL(wxEVT_HTTP_SHUTDOWN
, -1, CHTTPDownloadDialog::OnShutdown
)
137 DEFINE_LOCAL_EVENT_TYPE(wxEVT_HTTP_PROGRESS
)
138 DEFINE_LOCAL_EVENT_TYPE(wxEVT_HTTP_SHUTDOWN
)
143 CHTTPDownloadThread::CHTTPDownloadThread(const wxString
& url
, const wxString
& filename
, const wxString
& oldfilename
, HTTP_Download_File file_id
,
144 bool showDialog
, bool checkDownloadNewer
)
146 : CMuleThread(wxTHREAD_DETACHED
),
148 : CMuleThread(showDialog
? wxTHREAD_JOINABLE
: wxTHREAD_DETACHED
),
151 m_tempfile(filename
),
158 CHTTPDownloadDialog
* dialog
= new CHTTPDownloadDialog(this);
160 m_companion
= dialog
;
163 // Get the date on which the original file was last modified
164 // Only if it's the same URL we used for the last download and if the file exists.
165 if (checkDownloadNewer
&& thePrefs::GetLastHTTPDownloadURL(file_id
) == url
) {
166 wxFileName origFile
= wxFileName(oldfilename
);
167 if (origFile
.FileExists()) {
168 AddDebugLogLineN(logHTTP
, CFormat(wxT("URL %s matches and file %s exists, only download if newer")) % url
% oldfilename
);
169 m_lastmodified
= origFile
.GetModificationTime();
172 wxMutexLocker
lock(s_allThreadsMutex
);
173 s_allThreads
.insert(this);
177 // Format the given date to a RFC-2616 compliant HTTP date
178 // Example: Thu, 14 Jan 2010 15:40:23 GMT
179 wxString
CHTTPDownloadThread::FormatDateHTTP(const wxDateTime
& date
)
181 static const wxChar
* s_months
[] = { wxT("Jan"), wxT("Feb"), wxT("Mar"), wxT("Apr"), wxT("May"), wxT("Jun"), wxT("Jul"), wxT("Aug"), wxT("Sep"), wxT("Oct"), wxT("Nov"), wxT("Dec") };
182 static const wxChar
* s_dow
[] = { wxT("Sun"), wxT("Mon"), wxT("Tue"), wxT("Wed"), wxT("Thu"), wxT("Fri"), wxT("Sat") };
184 return CFormat(wxT("%s, %02d %s %d %02d:%02d:%02d GMT")) % s_dow
[date
.GetWeekDay(wxDateTime::UTC
)] % date
.GetDay(wxDateTime::UTC
) % s_months
[date
.GetMonth(wxDateTime::UTC
)] % date
.GetYear(wxDateTime::UTC
) % date
.GetHour(wxDateTime::UTC
) % date
.GetMinute(wxDateTime::UTC
) % date
.GetSecond(wxDateTime::UTC
);
188 CMuleThread::ExitCode
CHTTPDownloadThread::Entry()
194 wxHTTP
* url_handler
= NULL
;
196 AddDebugLogLineN(logHTTP
, wxT("HTTP download thread started"));
198 const CProxyData
* proxy_data
= thePrefs::GetProxyData();
199 bool use_proxy
= proxy_data
!= NULL
&& proxy_data
->m_proxyEnable
;
202 wxFFileOutputStream
outfile(m_tempfile
);
205 throw wxString(CFormat(_("Unable to create destination file %s for download!")) % m_tempfile
);
208 if (m_url
.IsEmpty()) {
209 // Nowhere to download from!
210 throw wxString(_("The URL to download can't be empty"));
213 url_handler
= new wxHTTP
;
214 url_handler
->SetProxyMode(use_proxy
);
216 // Build a conditional get request if the last modified date of the file being updated is known
217 if (m_lastmodified
.IsValid()) {
218 // Set a flag in the HTTP header that we only download if the file is newer.
219 // see: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.25
220 AddDebugLogLineN(logHTTP
, wxT("If-Modified-Since: ") + FormatDateHTTP(m_lastmodified
));
221 url_handler
->SetHeader(wxT("If-Modified-Since"), FormatDateHTTP(m_lastmodified
));
224 CScopedPtr
<wxInputStream
> url_read_stream(GetInputStream(url_handler
, m_url
, use_proxy
));
226 if (!url_read_stream
.get()) {
227 if (m_response
== 304) {
228 m_result
= HTTP_Skipped
;
229 AddDebugLogLineN(logHTTP
, wxT("Skipped download because requested file is not newer."));
230 throw wxString(wxEmptyString
);
232 m_result
= HTTP_Error
;
233 throw wxString(CFormat(_("The URL %s returned: %i - Error (%i)!")) % m_url
% m_response
% m_error
);
237 int download_size
= url_read_stream
->GetSize();
239 if (download_size
== -1) {
240 AddDebugLogLineN(logHTTP
, wxT("Download size not received, downloading until connection is closed"));
242 AddDebugLogLineN(logHTTP
, CFormat(wxT("Download size: %i")) % download_size
);
246 // Here is our read buffer
247 // <ken> Still, I'm sure 4092 is probably a better size.
248 // MP: Most people can download at least at 32kb/s from http...
249 const unsigned MAX_HTTP_READ
= 32768;
251 char buffer
[MAX_HTTP_READ
];
252 int current_read
= 0;
255 url_read_stream
->Read(buffer
, MAX_HTTP_READ
);
256 current_read
= url_read_stream
->LastRead();
258 total_read
+= current_read
;
259 outfile
.Write(buffer
,current_read
);
260 int current_write
= outfile
.LastWrite();
261 if (current_read
!= current_write
) {
262 throw wxString(_("Critical error while writing downloaded file"));
263 } else if (m_companion
) {
265 CMuleInternalEvent
evt(wxEVT_HTTP_PROGRESS
);
266 evt
.SetInt(total_read
);
267 evt
.SetExtraLong(download_size
);
268 wxPostEvent(m_companion
, evt
);
272 } while (current_read
&& !TestDestroy());
274 if (current_read
== 0) {
275 if (download_size
== -1) {
276 // Download was probably succesful.
277 AddLogLineN(CFormat(_("Downloaded %d bytes")) % total_read
);
278 m_result
= HTTP_Success
;
279 } else if (total_read
!= download_size
) {
280 m_result
= HTTP_Error
;
281 throw wxString(CFormat(_("Expected %d bytes, but downloaded %d bytes")) % download_size
% total_read
);
283 // Download was succesful.
284 m_result
= HTTP_Success
;
287 } catch (const wxString
& error
) {
288 if (wxFileExists(m_tempfile
)) {
289 wxRemoveFile(m_tempfile
);
291 if (!error
.IsEmpty()) {
296 if (m_result
== HTTP_Success
) {
297 thePrefs::SetLastHTTPDownloadURL(m_file_id
, m_url
);
301 url_handler
->Destroy();
304 AddDebugLogLineN(logHTTP
, wxT("HTTP download thread ended"));
310 void CHTTPDownloadThread::OnExit()
314 CMuleInternalEvent
termEvent(wxEVT_HTTP_SHUTDOWN
);
315 wxPostEvent(m_companion
, termEvent
);
319 // Notice the app that the file finished download
320 CMuleInternalEvent
evt(wxEVT_CORE_FINISHED_HTTP_DOWNLOAD
);
321 evt
.SetInt((int)m_file_id
);
322 evt
.SetExtraLong((long)m_result
);
323 wxPostEvent(wxTheApp
, evt
);
324 wxMutexLocker
lock(s_allThreadsMutex
);
325 s_allThreads
.erase(this);
329 //! This function's purpose is to handle redirections in a proper way.
330 wxInputStream
* CHTTPDownloadThread::GetInputStream(wxHTTP
* & url_handler
, const wxString
& location
, bool proxy
)
336 if (!location
.StartsWith(wxT("http://"))) {
337 // This is not a http url
338 throw wxString(_("Invalid URL for HTTP download or HTTP redirection (did you forget 'http://' ?)"));
343 // Remove the "http://"
344 wxString host
= location
.Right(location
.Len() - 7); // strlen("http://") -> 7
346 // I belive this is a bug...
347 // Sometimes "Location" header looks like this:
348 // "http://www.whatever.com:8080http://www.whatever.com/downloads/something.zip"
349 // So let's clean it...
351 int bad_url_pos
= host
.Find(wxT("http://"));
352 wxString location_url
;
353 if (bad_url_pos
!= -1) {
354 // Malformed Location field on header (bug above?)
355 location_url
= host
.Mid(bad_url_pos
);
356 host
= host
.Left(bad_url_pos
);
357 // After the first '/' non-http-related, it's the location part of the URL
358 location_url
= location_url
.Right(location_url
.Len() - 7).AfterFirst(wxT('/'));
360 // Regular Location field
361 // After the first '/', it's the location part of the URL
362 location_url
= host
.AfterFirst(wxT('/'));
363 // The host is everything till the first "/"
364 host
= host
.BeforeFirst(wxT('/'));
367 // Build the cleaned url now
368 wxString url
= wxT("http://") + host
+ wxT("/") + location_url
;
371 if (host
.Find(wxT(':')) != -1) {
372 // This http url has a port
373 port
= wxAtoi(host
.AfterFirst(wxT(':')));
374 host
= host
.BeforeFirst(wxT(':'));
380 if (!url_handler
->Connect(addr
, true)) {
381 throw wxString(_("Unable to connect to HTTP download server"));
384 wxInputStream
* url_read_stream
= url_handler
->GetInputStream(url
);
386 /* store the HTTP response code */
387 m_response
= url_handler
->GetResponse();
389 /* store the HTTP error code */
390 m_error
= url_handler
->GetError();
392 AddDebugLogLineN(logHTTP
, CFormat(wxT("Host: %s:%i\n")) % host
% port
);
393 AddDebugLogLineN(logHTTP
, CFormat(wxT("URL: %s\n")) % url
);
394 AddDebugLogLineN(logHTTP
, CFormat(wxT("Response: %i (Error: %i)")) % m_response
% m_error
);
397 AddDebugLogLineC(logHTTP
, wxT("WARNING: Void response on stream creation"));
398 // WTF? Why does this happen?
399 // This is probably produced by an already existing connection, because
400 // the input stream is created nevertheless. However, data is not the same.
401 delete url_read_stream
;
402 throw wxString(_("Invalid response from HTTP download server"));
405 if (m_response
== 301 // Moved permanently
406 || m_response
== 302 // Moved temporarily
407 // What about 300 (multiple choices)? Do we have to handle it?
410 // We have to remove the current stream.
411 delete url_read_stream
;
413 wxString new_location
= url_handler
->GetHeader(wxT("Location"));
415 url_handler
->Destroy();
416 if (!new_location
.IsEmpty()) {
417 url_handler
= new wxHTTP
;
418 url_handler
->SetProxyMode(proxy
);
419 if (m_lastmodified
.IsValid()) {
420 // Set a flag in the HTTP header that we only download if the file is newer.
421 // see: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.25
422 url_handler
->SetHeader(wxT("If-Modified-Since"), FormatDateHTTP(m_lastmodified
));
424 url_read_stream
= GetInputStream(url_handler
, new_location
, proxy
);
426 AddDebugLogLineC(logHTTP
, wxT("ERROR: Redirection code received with no URL"));
428 url_read_stream
= NULL
;
430 } else if (m_response
== 304) { // "Not Modified"
431 delete url_read_stream
;
432 url_handler
->Destroy();
433 url_read_stream
= NULL
;
437 return url_read_stream
;
440 void CHTTPDownloadThread::StopAll()
442 ThreadSet allThreads
;
444 wxMutexLocker
lock(s_allThreadsMutex
);
445 std::swap(allThreads
, s_allThreads
);
447 for (ThreadSet::iterator it
= allThreads
.begin(); it
!= allThreads
.end(); ++it
) {
452 CHTTPDownloadThread::ThreadSet
CHTTPDownloadThread::s_allThreads
;
453 wxMutex
CHTTPDownloadThread::s_allThreadsMutex
;
455 // File_checked_for_headers