tinyproxy/src/buffer.c

322 lines
9.1 KiB
C

/* tinyproxy - A fast light-weight HTTP proxy
* Copyright (C) 1999, 2001 Robert James Kaes <rjkaes@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/* The buffer used in each connection is a linked list of lines. As the lines
* are read in and written out the buffer expands and contracts. Basically,
* by using this method we can increase the buffer size dynamically. However,
* we have a hard limit of 64 KB for the size of the buffer. The buffer can be
* thought of as a queue were we act on both the head and tail. The various
* functions act on each end (the names are taken from what Perl uses to act on
* the ends of an array. :)
*/
#include "main.h"
#include "buffer.h"
#include "heap.h"
#include "log.h"
#define BUFFER_HEAD(x) (x)->head
#define BUFFER_TAIL(x) (x)->tail
struct bufline_s {
unsigned char *string; /* the actual string of data */
struct bufline_s *next; /* pointer to next in linked list */
size_t length; /* length of the string of data */
size_t pos; /* start sending from this offset */
};
/*
* The buffer structure points to the beginning and end of the buffer list
* (and includes the total size)
*/
struct buffer_s {
struct bufline_s *head; /* top of the buffer */
struct bufline_s *tail; /* bottom of the buffer */
size_t size; /* total size of the buffer */
};
/*
* Take a string of data and a length and make a new line which can be added
* to the buffer. The data IS copied, so make sure if you allocated your
* data buffer on the heap, delete it because you now have TWO copies.
*/
static struct bufline_s *makenewline (unsigned char *data, size_t length)
{
struct bufline_s *newline;
assert (data != NULL);
assert (length > 0);
newline = (struct bufline_s *) safemalloc (sizeof (struct bufline_s));
if (!newline)
return NULL;
newline->string = (unsigned char *) safemalloc (length);
if (!newline->string) {
safefree (newline);
return NULL;
}
memcpy (newline->string, data, length);
newline->next = NULL;
newline->length = length;
/* Position our "read" pointer at the beginning of the data */
newline->pos = 0;
return newline;
}
/*
* Free the allocated buffer line
*/
static void free_line (struct bufline_s *line)
{
assert (line != NULL);
if (!line)
return;
if (line->string)
safefree (line->string);
safefree (line);
}
/*
* Create a new buffer
*/
struct buffer_s *new_buffer (void)
{
struct buffer_s *buffptr;
buffptr = (struct buffer_s *) safemalloc (sizeof (struct buffer_s));
if (!buffptr)
return NULL;
/*
* Since the buffer is initially empty, set the HEAD and TAIL
* pointers to NULL since they can't possibly point anywhere at the
* moment.
*/
BUFFER_HEAD (buffptr) = BUFFER_TAIL (buffptr) = NULL;
buffptr->size = 0;
return buffptr;
}
/*
* Delete all the lines in the buffer and the buffer itself
*/
void delete_buffer (struct buffer_s *buffptr)
{
struct bufline_s *next;
assert (buffptr != NULL);
while (BUFFER_HEAD (buffptr)) {
next = BUFFER_HEAD (buffptr)->next;
free_line (BUFFER_HEAD (buffptr));
BUFFER_HEAD (buffptr) = next;
}
safefree (buffptr);
}
/*
* Return the current size of the buffer.
*/
size_t buffer_size (struct buffer_s *buffptr)
{
return buffptr->size;
}
/*
* Push a new line on to the end of the buffer.
*/
int add_to_buffer (struct buffer_s *buffptr, unsigned char *data, size_t length)
{
struct bufline_s *newline;
assert (buffptr != NULL);
assert (data != NULL);
assert (length > 0);
/*
* Sanity check here. A buffer with a non-NULL head pointer must
* have a size greater than zero, and vice-versa.
*/
if (BUFFER_HEAD (buffptr) == NULL)
assert (buffptr->size == 0);
else
assert (buffptr->size > 0);
/*
* Make a new line so we can add it to the buffer.
*/
if (!(newline = makenewline (data, length)))
return -1;
if (buffptr->size == 0)
BUFFER_HEAD (buffptr) = BUFFER_TAIL (buffptr) = newline;
else {
BUFFER_TAIL (buffptr)->next = newline;
BUFFER_TAIL (buffptr) = newline;
}
buffptr->size += length;
return 0;
}
/*
* Remove the first line from the top of the buffer
*/
static struct bufline_s *remove_from_buffer (struct buffer_s *buffptr)
{
struct bufline_s *line;
assert (buffptr != NULL);
assert (BUFFER_HEAD (buffptr) != NULL);
line = BUFFER_HEAD (buffptr);
BUFFER_HEAD (buffptr) = line->next;
buffptr->size -= line->length;
return line;
}
/*
* Reads the bytes from the socket, and adds them to the buffer.
* Takes a connection and returns the number of bytes read.
*/
#define READ_BUFFER_SIZE (1024 * 2)
ssize_t read_buffer (int fd, struct buffer_s * buffptr)
{
ssize_t bytesin;
unsigned char *buffer;
assert (fd >= 0);
assert (buffptr != NULL);
/*
* Don't allow the buffer to grow larger than MAXBUFFSIZE
*/
if (buffptr->size >= MAXBUFFSIZE)
return 0;
buffer = (unsigned char *) safemalloc (READ_BUFFER_SIZE);
if (!buffer) {
return -ENOMEM;
}
bytesin = read (fd, buffer, READ_BUFFER_SIZE);
if (bytesin > 0) {
if (add_to_buffer (buffptr, buffer, bytesin) < 0) {
log_message (LOG_ERR,
"readbuff: add_to_buffer() error.");
bytesin = -1;
}
} else if (bytesin == 0) {
/* connection was closed by client */
bytesin = -1;
} else {
switch (errno) {
#ifdef EWOULDBLOCK
case EWOULDBLOCK:
#else
# ifdef EAGAIN
case EAGAIN:
# endif
#endif
case EINTR:
bytesin = 0;
break;
default:
log_message (LOG_ERR,
"read_buffer: read() failed on fd %d: %s",
fd, strerror(errno));
bytesin = -1;
break;
}
}
safefree (buffer);
return bytesin;
}
/*
* Write the bytes in the buffer to the socket.
* Takes a connection and returns the number of bytes written.
*/
ssize_t write_buffer (int fd, struct buffer_s * buffptr)
{
ssize_t bytessent;
struct bufline_s *line;
assert (fd >= 0);
assert (buffptr != NULL);
if (buffptr->size == 0)
return 0;
/* Sanity check. It would be bad to be using a NULL pointer! */
assert (BUFFER_HEAD (buffptr) != NULL);
line = BUFFER_HEAD (buffptr);
bytessent =
send (fd, line->string + line->pos, line->length - line->pos,
MSG_NOSIGNAL);
if (bytessent >= 0) {
/* bytes sent, adjust buffer */
line->pos += bytessent;
if (line->pos == line->length)
free_line (remove_from_buffer (buffptr));
return bytessent;
} else {
switch (errno) {
#ifdef EWOULDBLOCK
case EWOULDBLOCK:
#else
# ifdef EAGAIN
case EAGAIN:
# endif
#endif
case EINTR:
return 0;
case ENOBUFS:
case ENOMEM:
log_message (LOG_ERR,
"writebuff: write() error [NOBUFS/NOMEM] \"%s\" on "
"file descriptor %d", strerror (errno),
fd);
return 0;
default:
log_message (LOG_ERR,
"writebuff: write() error \"%s\" on file descriptor %d",
strerror (errno), fd);
return -1;
}
}
}