/*
 * Copyright 2007-2010 Haiku Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		François Revol, revol@free.fr
 *		Jonas Sundström, jonas@kirilla.com
 *		Stephan Aßmus <superstippi@gmx.de>
 */
 
/*
 * urlwrapper: wraps URL mime types around command line apps
 * or other apps that don't handle them directly.
 */
#define DEBUG 0
 
#include <ctype.h>
#include <stdio.h>
#include <unistd.h>
 
#include <fs_volume.h>
#include <Alert.h>
#include <Debug.h>
#include <NodeInfo.h>
#include <Roster.h>
#include <String.h>
#include <Url.h>
 
#include "urlwrapper.h"
 
 
const char* kAppSig = "application/x-vnd.Haiku-urlwrapper";
const char* kTrackerSig = "application/x-vnd.Be-TRAK";
const char* kTerminalSig = "application/x-vnd.Haiku-Terminal";
const char* kBeShareSig = "application/x-vnd.Sugoi-BeShare";
const char* kIMSig = "application/x-vnd.m_eiman.sample_im_client";
const char* kVLCSig = "application/x-vnd.videolan-vlc";
 
const char* kURLHandlerSigBase = "application/x-vnd.Be.URL.";
 
 
UrlWrapper::UrlWrapper() : BApplication(kAppSig)
{
}
 
 
UrlWrapper::~UrlWrapper()
{
}
 
 
status_t
UrlWrapper::_Warn(const char* url)
{
	BString message("An application has requested the system to open the "
		"following url: \n");
	message << "\n" << url << "\n\n";
	message << "This type of URL has a potential security risk.\n";
	message << "Proceed anyway?";
	BAlert* alert = new BAlert("Warning", message.String(), "Proceed", "Stop", NULL,
		B_WIDTH_AS_USUAL, B_WARNING_ALERT);
	alert->SetShortcut(1, B_ESCAPE);
	int32 button;
	button = alert->Go();
	if (button == 0)
		return B_OK;
		
	return B_ERROR;
}
 
 
void
UrlWrapper::RefsReceived(BMessage* msg)
{
	char buff[B_PATH_NAME_LENGTH];
	int32 index = 0;
	entry_ref ref;
	char* args[] = { const_cast<char*>("urlwrapper"), buff, NULL };
	status_t err;
 
	while (msg->FindRef("refs", index++, &ref) == B_OK) {
		BFile f(&ref, B_READ_ONLY);
		BNodeInfo ni(&f);
		BString mimetype;
		BString extension(ref.name);
		extension.Remove(0, extension.FindLast('.') + 1);
		if (f.InitCheck() == B_OK && ni.InitCheck() == B_OK) {
			ni.GetType(mimetype.LockBuffer(B_MIME_TYPE_LENGTH));
			mimetype.UnlockBuffer();
 
			// Internet Explorer Shortcut
			if (mimetype == "text/x-url" || extension == "url") {
				// http://filext.com/file-extension/URL
				// http://www.cyanwerks.com/file-format-url.html
				off_t size;
				if (f.GetSize(&size) < B_OK)
					continue;
				BString contents;
				BString url;
				if (f.ReadAt(0LL, contents.LockBuffer(size), size) < B_OK)
					continue;
				contents.UnlockBuffer();
				while (contents.Length()) {
					BString line;
					int32 cr = contents.FindFirst('\n');
					if (cr < 0)
						cr = contents.Length();
					//contents.MoveInto(line, 0, cr);
					contents.CopyInto(line, 0, cr);
					contents.Remove(0, cr+1);
					line.RemoveAll("\r");
					if (!line.Length())
						continue;
					if (!line.ICompare("URL=", 4)) {
						line.MoveInto(url, 4, line.Length());
						break;
					}
				}
				if (url.Length()) {
					BUrl u(url.String());
					args[1] = (char*)u.UrlString().String();
					mimetype = kURLHandlerSigBase;
					mimetype += u.Protocol();
					err = be_roster->Launch(mimetype.String(), 1, args + 1);
					if (err != B_OK && err != B_ALREADY_RUNNING)
						err = be_roster->Launch(kAppSig, 1, args + 1);
					continue;
				}
			}
			if (mimetype == "text/x-webloc" || extension == "webloc") {
				// OSX url shortcuts
				// XML file + resource fork
				off_t size;
				if (f.GetSize(&size) < B_OK)
					continue;
				BString contents;
				BString url;
				char *buffer = contents.LockBuffer(size);
				const char bplist_match[] = "bplist00\xd1\x01\x02SURL_\x10";
				if (f.ReadAt(0LL, buffer, size) < B_OK)
					continue;
					printf("webloc\n");
				if (size > (sizeof(bplist_match) + 2)
					&& !strncmp(buffer, bplist_match, sizeof(bplist_match) - 1)) {
					// binary plist, let's be crude
					uint8 len = buffer[sizeof(bplist_match) - 1];
					url.SetTo(buffer + sizeof(bplist_match), len);
					*buffer = 0; // make sure we don't try to interpret as xml
				}
				contents.UnlockBuffer();
				int state = 0;
				while (contents.Length()) {
					BString line;
					int32 cr = contents.FindFirst('\n');
					if (cr < 0)
						cr = contents.Length();
					contents.CopyInto(line, 0, cr);
					contents.Remove(0, cr+1);
					line.RemoveAll("\r");
					if (!line.Length())
						continue;
					int32 s, e;
					switch (state) {
						case 0:
							if (!line.ICompare("<?xml", 5))
								state = 1;
							break;
						case 1:
							if (!line.ICompare("<plist", 6))
								state = 2;
							break;
						case 2:
							if (!line.ICompare("<dict>", 6))
								state = 3;
							break;
						case 3:
							if (line.IFindFirst("<key>URL</key>") > -1)
								state = 4;
							break;
						case 4:
							if ((s = line.IFindFirst("<string>")) > -1
								&& (e = line.IFindFirst("</string>")) > s) {
								state = 5;
								s += 8;
								line.MoveInto(url, s, e - s);
								break;
							} else
								state = 3;
							break;
						default:
							break;
					}
					if (state == 5) {
						break;
					}
				}
				if (url.Length()) {
					BUrl u(url.String());
					args[1] = (char*)u.UrlString().String();
					mimetype = kURLHandlerSigBase;
					mimetype += u.Protocol();
					err = be_roster->Launch(mimetype.String(), 1, args + 1);
					if (err != B_OK && err != B_ALREADY_RUNNING)
						err = be_roster->Launch(kAppSig, 1, args + 1);
					continue;
				}
			}
 
			// NetPositive Bookmark or any file with a META:url attribute
			if (f.ReadAttr("META:url", B_STRING_TYPE, 0LL, buff,
				B_PATH_NAME_LENGTH) > 0) {
				BUrl u(buff);
				args[1] = (char*)u.UrlString().String();
				mimetype = kURLHandlerSigBase;
				mimetype += u.Protocol();
				err = be_roster->Launch(mimetype.String(), 1, args + 1);
				if (err != B_OK && err != B_ALREADY_RUNNING)
					err = be_roster->Launch(kAppSig, 1, args + 1);
				continue;
			}
		}
	}
}
 
 
void
UrlWrapper::ArgvReceived(int32 argc, char** argv)
{
	if (argc <= 1)
		return;
	
	const char* failc = " || read -p 'Press any key'";
	const char* pausec = " ; read -p 'Press any key'";
	char* args[] = { (char *)"/bin/sh", (char *)"-c", NULL, NULL};
	status_t err;
 
	BUrl url(argv[1]);
 
	BString full = BUrl(url).SetProtocol(BString()).UrlString();
	BString proto = url.Protocol();
	BString host = url.Host();
	BString port = BString() << url.Port();
	BString user = url.UserInfo();
	BString pass = url.Password();
	BString path = url.Path();
 
	if (!url.IsValid()) {
		fprintf(stderr, "malformed url: '%s'\n", url.UrlString().String());
		return;
	}
	
	// XXX: debug
	PRINT(("PROTO='%s'\n", proto.String()));
	PRINT(("HOST='%s'\n", host.String()));
	PRINT(("PORT='%s'\n", port.String()));
	PRINT(("USER='%s'\n", user.String()));
	PRINT(("PASS='%s'\n", pass.String()));
	PRINT(("PATH='%s'\n", path.String()));
 
	if (proto == "about") {
		app_info info;
		BString sig;
		// BUrl could get an accessor for the full - proto part...
		sig = host << "/" << path;
		BMessage msg(B_ABOUT_REQUESTED);
		if (be_roster->GetAppInfo(sig.String(), &info) == B_OK) {
			BMessenger msgr(sig.String());
			msgr.SendMessage(&msg);
			return;
		}
		if (be_roster->Launch(sig.String(), &msg) == B_OK)
			return;
		be_roster->Launch("application/x-vnd.Haiku-About");
		return;
	}
	
	if (proto == "telnet") {
		BString cmd("telnet ");
		if (url.HasUserInfo())
			cmd << "-l " << user << " ";
		cmd << host;
		if (url.HasPort())
			cmd << " " << port;
		PRINT(("CMD='%s'\n", cmd.String()));
		cmd << failc;
		args[2] = (char*)cmd.String();
		be_roster->Launch(kTerminalSig, 3, args);
		return;
	}
	
	// see draft:
	// http://tools.ietf.org/wg/secsh/draft-ietf-secsh-scp-sftp-ssh-uri/
	if (proto == "ssh") {
		BString cmd("ssh ");
		
		if (url.HasUserInfo())
			cmd << "-l " << user << " ";
		if (url.HasPort())
			cmd << "-oPort=" << port << " ";
		cmd << host;
		PRINT(("CMD='%s'\n", cmd.String()));
		cmd << failc;
		args[2] = (char*)cmd.String();
		be_roster->Launch(kTerminalSig, 3, args);
		// TODO: handle errors
		return;
	}
 
	if (proto == "ftp") {
		BString cmd("ftp ");
		
		cmd << proto << "://";
		/*
		if (user.Length())
			cmd << "-l " << user << " ";
		cmd << host;
		*/
		cmd << full;
		PRINT(("CMD='%s'\n", cmd.String()));
		cmd << failc;
		args[2] = (char*)cmd.String();
		be_roster->Launch(kTerminalSig, 3, args);
		// TODO: handle errors
		return;
	}
	
	if (proto == "sftp") {
		BString cmd("sftp ");
		
		//cmd << url;
		if (url.HasPort())
			cmd << "-oPort=" << port << " ";
		if (url.HasUserInfo())
			cmd << user << "@";
		cmd << host;
		if (url.HasPath())
			cmd << ":" << path;
		PRINT(("CMD='%s'\n", cmd.String()));
		cmd << failc;
		args[2] = (char*)cmd.String();
		be_roster->Launch(kTerminalSig, 3, args);
		// TODO: handle errors
		return;
	}
 
	if (proto == "finger") {
		BString cmd("/bin/finger ");
		
		if (url.HasUserInfo())
			cmd << user;
		if (url.HasHost() == 0)
			host = "127.0.0.1";
		cmd << "@" << host;
		PRINT(("CMD='%s'\n", cmd.String()));
		cmd << pausec;
		args[2] = (char*)cmd.String();
		be_roster->Launch(kTerminalSig, 3, args);
		// TODO: handle errors
		return;
	}
 
	if (proto == "http" || proto == "https" /*|| proto == "ftp"*/) {
		BString cmd("/bin/wget ");
		
		//cmd << url;
		cmd << proto << "://";
		if (url.HasUserInfo())
			cmd << user << "@";
		cmd << full;
		PRINT(("CMD='%s'\n", cmd.String()));
		cmd << pausec;
		args[2] = (char*)cmd.String();
		be_roster->Launch(kTerminalSig, 3, args);
		// TODO: handle errors
		return;
	}
 
	if (proto == "file") {
		BMessage m(B_REFS_RECEIVED);
		entry_ref ref;
		_DecodeUrlString(path);
		if (get_ref_for_path(path.String(), &ref) < B_OK)
			return;
		m.AddRef("refs", &ref);
		be_roster->Launch(kTrackerSig, &m);
		return;
	}
 
	// XXX:TODO: split options
	if (proto == "query") {
		// mktemp ?
		BString qname("/tmp/query-url-temp-");
		qname << getpid() << "-" << system_time();
		BFile query(qname.String(), O_CREAT|O_EXCL);
		// XXX: should check for failure
		
		BString s;
		int32 v;
		
		_DecodeUrlString(full);
		// TODO: handle options (list of attrs in the column, ...)
 
		v = 'qybF'; // QuerY By Formula XXX: any #define for that ?
		query.WriteAttr("_trk/qryinitmode", B_INT32_TYPE, 0LL, &v, sizeof(v));
		s = "TextControl";
		query.WriteAttr("_trk/focusedView", B_STRING_TYPE, 0LL, s.String(),
			s.Length()+1);
		s = full;
		PRINT(("QUERY='%s'\n", s.String()));
		query.WriteAttr("_trk/qryinitstr", B_STRING_TYPE, 0LL, s.String(),
			s.Length()+1);
		query.WriteAttr("_trk/qrystr", B_STRING_TYPE, 0LL, s.String(),
			s.Length()+1);
		s = "application/x-vnd.Be-query";
		query.WriteAttr("BEOS:TYPE", 'MIMS', 0LL, s.String(), s.Length()+1);
		
 
		BEntry e(qname.String());
		entry_ref er;
		if (e.GetRef(&er) >= B_OK)
			be_roster->Launch(&er);
		return;
	}
 
	if (proto == "sh") {
		BString cmd(full);
		if (_Warn(url.UrlString()) != B_OK)
			return;
		PRINT(("CMD='%s'\n", cmd.String()));
		cmd << pausec;
		args[2] = (char*)cmd.String();
		be_roster->Launch(kTerminalSig, 3, args);
		// TODO: handle errors
		return;
	}
 
	if (proto == "beshare") {
		team_id team;
		BMessenger msgr(kBeShareSig);
		// if no instance is running, or we want a specific server, start it.
		if (!msgr.IsValid() || url.HasHost()) {
			be_roster->Launch(kBeShareSig, (BMessage*)NULL, &team);
			msgr = BMessenger(NULL, team);
		}
		if (url.HasHost()) {
			BMessage mserver('serv');
			mserver.AddString("server", host);
			msgr.SendMessage(&mserver);
			
		}
		if (url.HasPath()) {
			BMessage mquery('quer');
			mquery.AddString("query", path);
			msgr.SendMessage(&mquery);
		}
		// TODO: handle errors
		return;
	}
 
	if (proto == "icq" || proto == "msn") {
		// TODO
		team_id team;
		be_roster->Launch(kIMSig, (BMessage*)NULL, &team);
		BMessenger msgr(NULL, team);
		if (url.HasHost()) {
			BMessage mserver(B_REFS_RECEIVED);
			mserver.AddString("server", host);
			msgr.SendMessage(&mserver);
			
		}
		// TODO: handle errors
		return;
	}
 
	if (proto == "mms" || proto == "rtp" || proto == "rtsp") {
		args[0] = (char*)url.UrlString().String();
		be_roster->Launch(kVLCSig, 1, args);
		return;
	}
 
	if (proto == "nfs") {
		BString parameter(host);
		_DecodeUrlString(path);
		if (url.HasPort())
			parameter << ":" << port;
		//XXX: should not always be absolute! FIXME
		parameter << ":/" << path;
		BString prettyPath(path);
		prettyPath.Remove(0, prettyPath.FindLast("/") + 1);
		if (path == "" || path == "/")
			prettyPath = "root";
		prettyPath << " on " << host;
		prettyPath.Prepend("/");
		if (mkdir(prettyPath.String(), 0755) < 0) {
			perror("mkdir");
			return;
		}
		dev_t volume;
		uint32 flags = 0;
		fprintf(stderr, "parms:'%s'\n", parameter.String());
		volume = fs_mount_volume(prettyPath.String(), NULL, "nfs4", flags,
			parameter.String());
		if (volume < B_OK) {
			fprintf(stderr, "fs_mount_volume: %s\n", strerror(volume));
			return;
		}
 
		BMessage m(B_REFS_RECEIVED);
		entry_ref ref;
		if (get_ref_for_path(prettyPath.String(), &ref) < B_OK)
			return;
		m.AddRef("refs", &ref);
		be_roster->Launch(kTrackerSig, &m);
		return;
	}
 
	if (proto == "doi") {
		BString url("http://dx.doi.org/");
		BString mimetype;
 
		url << full;
		BUrl u(url.String());
		args[0] = const_cast<char*>("urlwrapper"); //XXX
		args[1] = (char*)u.UrlString().String();
		args[2] = NULL;
		mimetype = kURLHandlerSigBase;
		mimetype += u.Protocol();
 
		err = be_roster->Launch(mimetype.String(), 1, args + 1);
		if (err != B_OK && err != B_ALREADY_RUNNING)
			err = be_roster->Launch(kAppSig, 1, args + 1);
		// TODO: handle errors
		return;
	}
 
	/*
 
	More ?
	cf. http://en.wikipedia.org/wiki/URI_scheme
	cf. http://www.iana.org/assignments/uri-schemes.html
 
	Audio: (SoundPlay specific, identical to http:// to a shoutcast server)
 
	vnc: ?
	irc: ?
	im: http://tools.ietf.org/html/rfc3860
	
	svn: handled by checkitout
	cvs: handled by checkitout
	git: handled by checkitout
	rsync: handled by checkitout - http://tools.ietf.org/html/rfc5781
 
	smb: cifsmount ?
	nfs: mount_nfs ? http://tools.ietf.org/html/rfc2224
	ipp: http://tools.ietf.org/html/rfc3510
 
	mailto: ? Mail & Beam both handle it already (not fully though).
	imap: to describe mail accounts ? http://tools.ietf.org/html/rfc5092
	pop: http://tools.ietf.org/html/rfc2384
	mid: cid: as per RFC 2392
	http://www.rfc-editor.org/rfc/rfc2392.txt query MAIL:cid
	message:<MID> http://daringfireball.net/2007/12/message_urls_leopard_mail
 
	itps: pcast: podcast: s//http/ + parse xml to get url to mp3 stream...
	audio: s//http:/ + default MediaPlayer
	-- see http://forums.winamp.com/showthread.php?threadid=233130
 
	gps: ? I should submit an RFC for that one :)
 
	webcal: (is http: to .ics file)
 
	data: (but it's dangerous)
 
	*/
	
 
}
 
 
status_t
UrlWrapper::_DecodeUrlString(BString& string)
{
	// TODO: check for %00 and bail out!
	int32 length = string.Length();
	int i;
	for (i = 0; string[i] && i < length - 2; i++) {
		if (string[i] == '%' && isxdigit(string[i+1])
			&& isxdigit(string[i+2])) {
			int c;
			sscanf(string.String() + i + 1, "%02x", &c);
			string.Remove(i, 3);
			string.Insert((char)c, 1, i);
			length -= 2;
		}
	}
	
	return B_OK;
}
 
 
void
UrlWrapper::ReadyToRun(void)
{
	Quit();
}
 
 
// #pragma mark
 
 
int main(int argc, char** argv)
{
	UrlWrapper app;
	if (be_app)
		app.Run();
	return 0;
}
 

V773 The function was exited without releasing the 'alert' pointer. A memory leak is possible.