/* pipe.c for ed2k_gui
*
* creates and checks the named pipe opened for receiving ed2k:// links
*
* (c) 2001-2002 Tim-Philipp Muller <t.i.m@orange.net>
*/


/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include <glib.h>

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include "global.h"
#include "core-conn.h"
#include "downloads-list.h"
#include "http_get.h"

#include "options.h"
#include "pipe.h"
#include "status_page.h"
#include "statusbar.h"
#include "misc.h"
#include "misc_strings.h"

#ifdef G_OS_UNIX
# include <unistd.h>
# include <stdlib.h>
# include <sys/time.h>	// XXX - what's this for???
# include <sys/types.h>
# include <sys/stat.h>

# ifndef USE_POLL_EMULATOR
#  ifdef HAVE_SYS_POLL_H  /* Linux (and MacOSX with libpoll) apparently */
#   include <sys/poll.h>
#  else
#   ifdef HAVE_POLL_H
#    include <poll.h> /* FREEBSD, NETBSD, OPENBSD, SOLARIS - what about others? */
#   endif
#  endif
# else
#  include "poll-emulator.h"
# endif
#endif

#ifdef G_OS_WIN32
# include <Windows.h>
#endif

#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>


#if !defined(POLLRDNORM)
# define POLLRDNORM 0
#endif

#if !defined(POLLRDBAND)
# define POLLRDBAND 0
#endif


#define PIPE_BUF_SIZE   8192U  /* # of bytes to increase the buffer by each iteration */

static gchar        *pipename = NULL;
static guint         ioc_handler = 0;

#ifdef G_OS_WIN32
static HANDLE        hPipe = INVALID_HANDLE_VALUE;
#endif


static void          pipe_unlink (void);


/******************************************************************************
 *
 *   pipe_parse_message
 *
 *   parses message sent to pipe or message dropped into main window via
 *      drag'n'drop
 *
 ******************************************************************************/

void
pipe_parse_message (gchar *msg)
{
	gchar	*start, *msg_utf8;

	g_return_if_fail (msg!=NULL);

	/* replace '%xx' unescapes by real characters */
	misc_strings_unescape_url (msg);

	/* Check if it's an ed2k://|command|<advanced command>| link
	 *
	 * XXX - This is 'exploitable' in theory - people could set up
	 *	html-refresh with an ed2k://|command| link - thus the 'q'
	 *	and the 'm # X' commands are not allowed
	 */

	if ((start = strstr (msg, "|ipchanged|")))
	{
		static time_t    lastIPchange = 0;

		if (time(NULL)-lastIPchange< 60*60)
		{
			g_printerr ("%s", _(" You can't use |ipchanged| more often than once per hour.\n"));
			return;
		}

		lastIPchange = time(NULL);

		status_msg (_("received IP CHANGED indicator\n"));

		gui_download_list_pause_and_resume_all(download_list);

		return;
	}

	if ((start = strstr (msg, "|command|")))
	{
		gchar *end = strrchr (msg, '|');
		start+=9;
		if (end>start)
		{
			end[0]=0x00;
			while (*start==' ' && start<end) start++;	/* skip initial spaces */
			if (strncmp(start,"m ",2)!=0 && start[0]!='q')
			{
				gui_core_conn_send_command(core, start);
				status_msg (_("received ed2k:// command link ('%s')\n"), start);
			} else status_msg (_("received ed2k:// command link with not permitted command ('m x y' or 'q')!\n"));
		} else status_msg (_("received invalid ed2k:// command link.\n"));
		return;
	}

	msg_utf8 = TO_UTF8(msg);
	if (extract_ed2k_links (msg_utf8) <= 0 )	/* ... and don't clear search results list when extracting the links */
	{
		if (extract_server_links(msg, FALSE) > 0)	/* if not a file link, maybe server links? */
		{
			gui_core_conn_send_get_serverlist(core);
			status_msg (_("received ed2k:// server link(s)\n"));
		} else g_printerr (_("received something not an ed2k:// link (via pipe or drag'n'drop)\n"));
	} else status_msg (_("received ed2k:// file link\n"));

	FREE_UTF8(msg_utf8);
}


#if defined(G_OS_UNIX)

/******************************************************************************
 *
 *   onTimeoutPollPipe
 *
 ******************************************************************************/

static gboolean
onTimeoutPollPipe (gpointer fdptr)
{
	static guint8  *buf;      /* NULL */
	static gsize    bufsize;  /* 0 */
	struct pollfd   ps;

	gint fd = GPOINTER_TO_UINT(fdptr);

	ps.fd = fd;
	ps.events = POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI; // | POLLERR | POLLHUP;


	if (poll(&ps, 1, 0) < 0)
	{
		g_warning("poll() on pipe file descriptor failed.\n");
		pipe_unlink();
		return FALSE;
	}

	if ((ps.events & POLLNVAL))
	{
		g_warning ("got POLLNVAL on pipe (fd not open?!)\n");
		pipe_unlink();
		return FALSE;
	}

/*
	// Do we need to catch HUP as well on some systems?
	if (ps.events & POLLHUP)
		g_print ("HUP occured\n");

	if (ps.events & POLLERR)
		g_print ("ERR occured\n");
*/

	if (ps.events & (POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI))
	{
		guint8  tmpbuf[PIPE_BUF_SIZE];
		gint    ret;

		ret = read(fd, tmpbuf, sizeof(tmpbuf));

		if (ret < 0)
		{
			if (errno == EAGAIN)
				return TRUE;

//			perror("read() from pipe failed.\n");
		}
		else if (ret == 0)
		{
			if (buf)
			{
				pipe_parse_message(buf);

//				g_print("DONE READING DATA - received %u bytes altogether:\n\n%s\n\n", bufsize, buf);
				g_free(buf);
				buf = NULL;
				bufsize = 0;
			}

			return TRUE;
		}
		else /* ret > 0 */
		{
			while (ret > 0)
			{
				buf = g_realloc(buf, bufsize + ret + 1);
				memcpy(buf + bufsize, tmpbuf, ret);
				bufsize += ret;
				buf[bufsize-1] = 0x00;
//				g_print("IN event occured - ret = %d\n", ret);
				ret = read(fd, tmpbuf, sizeof(tmpbuf));
			}
		}
	}

	return TRUE;
}



/******************************************************************************
 *
 *   pipe_create
 *
 *   create a named pipe to receive ed2k-links etc.
 *
 *   returns TRUE if okay, or FALSE if failed
 *
 ******************************************************************************/

gboolean
pipe_create (void)
{
	gint	npipe;

	if (!pipename)
	{
		pipename = g_strdup(opt_get_opt_filename("pipe"));
	}

	g_return_val_if_fail (pipename!=NULL, FALSE);

	/* if pipe exists, delete and create new one to make sure it's a fifo and not a normal file! */
	if ( unlink (pipename) != 0  &&  errno!= ENOENT )
	{
		const gchar *msg1 = _("GUI: couldn't remove and re-create named pipe to listen for ed2k:// links\n");
		status_msg ("%s", msg1);
		status_msg (_("GUI: (possibly owned by other user?) well, it might work anyway...\n"));
		statusbar_msg (" %s", msg1);
		return FALSE;
	}

	if (mkfifo(pipename, 0700) < 0)
	{
		status_msg (_("GUI: Could not create named pipe '%s' - %s\n"), pipename, g_strerror(errno));
		return FALSE;
	} /* else status_msg ("GUI: Okay, created named pipe\n"); */

	if ((npipe = open (pipename, O_RDONLY | O_NONBLOCK)) < 0)
	{
		status_msg (_("GUI: Could not create named pipe '%s' - %s\n"), pipename, g_strerror(errno));
		return FALSE;
	}

	status_msg (_("GUI: Listening on named pipe '%s' for ed2k:// links.\n"), pipename);

	ioc_handler = g_timeout_add(500, (GSourceFunc) onTimeoutPollPipe, GUINT_TO_POINTER(npipe));

	g_atexit(&pipe_unlink);

	return TRUE;
}


/******************************************************************************
 *
 *   pipe_unlink
 *
 ******************************************************************************/

static void
pipe_unlink (void)
{
	if (ioc_handler > 0)
		g_source_remove(ioc_handler);

	ioc_handler = 0;

	if (pipename)
		unlink(pipename);
}

#elif defined(G_OS_WIN32)

/******************************************************************************
 *
 *   win32_create_pipe
 *
 ******************************************************************************/

HANDLE
win32_create_pipe (const gchar *pipe)
{
	HANDLE hPipe;

	hPipe = CreateNamedPipe (pipe, PIPE_ACCESS_DUPLEX,
	                         PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_NOWAIT,
	                         PIPE_UNLIMITED_INSTANCES,
	                         1024, PIPE_BUF_SIZE,
	                         1000, NULL);
	return hPipe;
}


/******************************************************************************
 *
 *   onTimeoutPollPipe
 *
 ******************************************************************************/

static gboolean
onTimeoutPollPipe (gpointer fdptr)
{
	gchar   tmpbuf[PIPE_BUF_SIZE+1];
	HANDLE  h;
	DWORD   ret;

	while (1)
	{
		ret = ConnectNamedPipe(hPipe,NULL) ? TRUE : (GetLastError()==ERROR_PIPE_CONNECTED);
		if(!ret)
		{
			if( GetLastError()!=ERROR_PIPE_LISTENING )
			{
				h = win32_create_pipe(pipename);
				CloseHandle(hPipe);
				hPipe = h;

				if(hPipe == INVALID_HANDLE_VALUE)
					return FALSE;
			}
			break;
		}

		ReadFile(hPipe, tmpbuf, PIPE_BUF_SIZE, &ret, NULL);

		if (ret > 0)
		{
			tmpbuf[ret] = 0;
			pipe_parse_message(tmpbuf);
		}

		WriteFile(hPipe, tmpbuf, sizeof(DWORD), &ret, NULL);

		h = win32_create_pipe(pipename);
		CloseHandle(hPipe);
		hPipe = h;

		if(hPipe == INVALID_HANDLE_VALUE)
			return FALSE;
	}

	return TRUE;
}


/******************************************************************************
 *
 *   pipe_create
 *
 *   create a named pipe to receive ed2k-links etc.
 *
 *   returns TRUE if okay, or FALSE if failed
 *
 ******************************************************************************/

gboolean
pipe_create (void)
{
	if(!pipename)
	{
		if( option_gui_instance>0 ) 
		    pipename = g_strdup_printf("\\\\.\\pipe\\ed2k-gtk-gui\\pipe.%u",option_gui_instance);
		else 
		    pipename = g_strdup("\\\\.\\pipe\\ed2k-gtk-gui\\pipe");
	}

	g_return_val_if_fail (pipename != NULL, FALSE);

	if(_access(pipename, 00) != -1)
	{
		const gchar *msg = _("GUI: Named pipe '%s' already in use\n");

		status_msg(msg, pipename);
		statusbar_msg(msg, pipename);

		return FALSE;
	}

	hPipe = win32_create_pipe(pipename);

	if (hPipe == INVALID_HANDLE_VALUE)
	{
		LPVOID      lpMsgBuf;

		if(FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
		                 NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
		                 (LPTSTR)&lpMsgBuf, 0, NULL ) )
		{
			status_msg (_("GUI: Could not create named pipe '%s' - %s\n"), pipename, (gchar*)lpMsgBuf);
			LocalFree(lpMsgBuf);
		}

		return FALSE;
	}

	ioc_handler = g_timeout_add(500, (GSourceFunc) onTimeoutPollPipe, NULL);

	g_atexit(&pipe_unlink);

	return TRUE;
}

/******************************************************************************
 *
 *   pipe_unlink
 *
 ******************************************************************************/

static void
pipe_unlink (void)
{
	if (ioc_handler>0)
		g_source_remove (ioc_handler);

	ioc_handler = 0;

	if (hPipe != INVALID_HANDLE_VALUE)
	{
		CloseHandle(hPipe);
		hPipe = INVALID_HANDLE_VALUE;
	}
}

#else /* Other OS - not Unix, not Win32 */

gboolean
pipe_create (void)
{
	return TRUE;
}

static void
pipe_unlink (void)
{
}

#endif






