1 #include "first.h" 2 3 /** 4 * the HTTP chunk-API 5 * 6 * 7 */ 8 9 #include "base.h" 10 #include "chunk.h" 11 #include "http_chunk.h" 12 #include "stat_cache.h" 13 #include "fdevent.h" 14 #include "log.h" 15 16 #include <sys/types.h> 17 #include <sys/stat.h> 18 19 #include <stdlib.h> 20 #include <unistd.h> 21 22 #include <errno.h> 23 #include <string.h> 24 25 static buffer * http_chunk_header(buffer *b, uintmax_t len) { 26 buffer_clear(b); 27 buffer_append_uint_hex(b, len); 28 buffer_append_string_len(b, CONST_STR_LEN("\r\n")); 29 return b; 30 } 31 32 static void http_chunk_append_len(server *srv, connection *con, uintmax_t len) { 33 buffer *b = http_chunk_header(srv->tmp_chunk_len, len); 34 chunkqueue_append_mem(con->write_queue, CONST_BUF_LEN(b)); 35 } 36 37 static int http_chunk_append_file_open_fstat(server *srv, connection *con, buffer *fn, struct stat *st) { 38 if (!con->conf.follow_symlink 39 && 0 != stat_cache_path_contains_symlink(srv, fn)) { 40 return -1; 41 } 42 43 return stat_cache_open_rdonly_fstat(fn, st, con->conf.follow_symlink); 44 } 45 46 static void http_chunk_append_file_fd_range(server *srv, connection *con, buffer *fn, int fd, off_t offset, off_t len) { 47 chunkqueue *cq = con->write_queue; 48 49 if (con->response.send_chunked) { 50 http_chunk_append_len(srv, con, (uintmax_t)len); 51 } 52 53 chunkqueue_append_file_fd(cq, fn, fd, offset, len); 54 55 if (con->response.send_chunked) { 56 chunkqueue_append_mem(cq, CONST_STR_LEN("\r\n")); 57 } 58 } 59 60 int http_chunk_append_file_range(server *srv, connection *con, buffer *fn, off_t offset, off_t len) { 61 struct stat st; 62 const int fd = http_chunk_append_file_open_fstat(srv, con, fn, &st); 63 if (fd < 0) return -1; 64 65 if (-1 == len) { 66 if (offset >= st.st_size) { 67 close(fd); 68 return (offset == st.st_size) ? 0 : -1; 69 } 70 len = st.st_size - offset; 71 } else if (st.st_size - offset < len) { 72 close(fd); 73 return -1; 74 } 75 76 http_chunk_append_file_fd_range(srv, con, fn, fd, offset, len); 77 return 0; 78 } 79 80 int http_chunk_append_file(server *srv, connection *con, buffer *fn) { 81 struct stat st; 82 const int fd = http_chunk_append_file_open_fstat(srv, con, fn, &st); 83 if (fd < 0) return -1; 84 http_chunk_append_file_fd(srv, con, fn, fd, st.st_size); 85 return 0; 86 } 87 88 void http_chunk_append_file_fd(server *srv, connection *con, buffer *fn, int fd, off_t sz) { 89 if (0 != sz) { 90 http_chunk_append_file_fd_range(srv, con, fn, fd, 0, sz); 91 } else { 92 close(fd); 93 } 94 } 95 96 static int http_chunk_append_to_tempfile(server *srv, connection *con, const char * mem, size_t len) { 97 chunkqueue * const cq = con->write_queue; 98 99 if (con->response.send_chunked) { 100 buffer *b = http_chunk_header(srv->tmp_chunk_len, len); 101 if (0 != chunkqueue_append_mem_to_tempfile(srv, cq, CONST_BUF_LEN(b))) { 102 return -1; 103 } 104 } 105 106 if (0 != chunkqueue_append_mem_to_tempfile(srv, cq, mem, len)) { 107 return -1; 108 } 109 110 if (con->response.send_chunked) { 111 if (0 != chunkqueue_append_mem_to_tempfile(srv, cq, CONST_STR_LEN("\r\n"))) { 112 return -1; 113 } 114 } 115 116 return 0; 117 } 118 119 static int http_chunk_append_cq_to_tempfile(server *srv, connection *con, chunkqueue *src, size_t len) { 120 chunkqueue * const cq = con->write_queue; 121 122 if (con->response.send_chunked) { 123 buffer *b = http_chunk_header(srv->tmp_chunk_len, len); 124 if (0 != chunkqueue_append_mem_to_tempfile(srv, cq, CONST_BUF_LEN(b))) { 125 return -1; 126 } 127 } 128 129 if (0 != chunkqueue_steal_with_tempfiles(srv, cq, src, len)) { 130 return -1; 131 } 132 133 if (con->response.send_chunked) { 134 if (0!=chunkqueue_append_mem_to_tempfile(srv,cq,CONST_STR_LEN("\r\n"))){ 135 return -1; 136 } 137 } 138 139 return 0; 140 } 141 142 static int http_chunk_uses_tempfile(server *srv, connection *con, size_t len) { 143 chunkqueue * const cq = con->write_queue; 144 chunk *c = cq->last; 145 UNUSED(srv); 146 147 /* current usage does not append_mem or append_buffer after appending 148 * file, so not checking if users of this interface have appended large 149 * (references to) files to chunkqueue, which would not be in memory 150 * (but included in calculation for whether or not to use temp file) */ 151 152 /*(allow slightly larger mem use if FDEVENT_STREAM_RESPONSE_BUFMIN 153 * to reduce creation of temp files when backend producer will be 154 * blocked until more data is sent to network to client)*/ 155 156 if ((c && c->type == FILE_CHUNK && c->file.is_temp) 157 || cq->bytes_in - cq->bytes_out + len 158 > 1024 * ((con->conf.stream_response_body & FDEVENT_STREAM_RESPONSE_BUFMIN) ? 128 : 64)) { 159 return 1; 160 } 161 162 return 0; 163 } 164 165 int http_chunk_append_buffer(server *srv, connection *con, buffer *mem) { 166 chunkqueue * const cq = con->write_queue; 167 size_t len = buffer_string_length(mem); 168 if (0 == len) return 0; 169 170 if (http_chunk_uses_tempfile(srv, con, len)) { 171 return http_chunk_append_to_tempfile(srv, con, mem->ptr, len); 172 } 173 174 if (con->response.send_chunked) { 175 http_chunk_append_len(srv, con, len); 176 } 177 178 /*(chunkqueue_append_buffer() might steal buffer contents)*/ 179 chunkqueue_append_buffer(cq, mem); 180 181 if (con->response.send_chunked) { 182 chunkqueue_append_mem(cq, CONST_STR_LEN("\r\n")); 183 } 184 185 return 0; 186 } 187 188 int http_chunk_append_mem(server *srv, connection *con, const char * mem, size_t len) { 189 chunkqueue * const cq = con->write_queue; 190 if (0 == len) return 0; 191 force_assert(NULL != mem); 192 193 if (http_chunk_uses_tempfile(srv, con, len)) { 194 return http_chunk_append_to_tempfile(srv, con, mem, len); 195 } 196 197 if (con->response.send_chunked) { 198 http_chunk_append_len(srv, con, len); 199 } 200 201 chunkqueue_append_mem(cq, mem, len); 202 203 if (con->response.send_chunked) { 204 chunkqueue_append_mem(cq, CONST_STR_LEN("\r\n")); 205 } 206 207 return 0; 208 } 209 210 int http_chunk_transfer_cqlen(server *srv, connection *con, chunkqueue *src, size_t len) { 211 chunkqueue * const cq = con->write_queue; 212 if (0 == len) return 0; 213 214 if (http_chunk_uses_tempfile(srv, con, len)) { 215 return http_chunk_append_cq_to_tempfile(srv, con, src, len); 216 } 217 218 if (con->response.send_chunked) { 219 http_chunk_append_len(srv, con, len); 220 } 221 222 chunkqueue_steal(cq, src, len); 223 224 if (con->response.send_chunked) { 225 chunkqueue_append_mem(cq, CONST_STR_LEN("\r\n")); 226 } 227 228 return 0; 229 } 230 231 void http_chunk_close(server *srv, connection *con) { 232 UNUSED(srv); 233 force_assert(NULL != con); 234 235 if (con->response.send_chunked) { 236 chunkqueue_append_mem(con->write_queue, CONST_STR_LEN("0\r\n\r\n")); 237 } 238 } 239