/*
 * Copyright (c) 2007 Peter Postma <peter@pointless.nl>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/types.h>
#include <sys/socket.h>

#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

static int	 google_connect(const char *, const char *);
static void	 google_close(int);
static char	*google_recv(int);
static ssize_t	 google_send(int, const char *, size_t);
static void	 google_parse(const char *);

static int
google_connect(const char *host, const char *port)
{
	struct addrinfo	*res, *ai, hints;
	struct pollfd	 fds[1];
	int		 error, sock, flags, rv, val, saved_errno;
	socklen_t	 optlen = sizeof(optlen);

	memset(&hints, 0, sizeof(hints));
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;

	if ((error = getaddrinfo(host, port, &hints, &res)) != 0) {
		fprintf(stderr, "Unable to resolve %s[%s]: %s\n",
		    host, port, gai_strerror(error));
		exit(EXIT_FAILURE);
	}
	errno = EADDRNOTAVAIL;
	for (sock = -1, ai = res; ai != NULL; ai = ai->ai_next) {
		sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
		if (sock == -1)
			continue;
		flags = fcntl(sock, F_GETFL, 0);
		if (flags == -1) {
			google_close(sock);
			sock = -1;
			continue;
		}
		if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) == -1) {
			google_close(sock);
			sock = -1;
			continue;
		}
		do {
			rv = connect(sock, ai->ai_addr, ai->ai_addrlen);
		} while (rv == -1 && errno == EINTR);

		if (rv == -1 && errno != EINPROGRESS)
			goto failed;

		do {
			fds[0].fd = sock;
			fds[0].events = POLLOUT;
			rv = poll(fds, 1, 20 * 1000);
		} while (rv == -1 && errno == EINTR);

		if (rv == -1) {
			goto failed;
		} else if (rv == 0) {
			errno = ETIMEDOUT;
			goto failed;
		} else if (getsockopt(sock, SOL_SOCKET, SO_ERROR,
		    (void *)&val, &optlen) == -1 || val != 0) {
			errno = ECONNREFUSED;
			goto failed;
		}
		if (fcntl(sock, F_SETFL, flags) == -1)
			goto failed;
		/* Got a working socket. */
		break;
 failed:
		saved_errno = errno;
		google_close(sock);
		errno = saved_errno;
		sock = -1;
	}
	freeaddrinfo(res);

	if (sock == -1) {
		fprintf(stderr, "Unable to connect to %s[%s]: %s\n",
		    host, port, strerror(errno));
		exit(EXIT_FAILURE);
	}

	return (sock);
}

static void
google_close(int fd)
{
	int rv;

	do {
		rv = close(fd);
	} while (rv == -1 && errno == EINTR);
}

static char *
google_recv(int fd)
{
	struct pollfd	 fds[1];
	ssize_t		 nbytes;
	size_t		 len = 0;
	size_t		 buflen = BUFSIZ;
	char		*buf;
	int		 rv;

	buf = malloc(buflen);
	if (buf == NULL) {
		fprintf(stderr, "Unable to allocate memory: %s\n",
		    strerror(errno));
		exit(EXIT_FAILURE);
	}

	for (;;) {
		do {
			fds[0].fd = fd;
			fds[0].events = POLLIN;
			rv = poll(fds, 1, 20 * 1000);
		} while (rv == -1 && errno == EINTR);

		if (rv == -1) {
			fprintf(stderr, "Unable to poll fd: %s\n",
			    strerror(errno));
			exit(EXIT_FAILURE);
		} else if (rv == 0) {
			fprintf(stderr, "Timeout waiting for data\n");
			exit(EXIT_FAILURE);
		}

		do {
			nbytes = recv(fd, buf + len, buflen - len - 1, 0);
		} while (nbytes == -1 && errno == EINTR);

		if (nbytes == -1) {
			fprintf(stderr, "Unable to receive data: %s\n",
			    strerror(errno));
			exit(EXIT_FAILURE);
		} else if (nbytes == 0)
			break;
		buf[nbytes + len] = '\0';
		len += nbytes;

		if (len >= buflen - 1) {
			if (len > (BUFSIZ * BUFSIZ)) {
				fprintf(stderr, "Unable to receive data: %s\n",
				    strerror(EMSGSIZE));
				exit(EXIT_FAILURE);
			}
			while (len >= buflen - 1)
				buflen *= 2;
			buf = realloc(buf, buflen);
			if (buf == NULL) {
				fprintf(stderr,
				    "Unable to reallocate memory: %s\n",
				    strerror(errno));
				exit(EXIT_FAILURE);
			}
		}
	}

	return (buf);
}

static ssize_t
google_send(int fd, const char *buf, size_t len)
{
	size_t	total, left;
	ssize_t	rv;

	total = 0;
	left = len;
	do {
		rv = send(fd, buf + total, left, 0);
		if (rv == -1) {
			fprintf(stderr, "Unable to send data: %s\n",
			    strerror(errno));
			exit(EXIT_FAILURE);
		}
		total += rv;
		left -= rv;
	} while (left > 1);

	return (total);
}

static void
google_parse(const char *data)
{
	const char	*buf;
	char		*href, *n, *p;
	char		 chunk[BUFSIZ];
	size_t		 size, count;

	count = 0;
	for (buf = data; *buf != '\0'; buf = n) {
		if ((p = strcasestr(buf, "<h2 class=r>")) == NULL)
			break;
		if ((n = strcasestr(p, "</h2>")) == NULL)
			break;
		size = n - p + 2;
		if (size > BUFSIZ)
			break;
		strlcpy(chunk, p, size);

		if ((p = strcasestr(chunk, "href=")) == NULL)
			continue;
		p = p + 5;
		while (*p != '\0' && *p != '"')
			p++;
		if (*p == '\0' || *++p == '\0')
			continue;
		href = p;

		while (*p != '\0' && *p != '"')
			p++;
		*p = '\0';

		printf("%d. %s\n", ++count, href);
	}

	if (count == 0)
		printf("Nothing found.\n");
}

static char *
url_encode(const char *str)
{
	char	*encoded;
	char	 num[4];
	size_t	 size, idx;

	size = strlen(str) + 1;
	encoded = malloc(size);
	if (encoded == NULL) {
		fprintf(stderr, "Unable to allocate memory: %s\n",
		    strerror(errno));
		exit(EXIT_FAILURE);
	}

	idx = 0;

#define SPECIAL_CHAR(x) \
    (((x) == '$') || ((x) == '-') || ((x) == '_') || ((x) == '.') || \
     ((x) == '+') || ((x) == '!') || ((x) == '*') || ((x) == '\'') || \
     ((x) == '(') || ((x) == ')') || ((x) == '/') || ((x) == '?') || \
     ((x) == ';') || ((x) == ':') || ((x) == '@') || ((x) == '=') || \
     ((x) == '&'))

	for (; *str != '\0'; str++) {
		if (!isalnum((unsigned char)*str) && !SPECIAL_CHAR(*str)) {
			snprintf(num, sizeof(num), "%%%02x", *str);

			/* Resize the encoded string and append the char. */
			size += sizeof(num);
			encoded = realloc(encoded, size);
			if (encoded == NULL) {
				fprintf(stderr,
				    "Unable to reallocate memory: %s\n",
				    strerror(errno));
				exit(EXIT_FAILURE);
			}
			strlcat(encoded, num, size);

			idx += sizeof(num) - 1;
		} else {
			encoded[idx++] = *str;
		}
		encoded[idx] = '\0';
	}

	return (encoded);
}

static void
usage(const char *progname)
{
	fprintf(stderr, "Usage: %s keyword ...\n", progname);
	exit(EXIT_FAILURE);
}

int
main(int argc, char *argv[])
{
	const char	 host[] = "www.google.nl";
	const char	 port[] = "http";
	const char	*progname;
	char		 request[BUFSIZ];
	char		 query[BUFSIZ];
	char		*buf;
	int		 ch, fd, i;

	progname = argv[0];

	while ((ch = getopt(argc, argv, "h")) != -1) {
		switch (ch) {
		case 'h':
		default:
			usage(progname);
		}
	}
	argc -= optind;
	argv += optind;

	if (argc < 1)
		usage(progname);

	memset(query, '\0', sizeof(query));
	memset(request, '\0', sizeof(request));

	for (i = 0; i < argc - 1; i++) {
		strlcat(query, argv[i], sizeof(query));
		strlcat(query, " ", sizeof(query));
	}
	strlcat(query, argv[i], sizeof(query));

	fd = google_connect(host, port);

	snprintf(request, sizeof(request), "GET /search?q=%s HTTP/1.0\r\n"
	    "Host: %s\r\nUser-Agent: Mozilla/5.0 (compatible; )\r\n\r\n",
	    url_encode(query), host);
	google_send(fd, request, strlen(request));

	buf = google_recv(fd);
	google_close(fd);

	google_parse(buf);

	return (EXIT_SUCCESS);
}

