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