1 #include "first.h" 2 3 /** 4 * the HTTP chunk-API 5 * 6 * 7 */ 8 9 #include "http_chunk.h" 10 #include "base.h" 11 #include "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 void http_chunk_len_append(chunkqueue * const cq, uintmax_t len) { 26 char buf[24]; /* 64-bit (8 bytes) is 16 hex chars (+2 \r\n, +1 \0 = 19) */ 27 #if 0 28 buffer b = { buf, 0, sizeof(buf) }; 29 buffer_append_uint_hex(&b, len); 30 buffer_append_string_len(&b, CONST_STR_LEN("\r\n")); 31 chunkqueue_append_mem(cq, b.ptr, b.used-1); 32 #else 33 int i = (int)(sizeof(buf)); 34 buf[--i] = '\n'; 35 buf[--i] = '\r'; 36 do { buf[--i] = "0123456789abcdef"[len & 0x0F]; } while (len >>= 4); 37 chunkqueue_append_mem(cq, buf+i, sizeof(buf)-i); 38 #endif 39 } 40 41 static int http_chunk_len_append_tempfile(chunkqueue * const cq, uintmax_t len, log_error_st * const errh) { 42 char buf[24]; /* 64-bit (8 bytes) is 16 hex chars (+2 \r\n, +1 \0 = 19) */ 43 #if 0 44 buffer b = { buf, 0, sizeof(buf) }; 45 buffer_append_uint_hex(&b, len); 46 buffer_append_string_len(&b, CONST_STR_LEN("\r\n")); 47 return chunkqueue_append_mem_to_tempfile(cq, b.ptr, b.used-1, errh); 48 #else 49 int i = (int)(sizeof(buf)); 50 buf[--i] = '\n'; 51 buf[--i] = '\r'; 52 do { buf[--i] = "0123456789abcdef"[len & 0x0F]; } while (len >>= 4); 53 return chunkqueue_append_mem_to_tempfile(cq, buf+i, sizeof(buf)-i, errh); 54 #endif 55 } 56 57 static int http_chunk_append_file_open_fstat(const request_st * const r, const buffer * const fn, struct stat * const st) { 58 return 59 (r->conf.follow_symlink 60 || !stat_cache_path_contains_symlink(fn, r->conf.errh)) 61 ? stat_cache_open_rdonly_fstat(fn, st, r->conf.follow_symlink) 62 : -1; 63 } 64 65 static int http_chunk_append_read_fd_range(request_st * const r, const buffer * const fn, const int fd, off_t offset, off_t len) { 66 /* note: this routine should not be used for range requests 67 * unless the total size of ranges requested is small */ 68 /* note: future: could read into existing MEM_CHUNK in cq->last if 69 * there is sufficient space, but would need to adjust for existing 70 * offset in for cq->bytes_in in chunkqueue_append_buffer_commit() */ 71 UNUSED(fn); 72 73 chunkqueue * const cq = r->write_queue; 74 75 if (r->resp_send_chunked) 76 http_chunk_len_append(cq, (uintmax_t)len); 77 78 if (0 != offset && -1 == lseek(fd, offset, SEEK_SET)) return -1; 79 buffer * const b = chunkqueue_append_buffer_open_sz(cq, len+2); 80 ssize_t rd; 81 offset = 0; 82 do { 83 rd = read(fd, b->ptr+offset, len-offset); 84 } while (rd > 0 ? (offset += rd, len -= rd) : errno == EINTR); 85 buffer_commit(b, offset); 86 87 if (r->resp_send_chunked) 88 buffer_append_string_len(b, CONST_STR_LEN("\r\n")); 89 90 chunkqueue_append_buffer_commit(cq); 91 return (rd >= 0) ? 0 : -1; 92 } 93 94 static void http_chunk_append_file_fd_range(request_st * const r, const buffer * const fn, const int fd, const off_t offset, const off_t len) { 95 chunkqueue * const cq = r->write_queue; 96 97 if (r->resp_send_chunked) 98 http_chunk_len_append(cq, (uintmax_t)len); 99 100 chunkqueue_append_file_fd(cq, fn, fd, offset, len); 101 102 if (r->resp_send_chunked) 103 chunkqueue_append_mem(cq, CONST_STR_LEN("\r\n")); 104 } 105 106 int http_chunk_append_file_range(request_st * const r, const buffer * const fn, const off_t offset, off_t len) { 107 struct stat st; 108 const int fd = http_chunk_append_file_open_fstat(r, fn, &st); 109 if (fd < 0) return -1; 110 111 if (-1 == len) { 112 if (offset >= st.st_size) { 113 close(fd); 114 return (offset == st.st_size) ? 0 : -1; 115 } 116 len = st.st_size - offset; 117 } 118 else if (st.st_size - offset < len) { 119 close(fd); 120 return -1; 121 } 122 123 http_chunk_append_file_fd_range(r, fn, fd, offset, len); 124 return 0; 125 } 126 127 int http_chunk_append_file(request_st * const r, const buffer * const fn) { 128 struct stat st; 129 const int fd = http_chunk_append_file_open_fstat(r, fn, &st); 130 if (fd < 0) return -1; 131 http_chunk_append_file_fd(r, fn, fd, st.st_size); 132 return 0; 133 } 134 135 int http_chunk_append_file_fd(request_st * const r, const buffer * const fn, const int fd, const off_t sz) { 136 if (sz > 32768) { 137 http_chunk_append_file_fd_range(r, fn, fd, 0, sz); 138 return 0; 139 } 140 141 /*(read small files into memory)*/ 142 int rc = (0 != sz) ? http_chunk_append_read_fd_range(r,fn,fd,0,sz) : 0; 143 close(fd); 144 return rc; 145 } 146 147 static int http_chunk_append_to_tempfile(request_st * const r, const char * const mem, const size_t len) { 148 chunkqueue * const cq = r->write_queue; 149 log_error_st * const errh = r->conf.errh; 150 151 if (r->resp_send_chunked 152 && 0 != http_chunk_len_append_tempfile(cq, len, errh)) 153 return -1; 154 155 if (0 != chunkqueue_append_mem_to_tempfile(cq, mem, len, errh)) 156 return -1; 157 158 if (r->resp_send_chunked 159 && 0 != 160 chunkqueue_append_mem_to_tempfile(cq, CONST_STR_LEN("\r\n"), errh)) 161 return -1; 162 163 return 0; 164 } 165 166 static int http_chunk_append_cq_to_tempfile(request_st * const r, chunkqueue * const src, const size_t len) { 167 chunkqueue * const cq = r->write_queue; 168 log_error_st * const errh = r->conf.errh; 169 170 if (r->resp_send_chunked 171 && 0 != http_chunk_len_append_tempfile(cq, len, errh)) 172 return -1; 173 174 if (0 != chunkqueue_steal_with_tempfiles(cq, src, len, errh)) 175 return -1; 176 177 if (r->resp_send_chunked 178 && 0 != 179 chunkqueue_append_mem_to_tempfile(cq, CONST_STR_LEN("\r\n"), errh)) 180 return -1; 181 182 return 0; 183 } 184 185 __attribute_pure__ 186 static int http_chunk_uses_tempfile(const request_st * const r, const chunkqueue * const cq, const size_t len) { 187 188 /* current usage does not append_mem or append_buffer after appending 189 * file, so not checking if users of this interface have appended large 190 * (references to) files to chunkqueue, which would not be in memory 191 * (but included in calculation for whether or not to use temp file) */ 192 193 /*(allow slightly larger mem use if FDEVENT_STREAM_RESPONSE_BUFMIN 194 * to reduce creation of temp files when backend producer will be 195 * blocked until more data is sent to network to client)*/ 196 197 const chunk * const c = cq->last; 198 return 199 ((c && c->type == FILE_CHUNK && c->file.is_temp) 200 || cq->bytes_in - cq->bytes_out + len 201 > ((r->conf.stream_response_body & FDEVENT_STREAM_RESPONSE_BUFMIN) 202 ? 128*1024 203 : 64*1024)); 204 } 205 206 int http_chunk_append_buffer(request_st * const r, buffer * const mem) { 207 size_t len = buffer_string_length(mem); 208 if (0 == len) return 0; 209 210 chunkqueue * const cq = r->write_queue; 211 212 if (http_chunk_uses_tempfile(r, cq, len)) 213 return http_chunk_append_to_tempfile(r, mem->ptr, len); 214 215 if (r->resp_send_chunked) 216 http_chunk_len_append(cq, len); 217 218 /*(chunkqueue_append_buffer() might steal buffer contents)*/ 219 chunkqueue_append_buffer(cq, mem); 220 221 if (r->resp_send_chunked) 222 chunkqueue_append_mem(cq, CONST_STR_LEN("\r\n")); 223 224 return 0; 225 } 226 227 int http_chunk_append_mem(request_st * const r, const char * const mem, const size_t len) { 228 if (0 == len) return 0; 229 force_assert(NULL != mem); 230 231 chunkqueue * const cq = r->write_queue; 232 233 if (http_chunk_uses_tempfile(r, cq, len)) 234 return http_chunk_append_to_tempfile(r, mem, len); 235 236 if (r->resp_send_chunked) 237 http_chunk_len_append(cq, len); 238 239 chunkqueue_append_mem(cq, mem, len); 240 241 if (r->resp_send_chunked) 242 chunkqueue_append_mem(cq, CONST_STR_LEN("\r\n")); 243 244 return 0; 245 } 246 247 int http_chunk_transfer_cqlen(request_st * const r, chunkqueue * const src, const size_t len) { 248 if (0 == len) return 0; 249 250 chunkqueue * const cq = r->write_queue; 251 252 if (http_chunk_uses_tempfile(r, cq, len)) 253 return http_chunk_append_cq_to_tempfile(r, src, len); 254 255 if (r->resp_send_chunked) 256 http_chunk_len_append(cq, len); 257 258 chunkqueue_steal(cq, src, len); 259 260 if (r->resp_send_chunked) 261 chunkqueue_append_mem(cq, CONST_STR_LEN("\r\n")); 262 263 return 0; 264 } 265 266 void http_chunk_close(request_st * const r) { 267 if (r->resp_send_chunked) 268 chunkqueue_append_mem(r->write_queue, CONST_STR_LEN("0\r\n\r\n")); 269 } 270