WCDOTCnull1 //
2 // This file is part of the aMule Project.
3 //
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 )
6 //
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
9 // respective authors.
10 //
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.
15 //
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.
20 //
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
24 //
25 
26 
27 #include <wx/wfstream.h>
28 #include <wx/protocol/http.h>
29 
30 
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
40 
41 
42 #ifndef AMULE_DAEMON
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"
46 
47 typedef wxGauge wxGaugeControl;
48 
49 DECLARE_LOCAL_EVENT_TYPE(wxEVT_HTTP_PROGRESS, wxANY_ID)
50 DECLARE_LOCAL_EVENT_TYPE(wxEVT_HTTP_SHUTDOWN, wxANY_ID)
51 
52 
53 class CHTTPDownloadDialog : public wxDialog
54 {
55 public:
56 	CHTTPDownloadDialog(CHTTPDownloadThread* thread)
57 	  : wxDialog(wxTheApp->GetTopWindow(), -1, _("Downloading..."),
58 			wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxSYSTEM_MENU)
59 	{
60 		downloadDlg(this, true)->Show(this, true);
61 
62 		m_progressbar = CastChild(ID_HTTPDOWNLOADPROGRESS, wxGaugeControl);
63 		m_progressbar->SetRange(100);
64 
65 		m_ani = CastChild(ID_ANIMATE, MuleGifCtrl);
66 		m_ani->LoadData((const char*)inetDownload, sizeof(inetDownload));
67 		m_ani->Start();
68 
69 		m_thread = thread;
70 	}
71 
72 	~CHTTPDownloadDialog() {
73 		StopThread();
74 	}
75 
76 	void UpdateGauge(int total, int current) {
77 		CFormat label( wxT("( %s / %s )") );
78 
79 		label % CastItoXBytes(current);
80 		if (total > 0) {
81 			label % CastItoXBytes(total);
82 		} else {
83 			label % _("Unknown");
84 		}
85 
86 		CastChild(IDC_DOWNLOADSIZE, wxStaticText)->SetLabel(label.GetString());
87 
88 		if (total && (total != m_progressbar->GetRange())) {
89 			m_progressbar->SetRange(total);
90 		}
91 
92 		if (current && (current <= total)) {
93 			m_progressbar->SetValue(current);
94 		}
95 
96 		Layout();
97 	}
98 
99 private:
100 	void StopThread() {
101 		if (m_thread) {
102 			m_thread->Stop();
103 			delete m_thread;
104 			m_thread = NULL;
105 		}
106 	}
107 
108 	void OnBtnCancel(wxCommandEvent& WXUNUSED(evt)) {
109 		AddLogLineN(_("HTTP download cancelled"));
110 		Show(false);
111 		StopThread();
112 	}
113 
114 	void OnProgress(CMuleInternalEvent& evt) {
115 		UpdateGauge(evt.GetExtraLong(), evt.GetInt());
116 	}
117 
118 	void OnShutdown(CMuleInternalEvent& WXUNUSED(evt)) {
119 		Show(false);
120 		Destroy();
121 	}
122 
123 	CMuleThread*	m_thread;
124 	MuleGifCtrl*	m_ani;
125 	wxGaugeControl* m_progressbar;
126 
127 	DECLARE_EVENT_TABLE()
128 };
129 
130 
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)
135 END_EVENT_TABLE()
136 
137 DEFINE_LOCAL_EVENT_TYPE(wxEVT_HTTP_PROGRESS)
138 DEFINE_LOCAL_EVENT_TYPE(wxEVT_HTTP_SHUTDOWN)
139 
140 #endif
141 
142 
143 CHTTPDownloadThread::CHTTPDownloadThread(const wxString& url, const wxString& filename, const wxString& oldfilename, HTTP_Download_File file_id,
144 										bool showDialog, bool checkDownloadNewer)
145 #ifdef AMULE_DAEMON
146 	: CMuleThread(wxTHREAD_DETACHED),
147 #else
148 	: CMuleThread(showDialog ? wxTHREAD_JOINABLE : wxTHREAD_DETACHED),
149 #endif
150 	  m_url(url),
151 	  m_tempfile(filename),
152 	  m_result(-1),
153 	  m_file_id(file_id),
154 	  m_companion(NULL)
155 {
156 	if (showDialog) {
157 #ifndef AMULE_DAEMON
158 		CHTTPDownloadDialog* dialog = new CHTTPDownloadDialog(this);
159 		dialog->Show(true);
160 		m_companion = dialog;
161 #endif
162 	}
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();
170 		}
171 	}
172 	wxMutexLocker lock(s_allThreadsMutex);
173 	s_allThreads.insert(this);
174 }
175 
176 
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)
180 {
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") };
183 
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);
185 }
186 
187 
188 CMuleThread::ExitCode CHTTPDownloadThread::Entry()
189 {
190 	if (TestDestroy()) {
191 		return NULL;
192 	}
193 
194 	wxHTTP* url_handler = NULL;
195 
196 	AddDebugLogLineN(logHTTP, wxT("HTTP download thread started"));
197 
198 	const CProxyData* proxy_data = thePrefs::GetProxyData();
199 	bool use_proxy = proxy_data != NULL && proxy_data->m_proxyEnable;
200 
201 	try {
202 		wxFFileOutputStream outfile(m_tempfile);
203 
204 		if (!outfile.Ok()) {
205 			throw wxString(CFormat(_("Unable to create destination file %s for download!")) % m_tempfile);
206 		}
207 
208 		if (m_url.IsEmpty()) {
209 			// Nowhere to download from!
210 			throw wxString(_("The URL to download can't be empty"));
211 		}
212 
213 		url_handler = new wxHTTP;
214 		url_handler->SetProxyMode(use_proxy);
215 
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));
222 		}
223 
224 		CScopedPtr<wxInputStream> url_read_stream(GetInputStream(url_handler, m_url, use_proxy));
225 
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);
231 			} else {
232 				m_result = HTTP_Error;
233 				throw wxString(CFormat(_("The URL %s returned: %i - Error (%i)!")) % m_url % m_response % m_error);
234 			}
235 		}
236 
237 		int download_size = url_read_stream->GetSize();
238 #ifdef __DEBUG__
239 		if (download_size == -1) {
240 			AddDebugLogLineN(logHTTP, wxT("Download size not received, downloading until connection is closed"));
241 		} else {
242 			AddDebugLogLineN(logHTTP, CFormat(wxT("Download size: %i")) % download_size);
243 		}
244 #endif
245 
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;
250 
251 		char buffer[MAX_HTTP_READ];
252 		int current_read = 0;
253 		int total_read = 0;
254 		do {
255 			url_read_stream->Read(buffer, MAX_HTTP_READ);
256 			current_read = url_read_stream->LastRead();
257 			if (current_read) {
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) {
264 #ifndef AMULE_DAEMON
265 					CMuleInternalEvent evt(wxEVT_HTTP_PROGRESS);
266 					evt.SetInt(total_read);
267 					evt.SetExtraLong(download_size);
268 					wxPostEvent(m_companion, evt);
269 #endif
270 				}
271 			}
272 		} while (current_read && !TestDestroy());
273 
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);
282 			} else {
283 				// Download was succesful.
284 				m_result = HTTP_Success;
285 			}
286 		}
287 	} catch (const wxString& error) {
288 		if (wxFileExists(m_tempfile)) {
289 			wxRemoveFile(m_tempfile);
290 		}
291 		if (!error.IsEmpty()) {
292 			AddLogLineC(error);
293 		}
294 	}
295 
296 	if (m_result == HTTP_Success) {
297 		thePrefs::SetLastHTTPDownloadURL(m_file_id, m_url);
298 	}
299 
300 	if (url_handler) {
301 		url_handler->Destroy();
302 	}
303 
304 	AddDebugLogLineN(logHTTP, wxT("HTTP download thread ended"));
305 
306 	return 0;
307 }
308 
309 
310 void CHTTPDownloadThread::OnExit()
311 {
312 #ifndef AMULE_DAEMON
313 	if (m_companion) {
314 		CMuleInternalEvent termEvent(wxEVT_HTTP_SHUTDOWN);
315 		wxPostEvent(m_companion, termEvent);
316 	}
317 #endif
318 
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);
326 }
327 
328 
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)
331 {
332 	// Extract the protocol name
333 	wxString protocol(location.BeforeFirst(wxT(':')));
334 
335 	if (TestDestroy()) {
336 		return NULL;
337 	}
338 
339 	if (protocol != wxT("http")) {
340 		// This is not a http url
341 		throw wxString(CFormat(_("Protocol not supported for HTTP download: %s")) % protocol);
342 	}
343 
344 	// Get the host
345 
346 	// Remove the "http://"
347 	wxString host = location.Right(location.Len() - 7); // strlen("http://") -> 7
348 
349 	// I belive this is a bug...
350 	// Sometimes "Location" header looks like this:
351 	// "http://www.whatever.com:8080http://www.whatever.com/downloads/something.zip"
352 	// So let's clean it...
353 
354 	int bad_url_pos = host.Find(wxT("http://"));
355 	wxString location_url;
356 	if (bad_url_pos != -1) {
357 		// Malformed Location field on header (bug above?)
358 		location_url = host.Mid(bad_url_pos);
359 		host = host.Left(bad_url_pos);
360 		// After the first '/' non-http-related, it's the location part of the URL
361 		location_url = location_url.Right(location_url.Len() - 7).AfterFirst(wxT('/'));
362 	} else {
363 		// Regular Location field
364 		// After the first '/', it's the location part of the URL
365 		location_url = host.AfterFirst(wxT('/'));
366 		// The host is everything till the first "/"
367 		host = host.BeforeFirst(wxT('/'));
368 	}
369 
370 	// Build the cleaned url now
371 	wxString url = wxT("http://") + host + wxT("/") + location_url;
372 
373 	int port = 80;
374 	if (host.Find(wxT(':')) != -1) {
375 		// This http url has a port
376 		port = wxAtoi(host.AfterFirst(wxT(':')));
377 		host = host.BeforeFirst(wxT(':'));
378 	}
379 
380 	wxIPV4address addr;
381 	addr.Hostname(host);
382 	addr.Service(port);
383 	if (!url_handler->Connect(addr, true)) {
384 		throw wxString(_("Unable to connect to HTTP download server"));
385 	}
386 
387 	wxInputStream* url_read_stream = url_handler->GetInputStream(url);
388 
389 	/* store the HTTP response code */
390 	m_response = url_handler->GetResponse();
391 
392 	/* store the HTTP error code */
393 	m_error = url_handler->GetError();
394 
395 	AddDebugLogLineN(logHTTP, CFormat(wxT("Host: %s:%i\n")) % host % port);
396 	AddDebugLogLineN(logHTTP, CFormat(wxT("URL: %s\n")) % url);
397 	AddDebugLogLineN(logHTTP, CFormat(wxT("Response: %i (Error: %i)")) % m_response % m_error);
398 
399 	if (!m_response) {
400 		AddDebugLogLineC(logHTTP, wxT("WARNING: Void response on stream creation"));
401 		// WTF? Why does this happen?
402 		// This is probably produced by an already existing connection, because
403 		// the input stream is created nevertheless. However, data is not the same.
404 		delete url_read_stream;
405 		throw wxString(_("Invalid response from HTTP download server"));
406 	}
407 
408 	if (m_response == 301  // Moved permanently
409 		|| m_response == 302 // Moved temporarily
410 		// What about 300 (multiple choices)? Do we have to handle it?
411 		) {
412 
413 		// We have to remove the current stream.
414 		delete url_read_stream;
415 
416 		wxString new_location = url_handler->GetHeader(wxT("Location"));
417 		AddDebugLogLineN(logHTTP, CFormat(wxT("Redirecting to: %s")) % new_location);
418 
419 		url_handler->Destroy();
420 		if (!new_location.IsEmpty()) {
421 			url_handler = new wxHTTP;
422 			url_handler->SetProxyMode(proxy);
423 			if (m_lastmodified.IsValid()) {
424 				// Set a flag in the HTTP header that we only download if the file is newer.
425 				// see: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.25
426 				url_handler->SetHeader(wxT("If-Modified-Since"), FormatDateHTTP(m_lastmodified));
427 			}
428 			url_read_stream = GetInputStream(url_handler, new_location, proxy);
429 		} else {
430 			AddDebugLogLineC(logHTTP, wxT("ERROR: Redirection code received with no URL"));
431 			url_handler = NULL;
432 			url_read_stream = NULL;
433 		}
434 	} else if (m_response == 304) {		// "Not Modified"
435 		delete url_read_stream;
436 		url_handler->Destroy();
437 		url_read_stream = NULL;
438 		url_handler = NULL;
439 	}
440 
441 	return url_read_stream;
442 }
443 
444 void CHTTPDownloadThread::StopAll()
445 {
446 	ThreadSet allThreads;
447 	{
448 		wxMutexLocker lock(s_allThreadsMutex);
449 		std::swap(allThreads, s_allThreads);
450 	}
451 	for (ThreadSet::iterator it = allThreads.begin(); it != allThreads.end(); ++it) {
452 		(*it)->Stop();
453 	}
454 }
455 
456 CHTTPDownloadThread::ThreadSet CHTTPDownloadThread::s_allThreads;
457 wxMutex CHTTPDownloadThread::s_allThreadsMutex;
458 
459 // File_checked_for_headers
460