#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#if HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif
#if HAVE_SYS_STAT_H
# include <sys/stat.h>
#endif
#if HAVE_UNISTD_H
# include <unistd.h>
#endif
#if HAVE_FCNTL_H
# include <fcntl.h>
#endif
#if HAVE_IO_H
# include <io.h>
#endif
#ifdef HAVE_ERRNO_H
# include <errno.h>
#endif
#ifdef HAVE_LIBCURL
# include <curl/curl.h>
#endif
#if !defined(HAVE_MEMCPY)
# define memcpy(d, s, n) (bcopy ((s), (d), (n)))
#endif
#if !defined(HAVE_MEMMOVE)
# define memmove(d, s, n) (bcopy ((s), (d), (n)))
#endif
#if !defined(O_BINARY) && defined(_O_BINARY)
# define O_BINARY _O_BINARY
#endif
#include "chunk.h"
#include "allocator.h"
static SIXELSTATUS
sixel_chunk_init(
sixel_chunk_t * const pchunk,
size_t initial_size)
{
SIXELSTATUS status = SIXEL_FALSE;
pchunk->max_size = initial_size;
pchunk->size = 0;
pchunk->buffer
= (unsigned char *)sixel_allocator_malloc(pchunk->allocator, pchunk->max_size);
if (pchunk->buffer == NULL) {
sixel_helper_set_additional_message(
"sixel_chunk_init: sixel_allocator_malloc() failed.");
status = SIXEL_BAD_ALLOCATION;
goto end;
}
status = SIXEL_OK;
end:
return status;
}
void
sixel_chunk_destroy(
sixel_chunk_t * const pchunk)
{
sixel_allocator_t *allocator;
if (pchunk) {
allocator = pchunk->allocator;
sixel_allocator_free(allocator, pchunk->buffer);
sixel_allocator_free(allocator, pchunk);
sixel_allocator_unref(allocator);
}
}
# ifdef HAVE_LIBCURL
static size_t
memory_write(void *ptr,
size_t size,
size_t len,
void *memory)
{
size_t nbytes = 0;
sixel_chunk_t *chunk;
if (ptr == NULL || memory == NULL) {
goto end;
}
chunk = (sixel_chunk_t *)memory;
if (chunk->buffer == NULL) {
goto end;
}
nbytes = size * len;
if (nbytes == 0) {
goto end;
}
if (chunk->max_size <= chunk->size + nbytes) {
do {
chunk->max_size *= 2;
} while (chunk->max_size <= chunk->size + nbytes);
chunk->buffer = (unsigned char*)sixel_allocator_realloc(chunk->allocator,
chunk->buffer,
chunk->max_size);
if (chunk->buffer == NULL) {
nbytes = 0;
goto end;
}
}
memcpy(chunk->buffer + chunk->size, ptr, nbytes);
chunk->size += nbytes;
end:
return nbytes;
}
# endif
static int
wait_file(int fd, int usec)
{
#if HAVE_SYS_SELECT_H
fd_set rfds;
struct timeval tv;
#endif
int ret = 1;
#if HAVE_SYS_SELECT_H
tv.tv_sec = usec / 1000000;
tv.tv_usec = usec % 1000000;
FD_ZERO(&rfds);
#if HAVE_DIAGNOSTIC_SIGN_CONVERSION
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wsign-conversion"
#endif
FD_SET(fd, &rfds);
#if HAVE_DIAGNOSTIC_SIGN_CONVERSION
# pragma GCC diagnostic pop
#endif
ret = select(fd + 1, &rfds, NULL, NULL, &tv);
#else
(void) fd;
(void) usec;
#endif
if (ret == 0) {
return (1);
}
if (ret < 0) {
return ret;
}
return (0);
}
static SIXELSTATUS
open_binary_file(
FILE **f,
char const *filename)
{
SIXELSTATUS status = SIXEL_FALSE;
#if HAVE_STAT
struct stat sb;
#endif
if (filename == NULL || strcmp(filename, "-") == 0) {
#if defined(O_BINARY)
# if HAVE__SETMODE
_setmode(fileno(stdin), O_BINARY);
# elif HAVE_SETMODE
setmode(fileno(stdin), O_BINARY);
# endif
#endif
*f = stdin;
status = SIXEL_OK;
goto end;
}
#if HAVE_STAT
if (stat(filename, &sb) != 0) {
status = (SIXEL_LIBC_ERROR | (errno & 0xff));
sixel_helper_set_additional_message("stat() failed.");
goto end;
}
if ((sb.st_mode & S_IFMT) == S_IFDIR) {
status = SIXEL_BAD_INPUT;
sixel_helper_set_additional_message("specified path is directory.");
goto end;
}
#endif
*f = fopen(filename, "rb");
if (!*f) {
status = (SIXEL_LIBC_ERROR | (errno & 0xff));
sixel_helper_set_additional_message("fopen() failed.");
goto end;
}
status = SIXEL_OK;
end:
return status;
}
static SIXELSTATUS
sixel_chunk_from_file(
char const *filename,
sixel_chunk_t *pchunk,
int const *cancel_flag
)
{
SIXELSTATUS status = SIXEL_FALSE;
int ret;
FILE *f;
size_t n;
size_t const bucket_size = 4096;
status = open_binary_file(&f, filename);
if (SIXEL_FAILED(status)) {
goto end;
}
for (;;) {
if (pchunk->max_size - pchunk->size < bucket_size) {
pchunk->max_size *= 2;
pchunk->buffer = (unsigned char *)sixel_allocator_realloc(pchunk->allocator,
pchunk->buffer,
pchunk->max_size);
if (pchunk->buffer == NULL) {
sixel_helper_set_additional_message(
"sixel_chunk_from_file: sixel_allocator_realloc() failed.");
status = SIXEL_BAD_ALLOCATION;
goto end;
}
}
if (isatty(fileno(f))) {
for (;;) {
if (*cancel_flag) {
status = SIXEL_INTERRUPTED;
goto end;
}
ret = wait_file(fileno(f), 10000);
if (ret < 0) {
sixel_helper_set_additional_message(
"sixel_chunk_from_file: wait_file() failed.");
status = SIXEL_RUNTIME_ERROR;
goto end;
}
if (ret == 0) {
break;
}
}
}
n = fread(pchunk->buffer + pchunk->size, 1, 4096, f);
if (n == 0) {
break;
}
pchunk->size += n;
}
if (f != stdin) {
fclose(f);
}
status = SIXEL_OK;
end:
return status;
}
static SIXELSTATUS
sixel_chunk_from_url(
char const *url,
sixel_chunk_t *pchunk,
int finsecure)
{
SIXELSTATUS status = SIXEL_FALSE;
# ifdef HAVE_LIBCURL
CURL *curl = NULL;
CURLcode code;
curl = curl_easy_init();
if (curl == NULL) {
status = SIXEL_CURL_ERROR & CURLE_FAILED_INIT;
sixel_helper_set_additional_message("curl_easy_init() failed.");
goto end;
}
code = curl_easy_setopt(curl, CURLOPT_URL, url);
if (code != CURLE_OK) {
status = SIXEL_CURL_ERROR & (code & 0xff);
sixel_helper_set_additional_message("curl_easy_setopt(CURLOPT_FOLLOWLOCATION) failed.");
goto end;
}
code = curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
if (code != CURLE_OK) {
status = SIXEL_CURL_ERROR & (code & 0xff);
sixel_helper_set_additional_message("curl_easy_setopt(CURLOPT_FOLLOWLOCATION) failed.");
goto end;
}
if (finsecure && strncmp(url, "https://", 8) == 0) {
code = curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
if (code != CURLE_OK) {
status = SIXEL_CURL_ERROR & (code & 0xff);
sixel_helper_set_additional_message("curl_easy_setopt(CURLOPT_SSL_VERIFYPEER) failed.");
goto end;
}
code = curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
if (code != CURLE_OK) {
status = SIXEL_CURL_ERROR & (code & 0xff);
sixel_helper_set_additional_message("curl_easy_setopt(CURLOPT_SSL_VERIFYHOST) failed.");
goto end;
}
}
code = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, memory_write);
if (code != CURLE_OK) {
status = SIXEL_CURL_ERROR & (code & 0xff);
sixel_helper_set_additional_message("curl_easy_setopt(CURLOPT_WRITEFUNCTION) failed.");
goto end;
}
code = curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)pchunk);
if (code != CURLE_OK) {
status = SIXEL_CURL_ERROR & (code & 0xff);
sixel_helper_set_additional_message("curl_easy_setopt(CURLOPT_WRITEDATA) failed.");
goto end;
}
code = curl_easy_perform(curl);
if (code != CURLE_OK) {
status = SIXEL_CURL_ERROR & (code & 0xff);
sixel_helper_set_additional_message("curl_easy_perform() failed.");
goto end;
}
status = SIXEL_OK;
# else
(void) url;
(void) pchunk;
(void) finsecure;
sixel_helper_set_additional_message(
"To specify URI schemes, you have to "
"configure this program with --with-libcurl "
"option at compile time.\n");
status = SIXEL_NOT_IMPLEMENTED;
goto end;
# endif
end:
# ifdef HAVE_LIBCURL
if (curl) {
curl_easy_cleanup(curl);
}
# endif
return status;
}
SIXELSTATUS
sixel_chunk_new(
sixel_chunk_t **ppchunk,
char const *filename,
int finsecure,
int const *cancel_flag,
sixel_allocator_t *allocator)
{
SIXELSTATUS status = SIXEL_FALSE;
if (ppchunk == NULL) {
sixel_helper_set_additional_message(
"sixel_chunk_new: ppchunk is null.");
status = SIXEL_BAD_ARGUMENT;
goto end;
}
if (allocator == NULL) {
sixel_helper_set_additional_message(
"sixel_chunk_new: allocator is null.");
status = SIXEL_BAD_ARGUMENT;
goto end;
}
*ppchunk = (sixel_chunk_t *)sixel_allocator_malloc(allocator, sizeof(sixel_chunk_t));
if (*ppchunk == NULL) {
sixel_helper_set_additional_message(
"sixel_chunk_new: sixel_allocator_malloc() failed.");
status = SIXEL_BAD_ALLOCATION;
goto end;
}
(*ppchunk)->allocator = allocator;
status = sixel_chunk_init(*ppchunk, 1024 * 32);
if (SIXEL_FAILED(status)) {
sixel_allocator_free(allocator, *ppchunk);
*ppchunk = NULL;
goto end;
}
sixel_allocator_ref(allocator);
if (filename != NULL && strstr(filename, "://")) {
status = sixel_chunk_from_url(filename, *ppchunk, finsecure);
} else {
status = sixel_chunk_from_file(filename, *ppchunk, cancel_flag);
}
if (SIXEL_FAILED(status)) {
sixel_chunk_destroy(*ppchunk);
*ppchunk = NULL;
goto end;
}
status = SIXEL_OK;
end:
return status;
}
#if HAVE_TESTS
static int
test1(void)
{
int nret = EXIT_FAILURE;
unsigned char *ptr = malloc(16);
#ifdef HAVE_LIBCURL
sixel_chunk_t chunk = {0, 0, 0, NULL};
int nread;
nread = memory_write(NULL, 1, 1, NULL);
if (nread != 0) {
goto error;
}
nread = memory_write(ptr, 1, 1, &chunk);
if (nread != 0) {
goto error;
}
nread = memory_write(ptr, 0, 1, &chunk);
if (nread != 0) {
goto error;
}
#else
nret = EXIT_SUCCESS;
goto error;
#endif
nret = EXIT_SUCCESS;
error:
free(ptr);
return nret;
}
static int
test2(void)
{
int nret = EXIT_FAILURE;
sixel_chunk_t *chunk = NULL;
SIXELSTATUS status = SIXEL_FALSE;
status = sixel_chunk_new(&chunk, NULL, 0, NULL, NULL);
if (status != SIXEL_BAD_ARGUMENT) {
goto error;
}
status = sixel_chunk_new(NULL, NULL, 0, NULL, NULL);
if (status != SIXEL_BAD_ARGUMENT) {
goto error;
}
nret = EXIT_SUCCESS;
error:
return nret;
}
static int
test3(void)
{
int nret = EXIT_FAILURE;
sixel_chunk_t *chunk;
sixel_allocator_t *allocator = NULL;
SIXELSTATUS status = SIXEL_FALSE;
sixel_debug_malloc_counter = 1;
status = sixel_allocator_new(&allocator, sixel_bad_malloc, NULL, NULL, NULL);
if (SIXEL_FAILED(status)) {
goto error;
}
status = sixel_chunk_new(&chunk, "../images/map8.six", 0, NULL, allocator);
if (status != SIXEL_BAD_ALLOCATION) {
goto error;
}
nret = EXIT_SUCCESS;
error:
return nret;
}
static int
test4(void)
{
int nret = EXIT_FAILURE;
sixel_chunk_t *chunk;
sixel_allocator_t *allocator = NULL;
SIXELSTATUS status = SIXEL_FALSE;
sixel_debug_malloc_counter = 2;
status = sixel_allocator_new(&allocator, sixel_bad_malloc, NULL, NULL, NULL);
if (SIXEL_FAILED(status)) {
goto error;
}
status = sixel_chunk_new(&chunk, "../images/map8.six", 0, NULL, allocator);
if (status != SIXEL_BAD_ALLOCATION) {
goto error;
}
nret = EXIT_SUCCESS;
error:
return nret;
}
int
sixel_chunk_tests_main(void)
{
int nret = EXIT_FAILURE;
size_t i;
typedef int (* testcase)(void);
static testcase const testcases[] = {
test1,
test2,
test3,
test4,
};
for (i = 0; i < sizeof(testcases) / sizeof(testcase); ++i) {
nret = testcases[i]();
if (nret != EXIT_SUCCESS) {
goto error;
}
}
nret = EXIT_SUCCESS;
error:
return nret;
}
#endif