From 44bd452609c8e429d324d4205460c656dd91bd5f Mon Sep 17 00:00:00 2001 From: Wilfried Goesgens Date: Sun, 6 Oct 2013 13:27:47 +0200 Subject: [PATCH] Implement range request for content download --- webcit/context_loop.c | 26 +++++++ webcit/crypto.c | 7 +- webcit/downloads.c | 10 +-- webcit/tcp_sockets.c | 153 ++++++++++++++++++++++++++++++++++++++++++ webcit/tcp_sockets.h | 1 + webcit/webcit.c | 27 +++++++- webcit/webcit.h | 7 +- 7 files changed, 222 insertions(+), 9 deletions(-) diff --git a/webcit/context_loop.c b/webcit/context_loop.c index ee30aaa43..a04c711bf 100644 --- a/webcit/context_loop.c +++ b/webcit/context_loop.c @@ -714,6 +714,31 @@ void Header_HandleAcceptEncoding(StrBuf *Line, ParsedHttpHdrs *hdr) hdr->HR.gzip_ok = 1; } } + +void Header_HandleContentRange(StrBuf *Line, ParsedHttpHdrs *hdr) +{ + const char *PRange = ChrPtr(Line); + + while ((*PRange != '=') && (*PRange != '\0')) + PRange ++; + if (*PRange == '=') + PRange ++; + if ((*PRange == '\0')) + return; + hdr->HaveRange = 1; + hdr->RangeStart = atol(PRange); + + while (isdigit(*PRange)) + PRange++; + + if (*PRange == '-') + PRange ++; + if ((*PRange == '\0')) + hdr->RangeTil = -1; + else + hdr->RangeTil = atol(PRange); +} + const char *ReqStrs[eNONE] = { "GET", "POST", @@ -798,6 +823,7 @@ void InitModule_CONTEXT (void) { + RegisterHeaderHandler(HKEY("RANGE"), Header_HandleContentRange); RegisterHeaderHandler(HKEY("CONTENT-LENGTH"), Header_HandleContentLength); RegisterHeaderHandler(HKEY("CONTENT-TYPE"), Header_HandleContentType); RegisterHeaderHandler(HKEY("X-FORWARDED-HOST"), Header_HandleXFFHost); /* Apache way... */ diff --git a/webcit/crypto.c b/webcit/crypto.c index d7b460cde..f2752fd57 100644 --- a/webcit/crypto.c +++ b/webcit/crypto.c @@ -501,7 +501,7 @@ void ssl_lock(int mode, int n, const char *file, int line) /* * Send binary data to the client encrypted. */ -void client_write_ssl(const StrBuf *Buf) +int client_write_ssl(const StrBuf *Buf) { const char *buf; int retval; @@ -509,7 +509,7 @@ void client_write_ssl(const StrBuf *Buf) long nbytes; char junk[1]; - if (THREADSSL == NULL) return; + if (THREADSSL == NULL) return -1; nbytes = nremain = StrLength(Buf); buf = ChrPtr(Buf); @@ -535,10 +535,11 @@ void client_write_ssl(const StrBuf *Buf) syslog(LOG_WARNING, "errno is %d\n", errno); } endtls(); - return; + return -1; } nremain -= retval; } + return 0; } diff --git a/webcit/downloads.c b/webcit/downloads.c index 836398208..8005d3da8 100644 --- a/webcit/downloads.c +++ b/webcit/downloads.c @@ -271,13 +271,15 @@ void download_file(void) StrBufCutLeft(Buf, 4); bytes = StrBufExtract_long(Buf, 0, '|'); StrBufExtract_token(ContentType, Buf, 3, '|'); - serv_read_binary(WCC->WBuf, bytes, Buf); - serv_puts("CLOS"); - StrBuf_ServGetln(Buf); + CheckGZipCompressionAllowed (SKEY(ContentType)); if (force_download) FlushStrBuf(ContentType); - http_transmit_thing(ChrPtr(ContentType), 0); + + serv_read_binary_to_http(ContentType, bytes, 0, 0); + serv_puts("CLOS"); + StrBuf_ServGetln(Buf); +// http_transmit_thing(ChrPtr(ContentType), 0); } else { StrBufCutLeft(Buf, 4); hprintf("HTTP/1.1 404 %s\n", ChrPtr(Buf)); diff --git a/webcit/tcp_sockets.c b/webcit/tcp_sockets.c index f1bcd5bc0..963007f87 100644 --- a/webcit/tcp_sockets.c +++ b/webcit/tcp_sockets.c @@ -456,6 +456,159 @@ int serv_read_binary(StrBuf *Ret, size_t total_len, StrBuf *Buf) } +int client_write(StrBuf *ThisBuf) +{ + wcsession *WCC = WC; + const char *ptr, *eptr; + long count; + ssize_t res = 0; + fd_set wset; + int fdflags; + + ptr = ChrPtr(ThisBuf); + count = StrLength(ThisBuf); + eptr = ptr + count; + + fdflags = fcntl(WC->Hdr->http_sock, F_GETFL); + + while ((ptr < eptr) && (WCC->Hdr->http_sock != -1)) { + if ((fdflags & O_NONBLOCK) == O_NONBLOCK) { + FD_ZERO(&wset); + FD_SET(WCC->Hdr->http_sock, &wset); + if (select(WCC->Hdr->http_sock + 1, NULL, &wset, NULL, NULL) == -1) { + syslog(LOG_INFO, "client_write: Socket select failed (%s)\n", strerror(errno)); + return -1; + } + } + + if ((WCC->Hdr->http_sock == -1) || + (res = write(WCC->Hdr->http_sock, + ptr, + count)) == -1) { + syslog(LOG_INFO, "client_write: Socket write failed (%s)\n", strerror(errno)); + wc_backtrace(LOG_INFO); + return res; + } + count -= res; + ptr += res; + } + return 0; +} + +/* + * Read binary data from server into memory using a series of server READ commands. + * returns the read content as StrBuf + */ +void serv_read_binary_to_http(StrBuf *MimeType, size_t total_len, int is_static, int detect_mime) +{ + wcsession *WCC = WC; + size_t bytes_read = 0; + size_t this_block = 0; + int rc = 6; + int ServerRc = 6; + int first = 1; + int chunked = 0; + StrBuf *BufHeader; + StrBuf *Buf; + + + if (WCC->Hdr->HaveRange) + { + WCC->Hdr->HaveRange++; + WCC->Hdr->TotalBytes = total_len; + /* open range? or beyound file border? correct the numbers. */ + if ((WCC->Hdr->RangeTil == -1) || (WCC->Hdr->RangeTil>= total_len)) + WCC->Hdr->RangeTil = total_len - 1; + bytes_read = WCC->Hdr->RangeStart; + total_len = WCC->Hdr->RangeTil; + } + else + chunked = total_len > SIZ * 10; /* TODO: disallow for HTTP / 1.0 */ + + if (chunked) + { + BufHeader=NewStrBuf(); + } + Buf = NewStrBuf(); + + http_transmit_headers(ChrPtr(MimeType), is_static, chunked); +#ifdef HAVE_OPENSSL + if (is_https) + client_write_ssl(WCC->HBuf); + else +#endif + client_write(WCC->HBuf); + + while ((bytes_read < total_len) && (ServerRc == 6)) { + + if (WCC->serv_sock==-1) { + FlushStrBuf(WCC->WBuf); + return; + } + + serv_printf("READ "SIZE_T_FMT"|"SIZE_T_FMT, bytes_read, total_len-bytes_read); + if ( (rc = StrBuf_ServGetln(Buf) > 0) && + (ServerRc = GetServerStatus(Buf, NULL), ServerRc == 6) ) + { + if (rc < 0) + return; + StrBufCutLeft(Buf, 4); + this_block = StrTol(Buf); + rc = StrBuf_ServGetBLOBBuffered(WCC->WBuf, this_block); + if (rc < 0) { + syslog(LOG_INFO, "Server connection broken during download\n"); + wc_backtrace(LOG_INFO); + if (WCC->serv_sock > 0) close(WCC->serv_sock); + WCC->serv_sock = (-1); + WCC->connected = 0; + WCC->logged_in = 0; + return; + } + bytes_read += rc; + + } + + if (chunked) + { + StrBufPrintf(BufHeader, "%s%x\r\n", + (first)?"":"\r\n", + StrLength (WCC->WBuf)); +#ifdef HAVE_OPENSSL + if (is_https) + rc = client_write_ssl(BufHeader); + else +#endif + rc = client_write(BufHeader); + if (rc < 0) + break; + } + +#ifdef HAVE_OPENSSL + if (is_https) + rc = client_write_ssl(WCC->WBuf); + else +#endif + rc = client_write(WCC->WBuf); + + if (rc < 0) + break; + first = 0; + FlushStrBuf(WCC->WBuf); + } + + if (chunked) + { + StrBufPrintf(BufHeader, "\r\n0\r\n\r\n"); +#ifdef HAVE_OPENSSL + if (is_https) + rc = client_write_ssl(BufHeader); + else +#endif + rc = client_write(BufHeader); + } + +} + int ClientGetLine(ParsedHttpHdrs *Hdr, StrBuf *Target) { const char *Error; diff --git a/webcit/tcp_sockets.h b/webcit/tcp_sockets.h index 8528539bf..454666905 100644 --- a/webcit/tcp_sockets.h +++ b/webcit/tcp_sockets.h @@ -40,6 +40,7 @@ int serv_write(const char *buf, int nbytes); int serv_putbuf(const StrBuf *string); int serv_printf(const char *format,...)__attribute__((__format__(__printf__,1,2))); int serv_read_binary(StrBuf *Ret, size_t total_len, StrBuf *Buf); +void serv_read_binary_to_http(StrBuf *MimeType, size_t total_len, int is_static, int detect_mime); int StrBuf_ServGetBLOB(StrBuf *buf, long BlobSize); int StrBuf_ServGetBLOBBuffered(StrBuf *buf, long BlobSize); int read_server_text(StrBuf *Buf, long *nLines); diff --git a/webcit/webcit.c b/webcit/webcit.c index 851829af9..89de551eb 100644 --- a/webcit/webcit.c +++ b/webcit/webcit.c @@ -141,7 +141,11 @@ void output_headers( int do_httpheaders, /* 1 = output HTTP headers */ wcsession *WCC = WC; char httpnow[128]; - hprintf("HTTP/1.1 200 OK\n"); + if (WCC->Hdr->HaveRange > 1) + hprintf("HTTP/1.1 206 Partial Content\r\n"); + else + hprintf("HTTP/1.1 200 OK\r\n"); + http_datestring(httpnow, sizeof httpnow, time(NULL)); if (do_httpheaders) { @@ -245,6 +249,27 @@ void http_transmit_thing(const char *content_type, int is_static) end_burst(); } +void http_transmit_headers(const char *content_type, int is_static, long is_chunked) +{ + wcsession *WCC = WC; + syslog(LOG_DEBUG, "http_transmit_thing(%s)%s", content_type, ((is_static > 0) ? " (static)" : "")); + output_headers(0, 0, 0, 0, 0, is_static); + + if (WCC->Hdr->HaveRange) + hprintf("Accept-Ranges: bytes\r\n" + "Content-Range: bytes %ld-%ld/%ld\r\n", + WCC->Hdr->RangeStart, + WCC->Hdr->RangeTil, + WCC->Hdr->TotalBytes); + + hprintf("Content-type: %s\r\n" + "Server: "PACKAGE_STRING"\r\n" + "%s" + "Connection: close\r\n\r\n", + content_type, + (is_chunked)?"Transfer-Encoding: chunked\r\n":""); +} + /* * Convenience functions to display a page containing only a string diff --git a/webcit/webcit.h b/webcit/webcit.h index bab66fd0c..5180a2e25 100644 --- a/webcit/webcit.h +++ b/webcit/webcit.h @@ -389,6 +389,10 @@ typedef struct _HdrRefs { typedef struct _ParsedHttpHdrs { int http_sock; /* HTTP server socket */ + long HaveRange; + long RangeStart; + long RangeTil; + long TotalBytes; const char *Pos; StrBuf *ReadBuf; @@ -564,7 +568,7 @@ void ssl_lock(int mode, int n, const char *file, int line); int starttls(int sock); extern SSL_CTX *ssl_ctx; int client_read_sslbuffer(StrBuf *buf, int timeout); -void client_write_ssl(const StrBuf *Buf); +int client_write_ssl(const StrBuf *Buf); #endif extern int is_https; @@ -691,6 +695,7 @@ extern char *days[]; long locate_user_vcard_in_this_room(message_summary **VCMsg, wc_mime_attachment **VCAtt); void http_transmit_thing(const char *content_type, int is_static); +void http_transmit_headers(const char *content_type, int is_static, long is_chunked); long unescape_input(char *buf); void check_thread_pool_size(void); void StrEndTab(StrBuf *Target, int tabnum, int num_tabs); -- 2.30.2