version 0.2.1
[fms.git] / libs / shttpd / io_file.c
1 /*
2  * Copyright (c) 2004-2005 Sergey Lyubka <valenok@gmail.com>
3  * All rights reserved
4  *
5  * "THE BEER-WARE LICENSE" (Revision 42):
6  * Sergey Lyubka wrote this file.  As long as you retain this notice you
7  * can do whatever you want with this stuff. If we meet some day, and you think
8  * this stuff is worth it, you can buy me a beer in return.
9  */
10
11 #include "defs.h"
12
13 static int
14 write_file(struct stream *stream, const void *buf, size_t len)
15 {
16         struct stat     st;
17         struct stream   *rem = &stream->conn->rem;
18         int             n, fd = stream->chan.fd;
19
20         assert(fd != -1);
21         n = write(fd, buf, len);
22
23         DBG(("put_file(%p, %d): %d bytes", (void *) stream, len, n));
24
25         if (n <= 0 || (rem->io.total >= (big_int_t) rem->headers_len)) {
26                 (void) fstat(fd, &st);
27                 stream->io.head = stream->headers_len =
28                     my_snprintf(stream->io.buf,
29                     stream->io.size, "HTTP/1.1 %d OK\r\n"
30                     "Content-Length: %lu\r\nConnection: close\r\n\r\n",
31                     stream->conn->status, st.st_size);
32                 stop_stream(stream);
33         }
34
35         return (n);
36 }
37
38 static int
39 read_file(struct stream *stream, void *buf, size_t len)
40 {
41 #ifdef USE_SENDFILE
42         struct  iovec   vec;
43         struct  sf_hdtr hd = {&vec, 1, NULL, 0}, *hdp = &hd;
44         int             sock, fd, n;
45         size_t          nbytes;
46         off_t           sent;
47
48         sock = stream->conn->rem.chan.sock;
49         fd = stream->chan.fd;
50
51         /* If this is the first call for this file, send the headers */
52         vec.iov_base = stream->io.buf;
53         vec.iov_len = stream->headers_len;
54         if (stream->io.total > 0)
55                 hdp = NULL;
56
57         nbytes = stream->content_len - stream->io.total;
58         n = sendfile(fd, sock, lseek(fd, 0, SEEK_CUR), nbytes, hdp, &sent, 0);
59
60         if (n == -1 && ERRNO != EAGAIN) {
61                 stream->flags &= ~FLAG_DONT_CLOSE;
62                 return (n);
63         }
64
65         stream->conn->ctx->out += sent;
66
67         /* If we have sent the HTTP headers in this turn, clear them off */
68         if (stream->io.total == 0) {
69                 assert(sent >= stream->headers_len);
70                 sent -= stream->headers_len;
71                 io_clear(&stream->io);
72         }
73
74         (void) lseek(fd, sent, SEEK_CUR);
75         stream->io.total += sent;
76         stream->flags |= FLAG_DONT_CLOSE;
77
78         return (0);
79 #endif /* USE_SENDFILE */
80
81         assert(stream->chan.fd != -1);
82         return (read(stream->chan.fd, buf, len));
83 }
84
85 static void
86 close_file(struct stream *stream)
87 {
88         assert(stream->chan.fd != -1);
89         (void) close(stream->chan.fd);
90 }
91
92 void
93 get_file(struct conn *c, struct stat *stp)
94 {
95         char            date[64], lm[64], etag[64], range[64] = "";
96         size_t          n, status = 200;
97         unsigned long   r1, r2;
98         const char      *fmt = "%a, %d %b %Y %H:%M:%S GMT", *msg = "OK";
99         big_int_t       cl; /* Content-Length */
100
101         if (c->mime_type.len == 0)
102                 get_mime_type(c->ctx, c->uri, strlen(c->uri), &c->mime_type); 
103         cl = (big_int_t) stp->st_size;
104
105         /* If Range: header specified, act accordingly */
106         if (c->ch.range.v_vec.len > 0 &&
107             (n = sscanf(c->ch.range.v_vec.ptr,"bytes=%lu-%lu",&r1, &r2)) > 0) {
108                 status = 206;
109                 (void) lseek(c->loc.chan.fd, r1, SEEK_SET);
110                 cl = n == 2 ? r2 - r1 + 1: cl - r1;
111                 (void) my_snprintf(range, sizeof(range),
112                     "Content-Range: bytes %lu-%lu/%lu\r\n",
113                     r1, r1 + cl - 1, (unsigned long) stp->st_size);
114                 msg = "Partial Content";
115         }
116
117         /* Prepare Etag, Date, Last-Modified headers */
118         (void) strftime(date, sizeof(date), fmt, localtime(&current_time));
119         (void) strftime(lm, sizeof(lm), fmt, localtime(&stp->st_mtime));
120         (void) my_snprintf(etag, sizeof(etag), "%lx.%lx",
121             (unsigned long) stp->st_mtime, (unsigned long) stp->st_size);
122
123         /*
124          * We do not do io_inc_head here, because it will increase 'total'
125          * member in io. We want 'total' to be equal to the content size,
126          * and exclude the headers length from it.
127          */
128         c->loc.io.head = c->loc.headers_len = my_snprintf(c->loc.io.buf,
129             c->loc.io.size,
130             "HTTP/1.1 %d %s\r\n"
131             "Date: %s\r\n"
132             "Last-Modified: %s\r\n"
133             "Etag: \"%s\"\r\n"
134             "Content-Type: %.*s\r\n"
135             "Content-Length: %lu\r\n"
136             "Accept-Ranges: bytes\r\n"
137             "%s\r\n",
138             status, msg, date, lm, etag,
139             c->mime_type.len, c->mime_type.ptr, cl, range);
140
141         c->status = status;
142         c->loc.content_len = cl;
143         c->loc.io_class = &io_file;
144         c->loc.flags |= FLAG_R | FLAG_ALWAYS_READY;
145
146         if (c->method == METHOD_HEAD)
147                 stop_stream(&c->loc);
148 }
149
150 const struct io_class   io_file =  {
151         "file",
152         read_file,
153         write_file,
154         close_file
155 };