/*
 * Copyright (c) 2003-2011
 * Distributed Systems Software.  All rights reserved.
 * See the file LICENSE for redistribution information.
 */

/*
 * This is a pure HTTP VFS layer.
 * As such, it cannot provide all of the virtual filestore functionality
 * (e.g., no HTTP "list" or "rename" operations are defined by RFC 2616).
 * But on the other hand, it does not require anything other than a basic
 * HTTP server on the other end.
 * XXX If necessary, the missing operations could be implemented in terms of
 * the defined methods (non-atomically, probably).
 */

#ifndef lint
static const char copyright[] =
"Copyright (c) 2003-2011\n\
Distributed Systems Software.  All rights reserved.";
static const char revid[] =
  "$Id: vfs_http.c 2528 2011-09-23 21:54:05Z brachman $";
#endif

#include "local.h"
#include "http.h"

static const char *log_module_name = "vfs_http";

static int vfs_http_open(Vfs_handle *, char *name_context);
static int vfs_http_close(Vfs_handle *handle);
static int vfs_http_control(Vfs_handle *, Vfs_control_op op, va_list ap);
static int vfs_http_get(Vfs_handle *handle, char *key, void **buffer,
						size_t *length);
static int vfs_http_getsize(Vfs_handle *handle, char *key, size_t *length);
static int vfs_http_put(Vfs_handle *handle, char *key, void *buffer,
						size_t length);
static int vfs_http_delete(Vfs_handle *handle, char *key);
static int vfs_http_exists(Vfs_handle *handle, char *key);
static int vfs_http_rename(Vfs_handle *handle, char *oldkey, char *newkey);
static int vfs_http_list(Vfs_handle *handle, int (*is_valid)(char *),
						 int (*compar)(const void *, const void *),
						 int (*add)(char *, char *, void ***), void ***keys);

static Vfs_switch vfs_http_conf = {
  "http",
  vfs_http_open,
  vfs_http_close,
  vfs_http_control,
  vfs_http_get,
  vfs_http_getsize,
  vfs_http_put,
  vfs_http_delete,
  vfs_http_exists,
  vfs_http_rename,
  vfs_http_list
};

static Vfs_switch vfs_https_conf = {
  "https",
  vfs_http_open,
  vfs_http_close,
  vfs_http_control,
  vfs_http_get,
  vfs_http_getsize,
  vfs_http_put,
  vfs_http_delete,
  vfs_http_exists,
  vfs_http_rename,
  vfs_http_list
};

typedef struct {
  Uri *uri;
} Handle;

static char *
getpath(Vfs_handle *handle, char *key)
{
  char *path;

  if (handle->sd->naming_context != NULL) {
	if (key == NULL)
	  path = ds_xprintf("%s", handle->sd->naming_context);
	else
	  path = ds_xprintf("%s/%s", handle->sd->naming_context, key);
  }
  else
	path = ds_xprintf("%s", key);

  return(path);
}

/*
 * Invoke an operation using http/https.
 * KWV are standard arguments provided at "open" time and needed for
 * all operations.
 * EKWV, if not NULL, are "extended" arguments; that is, arguments that are
 * specific to a particular operation.
 */
static int
invoke_url(Vfs_handle *handle, Http_method method,
		   char *key, Kwv *ekwv, char *body, int *status_code,
		   char **store_reply, size_t *store_reply_len)
{
  int i, reply_len, st;
  char *url, *reply;
  Ds *ds;
  Dsvec *v, *response_headers;
  Handle *h;
  Http_params *params;

  h = (Handle *) handle->h;

#ifdef NOTDEF
  if (handle->sd->item_type != NULL) {
	if (key == NULL)
	  url = ds_xprintf("%s/%s",
					   handle->sd->naming_context, handle->sd->item_type);
	else
	  url = ds_xprintf("%s/%s/%s",
					   handle->sd->naming_context, handle->sd->item_type, key);
  }
  else {
	if (key == NULL)
	  url = ds_xprintf("%s", handle->sd->naming_context);
	else
	  url = ds_xprintf("%s/%s", handle->sd->naming_context, key);
  }
#else
  url = getpath(handle, key);
#endif

  v = dsvec_init(NULL, sizeof(Http_params));

  for (i = 0; i < kwv_count(handle->kwv, NULL); i++)
	params = http_param(v, handle->kwv->pairs[i]->name,
						handle->kwv->pairs[i]->val, NULL, 0);

  if (ekwv != NULL) {
    for (i = 0; i < kwv_count(ekwv, NULL); i++)
	  params = http_param(v, ekwv->pairs[i]->name,
						  ekwv->pairs[i]->val, NULL, 0);
  }

  /*
   * If the operation fails, -1 will be returned and REPLY will be set to point
   * to an error message.
   */
  ds = ds_set(NULL, body);
  response_headers = dsvec_init(NULL, sizeof(char *));
  reply_len = -1;
  st = http_invoke(url, method, HTTP_SSL_URL_SCHEME,
				   dsvec_len(v), (Http_params *) dsvec_base(v), ds, NULL,
				   &reply, &reply_len, status_code, response_headers);

  if (st == -1) {
	if (*reply != '\0')
	  handle->error_msg = reply;
	else
	  handle->error_msg = "HTTP operation failed";
	return(-1);
  }

  if (*status_code < 200 || *status_code > 299) {
	if (*reply != '\0')
	  handle->error_msg = reply;
	else
	  handle->error_msg
		= ds_xprintf("HTTP operation returned status code %d", *status_code);
	return(-1);
  }

  if (store_reply_len != NULL)
	*store_reply_len = (size_t) reply_len;

  *store_reply = reply;
  return(0);
}

static int
vfs_http_open(Vfs_handle *handle, char *naming_context)
{
  Handle *h;

  if (handle->delete_flag) {
	/* XXX not implemented */
	handle->error_num = ENOSYS;
	handle->error_msg = strerror(handle->error_num);
	return(-1);
  }

  h = ALLOC(Handle);

  handle->h = (void *) h;

  return(0);
}

static int
vfs_http_close(Vfs_handle *handle)
{

  return(0);
}

static int
vfs_http_control(Vfs_handle *handle, Vfs_control_op op, va_list ap)
{

  log_msg((LOG_ERROR_LEVEL, "Invalid control request: %d", op));
  return(-1);
}

static int
vfs_http_get(Vfs_handle *handle, char *key, void **buffer, size_t *length)
{
  int st, status_code;
  char *reply;
  Handle *h;

  h = (Handle *) handle->h;

  st = invoke_url(handle, HTTP_GET_METHOD, key, NULL, NULL,
				  &status_code, &reply, length);
  if (st == -1)
	return(-1);

  *buffer = (void *) reply;

  return(0);
}

static int
vfs_http_getsize(Vfs_handle *handle, char *key, size_t *length)
{
  int st, status_code;
  char *reply;
  Handle *h;

  h = (Handle *) handle->h;

  st = invoke_url(handle, HTTP_GET_METHOD, key, NULL, NULL,
				  &status_code, &reply, length);
  if (st == -1)
	return(-1);

#ifdef NOTDEF
  /* WTF? */
  if (strnum(reply, STRNUM_UL, &len) == -1) {
	handle->error_msg = "Size value was out-of-range";
	return(-1);
  }
  *length = (size_t) len;
#endif

  return(0);
}

static int
vfs_http_put(Vfs_handle *handle, char *key, void *buffer, size_t length)
{
  int st, status_code;
  char *reply;
  Handle *h;
  Http_method method;

  h = (Handle *) handle->h;

  method = HTTP_PUT_METHOD;
  /* XXX append_flag? */
  st = invoke_url(handle, method, key, NULL, (char *) buffer,
				  &status_code, &reply, NULL);

  if (st == -1)
	return(-1);

  return(0);
}

static int
vfs_http_delete(Vfs_handle *handle, char *key)
{
  int st, status_code;
  char *reply;
  Handle *h;
  Http_method method;

  h = (Handle *) handle->h;

  method = HTTP_DELETE_METHOD;

  st = invoke_url(handle, method, key, NULL, NULL, &status_code, &reply, NULL);
  if (st == -1)
	return(-1);

  return(0);
}

static int
vfs_http_exists(Vfs_handle *handle, char *key)
{
  int st, status_code;
  char *reply;
  Handle *h;

  h = (Handle *) handle->h;

  st = invoke_url(handle, HTTP_HEAD_METHOD, key, NULL, NULL,
				  &status_code, &reply, NULL);
  if (st == -1)
	return(-1);

  if (status_code == 200)
	return(1);
  if (status_code == 404)
	return(0);
  return(-1);
}

static int
vfs_http_rename(Vfs_handle *handle, char *oldkey, char *newkey)
{

  return(-1);
}

static int
vfs_http_list(Vfs_handle *handle, int (*is_valid)(char *),
		int (*compar)(const void *, const void *),
		int (*add)(char *, char *, void ***), void ***names)
{

  return(-1);
}

Vfs_switch *
vfs_http_init(char *store_name)
{

  if (strcaseeq(store_name, "http"))
	  return(&vfs_http_conf);
  else if (strcaseeq(store_name, "https"))
	  return(&vfs_https_conf);
  return(NULL);
}

