1 //=============================================================================
2 //
3 //   File : libkvihttp.cpp
4 //   Creation date : Tue Apr 22 2003 02:00:12 GMT by Szymon Stefanek
5 //
6 //   This file is part of the KVIrc IRC client distribution
7 //   Copyright (C) 2003-2010 Szymon Stefanek (pragma at kvirc dot net)
8 //
9 //   This program is FREE software. You can redistribute it and/or
10 //   modify it under the terms of the GNU General Public License
11 //   as published by the Free Software Foundation; either version 2
12 //   of the License, or (at your option) any later version.
13 //
14 //   This program is distributed in the HOPE that it will be USEFUL,
15 //   but WITHOUT ANY WARRANTY; without even the implied warranty of
16 //   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17 //   See the GNU General Public License for more details.
18 //
19 //   You should have received a copy of the GNU General Public License
20 //   along with this program. If not, write to the Free Software Foundation,
21 //   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 //
23 //=============================================================================
24 
25 #include "HttpFileTransfer.h"
26 
27 #include "KviModule.h"
28 #include "KviCString.h"
29 #include "KviApplication.h"
30 #include "KviLocale.h"
31 #include "KviFileDialog.h"
32 #include "KviWindow.h"
33 #include "KviError.h"
34 #include "KviCommandFormatter.h"
35 #include "KviMainWindow.h"
36 
http_kvs_complete_get(KviKvsModuleCommandCall * c,QString & szUrl,QString & szFileName,const QString & szCallback)37 static bool http_kvs_complete_get(KviKvsModuleCommandCall * c, QString & szUrl, QString & szFileName, const QString & szCallback)
38 {
39 	if(szUrl.isEmpty())
40 	{
41 		c->warning(__tr2qs_ctx("No URL specified", "http"));
42 		return true;
43 	}
44 
45 	KviUrl url(szUrl);
46 
47 	QString tmp;
48 
49 	if(szFileName.isEmpty())
50 	{
51 		if(c->switches()->find('a', "auto-file-name"))
52 		{
53 			tmp = szUrl;
54 			tmp.replace('/', "_");
55 			tmp.replace(':', "_");
56 			tmp.replace('@', "_");
57 			tmp.replace('?', "_");
58 			// http____path_path2_path3_filename.ext
59 			g_pApp->getLocalKvircDirectory(szFileName, KviApplication::Incoming, tmp);
60 		}
61 		else
62 		{
63 			if(!KviFileDialog::askForSaveFileName(
64 			       szFileName,
65 			       __tr2qs_ctx("Choose a filename to save", "http"),
66 			       QString(),
67 			       QString(),
68 			       false,
69 			       false,
70 			       true,
71 			       g_pMainWindow))
72 				return true;
73 			if(szFileName.isEmpty())
74 				return true;
75 		}
76 	}
77 
78 	HttpFileTransfer * hft = new HttpFileTransfer();
79 
80 	bool bHead = c->switches()->find('h', "head");
81 
82 	if(c->switches()->getAsStringIfExisting('p', "post-data", tmp))
83 	{
84 		if(bHead)
85 		{
86 			c->warning(__tr2qs_ctx("The switch -p is incompatible with -h: -p takes precedence", "http"));
87 			bHead = false;
88 		}
89 		hft->request()->setPostData(tmp);
90 	}
91 
92 	hft->request()->setUrl(url);
93 	hft->request()->setProcessingType(bHead ? KviHttpRequest::HeadersOnly : KviHttpRequest::StoreToFile);
94 	hft->request()->setFileName(szFileName);
95 
96 	if(c->switches()->getAsStringIfExisting('e', "existing-file-action", tmp))
97 	{
98 		if(KviQString::equalCI(tmp, "e"))
99 			hft->request()->setExistingFileAction(KviHttpRequest::RenameExisting);
100 		else if(KviQString::equalCI(tmp, "i"))
101 			hft->request()->setExistingFileAction(KviHttpRequest::RenameIncoming);
102 		else if(KviQString::equalCI(tmp, "o"))
103 			hft->request()->setExistingFileAction(KviHttpRequest::Overwrite);
104 		else if(KviQString::equalCI(tmp, "r"))
105 			hft->request()->setExistingFileAction(KviHttpRequest::Resume);
106 	}
107 
108 	// FIXME: this should be numeric
109 	if(c->switches()->getAsStringIfExisting('m', "max-len", tmp))
110 	{
111 		bool bOk;
112 		unsigned int uContentLength = tmp.toUInt(&bOk);
113 		if(bOk)
114 			hft->request()->setMaxContentLength(uContentLength);
115 	}
116 
117 	// FIXME: this should be numeric
118 	if(c->switches()->getAsStringIfExisting('o', "offset", tmp))
119 	{
120 		bool bOk;
121 		unsigned int uContentOffset = tmp.toUInt(&bOk);
122 		if(bOk)
123 			hft->request()->setContentOffset(uContentOffset);
124 	}
125 
126 	// FIXME: this should be numeric
127 	if(c->switches()->getAsStringIfExisting('t', "timeout", tmp))
128 	{
129 		bool bOk;
130 		unsigned int uConnectionTimeout = tmp.toUInt(&bOk);
131 		if(bOk)
132 			hft->request()->setConnectionTimeout(uConnectionTimeout);
133 	}
134 
135 	if(c->switches()->getAsStringIfExisting('w', "winctrl", tmp))
136 	{
137 		if(!tmp.contains('h'))
138 			hft->invokeTransferWindow(tmp.contains('m'), tmp.contains('n'));
139 	}
140 	else
141 	{
142 		hft->invokeTransferWindow(false, false);
143 	}
144 
145 	KviKvsVariant * v = c->switches()->find('i', "identifier");
146 	if(v)
147 		hft->setMagicIdentifier(*v);
148 
149 	if(c->switches()->find('q', "quiet"))
150 		hft->setNotifyCompletion(false);
151 
152 	if(c->switches()->find('y', "no-output"))
153 		hft->setNoOutput(true);
154 
155 	if(!szCallback.isEmpty())
156 		hft->setCompletionCallback(szCallback);
157 
158 	if(c->switches()->find('c', "clear"))
159 		hft->setAutoClean(true);
160 
161 	if(!hft->startDownload())
162 	{
163 		tmp = hft->request()->lastError();
164 		c->warning(__tr2qs_ctx("Failed to start the get request: %Q", "http"), &tmp);
165 		delete hft;
166 	}
167 
168 	return true;
169 }
170 
171 /*
172 	@doc: http.get
173 	@type:
174 		command
175 	@title:
176 		http.get
177 	@keyterms:
178 		HTTP extension
179 	@short:
180 		Retrieves a file via HTTP GET
181 	@syntax:
182 		http.get [switches] <http_url> [save_file_name]
183 	@description:
184 		Attempts to download the file at <http_url> by using the HTTP GET or POST protocol.[br]
185 		If [save_file_name] is specified, then is is used as save file name, otherwise
186 		a save file dialog is displayed (unless -a is used).[br]
187 		The event OnHTTPGetTerminated is triggered upon the download completion (both
188 		in case of success or failure). If you want a callback command to be triggered
189 		instead please use [cmd]http.asyncGet[/cmd].
190 		If the URL contains a https:// prefix then a SSL connection will be used.
191 	@switches:
192 		!sw: -a=<auto_file_name> | --auto-file-name=<auto_file_name>
193 		Don't show the save file dialog but determine automatically a file name.
194 		The file is put in the KVIrc incoming directory and the file name
195 		is the processed URL.[br]
196 		[br]
197 		!sw: -e=<existing_file_action> | --existing-file-action=<existing_file_action>
198 		Specifies the action to be taken when the local file already exists.[br]
199 		The action can be one of "i","e","o" or "r".[br]
200 		"i" causes the incoming file to be automatically renamed by appending a
201 		non colliding suffix. "e" causes the existing file to be renamed
202 		by appending a non colliding suffix (the incoming file will then have its name preserved).
203 		"o" causes the existing file to be overwritten and "r" will attempt to resume
204 		a interrupted transfer.[br]
205 		The default is to rename the incoming file.[br]
206 		[br]
207 		!sw: -m=<max_content_length> | --max-len=<max_content_length>
208 		Causes content longer than <max_content_length> to be discarded.[br]
209 		This is mainly to prevent you from automatically downloading 300 MiB files
210 		and to prevent DOS attacks from malicious servers that do not report the Content-length header.[br]
211 		If the Content-length header is reported by the server then the transfer is aborted
212 		if the length exceeds <max_content_length>.[br]
213 		If the Content-length header is missing then the transfer is interrupted when
214 		the received data length exceeds <max_content_length>.[br]
215 		-m=0 means "accept any content length" (which is the default).[br]
216 		[br]
217 		!sw: -o=<content_offset> | --offset=<content_offset>
218 		Causes the download to start from position <content offset>.[br]
219 		This can be used to download only a part of the file starting at byte <content_offset>.[br]
220 		<content_offset> is used regardless if the file is resumed or not.
221 		Please note that you don't need to specify the content offset when using
222 		-e=r : the offset is automatically calculated. If you specify both -o=<content_offset>
223 		and -e=r then the file will be resumed, the transfer will start at the specified offset
224 		and the received stream will be appended to the existing file.(avoid it unless you know what you're doing:
225 		it's easy to download broken files).[br]
226 		[br]
227 		!sw: -t=<timeout_in_seconds> | --timeout=<timeout_in_seconds>
228 		Changes the default connection timeout to the <timeout_in_seconds>.
229 		A connection stuck for more than <timeout_in_seconds> seconds will be simply aborted.
230 		The default timeout is 60 seconds and is appropriate for most operations. Use with care.[br]
231 		[br]
232 		!sw: -h | --head
233 		Causes the connection to use the HTTP HEAD method that effectively
234 		does not transfer real data. The server sends only the response headers.
235 		This might be used in conjunction with the -v option to print the headers to the
236 		active window.[br]
237 		[br]
238 		!sw: -w=<flags> | --winctrl
239 		This switch controls the creation and visualization of the transfer window.
240 		<flags> can be any combination of 'm','n' and 'h'.
241 		The 'h' flag causes the window to not be created. The transfer will simply run in background.
242 		Note that with 'h' the user has no possibility to interact with the transfer.
243 		The 'm' flag causes the transfer window to be created as "minimized". 'm' does nothing
244 		if the window already exists. The 'n' flag causes the window to be [b]not[/b] activated (brought to top).
245 		[br]
246 		!sw: -i=<magic identifier> | --identifier=<magic identifier>
247 		This identifier is passed as $3 parameter to the [event:OnHTTPGetTerminated]OnHTTPGetTerminated[/event]
248 		when this transfer terminates. If this switch is not present then an empty string is used.
249 		With [cmd]http.asyncGet[/cmd] this parameter is passed to the callback command instead.
250 		[br]
251 		!sw: -p=<post data> | --post-data=<post data>
252 		The request is sent in form of a POST request. <post data> is the urlencoded payload of
253 		the request. -p is incompatible with -h.
254 		[br]
255 		!sw: -q | --quiet
256 		Do not notify download completion in the notifier window nor in the console.
257 		[br]
258 		!sw: -y | --no-output
259 		Suppress any output in the file transfer window. This will effectively disable
260 		the file transfer window highlighting (so the user will not be alerted by a failed
261 		download unless he's really watching the window). This is useful when you're notifying
262 		failures in some other way...
263 		[br]
264 		!sw: -c | --clear
265 		Automatically remove the transfer from the transfer list when terminated
266 	@seealso:
267 		[cmd]http.asyncGet[/cmd]
268 */
269 
http_kvs_cmd_get(KviKvsModuleCommandCall * c)270 static bool http_kvs_cmd_get(KviKvsModuleCommandCall * c)
271 {
272 	QString szUrl, szFileName;
273 	KVSM_PARAMETERS_BEGIN(c)
274 	KVSM_PARAMETER("url", KVS_PT_NONEMPTYSTRING, 0, szUrl)
275 	KVSM_PARAMETER("filename", KVS_PT_STRING, KVS_PF_OPTIONAL, szFileName)
276 	KVSM_PARAMETERS_END(c)
277 
278 	return http_kvs_complete_get(c, szUrl, szFileName, QString());
279 }
280 /*
281 	@doc: http.asyncGet
282 	@type:
283 		command
284 	@title:
285 		http.asyncGet
286 	@keyterms:
287 		HTTP extension
288 	@short:
289 		Retrieves a file via HTTP GET and triggers a callback
290 	@syntax:
291 		http.asyncGet [switches] (<http_url> [,save_file_name])
292 		{
293 			<callback command>
294 		}
295 	@description:
296 		Attempts to download the file at <http_url> by using the HTTP GET or POST protocol.[br]
297 		If [save_file_name] is specified, then is is used as save file name, otherwise
298 		a save file dialog is displayed (unless -a is used).[br]
299 		This command is really similar to [cmd]http.get[/cmd]: it has exactly the same
300 		parameters and switches (so also refer to its documentation).
301 		The only difference is that asyncGet triggers the <callback command> upon completion
302 		instead of the global OnHTTPGetTerminated event.
303 		The parameters passed to the callback are exactly the same.
304 		If the URL contains a https:// prefix then a SSL connection will be used.
305 	@seealso:
306 		[cmd]http.get[/cmd]
307 */
308 
http_kvs_cmd_asyncGet(KviKvsModuleCallbackCommandCall * c)309 static bool http_kvs_cmd_asyncGet(KviKvsModuleCallbackCommandCall * c)
310 {
311 	QString szUrl, szFileName;
312 	KVSM_PARAMETERS_BEGIN(c)
313 	KVSM_PARAMETER("url", KVS_PT_NONEMPTYSTRING, 0, szUrl)
314 	KVSM_PARAMETER("filename", KVS_PT_STRING, KVS_PF_OPTIONAL, szFileName)
315 	KVSM_PARAMETERS_END(c)
316 
317 	return http_kvs_complete_get(c, szUrl, szFileName, c->callback()->code());
318 }
319 
http_module_init(KviModule * m)320 static bool http_module_init(KviModule * m)
321 {
322 	HttpFileTransfer::init();
323 
324 	KVSM_REGISTER_SIMPLE_COMMAND(m, "get", http_kvs_cmd_get);
325 	KVSM_REGISTER_CALLBACK_COMMAND(m, "asyncGet", http_kvs_cmd_asyncGet);
326 
327 	return true;
328 }
329 
http_module_cleanup(KviModule *)330 static bool http_module_cleanup(KviModule *)
331 {
332 	HttpFileTransfer::done();
333 	return true;
334 }
335 
http_module_can_unload(KviModule *)336 static bool http_module_can_unload(KviModule *)
337 {
338 	return (HttpFileTransfer::runningTransfers() == 0);
339 }
340 
341 KVIRC_MODULE(
342     "Http",                                                         // module name
343     "4.0.0",                                                        // module version
344     "Copyright (C) 2003 Szymon Stefanek (pragma at kvirc dot net)", // author & (C)
345     "HTTP interface for KVIrc",
346     http_module_init,
347     http_module_can_unload,
348     0,
349     http_module_cleanup,
350     "http")
351