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 == NULL) |
---|
102 | c->mime_type = get_mime_type(c->ctx, c->uri, strlen(c->uri)); |
---|
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(¤t_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 | "Connection: close\r\n" |
---|
137 | "%s\r\n", |
---|
138 | status, msg, date, lm, etag, c->mime_type, cl, range); |
---|
139 | |
---|
140 | c->status = status; |
---|
141 | c->loc.content_len = cl; |
---|
142 | c->loc.io_class = &io_file; |
---|
143 | c->loc.flags |= FLAG_R | FLAG_ALWAYS_READY; |
---|
144 | |
---|
145 | if (c->method == METHOD_HEAD) |
---|
146 | stop_stream(&c->loc); |
---|
147 | } |
---|
148 | |
---|
149 | const struct io_class io_file = { |
---|
150 | "file", |
---|
151 | read_file, |
---|
152 | write_file, |
---|
153 | close_file |
---|
154 | }; |
---|