/*
* Copyright (c) 2012, Jonathan Schleifer <js@webkeks.org>
* 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 COPYRIGHT HOLDERS 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 COPYRIGHT OWNER 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 <stdlib.h>
#include <string.h>
#include "stream.h"
#define BUFFER_SIZE 4096
static bool
ctor(void *ptr, va_list args)
{
CFWStream *stream = ptr;
stream->ops = NULL;
stream->cache = NULL;
stream->cache_len = 0;
return true;
}
static void
dtor(void *ptr)
{
cfw_stream_close(ptr);
}
ssize_t
cfw_stream_read(void *ptr, void *buf, size_t len)
{
CFWStream *stream = ptr;
ssize_t ret;
if (stream == NULL || stream->ops == NULL)
return -1;
if (stream->cache == NULL) {
if ((ret = stream->ops->read(stream, buf, len)) < -1)
ret = -1;
return ret;
}
if (len >= stream->cache_len) {
ret = stream->cache_len;
memcpy(buf, stream->cache, stream->cache_len);
free(stream->cache);
stream->cache = NULL;
stream->cache_len = 0;
return ret;
} else {
char *tmp;
if ((tmp = malloc(stream->cache_len - len)) == NULL)
return -1;
memcpy(tmp, stream->cache + len, stream->cache_len - len);
memcpy(buf, stream->cache, len);
free(stream->cache);
stream->cache = tmp;
stream->cache_len -= len;
return len;
}
}
CFWString*
cfw_stream_read_line(void *ptr)
{
CFWStream *stream = ptr;
CFWString *ret;
char *buf, *ret_str, *new_cache;
ssize_t buf_len;
size_t i, ret_len;
/* Look if there is a line or \0 in our cache */
if (stream->cache != NULL) {
for (i = 0; i < stream->cache_len; i++) {
if (stream->cache[i] == '\n' ||
stream->cache[i] == '\0') {
ret_len = i;
if (i > 0 && stream->cache[i - 1] == '\r')
ret_len--;
ret_str = cfw_strndup(stream->cache, ret_len);
if (ret_str == NULL)
return NULL;
ret = cfw_create(cfw_string, (void*)NULL);
if (ret == NULL) {
free(ret_str);
return NULL;
}
cfw_string_set_nocopy(ret, ret_str, ret_len);
if (stream->cache_len > i + 1) {
if ((new_cache = malloc(
stream->cache_len - i - 1)) == NULL)
return NULL;
memcpy(new_cache, stream->cache + i + 1,
stream->cache_len - i - 1);
} else
new_cache = cfw_strdup("");
free(stream->cache);
stream->cache = new_cache;
stream->cache_len -= i + 1;
return ret;
}
}
}
/* Read and see if we get a newline or \0 */
if ((buf = malloc(BUFFER_SIZE)) == NULL)
return NULL;
for (;;) {
if (stream->ops->at_end(stream)) {
free(buf);
if (stream->cache == NULL)
return NULL;
ret_len = stream->cache_len;
if (ret_len > 0 && stream->cache[ret_len - 1] == '\r')
ret_len--;
ret_str = cfw_strndup(stream->cache, ret_len);
if (ret_str == NULL)
return NULL;
ret = cfw_create(cfw_string, (void*)NULL);
if (ret == NULL) {
free(ret_str);
return NULL;
}
cfw_string_set_nocopy(ret, ret_str, ret_len);
free(stream->cache);
stream->cache = NULL;
stream->cache_len = 0;
return ret;
}
buf_len = stream->ops->read(stream, buf, BUFFER_SIZE);
if (buf_len == -1) {
free(buf);
return NULL;
}
/* Look if there's a newline or \0 */
for (i = 0; i < buf_len; i++) {
if (buf[i] == '\n' || buf[i] == '\0') {
ret_len = stream->cache_len + i;
if ((ret_str = malloc(ret_len + 1)) == NULL) {
/*
* FIXME: We lost the current buffer.
* Mark the stream as broken?
*/
free(buf);
return NULL;
}
memcpy(ret_str, stream->cache,
stream->cache_len);
memcpy(ret_str + stream->cache_len, buf, i);
if (ret_len > 0 && ret_str[ret_len - 1] == '\r')
ret_len--;
ret_str[ret_len] = '\0';
ret = cfw_create(cfw_string, (void*)NULL);
if (ret == NULL) {
free(buf);
free(ret_str);
return NULL;
}
cfw_string_set_nocopy(ret, ret_str, ret_len);
if (buf_len > i + 1) {
new_cache = malloc(buf_len - i - 1);
if (new_cache == NULL) {
free(buf);
return NULL;
}
memcpy(new_cache, buf + i + 1,
buf_len - i - 1);
} else
new_cache = cfw_strdup("");
free(stream->cache);
stream->cache = new_cache;
stream->cache_len = buf_len - i - 1;
free(buf);
return ret;
}
}
/* There was no newline or \0 */
if (stream->cache_len + buf_len > 0) {
new_cache = realloc(stream->cache,
stream->cache_len + buf_len);
if (new_cache == NULL) {
free(buf);
return NULL;
}
memcpy(new_cache + stream->cache_len, buf, buf_len);
} else {
free(stream->cache);
new_cache = cfw_strdup("");
}
stream->cache = new_cache;
stream->cache_len += buf_len;
}
}
bool
cfw_stream_write(void *ptr, const void *buf, size_t len)
{
CFWStream *stream = ptr;
if (stream == NULL || stream->ops == NULL)
return false;
return stream->ops->write(stream, buf, len);
}
bool
cfw_stream_write_string(void *ptr, const char *str)
{
return cfw_stream_write(ptr, str, strlen(str));
}
bool
cfw_stream_write_line(void *ptr, const char *str)
{
char *tmp;
size_t len;
len = strlen(str);
if ((tmp = malloc(len + 2)) == NULL)
return false;
memcpy(tmp, str, len);
tmp[len] = '\n';
tmp[len + 1] = '\0';
if (!cfw_stream_write(ptr, tmp, len + 1)) {
free(tmp);
return false;
}
free(tmp);
return true;
}
bool
cfw_stream_at_end(void *ptr)
{
CFWStream *stream = ptr;
if (stream == NULL || stream->ops == NULL)
return true;
if (stream->cache != NULL)
return false;
return stream->ops->at_end(stream);
}
void
cfw_stream_close(void *ptr)
{
CFWStream *stream = ptr;
if (stream == NULL || stream->ops == NULL)
return;
stream->ops->close(stream);
}
static CFWClass class = {
.name = "CFWStream",
.size = sizeof(CFWStream),
.ctor = ctor,
.dtor = dtor
};
CFWClass *cfw_stream = &class;