/* Copyright 1998 Acorn Computers Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * HTTP (c.readdata) * * © Acorn Computers Ltd. 1997 */ #include #include #include #include "kernel.h" #include "swis.h" #include "sys/errno.h" #include "module.h" #include "socklib.h" #include "cookie.h" #include "protocol.h" #include "writedata.h" #include "header.h" #include "generic.h" static void http_set_return(_kernel_swi_regs *r, int flags, int size, int left, Session *ses); static void http_continue_dns(_kernel_swi_regs *r, Session *ses); static _kernel_oserror *http_exec_readdata(_kernel_swi_regs *r); _kernel_oserror *https_readdata(_kernel_swi_regs *r) { return http_readdata(r); } /*************************************************************/ /* _kernel_oserror *http_readdata(_kernel_swi_regs *r) */ /*************************************************************/ /* The call to get the actual data from a site. The values */ /* in the registers are: */ /* r0 = flags word */ /* r1 = poll word */ /* r2 = buffer */ /* r3 = sizeof(buffer) */ /* */ /* On exit: */ /* r0 = protocol status */ /* r1 = pollword of client */ /* r2 = buffer (contents modified) */ /* r3 = unchanged */ /* r4 = bytes read */ /* r5 = bytes left (-1 if unknown) */ /*************************************************************/ _kernel_oserror *http_readdata(_kernel_swi_regs *r) { /* This wrapper is used to ensure that R2 and R3 are preserved across the SWI * This is needed because in order to generate the client's data buffer piecemeal * (we are rewriting the headers to help the browser out and removing some that * do not concern it at all), we keep updating R2 and R3 to avoid writing off * the end of the buffer. * We also take the opportunity to initialise R4 and R5 safely. */ const int const_buffer = r->r[2]; const int const_buflen = r->r[3]; _kernel_oserror *result; #ifdef TRACE protocol_debug("Buffer (R2) is &%08x; Size (R3) = %d\n", const_buffer, const_buflen); #endif r->r[4] = 0; /* No data in client buffer yet */ r->r[5] = -1; /* Unknown how much more data to read */ result = http_exec_readdata(r); r->r[2] = const_buffer; /* Restore buffer address */ r->r[3] = const_buflen; /* Restore buffer size */ return result; } static int http_read_more_header(_kernel_swi_regs *r, Session *ses) { /* We are still receiving the response header */ int space, consumed, dataread; if (ses->buffer == NULL) { /* Force the next block to allocate some RMA - it can do this * because: realloc(NULL, size) == malloc(size) */ ses->bufptr = 0; ses->bufsize = 0; ses->current_header = NULL; } space = ses->bufsize - ses->bufptr; if (space <= 0) { /* Header is just too long. What do we do now? Extend buffer */ char *new_buffer = module_realloc(ses->buffer, ses->bufptr + INTERNAL_BUFFER_SIZE + 4); if (new_buffer != NULL) { ses->bufsize = ses->bufptr + INTERNAL_BUFFER_SIZE; space = ses->bufsize - ses->bufptr; ses->buffer = new_buffer; } else { #ifdef TRACE protocol_debug("module_realloc failed!\n"); #endif ses->done = HTTP_ERROR; http_set_return(r, status_ABORTED, r->r[4], 0, ses); return 0; } } /* Read a *COPY* of the data from the TCP/IP stack. We do this because we * do not know how big the client's buffer is, so it is much safer to fit * it into our buffer that we know is going to be large enough. */ ses->bufptr = 0; space = ses->bufsize; dataread = ses->op->s_recv(ses->sd, ses->buffer, space, MSG_PEEK); /* Right. We only need to cope with this situation as the later code * for the body reader will pick up the same situation when it attempts * the socketread call in a moment. */ if (dataread <= 0) return 1; #ifdef TRACE protocol_debug("receiving_header: peeking at data (had %d, got %d more):\n", ses->bufptr, dataread); protocol_dump(ses->buffer, ses->bufptr + dataread); #endif ses->bufptr += dataread; ses->buffer[ses->bufptr] = '\0'; /* Terminate header buffer */ /* Parse the available headers - find out how much "input" has * been consumed from the input stream. */ consumed = parse_http_header(ses->buffer, ses->bufptr, ses, r); if (consumed > 0) { /* Discard the data that we have already handled */ #ifdef TRACE if (consumed > dataread) { protocol_debug("Consuming %d - only had %d to discard in first place!\n", consumed, dataread); } #endif (void) ses->op->s_recv(ses->sd, ses->buffer, consumed, 0); #ifdef TRACE protocol_debug("receiving_header: discarding data from socket (size=%d):\n", consumed); protocol_dump(ses->buffer, consumed); #endif } if (ses->donehead == FALSE) { /* Still not finished reading the headers - so we'll now leave the function */ http_set_return(r, status_READING_REPLY, r->r[4], -2, ses); return 0; } #ifdef COOKIE if (ses->chunking == FALSE) { move_cookies_from_queue_to_list(1); } #endif /* May be possible to set this to 1, but it WORKS if it is zero :-) */ http_set_return(r, status_READING_REPLY, r->r[4], -1, ses); return 0; /* Say we've not finished reading headers yet */ } /* This function is called as a pre-processor to the normal data reader. It * needs to arrange that the data reader will read valid data into the client's * buffer, and to prevent it doing that if it determines that it must not * interfere (eg. it's still in the middle of decoding a chunk header) */ static int http_process_chunks(_kernel_swi_regs *r, Session *ses, int *maxbytes) { if (ses->chunk_bytes > 0) { if (ses->chunk_bytes < *maxbytes) { /* Client's buffer was too large. Limit read to end of chunk */ *maxbytes = ses->chunk_bytes; } return 1; } /* To get here, we know that there are no active chunks being received. Therefore, * we must be parsing something like a chunk header. We can "borrow" the same * header decoder from the normal HTTP header decoder. */ if (ses->chunk_bytes == -1) { ses->chunk_state = chunkstate_wait_chunk_size; ses->bufptr = 0; } else if (ses->chunk_state == chunkstate_reading_chunk_data) { /* We have just finished receiving a "chunk-data" (RFC2068, page 25) */ ses->chunk_state = chunkstate_wait_chunk_ending_crlf; ses->bufptr = 0; } /* We have now determined what state we are in. We can safely call * the header buffer parser */ return http_read_more_header(r, ses); /* if (http_read_more_data(r, res) == 0) return 0; if (ses->chunk_data == chunkstate_reading_chunk_data && ses->chunk_bytes > 0) { return http_process_chunks(r, ses, maxbytes); } return 1; */ } /* This function is called by the state table dispatcher to handle the case where * we are reading data from the remote server */ static void http_reading_response(_kernel_swi_regs *r, Session *ses) { int dataread = 0, rawread; char *buffer; int bufsize, toread; if (ses->donehead == FALSE) { if (http_read_more_header(r, ses) == 0) { return; } /* To fall out of this if statement, one of the following conditions has occurred: * 1) recv call returned 0 (socket closed by remote end) * 2) recv call returned -1 (error - although could be EWOULDBLOCK) */ } errno = 0; buffer = (char *) r->r[2]; bufsize = toread = r->r[3]; if (bufsize <= 0) { http_set_return(r, status_READING_REPLY, r->r[4], -1, ses); return; } if (ses->chunking && ses->donehead != FALSE) { /* We are doing a chunked entity body - dechunk it */ /* The use of a locally scoped copy of bufsize is VITAL to the correct * operation of this function. If we don't use the address of a variable * that is scoped to within this block, the compiler will refuse (correctly) * to tail-optimise the call to this function when a chunked transfer is * in progress because as far as it is concerned, our "bufsize" is still live * during the recursive call. * * If the call is not tail-optimised we could run into nasty trouble (namely * the limit of the SVC stack). * */ int newsize = toread; /* First rule is that we don't know the entity header size */ r->r[5] = -1; if (http_process_chunks(r, ses, &newsize) == 0) { /* If function returns zero, then we need to bail out here * because we've done our processing work already */ return; } /*if (ses->chunk_state != chunkstate_reading_chunk_data) { return; }*/ toread = newsize; /* To get here, the chunker has determined that it is safe to read data directly * into the client buffer. The amount of data to read is stored in bufsize, which * may have been decreased by the chunker to ensure we don't read past the end * of the current chunk. */ } #ifdef COMPRESSION if (ses->compression) { #ifdef TEST_COMPRESSION_CODE int i, result, readthistime; dataread=0; rawread=0; for (i=0; i4?4:(toread-i), &readthistime); if (result<0 && errno == EWOULDBLOCK && readthistime > 0) { rawread+=readthistime; continue; } else if (result < 0) { #ifdef TRACE protocol_debug("Error!!! %d\n", errno); #endif dataread=-1; /*??*/ rawread+=readthistime; break; } else if (result == 0) { #ifdef TRACE protocol_debug("result was zero\n"); #endif rawread+=readthistime; ses->compression=compression_NONE; #ifdef TRACE protocol_debug("rawread ends as %d, dataread ends as %d\n", rawread, dataread); #endif break; } dataread+=result; rawread+=readthistime; } #else dataread = decompress(ses, buffer, bufsize, toread, &rawread); #ifdef TRACE protocol_fast_debug("decompress returns %d, rawread=%d\n", dataread, rawread); #endif #endif } else #endif { dataread = rawread = ses->op->s_recv(ses->sd, buffer, toread, 0); if (dataread == -1) rawread = 0; } #ifdef TRACE protocol_debug("receiving_body state: recv returns %d (rawread now %d) (errno now %d)\n", dataread, rawread, errno); if (dataread > 0) { protocol_dump(buffer, dataread); } #endif if (ses->chunking) { ses->chunk_bytes -= rawread; } if (dataread > 0) { ses->sent += dataread; http_write_data_to_client(r, buffer, dataread); if (ses->chunking) { if (ses->chunk_bytes == 0 && r->r[3] > 0) { /* See comment in block containing call to http_process_chunks about * this tail-recursive call. */ /*http_reading_response(r, ses); return;*/ } } http_set_return(r, status_READING_REPLY, r->r[4], -1, ses); } else if (dataread == 0) { ses->done = HTTP_RECEIVED; http_set_return(r, status_ALL_DATA_RECEIVED, r->r[4], 0, ses); if (ses->sd != -1) { #ifdef TRACE protocol_debug("socketclose(%d) in body recv state\n", ses->sd); #endif close_socket(ses, &ses->sd); } } else if ((errno == 0) || (errno==EWOULDBLOCK) || (errno==EINPROGRESS)) { http_set_return(r, status_READING_REPLY, r->r[4], -1, ses); } else { ses->done = HTTP_ERROR_READ; http_set_return(r, status_READING_REPLY, r->r[4], -1, ses); if (ses->sd != -1) { #ifdef TRACE protocol_debug("socketclose(%d) in error read state\n", ses->sd); #endif close_socket(ses, &ses->sd); } } } static _kernel_oserror *http_exec_readdata(_kernel_swi_regs *r) { Session *ses; /* * Check to see session exists and has connected cleanly first * If it doesnt/hasnt, reject this request cleanly */ r->r[4] = 0; r->r[5] = 0; /* Find the relevant session block - trap it not being found */ ses = find_session(r->r[1]); if (ses == NULL) { http_set_return(r, status_ABORTED, 0, 0, ses); return NULL; } #ifdef TRACE protocol_debug("Session %p starts in state %s (sd=%d)\n",ses,protocol_states(ses->done),ses->sd); #endif /* Dispatch from the FSM of the HTTP module. ses->done is the current state */ switch (ses->done) { /* The first two, if we see them, are error conditions so panic */ case HTTP_NEWSESSION: case HTTP_NOHOST: case HTTP_ERROR_NOLINK: /* Should never happen - erk! */ http_set_return(r, status_ABORTED, 0, 0, ses); ses->done = HTTP_ERROR; break; case HTTP_DNS: http_continue_dns(r, ses); break; /* * We are still trying to connect or send the request */ case HTTP_CONNECTING: case HTTP_SENDING_REQUEST: http_retry_request(ses); #ifdef TRACE protocol_debug("New state after http_retry_request is %s\n", protocol_states(ses->done)); #endif if (ses->done == HTTP_CONNECTING) { http_set_return(r, status_NOT_YET_CONNECTED, 0,-1,ses); } else if (ses->done == HTTP_SENDING_REQUEST) { http_set_return(r, status_CONNECTED_TO_SERVER, 0,-1, ses); } else if (ses->done >= HTTP_CONNECT_TIMED_OUT) { http_set_return(r, status_NOT_YET_CONNECTED, 0, -1, ses); } else { ses->done = HTTP_RECEIVING_BODY; ses->donehead = FALSE; http_set_return(r, status_WAIT_INITIAL_RESPONSE, 0, -1, ses); } break; /* We are sending data back to the client now. */ case HTTP_SENT_REQUEST: case HTTP_RECEIVING: case HTTP_RECEIVING_BODY: http_reading_response(r, ses); if (r->r[5] == 0) { ses->done = HTTP_RECEIVED; } break; /* Everything is done and hunky dorey */ case HTTP_RECEIVED: case HTTP_DONE: http_set_return(r, status_ALL_DATA_RECEIVED, r->r[4], 0, ses); if (ses->sd != -1) { #ifdef TRACE protocol_debug("socketclose(%d) in received/done state\n", ses->sd); #endif close_socket(ses, &ses->sd); } break; default: r->r[0] = 0; return return_error(ses->done); } #ifdef TRACE protocol_debug("ReadData exit session:%p status %s (sd=%d)\n", ses, protocol_states(ses->done), ses->sd); #endif return NULL; } /*************************************************************/ /* static void http_set_return(_kernel_swi_regs *r, int .... */ /*************************************************************/ /* Set the return values in various registers ready to */ /* return back to the swi invoking program. */ /*************************************************************/ /* Grrr. Until version 0.45, http_set_return blindly indirected * through the passed session pointer. This is NOT good when * other parts of this module pass NULL session pointers in! */ static void http_set_return(_kernel_swi_regs *r, int flags, int size, int left, Session *ses) { /* If cookies in queue, set bit 16 of return word */ #ifdef COOKIE if (cookie_queue_root != NULL) flags = flags | (1<<16); #endif r->r[0] = flags; *((int *)r->r[1]) = flags; r->r[4] = size; if (ses == NULL) { r->r[5] = left; return; } /*ses->sent += size;*/ if (left == -2) { r->r[5] = -1; } else if (left < 0) { r->r[5] = (ses->size == -1) ? -1 : ((ses->sent < ses->size) ? ses->size - ses->sent : 0); } else { r->r[5] = left; } #ifdef TML Printf("http:set_return> session:%x flags %x chunk %d remaining %d\n",ses,flags,size,r->r[5]); #endif } static void http_continue_dns(_kernel_swi_regs *r, Session *ses) { r->r[0] = *((int *)r->r[1]) = status_NOT_YET_CONNECTED; r->r[4] = 0; r->r[5] = -1; ses->sd = opensock(ses->host, ses->port, NULL, &ses->done, ses->sd, ses); /* We failed to create the connecting socket! */ if (ses->sd < 0) { if ((ses->sd == -ENETUNREACH) || (ses->sd == -EWOULDBLOCK) || (ses->sd == -EHOSTUNREACH)) { ses->done = HTTP_ERROR_NOLINK; } else { ses->done = ses->endhost ? HTTP_ERROR_NO_PROXY : HTTP_ERROR_HOSTNAME; } #ifdef TML Printf("http:connect> OpenSocket returning errno=%d : status %s\n",-ses->sd,protocol_states(ses->done)); #endif ses->sd = -1; return; } if (ses->done == HTTP_CONNECTING) { http_retry_request(ses); #ifdef TRACE protocol_debug("After http_retry_request in DNS lookup state, state is now %s\n", protocol_states(ses->done)); #endif r->r[0] = *(int *)r->r[1] = status_CONNECTED_TO_SERVER; } }