xref: /haiku/src/bin/urlwrapper.cpp (revision eb47b26534e55948dbb8860916c671f9cf6f37f9)
1 /*
2  * Copyright 2007-2009 Haiku Inc. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		François Revol, revol@free.fr
7  *		Jonas Sundström, jonas@kirilla.com
8  */
9 
10 /*
11  * urlwrapper: wraps URL mime types around command line apps
12  * or other apps that don't handle them directly.
13  */
14 #define DEBUG 0
15 
16 #include <ctype.h>
17 #include <stdio.h>
18 #include <unistd.h>
19 
20 #include <Alert.h>
21 #include <Debug.h>
22 #include <NodeInfo.h>
23 #include <Roster.h>
24 #include <String.h>
25 #include <Url.h>
26 
27 #include "urlwrapper.h"
28 
29 
30 const char* kAppSig = "application/x-vnd.Haiku-urlwrapper";
31 const char* kTrackerSig = "application/x-vnd.Be-TRAK";
32 const char* kTerminalSig = "application/x-vnd.Haiku-Terminal";
33 const char* kBeShareSig = "application/x-vnd.Sugoi-BeShare";
34 const char* kIMSig = "application/x-vnd.m_eiman.sample_im_client";
35 const char* kVLCSig = "application/x-vnd.videolan-vlc";
36 
37 
38 UrlWrapper::UrlWrapper() : BApplication(kAppSig)
39 {
40 }
41 
42 
43 UrlWrapper::~UrlWrapper()
44 {
45 }
46 
47 
48 status_t
49 UrlWrapper::_Warn(const char* url)
50 {
51 	BString message("An application has requested the system to open the "
52 		"following url: \n");
53 	message << "\n" << url << "\n\n";
54 	message << "This type of URL has a potential security risk.\n";
55 	message << "Proceed anyway?";
56 	BAlert* alert = new BAlert("Warning", message.String(), "Proceed", "Stop", NULL,
57 		B_WIDTH_AS_USUAL, B_WARNING_ALERT);
58 	int32 button;
59 	button = alert->Go();
60 	if (button == 0)
61 		return B_OK;
62 
63 	return B_ERROR;
64 }
65 
66 
67 void
68 UrlWrapper::RefsReceived(BMessage* msg)
69 {
70 	char buff[B_PATH_NAME_LENGTH];
71 	int32 index = 0;
72 	entry_ref ref;
73 	char* args[] = { const_cast<char*>("urlwrapper"), buff, NULL };
74 	status_t err;
75 
76 	while (msg->FindRef("refs", index++, &ref) == B_OK) {
77 		BFile f(&ref, B_READ_ONLY);
78 		BNodeInfo ni(&f);
79 		BString mimetype;
80 		if (f.InitCheck() == B_OK && ni.InitCheck() == B_OK) {
81 			ni.GetType(mimetype.LockBuffer(B_MIME_TYPE_LENGTH));
82 			mimetype.UnlockBuffer();
83 
84 			// Internet Explorer Shortcut
85 			if (mimetype == "text/x-url") {
86 				// http://filext.com/file-extension/URL
87 				// http://www.cyanwerks.com/file-format-url.html
88 				off_t size;
89 				if (f.GetSize(&size) < B_OK)
90 					continue;
91 				BString contents, url;
92 				if (f.ReadAt(0LL, contents.LockBuffer(size), size) < B_OK)
93 					continue;
94 				while (contents.Length()) {
95 					BString line;
96 					int32 cr = contents.FindFirst('\n');
97 					if (cr < 0)
98 						cr = contents.Length();
99 					//contents.MoveInto(line, 0, cr);
100 					contents.CopyInto(line, 0, cr);
101 					contents.Remove(0, cr+1);
102 					line.RemoveAll("\r");
103 					if (!line.Length())
104 						continue;
105 					if (!line.ICompare("URL=", 4)) {
106 						line.MoveInto(url, 4, line.Length());
107 						break;
108 					}
109 				}
110 				if (url.Length()) {
111 					args[1] = (char*)url.String();
112 					err = be_roster->Launch("application/x-vnd.Be.URL.http", 1,
113 						args+1);
114 					continue;
115 				}
116 			}
117 
118 			// NetPositive Bookmark or any file with a META:url attribute
119 			if (f.ReadAttr("META:url", B_STRING_TYPE, 0LL, buff,
120 				B_PATH_NAME_LENGTH) > 0) {
121 				err = be_roster->Launch("application/x-vnd.Be.URL.http", 1,
122 					args+1);
123 				continue;
124 			}
125 		}
126 	}
127 }
128 
129 
130 void
131 UrlWrapper::ArgvReceived(int32 argc, char** argv)
132 {
133 	if (argc <= 1)
134 		return;
135 
136 	const char* failc = " || read -p 'Press any key'";
137 	const char* pausec = " ; read -p 'Press any key'";
138 	char* args[] = { (char *)"/bin/sh", (char *)"-c", NULL, NULL};
139 
140 	BPrivate::Support::BUrl url(argv[1]);
141 
142 	BString full = url.Full();
143 	BString proto = url.Proto();
144 	BString host = url.Host();
145 	BString port = url.Port();
146 	BString user = url.User();
147 	BString pass = url.Pass();
148 	BString path = url.Path();
149 
150 	if (url.InitCheck() < 0) {
151 		fprintf(stderr, "malformed url: '%s'\n", url.String());
152 		return;
153 	}
154 
155 	// XXX: debug
156 	PRINT(("PROTO='%s'\n", proto.String()));
157 	PRINT(("HOST='%s'\n", host.String()));
158 	PRINT(("PORT='%s'\n", port.String()));
159 	PRINT(("USER='%s'\n", user.String()));
160 	PRINT(("PASS='%s'\n", pass.String()));
161 	PRINT(("PATH='%s'\n", path.String()));
162 
163 	if (proto == "telnet") {
164 		BString cmd("telnet ");
165 		if (url.HasUser())
166 			cmd << "-l " << user << " ";
167 		cmd << host;
168 		if (url.HasPort())
169 			cmd << " " << port;
170 		PRINT(("CMD='%s'\n", cmd.String()));
171 		cmd << failc;
172 		args[2] = (char*)cmd.String();
173 		be_roster->Launch(kTerminalSig, 3, args);
174 		return;
175 	}
176 
177 	// see draft:
178 	// http://tools.ietf.org/wg/secsh/draft-ietf-secsh-scp-sftp-ssh-uri/
179 	if (proto == "ssh") {
180 		BString cmd("ssh ");
181 
182 		if (url.HasUser())
183 			cmd << "-l " << user << " ";
184 		if (url.HasPort())
185 			cmd << "-oPort=" << port << " ";
186 		cmd << host;
187 		PRINT(("CMD='%s'\n", cmd.String()));
188 		cmd << failc;
189 		args[2] = (char*)cmd.String();
190 		be_roster->Launch(kTerminalSig, 3, args);
191 		// TODO: handle errors
192 		return;
193 	}
194 
195 	if (proto == "ftp") {
196 		BString cmd("ftp ");
197 
198 		/*
199 		if (user.Length())
200 			cmd << "-l " << user << " ";
201 		cmd << host;
202 		*/
203 		cmd << full;
204 		PRINT(("CMD='%s'\n", cmd.String()));
205 		cmd << failc;
206 		args[2] = (char*)cmd.String();
207 		be_roster->Launch(kTerminalSig, 3, args);
208 		// TODO: handle errors
209 		return;
210 	}
211 
212 	if (proto == "sftp") {
213 		BString cmd("sftp ");
214 
215 		//cmd << url;
216 		if (url.HasPort())
217 			cmd << "-oPort=" << port << " ";
218 		if (url.HasUser())
219 			cmd << user << "@";
220 		cmd << host;
221 		if (url.HasPath())
222 			cmd << ":" << path;
223 		PRINT(("CMD='%s'\n", cmd.String()));
224 		cmd << failc;
225 		args[2] = (char*)cmd.String();
226 		be_roster->Launch(kTerminalSig, 3, args);
227 		// TODO: handle errors
228 		return;
229 	}
230 
231 	if (proto == "finger") {
232 		BString cmd("/bin/finger ");
233 
234 		if (url.HasUser())
235 			cmd << user;
236 		if (url.HasHost() == 0)
237 			host = "127.0.0.1";
238 		cmd << "@" << host;
239 		PRINT(("CMD='%s'\n", cmd.String()));
240 		cmd << pausec;
241 		args[2] = (char*)cmd.String();
242 		be_roster->Launch(kTerminalSig, 3, args);
243 		// TODO: handle errors
244 		return;
245 	}
246 
247 	if (proto == "http") {
248 		BString cmd("/bin/wget ");
249 
250 		//cmd << url;
251 		if (url.HasUser())
252 			cmd << user << "@";
253 		cmd << full;
254 		PRINT(("CMD='%s'\n", cmd.String()));
255 		cmd << pausec;
256 		args[2] = (char*)cmd.String();
257 		be_roster->Launch(kTerminalSig, 3, args);
258 		// TODO: handle errors
259 		return;
260 	}
261 
262 	if (proto == "file") {
263 		BMessage m(B_REFS_RECEIVED);
264 		entry_ref ref;
265 		_DecodeUrlString(path);
266 		if (get_ref_for_path(path.String(), &ref) < B_OK)
267 			return;
268 		m.AddRef("refs", &ref);
269 		be_roster->Launch(kTrackerSig, &m);
270 		return;
271 	}
272 
273 	// XXX:TODO: split options
274 	if (proto == "query") {
275 		// mktemp ?
276 		BString qname("/tmp/query-url-temp-");
277 		qname << getpid() << "-" << system_time();
278 		BFile query(qname.String(), O_CREAT|O_EXCL);
279 		// XXX: should check for failure
280 
281 		BString s;
282 		int32 v;
283 
284 		_DecodeUrlString(full);
285 		// TODO: handle options (list of attrs in the column, ...)
286 
287 		v = 'qybF'; // QuerY By Formula XXX: any #define for that ?
288 		query.WriteAttr("_trk/qryinitmode", B_INT32_TYPE, 0LL, &v, sizeof(v));
289 		s = "TextControl";
290 		query.WriteAttr("_trk/focusedView", B_STRING_TYPE, 0LL, s.String(),
291 			s.Length()+1);
292 		s = full;
293 		PRINT(("QUERY='%s'\n", s.String()));
294 		query.WriteAttr("_trk/qryinitstr", B_STRING_TYPE, 0LL, s.String(),
295 			s.Length()+1);
296 		query.WriteAttr("_trk/qrystr", B_STRING_TYPE, 0LL, s.String(),
297 			s.Length()+1);
298 		s = "application/x-vnd.Be-query";
299 		query.WriteAttr("BEOS:TYPE", 'MIMS', 0LL, s.String(), s.Length()+1);
300 
301 
302 		BEntry e(qname.String());
303 		entry_ref er;
304 		if (e.GetRef(&er) >= B_OK)
305 			be_roster->Launch(&er);
306 		return;
307 	}
308 
309 	if (proto == "sh") {
310 		BString cmd(full);
311 		if (_Warn(url.String()) != B_OK)
312 			return;
313 		PRINT(("CMD='%s'\n", cmd.String()));
314 		cmd << pausec;
315 		args[2] = (char*)cmd.String();
316 		be_roster->Launch(kTerminalSig, 3, args);
317 		// TODO: handle errors
318 		return;
319 	}
320 
321 	if (proto == "beshare") {
322 		team_id team;
323 		BMessenger msgr(kBeShareSig);
324 		// if no instance is running, or we want a specific server, start it.
325 		if (!msgr.IsValid() || url.HasHost()) {
326 			be_roster->Launch(kBeShareSig, (BMessage*)NULL, &team);
327 			msgr = BMessenger(NULL, team);
328 		}
329 		if (url.HasHost()) {
330 			BMessage mserver('serv');
331 			mserver.AddString("server", host);
332 			msgr.SendMessage(&mserver);
333 
334 		}
335 		if (url.HasPath()) {
336 			BMessage mquery('quer');
337 			mquery.AddString("query", path);
338 			msgr.SendMessage(&mquery);
339 		}
340 		// TODO: handle errors
341 		return;
342 	}
343 
344 	if (proto == "icq" || proto == "msn") {
345 		// TODO
346 		team_id team;
347 		be_roster->Launch(kIMSig, (BMessage*)NULL, &team);
348 		BMessenger msgr(NULL, team);
349 		if (url.HasHost()) {
350 			BMessage mserver(B_REFS_RECEIVED);
351 			mserver.AddString("server", host);
352 			msgr.SendMessage(&mserver);
353 
354 		}
355 		// TODO: handle errors
356 		return;
357 	}
358 
359 	if (proto == "mms" || proto == "rtp" || proto == "rtsp") {
360 		args[0] = (char*)url.String();
361 		be_roster->Launch(kVLCSig, 1, args);
362 		return;
363 	}
364 
365 	// Audio: ?
366 
367 	// vnc: ?
368 	// irc: ?
369 	//
370 	// svn: ?
371 	// cvs: ?
372 	// smb: cifsmount ?
373 	// nfs: mount_nfs ?
374 	//
375 	// mailto: ? Mail & Beam both handle it already (not fully though).
376 	//
377 	// mid: cid: as per RFC 2392
378 	// http://www.rfc-editor.org/rfc/rfc2392.txt query MAIL:cid
379 	//
380 	// itps: pcast: podcast: s//http/ + parse xml to get url to mp3 stream...
381 	// audio: s//http:/ + default MediaPlayer
382 	// -- see http://forums.winamp.com/showthread.php?threadid=233130
383 	//
384 	// gps: ? I should submit an RFC for that one :)
385 
386 }
387 
388 
389 status_t
390 UrlWrapper::_DecodeUrlString(BString& string)
391 {
392 	// TODO: check for %00 and bail out!
393 	int32 length = string.Length();
394 	int i;
395 	for (i = 0; string[i] && i < length - 2; i++) {
396 		if (string[i] == '%' && isxdigit(string[i+1])
397 			&& isxdigit(string[i+2])) {
398 			int c;
399 			sscanf(string.String() + i + 1, "%02x", &c);
400 			string.Remove(i, 3);
401 			string.Insert((char)c, 1, i);
402 			length -= 2;
403 		}
404 	}
405 
406 	return B_OK;
407 }
408 
409 
410 void
411 UrlWrapper::ReadyToRun(void)
412 {
413 	Quit();
414 }
415 
416 
417 // #pragma mark
418 
419 
420 int main(int argc, char** argv)
421 {
422 	UrlWrapper app;
423 	if (be_app)
424 		app.Run();
425 	return 0;
426 }
427 
428