version 0.1.6
[fms.git] / libs / shttpd / io_file.c
diff --git a/libs/shttpd/io_file.c b/libs/shttpd/io_file.c
new file mode 100644 (file)
index 0000000..d2b3fb6
--- /dev/null
@@ -0,0 +1,154 @@
+/*
+ * Copyright (c) 2004-2005 Sergey Lyubka <valenok@gmail.com>
+ * All rights reserved
+ *
+ * "THE BEER-WARE LICENSE" (Revision 42):
+ * Sergey Lyubka wrote this file.  As long as you retain this notice you
+ * can do whatever you want with this stuff. If we meet some day, and you think
+ * this stuff is worth it, you can buy me a beer in return.
+ */
+
+#include "defs.h"
+
+static int
+write_file(struct stream *stream, const void *buf, size_t len)
+{
+       struct stat     st;
+       struct stream   *rem = &stream->conn->rem;
+       int             n, fd = stream->chan.fd;
+
+       assert(fd != -1);
+       n = write(fd, buf, len);
+
+       DBG(("put_file(%p, %d): %d bytes", (void *) stream, len, n));
+
+       if (n <= 0 || (rem->io.total >= (big_int_t) rem->headers_len)) {
+               (void) fstat(fd, &st);
+               stream->io.head = stream->headers_len =
+                   my_snprintf(stream->io.buf,
+                   stream->io.size, "HTTP/1.1 %d OK\r\n"
+                   "Content-Length: %lu\r\nConnection: close\r\n\r\n",
+                   stream->conn->status, st.st_size);
+               stop_stream(stream);
+       }
+
+       return (n);
+}
+
+static int
+read_file(struct stream *stream, void *buf, size_t len)
+{
+#ifdef USE_SENDFILE
+       struct  iovec   vec;
+       struct  sf_hdtr hd = {&vec, 1, NULL, 0}, *hdp = &hd;
+       int             sock, fd, n;
+       size_t          nbytes;
+       off_t           sent;
+
+       sock = stream->conn->rem.chan.sock;
+       fd = stream->chan.fd;
+
+       /* If this is the first call for this file, send the headers */
+       vec.iov_base = stream->io.buf;
+       vec.iov_len = stream->headers_len;
+       if (stream->io.total > 0)
+               hdp = NULL;
+
+       nbytes = stream->content_len - stream->io.total;
+       n = sendfile(fd, sock, lseek(fd, 0, SEEK_CUR), nbytes, hdp, &sent, 0);
+
+       if (n == -1 && ERRNO != EAGAIN) {
+               stream->flags &= ~FLAG_DONT_CLOSE;
+               return (n);
+       }
+
+       stream->conn->ctx->out += sent;
+
+       /* If we have sent the HTTP headers in this turn, clear them off */
+       if (stream->io.total == 0) {
+               assert(sent >= stream->headers_len);
+               sent -= stream->headers_len;
+               io_clear(&stream->io);
+       }
+
+       (void) lseek(fd, sent, SEEK_CUR);
+       stream->io.total += sent;
+       stream->flags |= FLAG_DONT_CLOSE;
+
+       return (0);
+#endif /* USE_SENDFILE */
+
+       assert(stream->chan.fd != -1);
+       return (read(stream->chan.fd, buf, len));
+}
+
+static void
+close_file(struct stream *stream)
+{
+       assert(stream->chan.fd != -1);
+       (void) close(stream->chan.fd);
+}
+
+void
+get_file(struct conn *c, struct stat *stp)
+{
+       char            date[64], lm[64], etag[64], range[64] = "";
+       size_t          n, status = 200;
+       unsigned long   r1, r2;
+       const char      *fmt = "%a, %d %b %Y %H:%M:%S GMT", *msg = "OK";
+       big_int_t       cl; /* Content-Length */
+
+       if (c->mime_type == NULL)
+               c->mime_type = get_mime_type(c->ctx, c->uri, strlen(c->uri));
+       cl = (big_int_t) stp->st_size;
+
+       /* If Range: header specified, act accordingly */
+       if (c->ch.range.v_vec.len > 0 &&
+           (n = sscanf(c->ch.range.v_vec.ptr,"bytes=%lu-%lu",&r1, &r2)) > 0) {
+               status = 206;
+               (void) lseek(c->loc.chan.fd, r1, SEEK_SET);
+               cl = n == 2 ? r2 - r1 + 1: cl - r1;
+               (void) my_snprintf(range, sizeof(range),
+                   "Content-Range: bytes %lu-%lu/%lu\r\n",
+                   r1, r1 + cl - 1, (unsigned long) stp->st_size);
+               msg = "Partial Content";
+       }
+
+       /* Prepare Etag, Date, Last-Modified headers */
+       (void) strftime(date, sizeof(date), fmt, localtime(&current_time));
+       (void) strftime(lm, sizeof(lm), fmt, localtime(&stp->st_mtime));
+       (void) my_snprintf(etag, sizeof(etag), "%lx.%lx",
+           (unsigned long) stp->st_mtime, (unsigned long) stp->st_size);
+
+       /*
+        * We do not do io_inc_head here, because it will increase 'total'
+        * member in io. We want 'total' to be equal to the content size,
+        * and exclude the headers length from it.
+        */
+       c->loc.io.head = c->loc.headers_len = my_snprintf(c->loc.io.buf,
+           c->loc.io.size,
+           "HTTP/1.1 %d %s\r\n"
+           "Date: %s\r\n"
+           "Last-Modified: %s\r\n"
+           "Etag: \"%s\"\r\n"
+           "Content-Type: %s\r\n"
+           "Content-Length: %lu\r\n"
+           "Connection: close\r\n"
+           "%s\r\n",
+           status, msg, date, lm, etag, c->mime_type, cl, range);
+
+       c->status = status;
+       c->loc.content_len = cl;
+       c->loc.io_class = &io_file;
+       c->loc.flags |= FLAG_R | FLAG_ALWAYS_READY;
+
+       if (c->method == METHOD_HEAD)
+               stop_stream(&c->loc);
+}
+
+const struct io_class  io_file =  {
+       "file",
+       read_file,
+       write_file,
+       close_file
+};