/****************************************************************** * $Id: nanohttp-stream.c,v 1.2 2004/10/15 14:26:15 snowdrop Exp $ * * CSOAP Project: A http client/server library in C * Copyright (C) 2003-2004 Ferhat Ayaz * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library 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. * * Email: ferhatayaz@yahoo.com ******************************************************************/ #include #ifdef MEM_DEBUG #include #endif void _log_str(char *fn, char *str, int size) { /* FILE *f = fopen(fn, "ab"); if (!f) f=fopen(fn,"wb"); fwrite(str, size, 1, f); fflush(f); fclose(f); */ } /* ------------------------------------------------------------------- HTTP INPUT STREAM ------------------------------------------------------------------- */ static int _http_stream_is_content_length(hpair_t *header) { return hpairnode_get_ignore_case(header, HEADER_CONTENT_LENGTH) != NULL; } static int _http_stream_is_chunked(hpair_t *header) { char *chunked; chunked = hpairnode_get_ignore_case(header, HEADER_TRANSFER_ENCODING); if (chunked != NULL) { if (!strcmp(chunked, TRANSFER_ENCODING_CHUNKED)) { return 1; } } return 0; } /** Creates a new input stream. */ http_input_stream_t *http_input_stream_new(hsocket_t sock, hpair_t *header) { http_input_stream_t *result; char *content_length; char *chunked; /* Paranoya check */ /*if (header == NULL) return NULL; */ /* Create object */ result = (http_input_stream_t*)malloc(sizeof(http_input_stream_t)); result->sock = sock; result->err = H_OK; /* Find connection type */ hpairnode_dump_deep(header); /* Check if Content-type */ if (_http_stream_is_content_length(header)) { log_verbose1("Stream transfer with 'Content-length'"); content_length = hpairnode_get_ignore_case(header, HEADER_CONTENT_LENGTH); result->content_length = atoi(content_length); result->received = 0; result->type = HTTP_TRANSFER_CONTENT_LENGTH; } /* Check if Chunked */ else if (_http_stream_is_chunked(header)) { log_verbose1("Stream transfer with 'chunked'"); result->type = HTTP_TRANSFER_CHUNKED; result->chunk_size = -1; result->received = -1; } /* Assume connection close */ else { log_verbose1("Stream transfer with 'Connection: close'"); result->type = HTTP_TRANSFER_CONNECTION_CLOSE; result->connection_closed = 0; result->received = 0; } return result; } /** Creates a new input stream from file. This function was added for MIME messages and for debugging. */ http_input_stream_t *http_input_stream_new_from_file(const char* filename) { http_input_stream_t *result; FILE *fd = fopen(filename, "rb"); if (fd == NULL) return NULL; /* Create object */ result = (http_input_stream_t*)malloc(sizeof(http_input_stream_t)); result->type = HTTP_TRANSFER_FILE; result->fd = fd; return result; } /** Free input stream */ void http_input_stream_free(http_input_stream_t *stream) { if (stream->type == HTTP_TRANSFER_FILE && stream->fd) fclose(stream->fd); free(stream); } static int _http_input_stream_is_content_length_ready( http_input_stream_t *stream) { return (stream->content_length > stream->received); } static int _http_input_stream_is_chunked_ready( http_input_stream_t *stream) { return stream->chunk_size != 0; } static int _http_input_stream_is_connection_closed_ready( http_input_stream_t *stream) { return !stream->connection_closed; } static int _http_input_stream_is_file_ready( http_input_stream_t *stream) { return !feof(stream->fd); } static int _http_input_stream_content_length_read( http_input_stream_t *stream, byte_t *dest, int size) { int status; /* check limit */ if (stream->content_length - stream->received < size) size = stream->content_length - stream->received; /* read from socket */ status = hsocket_read(stream->sock, dest, size, 1); if (status == -1) { stream->err = HSOCKET_ERROR_RECEIVE; return -1; } stream->received += status; return status; } static int _http_input_stream_chunked_read_chunk_size( http_input_stream_t *stream) { char chunk[25]; int status, i = 0; int chunk_size; while (1) { status = hsocket_read(stream->sock, &(chunk[i]), 1, 1); if (status == -1) { stream->err = HSOCKET_ERROR_RECEIVE; return -1; } if (chunk[i] == '\r' || chunk[i] == ';') { chunk[i] = '\0'; } else if (chunk[i] == '\n') { chunk[i] = '\0'; /* double check*/ chunk_size = strtol(chunk, (char **) NULL, 16); /* hex to dec */ /* log_verbose3("chunk_size: '%s' as dec: '%d'", chunk, chunk_size);*/ return chunk_size; } if (i == 24) { stream->err = STREAM_ERROR_NO_CHUNK_SIZE; return -1; } else i++; } /* this should never happens */ stream->err = STREAM_ERROR_NO_CHUNK_SIZE; return -1; } static int _http_input_stream_chunked_read( http_input_stream_t *stream, byte_t *dest, int size) { int status, counter; int remain, read=0; char ch; while (size > 0) { remain = stream->chunk_size - stream->received ; if (remain == 0 && stream->chunk_size != -1) { /* This is not the first chunk. so skip new line until chunk size string */ counter = 100; /* maximum for stop infinity */ while (1) { status = hsocket_read(stream->sock, &ch, 1, 1); if (status == -1) { stream->err = HSOCKET_ERROR_RECEIVE; return -1; } if (ch == '\n') { break; } if (counter-- == 0) { stream->err = STREAM_ERROR_WRONG_CHUNK_SIZE; return -1; } } } if (remain == 0) { /* receive new chunk size */ stream->chunk_size = _http_input_stream_chunked_read_chunk_size(stream); stream->received = 0; if (stream->chunk_size < 0) { /* TODO (#1#): set error flag */ return stream->chunk_size; } else if (stream->chunk_size == 0) { return read; } remain = stream->chunk_size; } /* show remaining chunk size in socket */ if (remain < size) { /* read from socket */ status = hsocket_read(stream->sock, &(dest[read]), remain, 1); if (status == -1) { stream->err = HSOCKET_ERROR_RECEIVE; return -1; } } else { /* read from socket */ status = hsocket_read(stream->sock, &(dest[read]), size, 1); if (status == -1) { stream->err = HSOCKET_ERROR_RECEIVE; return -1; } } read += status; size -= status; stream->received += status; } return read; } static int _http_input_stream_connection_closed_read( http_input_stream_t *stream, byte_t *dest, int size) { int status; /* read from socket */ status = hsocket_read(stream->sock, dest, size, 0); if (status == -1) { stream->err = HSOCKET_ERROR_RECEIVE; return -1; } if (status == 0) stream->connection_closed = 1; stream->received += status; _log_str("stream.in", dest, size); return status; } static int _http_input_stream_file_read( http_input_stream_t *stream, byte_t *dest, int size) { int readed; readed = fread(dest, 1, size, stream->fd); if (readed == -1) { stream->err = HSOCKET_ERROR_RECEIVE; return -1; } return readed; } /** Returns the actual status of the stream. */ int http_input_stream_is_ready(http_input_stream_t *stream) { /* paranoya check */ if (stream == NULL) return 0; /* reset error flag */ stream->err = H_OK; switch (stream->type) { case HTTP_TRANSFER_CONTENT_LENGTH: return _http_input_stream_is_content_length_ready(stream); case HTTP_TRANSFER_CHUNKED: return _http_input_stream_is_chunked_ready(stream); case HTTP_TRANSFER_CONNECTION_CLOSE: return _http_input_stream_is_connection_closed_ready(stream); case HTTP_TRANSFER_FILE: return _http_input_stream_is_file_ready(stream); default: return 0; } } /** Returns the actual read bytes <0 on error */ int http_input_stream_read(http_input_stream_t *stream, byte_t *dest, int size) { int readed=0; /* paranoya check */ if (stream == NULL) { return -1; } /* reset error flag */ stream->err = H_OK; switch (stream->type) { case HTTP_TRANSFER_CONTENT_LENGTH: readed=_http_input_stream_content_length_read(stream, dest, size); break; case HTTP_TRANSFER_CHUNKED: readed=_http_input_stream_chunked_read(stream, dest, size); break; case HTTP_TRANSFER_CONNECTION_CLOSE: readed=_http_input_stream_connection_closed_read(stream, dest, size); break; case HTTP_TRANSFER_FILE: readed=_http_input_stream_file_read(stream, dest, size); break; default: stream->err = STREAM_ERROR_INVALID_TYPE; return -1; } return readed; } /* ------------------------------------------------------------------- HTTP OUTPUT STREAM ------------------------------------------------------------------- */ /** Creates a new output stream. Transfer code will be found from header. */ http_output_stream_t *http_output_stream_new(hsocket_t sock, hpair_t *header) { http_output_stream_t *result; char *content_length; char *chunked; /* Paranoya check */ /* if (header == NULL) return NULL; */ /* Create object */ result = (http_output_stream_t*)malloc(sizeof(http_output_stream_t)); result->sock = sock; result->sent = 0; /* Find connection type */ /* Check if Content-type */ if (_http_stream_is_content_length(header)) { log_verbose1("Stream transfer with 'Content-length'"); content_length = hpairnode_get_ignore_case(header, HEADER_CONTENT_LENGTH); result->content_length = atoi(content_length); result->type = HTTP_TRANSFER_CONTENT_LENGTH; } /* Check if Chunked */ else if (_http_stream_is_chunked(header)) { log_verbose1("Stream transfer with 'chunked'"); result->type = HTTP_TRANSFER_CHUNKED; } /* Assume connection close */ else { log_verbose1("Stream transfer with 'Connection: close'"); result->type = HTTP_TRANSFER_CONNECTION_CLOSE; } return result; } /** Free output stream */ void http_output_stream_free(http_output_stream_t *stream) { free(stream); } /** Writes 'size' bytes of 'bytes' into stream. Returns socket error flags or H_OK. */ hstatus_t http_output_stream_write(http_output_stream_t *stream, const byte_t *bytes, int size) { int status; char chunked[15]; if (stream->type == HTTP_TRANSFER_CHUNKED) { sprintf(chunked, "%x\r\n", size); status = hsocket_send(stream->sock, chunked); if (status != H_OK) return status; } if (size > 0) { _log_str("stream.out", (char*)bytes, size); status = hsocket_nsend(stream->sock, bytes, size); if (status != H_OK) return status; } if (stream->type == HTTP_TRANSFER_CHUNKED) { status = hsocket_send(stream->sock, "\r\n"); if (status != H_OK) return status; } return H_OK; } /** Writes 'strlen()' bytes of 'str' into stream. Returns socket error flags or H_OK. */ int http_output_stream_write_string(http_output_stream_t *stream, const char *str) { return http_output_stream_write(stream, str, strlen(str)); } int http_output_stream_flush(http_output_stream_t *stream) { int status; if (stream->type == HTTP_TRANSFER_CHUNKED) { status = hsocket_send(stream->sock, "0\r\n\r\n"); if (status != H_OK) return status; } return H_OK; }