294 lines
6.8 KiB
C
294 lines
6.8 KiB
C
/* $Id$
|
|
|
|
Part of SWI-Prolog
|
|
|
|
Author: Jan Wielemaker
|
|
E-mail: J.Wielemaker@uva.nl
|
|
WWW: http://www.swi-prolog.org
|
|
Copyright (C): 2007-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
|
|
*/
|
|
|
|
#define O_DEBUG 1
|
|
#include <SWI-Stream.h>
|
|
#include <SWI-Prolog.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
#include <time.h>
|
|
#include <errno.h>
|
|
|
|
#define MAXHDR 1024 /* max size of chink header line */
|
|
|
|
static atom_t ATOM_close_parent; /* close_parent(Bool) */
|
|
static atom_t ATOM_max_chunk_size; /* max_chunk_size(Int) */
|
|
|
|
|
|
/*******************************
|
|
* TYPES *
|
|
*******************************/
|
|
|
|
#define BUFSIZE SIO_BUFSIZE /* raw I/O buffer */
|
|
|
|
typedef struct chunked_context
|
|
{ IOSTREAM *stream; /* Original stream */
|
|
IOSTREAM *chunked_stream; /* Stream I'm handle of */
|
|
int close_parent; /* close parent on close */
|
|
IOENC parent_encoding; /* Saved encoding of parent */
|
|
size_t avail; /* data available */
|
|
} chunked_context;
|
|
|
|
|
|
static chunked_context*
|
|
alloc_chunked_context(IOSTREAM *s)
|
|
{ chunked_context *ctx = PL_malloc(sizeof(*ctx));
|
|
|
|
memset(ctx, 0, sizeof(*ctx));
|
|
ctx->stream = s;
|
|
ctx->close_parent = FALSE;
|
|
|
|
return ctx;
|
|
}
|
|
|
|
|
|
static void
|
|
free_chunked_context(chunked_context *ctx)
|
|
{ if ( ctx->stream->upstream )
|
|
Sset_filter(ctx->stream, NULL);
|
|
else
|
|
PL_release_stream(ctx->stream);
|
|
|
|
PL_free(ctx);
|
|
}
|
|
|
|
|
|
/*******************************
|
|
* CHUNKED I/O *
|
|
*******************************/
|
|
|
|
static ssize_t /* decode */
|
|
chunked_read(void *handle, char *buf, size_t size)
|
|
{ chunked_context *ctx = handle;
|
|
|
|
for(;;)
|
|
{ if ( ctx->avail > 0 ) /* data waiting */
|
|
{ size_t max_rd = ctx->avail < size ? ctx->avail : size;
|
|
ssize_t rc;
|
|
|
|
if ( (rc = Sfread(buf, sizeof(char), max_rd, ctx->stream)) > 0 )
|
|
{ ctx->avail -= rc;
|
|
|
|
if ( ctx->avail == 0 )
|
|
{ if ( Sgetc(ctx->stream) != '\r' ||
|
|
Sgetc(ctx->stream) != '\n' )
|
|
{ Sseterr(ctx->chunked_stream, 0, "Chunk not followed by \\r\\n");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
} else if ( rc == 0 )
|
|
{ Sseterr(ctx->chunked_stream, 0, "Unexpected EOF in chunked data");
|
|
return -1;
|
|
} else
|
|
{ return -1;
|
|
}
|
|
} else
|
|
{ char hdr[MAXHDR];
|
|
char *s;
|
|
|
|
|
|
if ( (s = Sfgets(hdr, sizeof(hdr), ctx->stream)) )
|
|
{ char *ehdr;
|
|
long len;
|
|
|
|
errno = 0;
|
|
len = strtol(hdr, &ehdr, 16);
|
|
if ( errno || len < 0 )
|
|
{ Sseterr(ctx->chunked_stream, 0, "Bad chunk length");
|
|
return -1;
|
|
}
|
|
if ( len == 0 )
|
|
{ do
|
|
{ s = Sfgets(hdr, sizeof(hdr), ctx->stream);
|
|
} while ( s && strcmp(s, "\r\n") != 0 );
|
|
if ( s )
|
|
return 0;
|
|
Sseterr(ctx->chunked_stream, 0, "Bad end-of-stream");
|
|
return -1;
|
|
}
|
|
ctx->avail = len;
|
|
/*continue*/
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static ssize_t /* encode */
|
|
chunked_write(void *handle, char *buf, size_t size)
|
|
{ chunked_context *ctx = handle;
|
|
|
|
if ( Sfprintf(ctx->stream, "%x\r\n", size) >= 0 &&
|
|
Sfwrite(buf, sizeof(char), size, ctx->stream) == size &&
|
|
Sfprintf(ctx->stream, "\r\n") >= 0 )
|
|
return size;
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
static int
|
|
chunked_control(void *handle, int op, void *data)
|
|
{ chunked_context *ctx = handle;
|
|
|
|
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
|
|
chunked_close(void *handle)
|
|
{ chunked_context *ctx = handle;
|
|
int rc = 0;
|
|
|
|
DEBUG(1, Sdprintf("chunked_close() ...\n"));
|
|
|
|
if ( (ctx->chunked_stream->flags & SIO_OUTPUT) )
|
|
{ if ( Sfprintf(ctx->stream, "0\r\n\r\n") < 0 )
|
|
rc = -1;
|
|
}
|
|
|
|
ctx->stream->encoding = ctx->parent_encoding;
|
|
|
|
if ( ctx->close_parent )
|
|
{ IOSTREAM *parent = ctx->stream;
|
|
int rc2;
|
|
|
|
free_chunked_context(ctx);
|
|
rc2 = Sclose(parent);
|
|
if ( rc == 0 )
|
|
rc = rc2;
|
|
} else
|
|
{ free_chunked_context(ctx);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
static IOFUNCTIONS chunked_functions =
|
|
{ chunked_read,
|
|
chunked_write,
|
|
NULL, /* seek */
|
|
chunked_close,
|
|
chunked_control, /* zcontrol */
|
|
NULL, /* seek64 */
|
|
};
|
|
|
|
|
|
/*******************************
|
|
* PROLOG CONNECTION *
|
|
*******************************/
|
|
|
|
#define COPY_FLAGS (SIO_INPUT|SIO_OUTPUT| \
|
|
SIO_TEXT| \
|
|
SIO_REPXML|SIO_REPPL|\
|
|
SIO_RECORDPOS)
|
|
|
|
static foreign_t
|
|
pl_http_chunked_open(term_t org, term_t new, term_t options)
|
|
{ term_t tail = PL_copy_term_ref(options);
|
|
term_t head = PL_new_term_ref();
|
|
chunked_context *ctx;
|
|
IOSTREAM *s, *s2;
|
|
int close_parent = FALSE;
|
|
int max_chunk_size = 0;
|
|
|
|
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_max_chunk_size )
|
|
{ if ( !get_int_ex(arg, &max_chunk_size) )
|
|
return FALSE;
|
|
if ( max_chunk_size <= 0 )
|
|
return domain_error(arg, "positive_integer");
|
|
} else if ( name == ATOM_close_parent )
|
|
{ if ( !get_bool_ex(arg, &close_parent) )
|
|
return FALSE;
|
|
}
|
|
}
|
|
if ( !PL_get_nil(tail) )
|
|
return type_error(tail, "list");
|
|
|
|
if ( !PL_get_stream_handle(org, &s) )
|
|
return FALSE; /* Error */
|
|
ctx = alloc_chunked_context(s);
|
|
ctx->close_parent = close_parent;
|
|
|
|
if ( !(s2 = Snew(ctx,
|
|
(s->flags©_FLAGS)|SIO_FBUF,
|
|
&chunked_functions)) )
|
|
{ free_chunked_context(ctx); /* no memory */
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
if ( max_chunk_size > 0 )
|
|
{ char *buf = PL_malloc(max_chunk_size);
|
|
Ssetbuffer(s2, buf, max_chunk_size);
|
|
}
|
|
|
|
s2->encoding = s->encoding;
|
|
ctx->parent_encoding = s->encoding;
|
|
s->encoding = ENC_OCTET;
|
|
ctx->chunked_stream = s2;
|
|
if ( PL_unify_stream(new, s2) )
|
|
{ Sset_filter(s, s2);
|
|
PL_release_stream(s);
|
|
|
|
return TRUE;
|
|
} else
|
|
{ return instantiation_error();
|
|
}
|
|
}
|
|
|
|
|
|
/*******************************
|
|
* INSTALL *
|
|
*******************************/
|
|
|
|
static void
|
|
install_http_chunked()
|
|
{ ATOM_close_parent = PL_new_atom("close_parent");
|
|
ATOM_max_chunk_size = PL_new_atom("max_chunk_size");
|
|
|
|
PL_register_foreign("http_chunked_open", 3, pl_http_chunked_open, 0);
|
|
}
|