PRIVATE int HTBufferWriter_lazyFlush (HTOutputStream * me) { HTNet * net; int delay; if (me->read <= me->data) { return HT_OK; /* nothing to flush */ } /* ** If we are allowed to delay the flush then set a timer with the ** delay descibed by our delay variable. If we can't delay then flush ** right away. */ delay = HTHost_findWriteDelay(me->host, me->lastFlushTime, me->read - me->data); /* ** Flush immediately */ if (!delay) { int status; HTTRACE(STREAM_TRACE, "Buffer...... Flushing %p\n" _ me); if ((status = HTBufferWriter_flush(me)) && me->timer) { HTTimer_delete(me->timer); me->timer = NULL; } return status; } /* ** Set a timer and tell the host we've done the write if ** we have not already started a timer earlier. If a timer ** does already exist then make sure that it hasn't expired. ** This can be the case if we have a really slow client that ** can't parse the data fast enough. */ if (!me->timer) { net = HTHost_getWriteNet(me->host); me->timer = HTTimer_new(NULL, FlushEvent, me, delay, YES, NO); HTHost_unregister(me->host, net, HTEvent_WRITE); HTTRACE(STREAM_TRACE, "Buffer...... Waiting %dms on %p\n" _ delay _ me); } else { if (HTTimer_hasTimerExpired(me->timer)) { HTTRACE(STREAM_TRACE, "Buffer...... Dispatching old timer %p\n" _ me->timer); HTTimer_dispatch(me->timer); me->timer = NULL; } else { HTTRACE(STREAM_TRACE, "Buffer...... Waiting on unexpired timer %p\n" _ me->timer); } } return HT_OK; }
PRIVATE int NewsEvent (SOCKET soc, void * pVoid, HTEventType type) { news_info *news = (news_info *)pVoid; int status = HT_ERROR; HTNet * net = news->net; HTRequest * request = HTNet_request(net); HTParentAnchor * anchor = HTRequest_anchor(request); char * url = HTAnchor_physical(anchor); HTHost * host = HTNet_host(net); /* ** Initiate a new nntp structure and bind to request structure ** This is actually state NNTP_BEGIN, but it can't be in the state ** machine as we need the structure first. */ if (type == HTEvent_CLOSE) { /* Interrupted */ HTRequest_addError(request, ERR_FATAL, NO, HTERR_INTERRUPTED, NULL, 0, "HTLoadHTTP"); HTNewsCleanup(request, HT_INTERRUPTED); return HT_OK; } else news = (news_info *) HTNet_context(net); /* Get existing copy */ /* Now jump into the machine. We know the state from the previous run */ while (1) { switch (news->state) { case NEWS_BEGIN: news->state = (!strchr(url, '@') && strchr(url, '*')) ? NEWS_SEEK_CACHE : NEWS_NEED_CONNECTION; break; case NEWS_SEEK_CACHE: if (HTNewsCache_before(request, NULL, 0) == HT_LOADED) news->state = NEWS_SUCCESS; else news->state = NEWS_NEED_CONNECTION; break; case NEWS_NEED_CONNECTION: /* Let's set up a connection */ if (!strncasecomp(url, "news:", 5)) { HTUserProfile * up = HTRequest_userProfile(request); char * newshost = HTUserProfile_news(up); StrAllocCopy(news->name, url+5); if (newshost) { char *newshack = NULL; /* Then we can use HTParse :-) */ StrAllocCopy(newshack, "news://"); StrAllocCat(newshack, newshost); status = HTHost_connect(host, net, (char *) newshack); host = HTNet_host(net); HT_FREE(newshack); } else news->state = NEWS_ERROR; } else if (!strncasecomp(url, "nntp:", 5)) { news->name = HTParse(url, "", PARSE_PATH); status = HTHost_connect(host, net, url); host = HTNet_host(net); } else { HTTRACE(PROT_TRACE, "News........ Huh?"); news->state = NEWS_ERROR; } if (status == HT_OK) { BOOL greeting = NO; /* Set up the persistent connection */ if (!HTNet_persistent(net)) { HTNet_setPersistent(net, YES, HT_TP_SINGLE); greeting = YES; } /* ** Check the protocol class to see if we have connected to a ** the right class of server, in this case HTTP. */ { HTHost * host = HTNet_host(net); char * s_class = HTHost_class(host); if (s_class && strcasecomp(s_class, "nntp")) { HTRequest_addError(request, ERR_FATAL, NO, HTERR_CLASS, NULL, 0, "HTLoadNews"); news->state = NEWS_ERROR; break; } HTHost_setClass(host, "nntp"); } /* ** Create the stream pipe FROM the channel to the application. ** The target for the input stream pipe is set up using the ** stream stack. */ { HTStream * rstream = HTNewsStatus_new(request, news, host); HTNet_setReadStream(net, rstream); HTRequest_setOutputConnected(request, YES); } /* ** Create the stream pipe TO the channel from the application ** and hook it up to the request object */ { HTOutputStream * output = HTNet_getOutput(net, NULL, 0); HTRequest_setInputStream(request, (HTStream *) output); } news->state = greeting ? NEWS_NEED_GREETING : NEWS_NEED_SWITCH; } else if (status == HT_WOULD_BLOCK || status == HT_PENDING) return HT_OK; else news->state = NEWS_ERROR; break; case NEWS_NEED_GREETING: status = HTHost_read(HTNet_host(net), net); if (status == HT_WOULD_BLOCK) return HT_OK; else if (status == HT_LOADED) { if (news->repcode/100 == 2) news->state = NEWS_NEED_SWITCH; else news->state = NEWS_ERROR; } else news->state = NEWS_ERROR; break; case NEWS_NEED_SWITCH: { HTMethod method = HTRequest_method(request); /* ** Find out what to ask the news server. Syntax of address is ** xxx@yyy Article ** <xxx@yyy> Same article ** xxxxx News group (no "@") */ if (method == METHOD_GET) { if (strchr(url, '@')) { /* ARTICLE */ if (*(news->name) != '<') { /* Add '<' and '>' */ char *newart; if ((newart = (char *) HT_MALLOC(strlen(news->name)+3)) == NULL) HT_OUTOFMEM("HTLoadNews"); sprintf(newart, "<%s>", news->name); HT_FREE(news->name); news->name = newart; } news->state = NEWS_NEED_ARTICLE; } else if (strchr(url, '*')) news->state = NEWS_NEED_LIST; else news->state = NEWS_NEED_GROUP; } else if (method == METHOD_POST) news->state = NEWS_NEED_POST; else { HTRequest_addError(request, ERR_FATAL, NO, HTERR_NOT_IMPLEMENTED,NULL, 0,"HTLoadNews"); news->state = NEWS_ERROR; } HTUnEscape(news->name); HTCleanTelnetString(news->name); } break; case NEWS_NEED_ARTICLE: if (!news->sent) { status = SendCommand(request, news, "ARTICLE", news->name); if (status == HT_WOULD_BLOCK) return HT_OK; else if (status == HT_ERROR) news->state = NEWS_ERROR; news->format = WWW_MIME; /* ** Set the default content type to plain text as news servers ** almost never send any useful information about the length ** of the body or the type - the success of MIME! */ HTAnchor_setFormat(anchor, WWW_PLAINTEXT); news->sent = YES; } else { status = HTHost_read(HTNet_host(net), net); if (status == HT_WOULD_BLOCK) return HT_OK; else if (status == HT_OK) news->state = NEWS_NEED_BODY; else if (status == HT_LOADED) { news->state = (news->repcode/100 == 2) ? NEWS_SUCCESS : NEWS_ERROR; } else news->state = NEWS_ERROR; news->sent = NO; } break; #if HT_LISTGROUP case NEWS_NEED_LGRP: if (!news->sent) { status = SendCommand(request, news, "LIST", "NEWSGROUPS"); if (status == HT_WOULD_BLOCK) return HT_OK; else if (status == HT_ERROR) news->state = NEWS_ERROR; news->format = WWW_NNTP_LIST; news->sent = YES; } else { status = HTHost_read(HTNet_host(net), net); if (status == HT_WOULD_BLOCK) return HT_OK; else if (status == HT_OK) news->state = NEWS_NEED_BODY; else if (status == HT_LOADED) { news->state = (news->repcode/100 == 2) ? NEWS_SUCCESS : NEWS_NEED_LIST; } else news->state = NEWS_ERROR; news->sent = NO; } break; #endif /* HT_LISTGROUP */ case NEWS_NEED_LIST: if (!news->sent) { status = SendCommand(request, news, "LIST", NULL); if (status == HT_WOULD_BLOCK) return HT_OK; else if (status == HT_ERROR) news->state = NEWS_ERROR; news->format = WWW_NNTP_LIST; news->sent = YES; } else { status = HTHost_read(HTNet_host(net), net); if (status == HT_WOULD_BLOCK) return HT_OK; else if (status == HT_OK) news->state = NEWS_NEED_BODY; else if (status == HT_LOADED) { news->state = (news->repcode/100 == 2) ? NEWS_SUCCESS : NEWS_ERROR; } else news->state = NEWS_ERROR; news->sent = NO; } break; case NEWS_NEED_GROUP: if (!news->sent) { status = SendCommand(request, news, "GROUP", news->name); if (status == HT_WOULD_BLOCK) return HT_OK; else if (status == HT_ERROR) news->state = NEWS_ERROR; news->sent = YES; } else { status = HTHost_read(HTNet_host(net), net); if (status == HT_WOULD_BLOCK) return HT_OK; else if (status == HT_LOADED) { if (news->repcode/100 == 2) { if (sscanf(news->reply, "%d%d%d", &news->total, &news->first, &news->last) == 3) { if (MaxArt && news->total>MaxArt) news->last = news->first-MaxArt; news->current = news->first; /* If no content in this group */ if (news->first == news->last) { HTRequest_addError(request, ERR_FATAL, NO, HTERR_NO_CONTENT, NULL, 0, "HTLoadNews"); news->state = NEWS_NO_DATA; break; } news->state = NEWS_NEED_XOVER; } else news->state = NEWS_ERROR; } else news->state = NEWS_ERROR; } else news->state = NEWS_ERROR; news->sent = NO; } break; case NEWS_NEED_XOVER: if (!news->sent) { char buf[20]; sprintf(buf, "%d-%d", news->first, news->last); status = SendCommand(request, news, "XOVER", buf); if (status == HT_WOULD_BLOCK) return HT_OK; else if (status == HT_ERROR) news->state = NEWS_ERROR; news->format = WWW_NNTP_OVER; news->sent = YES; } else { status = HTHost_read(HTNet_host(net), net); if (status == HT_WOULD_BLOCK) return HT_OK; else if (status == HT_OK) news->state = NEWS_NEED_BODY; else if (status == HT_LOADED) { if (news->repcode/100 == 2) news->state = NEWS_SUCCESS; else { news->format = WWW_NNTP_HEAD; news->state = NEWS_NEED_HEAD; } } else news->state = NEWS_ERROR; news->sent = NO; } break; case NEWS_NEED_HEAD: if (!news->sent) { char buf[10]; sprintf(buf, "%d", news->current++); status = SendCommand(request, news, "HEAD", buf); if (status == HT_WOULD_BLOCK) return HT_OK; else if (status == HT_ERROR) news->state = NEWS_ERROR; news->sent = YES; } else { status = HTHost_read(HTNet_host(net), net); if (status == HT_WOULD_BLOCK) return HT_OK; else if (status == HT_LOADED) { if (news->repcode/100 == 2) { if (news->current > news->last) news->state = NEWS_SUCCESS; } else news->state = NEWS_ERROR; } else news->state = NEWS_ERROR; news->sent = NO; } break; case NEWS_NEED_POST: { HTStream * oldinput = HTRequest_inputStream(request); HTStream * newinput = HTNewsPost_new(request, HTBuffer_new(oldinput, request,512)); HTRequest_setInputStream(request, newinput); /* Remember to convert to CRLF */ } news->state = NEWS_NEED_BODY; break; case NEWS_NEED_BODY: if (type == HTEvent_WRITE || type == HTEvent_BEGIN) { if (HTRequest_isDestination(request)) { HTRequest * source = HTRequest_source(request); HTNet * srcnet = HTRequest_net(source); if (srcnet) { HTHost_register(HTNet_host(srcnet), srcnet, HTEvent_READ); HTHost_unregister(HTNet_host(srcnet), srcnet, HTEvent_WRITE); } return HT_OK; } /* ** Should we use the input stream directly or call the post ** callback function to send data down to the network? */ { HTStream * input = HTRequest_inputStream(request); HTPostCallback * pcbf = HTRequest_postCallback(request); if (pcbf) { status = pcbf(request, input); if (status == HT_PAUSE || status == HT_LOADED) type = HTEvent_READ; } else { status = (*input->isa->flush)(input); type = HTEvent_READ; } if (status == HT_WOULD_BLOCK) return HT_OK; } status = request->PostCallback ? request->PostCallback(request, request->input_stream) : (*request->input_stream->isa->flush)(request->input_stream); if (status == HT_WOULD_BLOCK) return HT_OK; else type = HTEvent_READ; /* Trick to ensure that we do READ */ } else if (type == HTEvent_READ) { status = HTHost_read(HTNet_host(net), net); if (status == HT_WOULD_BLOCK) return HT_OK; else if (status == HT_LOADED) news->state = NEWS_SUCCESS; else news->state = NEWS_ERROR; } else { news->state = NEWS_ERROR; } break; case NEWS_SUCCESS: HTNewsCleanup(request, HT_LOADED); return HT_OK; break; case NEWS_NO_DATA: HTNewsCleanup(request, HT_NO_DATA); return HT_OK; break; case NEWS_ERROR: HTNewsCleanup(request, HT_NOT_FOUND); return HT_OK; break; } } /* End of while(1) */ }
/* Write to the socket ** ** According to Solaris 2.3 man on write: ** ** o If O_NONBLOCK and O_NDELAY are clear, write() blocks ** until the data can be accepted. ** ** o If O_NONBLOCK or O_NDELAY is set, write() does not ** block the process. If some data can be written ** without blocking the process, write() writes what it ** can and returns the number of bytes written. Other- ** wise, if O_NONBLOCK is set, it returns - 1 and sets ** errno to EAGAIN or if O_NDELAY is set, it returns 0. ** ** According to SunOS 4.1.1 man on write: ** ** + If the descriptor is marked for non-blocking I/O ** using fcntl() to set the FNONBLOCK or O_NONBLOCK ** flag (defined in <sys/fcntl.h>), write() requests ** for {PIPE_BUF} (see pathconf(2V)) or fewer bytes ** either succeed completely and return nbyte, or ** return -1 and set errno to EAGAIN. A write() request ** for greater than {PIPE_BUF} bytes either transfers ** what it can and returns the number of bytes written, ** or transfers no data and returns -1 and sets errno ** to EAGAIN. If a write() request is greater than ** {PIPE_BUF} bytes and all data previously written to ** the pipe has been read, write() transfers at least ** {PIPE_BUF} bytes. */ PRIVATE int HTWriter_write (HTOutputStream * me, const char * buf, int len) { HTHost * host = me->host; SOCKET soc = HTChannel_socket(HTHost_channel(host)); HTNet * net = HTHost_getWriteNet(host); int b_write; char * wrtp; const char *limit = buf+len; /* If we don't have a Net object then return right away */ if (!net) { HTTRACE(STREAM_TRACE, "Write Socket No Net object %d (offset %d)\n" _ soc _ me->offset); return HT_ERROR; } #ifdef NOT_ASCII if (len && !me->ascbuf) { /* Generate new buffer */ const char *orig = buf; char *dest; int cnt; if ((me->ascbuf = (char *) HT_MALLOC(len)) == NULL) HT_OUTOFMEM("HTWriter_write"); dest = me->ascbuf; for (cnt=0; cnt<len; cnt++) { *dest = TOASCII(*orig); dest++, orig++; } wrtp = me->ascbuf; limit = me->ascbuf+len; } #else if (!me->offset) wrtp = (char *) buf; else { wrtp = (char *) buf + me->offset; len -= me->offset; me->offset = 0; } #endif /* Write data to the network */ while (wrtp < limit) { if ((b_write = NETWRITE(soc, wrtp, len)) < 0) { #ifdef EAGAIN if (socerrno == EAGAIN || socerrno == EWOULDBLOCK)/* POSIX, SVR4 */ #else if (socerrno == EWOULDBLOCK) /* BSD */ #endif { HTHost_register(host, net, HTEvent_WRITE); me->offset = wrtp - buf; HTTRACE(STREAM_TRACE, "Write Socket WOULD BLOCK %d (offset %d)\n" _ soc _ me->offset); return HT_WOULD_BLOCK; #ifdef EINTR } else if (socerrno == EINTR) { /* ** EINTR A signal was caught during the write opera- ** tion and no data was transferred. */ HTTRACE(STREAM_TRACE, "Write Socket call interrupted - try again\n"); continue; #endif } else { host->broken_pipe = YES; #ifdef EPIPE if (socerrno == EPIPE) { /* JK: an experimental bug solution proposed by Olga and Mikhael */ HTTRACE(STREAM_TRACE, "Write Socket got EPIPE\n"); HTHost_unregister(host, net, HTEvent_WRITE); HTHost_register(host, net, HTEvent_CLOSE); /* @@ JK: seems that some functions check the errors as part of the flow control */ HTRequest_addSystemError(net->request, ERR_FATAL, socerrno, NO, "NETWRITE"); return HT_CLOSED; } #endif /* EPIPE */ /* all errors that aren't EPIPE */ HTRequest_addSystemError(net->request, ERR_FATAL, socerrno, NO, "NETWRITE"); return HT_ERROR; } } /* We do this unconditionally, should we check to see if we ever blocked? */ HTTRACEDATA(wrtp, b_write, "Writing to socket %d" _ soc); HTNet_addBytesWritten(net, b_write); wrtp += b_write; len -= b_write; HTTRACE(STREAM_TRACE, "Write Socket %d bytes written to %d\n" _ b_write _ soc); { HTAlertCallback *cbf = HTAlert_find(HT_PROG_WRITE); if (cbf) { int tw = HTNet_bytesWritten(net); (*cbf)(net->request, HT_PROG_WRITE, HT_MSG_NULL, NULL, &tw, NULL); } } } #ifdef NOT_ASCII HT_FREE(me->ascbuf); #endif return HT_OK; }
PRIVATE int HTSSLReader_read (HTInputStream * me) { HTHost * host = me->host; SOCKET soc = HTChannel_socket(me->ch); HTNet * net = HTHost_getReadNet(host); HTRequest * request = HTNet_request(net); int status; if (!net->readStream) { HTTRACE(STREAM_TRACE, "HTSSLReader. No read stream for net object %p\n" _ net); return HT_ERROR; } /* Setting SSL */ if (!me->htssl) { if ((me->htssl = HTSSL_new(soc)) == NULL) { HTRequest_addSystemError(net->request, ERR_FATAL, socerrno, NO, "SSLREAD"); return HT_ERROR; } } /* Read from socket if we got rid of all the data previously read */ do { /* Don't read if we have to push unwritten data from last call */ if (me->write >= me->read) { me->b_read = 0; me->data[0] ='\0'; me->b_read = HTSSL_read(me->htssl, soc, me->data, INPUT_BUFFER_SIZE); status = HTSSL_getError(me->htssl, me->b_read); HTTRACE(STREAM_TRACE, "HTSSLReader. SSL returned %d\n" _ status); /* Check what we got done */ switch (status) { case SSL_ERROR_NONE: HTTRACEDATA(me->data, me->b_read, "Reading from socket %d" _ soc); me->write = me->data; me->read = me->data + me->b_read; HTTRACE(STREAM_TRACE, "HTSSLReader. %d bytes read from socket %d\n" _ me->b_read _ soc); /* Make progress notification */ if (request) { HTAlertCallback * cbf = HTAlert_find(HT_PROG_READ); if (HTNet_rawBytesCount(net)) HTNet_addBytesRead(net, me->b_read); if (cbf) { int tr = HTNet_bytesRead(net); (*cbf)(request, HT_PROG_READ, HT_MSG_NULL, NULL, &tr, NULL); } } break; case SSL_ERROR_WANT_READ: HTTRACE(STREAM_TRACE, "HTSSLReader. WOULD BLOCK fd %d\n" _ soc); HTHost_register(host, net, HTEvent_READ); /* ** There seems to be a bug as even though it says "read finished" ** it doesn't say that it wants to write. We therefore have to make ** an explicit flush to make sure that we don't block forever. */ HTHost_forceFlush(host); return HT_WOULD_BLOCK; case SSL_ERROR_WANT_WRITE: return HTHost_forceFlush(host); case SSL_ERROR_WANT_X509_LOOKUP: /* @@@ what to do here? @@@ */ return HT_OK; case SSL_ERROR_ZERO_RETURN: case SSL_ERROR_SSL: case SSL_ERROR_SYSCALL: HTTRACE(PROT_TRACE, "HTSSLReader. FIN received on socket %d\n" _ soc); HTHost_unregister(host, net, HTEvent_READ); HTHost_register(host, net, HTEvent_CLOSE); HTSSL_close(me->htssl); HTSSL_free(me->htssl); me->htssl = NULL; return HT_CLOSED; } } #ifdef FIND_SIGNATURES { char * ptr = me->data; int len = me->b_read; while ((ptr = strnstr(ptr, &len, "HTTP/1.1 200 OK")) != NULL) { HTTRACE(STREAM_TRACE, "HTSSLReader. Signature found at 0x%x of 0x%x.\n" _ ptr - me->data _ me->b_read); ptr++; len--; } } #endif /* FIND_SIGNATURES */ #ifdef NOT_ASCII { char *p = me->data; while (p < me->read) { *p = FROMASCII(*p); p++; } } #endif /* NOT_ASCII */ /* Now push the data down the stream */ if ((status = (*net->readStream->isa->put_block) (net->readStream, me->write, me->b_read)) != HT_OK) { if (status == HT_WOULD_BLOCK) { HTTRACE(STREAM_TRACE, "HTSSLReader. Target WOULD BLOCK\n"); HTHost_unregister(host, net, HTEvent_READ); return HT_WOULD_BLOCK; } else if (status == HT_PAUSE) { HTTRACE(STREAM_TRACE, "HTSSLReader. Target PAUSED\n"); HTHost_unregister(host, net, HTEvent_READ); return HT_PAUSE; /* CONTINUE code or stream code means data was consumed */ } else if (status == HT_CONTINUE || status > 0) { if (status == HT_CONTINUE) { HTTRACE(STREAM_TRACE, "HTSSLReader. CONTINUE\n"); } else HTTRACE(STREAM_TRACE, "HTSSLReader. Target returns %d\n" _ status); /* me->write = me->read; */ return status; } else { /* We have a real error */ HTTRACE(STREAM_TRACE, "HTSSLReader. Target ERROR %d\n" _ status); return status; } } me->write = me->read; { int remaining = HTHost_remainingRead(host); if (remaining > 0) { HTTRACE(STREAM_TRACE, "HTSSLReader. DIDN'T CONSUME %d BYTES: `%s\'\n" _ remaining _ me->read); HTHost_setConsumed(host, remaining); } } } while (net->preemptive); HTHost_register(host, net, HTEvent_READ); return HT_WOULD_BLOCK; }