746 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			746 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*  Part of SWI-Prolog
 | 
						|
 | 
						|
    Author:        Jan Wielemaker
 | 
						|
    E-mail:        J.Wielemaker@uva.nl
 | 
						|
    WWW:           http://www.swi-prolog.org
 | 
						|
    Copyright (C): 2009, University of Amsterdam
 | 
						|
 | 
						|
    This library is free software; you can redistribute it and/or
 | 
						|
    modify it under the terms of the GNU Lesser General Public
 | 
						|
    License as published by the Free Software Foundation; either
 | 
						|
    version 2.1 of the License, or (at your option) any later version.
 | 
						|
 | 
						|
    This library is distributed in the hope that it will be useful,
 | 
						|
    but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 | 
						|
    Lesser General Public License for more details.
 | 
						|
 | 
						|
    You should have received a copy of the GNU Lesser General Public
 | 
						|
    License along with this library; if not, write to the Free Software
 | 
						|
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 | 
						|
*/
 | 
						|
 | 
						|
#include <SWI-Stream.h>
 | 
						|
#include <SWI-Prolog.h>
 | 
						|
#include <stdlib.h>
 | 
						|
#include <string.h>
 | 
						|
#include <assert.h>
 | 
						|
 | 
						|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | 
						|
The task of cgi_stream.c is to interface between the actual wrapper code
 | 
						|
that implements an HTTP location and the   socket sending a reply to the
 | 
						|
client.  In particular, we want to deal with:
 | 
						|
 | 
						|
    * Separating the header from the body of the reply
 | 
						|
    * Chunked or traditional transfer encoding
 | 
						|
    * Connection management (Keep-alife)
 | 
						|
    * Thread management
 | 
						|
 | 
						|
The original HTTP infrastructure has an `accept thread' that accepts new
 | 
						|
connections. The connection is handed to a   thread  that reads the HTTP
 | 
						|
header and calls a handler with the  output redirected to a memory file,
 | 
						|
processing the reply-header and reply-data   after the handler finished.
 | 
						|
This is a clean and modular design,   but it cannot deal with especially
 | 
						|
chunked encoding and  thread  management.   This  module  remedies these
 | 
						|
issues.
 | 
						|
 | 
						|
To do this, the  stream  provides   a  three  call-backs. Initially, the
 | 
						|
stream is in line-buffering mode (SIO_LBUF),   waiting for the header to
 | 
						|
become complete. At that moment it calls   the  hook, passing event type
 | 
						|
'header' and the stream. This processes the   head and combines the head
 | 
						|
with the request, deciding on:
 | 
						|
 | 
						|
    * The final header
 | 
						|
    * The transfer encoding (chunked/none)
 | 
						|
    * The content encoding (octet/utf8)
 | 
						|
    * The connection (Keep-Alife/close)
 | 
						|
 | 
						|
Now, the stream is placed in  full   buffering  mode  (SIO_FBUF). If the
 | 
						|
transfer encoding is 'chunked'  it  immediately   calls  the  hook using
 | 
						|
'send_header' to emit the current header.   Output continues. In chunked
 | 
						|
mode sending the chunks, otherwisse collecting   the  data. On close, it
 | 
						|
writes an empty block (chunked mode)  or   (normal  mode) calls the hook
 | 
						|
'send_header' which now has access to   the  content-length, followed by
 | 
						|
the data.
 | 
						|
 | 
						|
Note that the work-flow is kept with the stream. This allows passing the
 | 
						|
cgi stream from thread to thread while keeping track of the work-flow.
 | 
						|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
 | 
						|
 | 
						|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | 
						|
TODO
 | 
						|
 | 
						|
	* Error handling (many places)
 | 
						|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
 | 
						|
 | 
						|
 | 
						|
		 /*******************************
 | 
						|
		 *	      CONSTANTS		*
 | 
						|
		 *******************************/
 | 
						|
 | 
						|
static atom_t ATOM_header;		/* header */
 | 
						|
static atom_t ATOM_header_codes;	/* header_codes */
 | 
						|
static atom_t ATOM_send_header;		/* send_header */
 | 
						|
static atom_t ATOM_data;		/* data */
 | 
						|
static atom_t ATOM_discarded;		/* discarded */
 | 
						|
static atom_t ATOM_request;		/* request */
 | 
						|
static atom_t ATOM_client;		/* client */
 | 
						|
static atom_t ATOM_chunked;		/* chunked */
 | 
						|
static atom_t ATOM_none;		/* none */
 | 
						|
static atom_t ATOM_state;		/* state */
 | 
						|
static atom_t ATOM_transfer_encoding;	/* transfer_encoding */
 | 
						|
static atom_t ATOM_connection;		/* connection */
 | 
						|
static atom_t ATOM_keep_alife;		/* keep_alife */
 | 
						|
static atom_t ATOM_close;		/* close */
 | 
						|
static atom_t ATOM_content_length;	/* content_length */
 | 
						|
static atom_t ATOM_id;			/* id */
 | 
						|
static predicate_t PREDICATE_call3;	/* Goal, Event, Handle */
 | 
						|
 | 
						|
 | 
						|
		 /*******************************
 | 
						|
		 *	      CONTEXT		*
 | 
						|
		 *******************************/
 | 
						|
 | 
						|
#define BUFSIZE SIO_BUFSIZE		/* raw I/O buffer */
 | 
						|
 | 
						|
typedef enum
 | 
						|
{ CGI_HDR  = 0,
 | 
						|
  CGI_DATA,
 | 
						|
  CGI_DISCARDED
 | 
						|
} cgi_state;
 | 
						|
 | 
						|
#define CGI_MAGIC 0xa85ce042
 | 
						|
 | 
						|
typedef struct cgi_context
 | 
						|
{ IOSTREAM	   *stream;		/* Original stream */
 | 
						|
  IOSTREAM	   *cgi_stream;		/* Stream I'm handle of */
 | 
						|
  IOENC		    parent_encoding;	/* Saved encoding of parent */
 | 
						|
					/* Prolog attributes */
 | 
						|
  module_t	    module;		/* Calling module */
 | 
						|
  record_t	    hook;		/* Hook called on action */
 | 
						|
  record_t	    request;		/* Associated request term */
 | 
						|
  record_t	    header;		/* Associated reply header term */
 | 
						|
  atom_t	    transfer_encoding;	/* Current transfer encoding */
 | 
						|
  atom_t	    connection;		/* Keep alife? */
 | 
						|
					/* state */
 | 
						|
  cgi_state	    state;		/* Current state */
 | 
						|
					/* data buffering */
 | 
						|
  size_t	    data_offset;	/* Start of real data */
 | 
						|
  char		   *data;		/* Buffered data */
 | 
						|
  size_t	    datasize;		/* #bytes buffered */
 | 
						|
  size_t	    dataallocated;	/* #bytes allocated */
 | 
						|
  int		    id;			/* Identifier */
 | 
						|
  unsigned int	    magic;		/* CGI_MAGIC */
 | 
						|
} cgi_context;
 | 
						|
 | 
						|
 | 
						|
static int start_chunked_encoding(cgi_context *ctx);
 | 
						|
static ssize_t cgi_chunked_write(cgi_context *ctx, char *buf, size_t size);
 | 
						|
 | 
						|
 | 
						|
		 /*******************************
 | 
						|
		 *	     ALLOC/FREE		*
 | 
						|
		 *******************************/
 | 
						|
 | 
						|
static cgi_context*
 | 
						|
alloc_cgi_context(IOSTREAM *s)
 | 
						|
{ cgi_context *ctx = PL_malloc(sizeof(*ctx));
 | 
						|
 | 
						|
  memset(ctx, 0, sizeof(*ctx));
 | 
						|
  ctx->magic  = CGI_MAGIC;
 | 
						|
  ctx->stream = s;
 | 
						|
 | 
						|
  return ctx;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static void
 | 
						|
free_cgi_context(cgi_context *ctx)
 | 
						|
{ if ( ctx->stream->upstream )
 | 
						|
    Sset_filter(ctx->stream, NULL);
 | 
						|
  else
 | 
						|
    PL_release_stream(ctx->stream);
 | 
						|
 | 
						|
  if ( ctx->data )       free(ctx->data);
 | 
						|
  if ( ctx->hook )       PL_erase(ctx->hook);
 | 
						|
  if ( ctx->request )    PL_erase(ctx->request);
 | 
						|
  if ( ctx->header )     PL_erase(ctx->header);
 | 
						|
  if ( ctx->connection ) PL_unregister_atom(ctx->connection);
 | 
						|
 | 
						|
  ctx->magic = 0;
 | 
						|
  PL_free(ctx);
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int
 | 
						|
grow_data_buffer(cgi_context *ctx, size_t size)
 | 
						|
{ size_t newsize;
 | 
						|
 | 
						|
  if ( ctx->dataallocated == 0 )
 | 
						|
    newsize = SIO_BUFSIZE;
 | 
						|
  else
 | 
						|
    newsize = ctx->dataallocated;
 | 
						|
 | 
						|
  while(newsize < size)
 | 
						|
    newsize *= 2;
 | 
						|
  if ( ctx->data )
 | 
						|
  { void *p;
 | 
						|
 | 
						|
    if ( !(p=realloc(ctx->data, newsize)) )
 | 
						|
      return -1;
 | 
						|
    ctx->data = p;
 | 
						|
    ctx->dataallocated = newsize;
 | 
						|
  } else
 | 
						|
  { if ( !(ctx->data = malloc(newsize)) )
 | 
						|
      return -1;
 | 
						|
 | 
						|
    ctx->dataallocated = newsize;
 | 
						|
  }
 | 
						|
 | 
						|
  return 0;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
		 /*******************************
 | 
						|
		 *	     PROPERTIES		*
 | 
						|
		 *******************************/
 | 
						|
 | 
						|
static IOFUNCTIONS cgi_functions;
 | 
						|
 | 
						|
static int
 | 
						|
get_cgi_stream(term_t t, IOSTREAM **sp, cgi_context **ctx)
 | 
						|
{ IOSTREAM *s;
 | 
						|
 | 
						|
  if ( !PL_get_stream_handle(t, &s) )
 | 
						|
    return FALSE;
 | 
						|
  if ( s->functions != &cgi_functions )
 | 
						|
  { PL_release_stream(s);
 | 
						|
    return type_error(t, "cgi_stream");
 | 
						|
  }
 | 
						|
 | 
						|
  *sp = s;
 | 
						|
  *ctx = s->handle;
 | 
						|
 | 
						|
  return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int
 | 
						|
unify_record(term_t t, record_t r)
 | 
						|
{ if ( r )
 | 
						|
  { term_t t2 = PL_new_term_ref();
 | 
						|
    PL_recorded(r, t2);
 | 
						|
    return PL_unify(t, t2);
 | 
						|
  }
 | 
						|
  return FALSE;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static foreign_t
 | 
						|
is_cgi_stream(term_t cgi)
 | 
						|
{ IOSTREAM *s;
 | 
						|
  int rc;
 | 
						|
 | 
						|
  if ( !PL_get_stream_handle(cgi, &s) )
 | 
						|
    return FALSE;
 | 
						|
  rc = (s->functions == &cgi_functions);
 | 
						|
  PL_release_stream(s);
 | 
						|
 | 
						|
  return rc;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static foreign_t
 | 
						|
cgi_property(term_t cgi, term_t prop)
 | 
						|
{ IOSTREAM *s;
 | 
						|
  cgi_context *ctx;
 | 
						|
  term_t arg = PL_new_term_ref();
 | 
						|
  atom_t name;
 | 
						|
  int arity;
 | 
						|
  int rc = TRUE;
 | 
						|
 | 
						|
  if ( !get_cgi_stream(cgi, &s, &ctx) )
 | 
						|
    return FALSE;
 | 
						|
 | 
						|
  if ( !PL_get_name_arity(prop, &name, &arity) || arity != 1 )
 | 
						|
  { rc = type_error(prop, "cgi_property");
 | 
						|
    goto out;
 | 
						|
  }
 | 
						|
 | 
						|
  _PL_get_arg(1, prop, arg);
 | 
						|
  if ( name == ATOM_request )
 | 
						|
  { if ( ctx->request )
 | 
						|
      rc = unify_record(arg, ctx->request);
 | 
						|
    else
 | 
						|
      rc = PL_unify_nil(arg);
 | 
						|
  } else if ( name == ATOM_header )
 | 
						|
  { if ( ctx->header )
 | 
						|
      rc = unify_record(arg, ctx->header);
 | 
						|
     else
 | 
						|
      rc = PL_unify_nil(arg);
 | 
						|
  } else if ( name == ATOM_id )
 | 
						|
  { rc = PL_unify_integer(arg, ctx->id);
 | 
						|
  } else if ( name == ATOM_client )
 | 
						|
  { rc = PL_unify_stream(arg, ctx->stream);
 | 
						|
  } else if ( name == ATOM_transfer_encoding )
 | 
						|
  { rc = PL_unify_atom(arg, ctx->transfer_encoding);
 | 
						|
  } else if ( name == ATOM_connection )
 | 
						|
  { rc = PL_unify_atom(arg, ctx->connection ? ctx->connection : ATOM_close);
 | 
						|
  } else if ( name == ATOM_content_length )
 | 
						|
  { rc = PL_unify_int64(arg, ctx->datasize - ctx->data_offset);
 | 
						|
  } else if ( name == ATOM_header_codes )
 | 
						|
  { if ( ctx->data_offset > 0 )
 | 
						|
      rc = PL_unify_chars(arg, PL_CODE_LIST, ctx->data_offset, ctx->data);
 | 
						|
    else
 | 
						|
      rc = existence_error(cgi, "header");
 | 
						|
  } else if ( name == ATOM_state )
 | 
						|
  { atom_t state;
 | 
						|
 | 
						|
    switch(ctx->state)
 | 
						|
    { case CGI_HDR:       state = ATOM_header; break;
 | 
						|
      case CGI_DATA:      state = ATOM_data; break;
 | 
						|
      case CGI_DISCARDED: state = ATOM_discarded; break;
 | 
						|
      default:
 | 
						|
	assert(0);
 | 
						|
    }
 | 
						|
 | 
						|
    rc = PL_unify_atom(arg, state);
 | 
						|
  } else
 | 
						|
  { rc = existence_error(prop, "cgi_property");
 | 
						|
  }
 | 
						|
 | 
						|
out:
 | 
						|
  PL_release_stream(s);
 | 
						|
  return rc;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int
 | 
						|
set_term(record_t *r, term_t t)
 | 
						|
{ if ( *r )
 | 
						|
    PL_erase(*r);
 | 
						|
  *r = PL_record(t);
 | 
						|
 | 
						|
  return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int
 | 
						|
set_atom(atom_t *a, term_t t)
 | 
						|
{ atom_t new;
 | 
						|
 | 
						|
  if ( !PL_get_atom(t, &new) )
 | 
						|
    return type_error(t, "atom");
 | 
						|
 | 
						|
  if ( *a != new )
 | 
						|
  { if ( *a )
 | 
						|
      PL_unregister_atom(*a);
 | 
						|
    *a = new;
 | 
						|
    PL_register_atom(new);
 | 
						|
  }
 | 
						|
 | 
						|
  return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static foreign_t
 | 
						|
cgi_set(term_t cgi, term_t prop)
 | 
						|
{ IOSTREAM *s;
 | 
						|
  cgi_context *ctx;
 | 
						|
  term_t arg = PL_new_term_ref();
 | 
						|
  atom_t name;
 | 
						|
  int arity;
 | 
						|
  int rc = TRUE;
 | 
						|
 | 
						|
  if ( !get_cgi_stream(cgi, &s, &ctx) )
 | 
						|
    return FALSE;
 | 
						|
 | 
						|
  if ( !PL_get_name_arity(prop, &name, &arity) || arity != 1 )
 | 
						|
  { rc = type_error(prop, "cgi_property");
 | 
						|
    goto out;
 | 
						|
  }
 | 
						|
 | 
						|
  _PL_get_arg(1, prop, arg);
 | 
						|
  if ( name == ATOM_request )
 | 
						|
  { rc = set_term(&ctx->request, arg);
 | 
						|
  } else if ( name == ATOM_header )
 | 
						|
  { rc = set_term(&ctx->header, arg);
 | 
						|
  } else if ( name == ATOM_connection )
 | 
						|
  { rc = set_atom(&ctx->connection, arg);
 | 
						|
  } else if ( name == ATOM_transfer_encoding )
 | 
						|
  { atom_t enc;
 | 
						|
 | 
						|
    if ( !PL_get_atom(arg, &enc) )
 | 
						|
      return type_error(arg, "atom");
 | 
						|
 | 
						|
    if ( ctx->transfer_encoding != enc )
 | 
						|
    { if ( enc == ATOM_chunked )
 | 
						|
      { ctx->transfer_encoding = enc;
 | 
						|
	rc = start_chunked_encoding(ctx);
 | 
						|
      } else
 | 
						|
      { rc = domain_error(arg, "transfer_encoding");
 | 
						|
      }
 | 
						|
    }
 | 
						|
  } else
 | 
						|
  { rc = existence_error(prop, "cgi_property");
 | 
						|
  }
 | 
						|
 | 
						|
out:
 | 
						|
  PL_release_stream(s);
 | 
						|
  return rc;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static foreign_t
 | 
						|
cgi_discard(term_t cgi)
 | 
						|
{ IOSTREAM *s;
 | 
						|
  cgi_context *ctx;
 | 
						|
 | 
						|
  if ( !get_cgi_stream(cgi, &s, &ctx) )
 | 
						|
    return FALSE;
 | 
						|
 | 
						|
  ctx->state = CGI_DISCARDED;
 | 
						|
					/* empty buffer to avoid write */
 | 
						|
  ctx->cgi_stream->bufp = ctx->cgi_stream->buffer;
 | 
						|
  PL_release_stream(s);
 | 
						|
 | 
						|
  return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
		 /*******************************
 | 
						|
		 *	      HOOKS		*
 | 
						|
		 *******************************/
 | 
						|
 | 
						|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | 
						|
Call hook on the data we collected sofar.   The  hook is called with the
 | 
						|
following additional arguments:
 | 
						|
 | 
						|
    * Event-type (header, data)
 | 
						|
    * An input stream pointing to the collected data
 | 
						|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
 | 
						|
 | 
						|
static int
 | 
						|
call_hook(cgi_context *ctx, atom_t event)
 | 
						|
{ fid_t fid = PL_open_foreign_frame();
 | 
						|
  term_t av = PL_new_term_refs(3);
 | 
						|
  qid_t qid;
 | 
						|
  int rc;
 | 
						|
 | 
						|
  PL_recorded(ctx->hook, av+0);
 | 
						|
  PL_put_atom(av+1, event);
 | 
						|
  PL_unify_stream(av+2, ctx->cgi_stream);
 | 
						|
  qid = PL_open_query(ctx->module, PL_Q_CATCH_EXCEPTION, PREDICATE_call3, av);
 | 
						|
  rc = PL_next_solution(qid);
 | 
						|
 | 
						|
  if ( !rc )
 | 
						|
  { term_t ex;
 | 
						|
 | 
						|
    if ( (ex = PL_exception(qid)) )
 | 
						|
    { Sset_exception(ctx->cgi_stream, ex);
 | 
						|
 | 
						|
    } else
 | 
						|
    { char buf[256];
 | 
						|
      Ssprintf(buf, "CGI Hook %s failed", PL_atom_chars(event));
 | 
						|
 | 
						|
      Sseterr(ctx->cgi_stream, SIO_WARN, buf);
 | 
						|
    }
 | 
						|
 | 
						|
    PL_cut_query(qid);
 | 
						|
    PL_close_foreign_frame(fid);
 | 
						|
 | 
						|
    return FALSE;
 | 
						|
  }
 | 
						|
  PL_close_query(qid);
 | 
						|
  PL_discard_foreign_frame(fid);
 | 
						|
 | 
						|
  return TRUE;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int
 | 
						|
start_chunked_encoding(cgi_context *ctx)
 | 
						|
{ if ( call_hook(ctx, ATOM_send_header) )
 | 
						|
  { if ( ctx->datasize > ctx->data_offset )
 | 
						|
    { int rc = cgi_chunked_write(ctx,
 | 
						|
				 &ctx->data[ctx->data_offset],
 | 
						|
				 ctx->datasize - ctx->data_offset);
 | 
						|
      if ( rc == -1 )
 | 
						|
	return FALSE;
 | 
						|
    }
 | 
						|
 | 
						|
    return TRUE;
 | 
						|
  }
 | 
						|
 | 
						|
  return FALSE;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static size_t
 | 
						|
find_data(cgi_context *ctx, size_t start)
 | 
						|
{ const char *s = &ctx->data[start];
 | 
						|
  const char *e = &ctx->data[ctx->datasize-2];
 | 
						|
 | 
						|
  for(; s<=e; s++)
 | 
						|
  { if ( s[0] == '\r' && s[1] == '\n' &&
 | 
						|
	 s <= e-2 &&
 | 
						|
	 s[2] == '\r' && s[3] == '\n' )
 | 
						|
      return &s[4] - ctx->data;
 | 
						|
    if ( s[0] == '\n' && s[1] == '\n' )
 | 
						|
      return &s[2] - ctx->data;
 | 
						|
  }
 | 
						|
 | 
						|
  return (size_t)-1;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
 | 
						|
		 /*******************************
 | 
						|
		 *	   IO FUNCTIONS		*
 | 
						|
		 *******************************/
 | 
						|
 | 
						|
static ssize_t				/* encode */
 | 
						|
cgi_chunked_write(cgi_context *ctx, char *buf, size_t size)
 | 
						|
{ if ( Sfprintf(ctx->stream, "%x\r\n", size) < 0 )
 | 
						|
    return -1;
 | 
						|
  if ( size > 0 &&
 | 
						|
       Sfwrite(buf, sizeof(char), size, ctx->stream) != size )
 | 
						|
    return -1;
 | 
						|
  if ( Sfprintf(ctx->stream, "\r\n") < 0 )
 | 
						|
    return -1;
 | 
						|
  if ( Sflush(ctx->stream) < 0 )
 | 
						|
    return -1;
 | 
						|
 | 
						|
  return size;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static ssize_t
 | 
						|
cgi_write(void *handle, char *buf, size_t size)
 | 
						|
{ cgi_context *ctx = handle;
 | 
						|
 | 
						|
  DEBUG(1, Sdprintf("cgi_write(%ld bytes)\n", (long)size));
 | 
						|
 | 
						|
  if ( ctx->state == CGI_DISCARDED )
 | 
						|
  { Sseterr(ctx->cgi_stream, SIO_FERR, "CGI stream was discarded");
 | 
						|
    return -1;
 | 
						|
  }
 | 
						|
 | 
						|
  if ( ctx->transfer_encoding == ATOM_chunked )
 | 
						|
  { return cgi_chunked_write(ctx, buf, size);
 | 
						|
  } else
 | 
						|
  { size_t osize = ctx->datasize;
 | 
						|
    size_t dstart;
 | 
						|
 | 
						|
    if ( osize+size > ctx->dataallocated )
 | 
						|
    { if ( grow_data_buffer(ctx, osize+size) < 0 )
 | 
						|
	return -1;			/* no memory */
 | 
						|
    }
 | 
						|
    memcpy(&ctx->data[osize], buf, size);
 | 
						|
    ctx->datasize = osize+size;
 | 
						|
    osize = (osize > 4 ? osize-4 : 0);	/* 4 is max size of the separator */
 | 
						|
 | 
						|
    if ( ctx->state == CGI_HDR &&
 | 
						|
	 (dstart=find_data(ctx, osize)) != ((size_t)-1) )
 | 
						|
    { assert(dstart <= ctx->datasize);
 | 
						|
      ctx->data_offset = dstart;
 | 
						|
      ctx->state = CGI_DATA;
 | 
						|
      if ( !call_hook(ctx, ATOM_header) )
 | 
						|
      { ctx->state = CGI_DISCARDED;
 | 
						|
	return -1;			/* TBD: pass error kindly */
 | 
						|
      }
 | 
						|
      ctx->cgi_stream->flags &= ~(SIO_FBUF|SIO_LBUF|SIO_NBUF);
 | 
						|
      ctx->cgi_stream->flags |= SIO_FBUF;
 | 
						|
    }
 | 
						|
 | 
						|
    return size;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int
 | 
						|
cgi_control(void *handle, int op, void *data)
 | 
						|
{ cgi_context *ctx = handle;
 | 
						|
 | 
						|
  if ( ctx->magic != CGI_MAGIC )
 | 
						|
  { DEBUG(0, Sdprintf("OOPS: cgi_control(%d): invalid handle\n", op));
 | 
						|
    errno = EINVAL;
 | 
						|
    return -1;
 | 
						|
  }
 | 
						|
 | 
						|
  switch(op)
 | 
						|
  { case SIO_FLUSHOUTPUT:
 | 
						|
    case SIO_SETENCODING:
 | 
						|
      return 0;				/* allow switching encoding */
 | 
						|
    default:
 | 
						|
      if ( ctx->stream->functions->control )
 | 
						|
	return (*ctx->stream->functions->control)(ctx->stream->handle, op, data);
 | 
						|
      return -1;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int
 | 
						|
cgi_close(void *handle)
 | 
						|
{ cgi_context *ctx = handle;
 | 
						|
  int rc = 0;
 | 
						|
 | 
						|
  DEBUG(1, Sdprintf("cgi_close()\n"));
 | 
						|
 | 
						|
  switch( ctx->state )
 | 
						|
  { case CGI_DATA:
 | 
						|
    { if ( ctx->transfer_encoding == ATOM_chunked )
 | 
						|
      { if ( cgi_chunked_write(ctx, NULL, 0) < 0 )
 | 
						|
	{ rc = -1;
 | 
						|
	  goto out;
 | 
						|
	}
 | 
						|
      } else
 | 
						|
      { size_t clen = ctx->datasize - ctx->data_offset;
 | 
						|
	const char *dstart = &ctx->data[ctx->data_offset];
 | 
						|
 | 
						|
	if ( !call_hook(ctx, ATOM_send_header) )
 | 
						|
	{ rc = -1;
 | 
						|
	  goto out;
 | 
						|
	}
 | 
						|
	if ( Sfwrite(dstart, sizeof(char), clen, ctx->stream) != clen ||
 | 
						|
	     Sflush(ctx->stream) < 0 )
 | 
						|
	{ rc = -1;
 | 
						|
	  goto out;
 | 
						|
	}
 | 
						|
      }
 | 
						|
 | 
						|
      break;
 | 
						|
    }
 | 
						|
    case CGI_HDR:
 | 
						|
      break;
 | 
						|
    case CGI_DISCARDED:
 | 
						|
      goto out;
 | 
						|
  }
 | 
						|
 | 
						|
  if ( !call_hook(ctx, ATOM_close) )	/* what if we had no header sofar? */
 | 
						|
    rc = -1;				/* TBD: pass error kindly */
 | 
						|
 | 
						|
out:
 | 
						|
  ctx->stream->encoding = ctx->parent_encoding;
 | 
						|
  free_cgi_context(ctx);
 | 
						|
 | 
						|
  return rc;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static IOFUNCTIONS cgi_functions =
 | 
						|
{ NULL,					/* read */
 | 
						|
  cgi_write,
 | 
						|
  NULL,					/* seek */
 | 
						|
  cgi_close,
 | 
						|
  cgi_control,				/* control */
 | 
						|
  NULL,					/* seek64 */
 | 
						|
};
 | 
						|
 | 
						|
 | 
						|
		 /*******************************
 | 
						|
		 *	       OPEN		*
 | 
						|
		 *******************************/
 | 
						|
 | 
						|
static int current_id = 0;		/* TBD: MT: lock */
 | 
						|
 | 
						|
#define CGI_COPY_FLAGS (SIO_OUTPUT| \
 | 
						|
			SIO_TEXT| \
 | 
						|
			SIO_REPXML|SIO_REPPL|\
 | 
						|
			SIO_RECORDPOS)
 | 
						|
 | 
						|
static foreign_t
 | 
						|
pl_cgi_open(term_t org, term_t new, term_t closure, term_t options)
 | 
						|
{ term_t tail = PL_copy_term_ref(options);
 | 
						|
  term_t head = PL_new_term_ref();
 | 
						|
  cgi_context *ctx;
 | 
						|
  IOSTREAM *s, *s2;
 | 
						|
  module_t module = NULL;
 | 
						|
  term_t hook = PL_new_term_ref();
 | 
						|
  record_t request = 0;
 | 
						|
 | 
						|
  PL_strip_module(closure, &module, hook);
 | 
						|
  if ( !PL_is_callable(hook) )
 | 
						|
    return type_error(closure, "callable");
 | 
						|
 | 
						|
  while(PL_get_list(tail, head, tail))
 | 
						|
  { atom_t name;
 | 
						|
    int arity;
 | 
						|
    term_t arg = PL_new_term_ref();
 | 
						|
 | 
						|
    if ( !PL_get_name_arity(head, &name, &arity) || arity != 1 )
 | 
						|
      return type_error(head, "option");
 | 
						|
    _PL_get_arg(1, head, arg);
 | 
						|
    if ( name == ATOM_request )
 | 
						|
    { request = PL_record(arg);
 | 
						|
    } else
 | 
						|
      return existence_error(head, "cgi_open_option");
 | 
						|
  }
 | 
						|
  if ( !PL_get_nil(tail) )
 | 
						|
    return type_error(tail, "list");
 | 
						|
 | 
						|
  if ( !PL_get_stream_handle(org, &s) )
 | 
						|
    return FALSE;			/* Error */
 | 
						|
  if ( !(s->flags&SIO_OUTPUT) )		/* only allow output stream */
 | 
						|
  { PL_release_stream(s);
 | 
						|
    return permission_error("stream", "write", org);
 | 
						|
  }
 | 
						|
 | 
						|
  ctx = alloc_cgi_context(s);
 | 
						|
  ctx->hook = PL_record(hook);
 | 
						|
  ctx->module = module;
 | 
						|
  ctx->request = request;
 | 
						|
  ctx->transfer_encoding = ATOM_none;
 | 
						|
  if ( !(s2 = Snew(ctx,
 | 
						|
		   (s->flags&CGI_COPY_FLAGS)|SIO_LBUF,
 | 
						|
		   &cgi_functions)) )
 | 
						|
  { free_cgi_context(ctx);			/* no memory */
 | 
						|
 | 
						|
    return FALSE;
 | 
						|
  }
 | 
						|
 | 
						|
  s2->encoding = ENC_ASCII;		/* Header is ASCII only */
 | 
						|
  ctx->parent_encoding = s->encoding;
 | 
						|
  s->encoding = ENC_OCTET;
 | 
						|
  ctx->cgi_stream = s2;
 | 
						|
  if ( PL_unify_stream(new, s2) )
 | 
						|
  { Sset_filter(s, s2);
 | 
						|
    PL_release_stream(s);
 | 
						|
    ctx->id = ++current_id;
 | 
						|
 | 
						|
    return TRUE;
 | 
						|
  } else
 | 
						|
  { return instantiation_error();
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static void
 | 
						|
install_cgi_stream()
 | 
						|
{ ATOM_header		 = PL_new_atom("header");
 | 
						|
  ATOM_header_codes	 = PL_new_atom("header_codes");
 | 
						|
  ATOM_send_header	 = PL_new_atom("send_header");
 | 
						|
  ATOM_data		 = PL_new_atom("data");
 | 
						|
  ATOM_discarded	 = PL_new_atom("discarded");
 | 
						|
  ATOM_request		 = PL_new_atom("request");
 | 
						|
  ATOM_header		 = PL_new_atom("header");
 | 
						|
  ATOM_client		 = PL_new_atom("client");
 | 
						|
  ATOM_chunked		 = PL_new_atom("chunked");
 | 
						|
  ATOM_state		 = PL_new_atom("state");
 | 
						|
  ATOM_none		 = PL_new_atom("none");
 | 
						|
  ATOM_transfer_encoding = PL_new_atom("transfer_encoding");
 | 
						|
  ATOM_close             = PL_new_atom("close");
 | 
						|
  ATOM_keep_alife        = PL_new_atom("keep_alife");
 | 
						|
  ATOM_connection        = PL_new_atom("connection");
 | 
						|
  ATOM_content_length    = PL_new_atom("content_length");
 | 
						|
  ATOM_id  	         = PL_new_atom("id");
 | 
						|
 | 
						|
  PREDICATE_call3   = PL_predicate("call", 3, "system");
 | 
						|
 | 
						|
  PL_register_foreign("cgi_open",      4, pl_cgi_open,	 PL_FA_TRANSPARENT);
 | 
						|
  PL_register_foreign("is_cgi_stream", 1, is_cgi_stream, 0);
 | 
						|
  PL_register_foreign("cgi_property",  2, cgi_property,	 0);
 | 
						|
  PL_register_foreign("cgi_set",       2, cgi_set,	 0);
 | 
						|
  PL_register_foreign("cgi_discard",   1, cgi_discard,	 0);
 | 
						|
}
 |