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