PUT works again
[krsd] / src / handler / storage.c
1 /*
2  * rs-serve - (c) 2013 Niklas E. Cathor
3  *
4  * This program is distributed in the hope that it will be useful,
5  * but WITHOUT ANY WARRANTY; without even the implied warranty of
6  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
7  * GNU Affero General Public License for more details.
8  *
9  * You should have received a copy of the GNU Affero General Public License
10  * along with this program. If not, see <http://www.gnu.org/licenses/>.
11  */
12
13 #include "rs-serve.h"
14
15 /*
16  * Storage Handler
17  * ---------------
18  *
19  * Gets parsed requests and performs the requested actions / sends the requested
20  * response.
21  *
22  */
23
24 static char *escape_name(const char *name);
25 static char *make_etag(struct stat *stat_buf);
26 static char *make_disk_path(char *user, char *path, char **storage_root);
27 static evhtp_res serve_directory(evhtp_request_t *request, char *disk_path,
28                                  struct stat *stat_buf);
29 static int serve_file_head(evhtp_request_t *request_t, char *disk_path,
30                            struct stat *stat_buf,const char *mime_type);
31 static int serve_file(evhtp_request_t *request, const char *disk_path,
32                       struct stat *stat_buf);
33 static evhtp_res handle_get_or_head(evhtp_request_t *request, int include_body);
34 static int content_type_to_xattr(int fd, const char *content_type);
35
36
37 evhtp_res storage_handle_head(evhtp_request_t *request) {
38   return handle_get_or_head(request, 0);
39 }
40
41 evhtp_res storage_handle_get(evhtp_request_t *request) {
42   return handle_get_or_head(request, 1);
43 }
44
45 /* struct rs_put_context { */
46 /*   int fd; */
47 /*   struct evbuffer *buf; */
48 /*   evhtp_request_t *request; */
49 /* }; */
50
51 /* static void finalize_put(struct rs_put_context *context) { */
52 /*   close(context->fd); */
53 /*   evbuffer_free(context->buf); */
54 /*   evhtp_send_reply(context->request, 200); */
55 /*   free(context); */
56 /* } */
57
58 /* static void write_from_put(struct bufferevent *bev, void *arg) { */
59 /*   log_debug("write_from_put()"); */
60 /*   struct rs_put_context *context = arg; */
61 /*   bufferevent_read_buffer(bev, context->buf); */
62 /*   size_t count = evbuffer_get_length(context->buf); */
63 /*   if(count == 0) { */
64 /*     log_debug("got 0 bytes, finalizing"); */
65 /*     finalize_put(context); */
66 /*   } else { */
67 /*     log_debug("got %d bytes, writing", count); */
68 /*     evbuffer_write(context->buf, context->fd); */
69 /*   } */
70 /* } */
71
72 /* static void put_event(struct bufferevent *bev, short what, void *arg) { */
73 /*   log_debug("PUT event:"); */
74 /*   log_debug(" - BEV_EVENT_READING: %d", (what & BEV_EVENT_READING) ? 1 : 0); */
75 /*   log_debug(" - BEV_EVENT_WRITING: %d", (what & BEV_EVENT_WRITING) ? 1 : 0); */
76 /*   log_debug(" - BEV_EVENT_EOF: %d", (what & BEV_EVENT_EOF) ? 1 : 0); */
77 /*   log_debug(" - BEV_EVENT_ERROR: %d", (what & BEV_EVENT_ERROR) ? 1 : 0); */
78 /*   log_debug(" - BEV_EVENT_TIMEOUT: %d", (what & BEV_EVENT_TIMEOUT) ? 1 : 0); */
79 /*   log_debug(" - BEV_EVENT_CONNECTED: %d", (what & BEV_EVENT_CONNECTED) ? 1 : 0); */
80 /* } */
81
82 evhtp_res storage_handle_put(evhtp_request_t *request) {
83   char *storage_root = NULL;
84   char *disk_path = make_disk_path(request->uri->path->match_start,
85                                    request->uri->path->match_end,
86                                    &storage_root);
87   if(disk_path == NULL) {
88     return 500;
89   }
90   char *disk_path_copy = strdup(disk_path);
91   if(disk_path_copy == NULL) {
92     log_error("strdup() failed: %s", strerror(errno));
93     free(disk_path);
94     free(storage_root);
95     return 500;
96   }
97   char *dir_path = dirname(disk_path_copy);
98   char *saveptr = NULL;
99   char *dir_name;
100   int dirfd = open(storage_root, O_RDONLY), prevfd;
101   if(dirfd == -1) {
102     log_error("failed to open() storage path (\"%s\"): %s", storage_root, strerror(errno));
103     free(disk_path);
104     free(disk_path_copy);
105     free(storage_root);
106     return 500;
107   }
108   struct stat dir_stat;
109   for(dir_name = strtok_r(dir_path, "/", &saveptr);
110       dir_name != NULL;
111       dir_name = strtok_r(NULL, "/", &saveptr)) {
112     if(fstatat(dirfd, dir_name, &dir_stat, 0) == 0) {
113       if(! S_ISDIR(dir_stat.st_mode)) {
114         // exists, but not a directory
115         log_error("Can't PUT to %s, found a non-directory parent.", request->uri->path->full);
116         close(dirfd);
117         free(disk_path);
118         free(disk_path_copy);
119         free(storage_root);
120         return 400;
121       } else {
122         // directory exists
123       }
124     } else {
125       if(mkdirat(dirfd, dir_name, S_IRWXU | S_IRWXG) != 0) {
126         log_error("mkdirat() failed: %s", strerror(errno));
127         close(dirfd);
128         free(disk_path);
129         free(disk_path_copy);
130         free(storage_root);
131         return 500;
132       }
133     }
134     prevfd = dirfd;
135     dirfd = openat(prevfd, dir_name, O_RDONLY);
136     close(prevfd);
137     if(dirfd == -1) {
138       log_error("failed to openat() next directory (\"%s\"): %s",
139                 dir_name, strerror(errno));
140       free(disk_path);
141       free(disk_path_copy);
142       free(storage_root);
143       return 500;
144     }
145   }
146
147   free(disk_path_copy);
148   free(storage_root);
149   close(dirfd);
150
151   // open (and possibly create) file
152   int fd = open(disk_path, O_NONBLOCK | O_CREAT | O_WRONLY | O_TRUNC,
153                 RS_FILE_CREATE_MODE);
154
155   if(fd == -1) {
156     log_error("open() failed to open file \"%s\": %s", disk_path, strerror(errno));
157     free(disk_path);
158     return 500;
159   }
160
161   /* struct rs_put_context *context = malloc(sizeof(struct rs_put_context)); */
162   /* if(context == NULL) { */
163   /*   log_error("malloc() failed: %s", strerror(errno)); */
164   /*   return 500; */
165   /* } */
166
167   evbuffer_write(request->buffer_in, fd);
168
169   char *content_type = "application/octet-stream; charset=binary";
170   evhtp_kv_t *content_type_header = evhtp_headers_find_header(request->headers_in, "Content-Type");
171
172   if(content_type_header != NULL) {
173     content_type = content_type_header->val;
174   }
175   
176   if(content_type_to_xattr(fd, content_type) != 0) {
177     log_error("Setting xattr for content type failed. Ignoring.");
178   }
179
180   close(fd);
181
182   struct stat stat_buf;
183   memset(&stat_buf, 0, sizeof(struct stat));
184   if(stat(disk_path, &stat_buf) != 0) {
185     log_error("failed to stat() file after writing: %s", strerror(errno));
186     free(disk_path);
187     return 500;
188   }
189
190   char *etag_string = make_etag(&stat_buf);
191
192   ADD_RESP_HEADER_CP(request, "Content-Type", content_type);
193   ADD_RESP_HEADER_CP(request, "ETag", etag_string);
194
195   free(etag_string);
196   free(disk_path);
197
198   return EVHTP_RES_OK;
199 }
200
201 int storage_handle_delete(struct rs_request *request) {
202   return 501;
203 }
204
205 static char *make_etag(struct stat *stat_buf) {
206   char *etag = malloc(21);
207   if(etag == NULL) {
208     log_error("malloc() failed: %s", strerror(errno));
209     return NULL;
210   }
211   snprintf(etag, 20, "%lld", ((long long int)stat_buf->st_mtime) * 1000);
212   return etag;
213 }
214
215 // escape backslashes (/) and double quotes (") to put the given string
216 // in quoted JSON strings.
217 static char *escape_name(const char *name) {
218   int max_len = strlen(name) * 2, i = 0;
219   char *escaped = malloc(max_len + 1);
220   if(escaped == NULL) {
221     perror("malloc() failed");
222     return NULL;
223   }
224   const char *name_p;
225   for(name_p = name; *name_p != 0; name_p++) {
226     if(*name_p == '"' || *name_p == '\\') {
227       escaped[i++] = '\\';
228     }
229     escaped[i++] = *name_p;
230   }
231   escaped[i++] = 0;
232   escaped = realloc(escaped, i);
233   return escaped;
234 }
235
236 // serve a directory response for the given request
237 static evhtp_res serve_directory(evhtp_request_t *request, char *disk_path, struct stat *stat_buf) {
238   size_t disk_path_len = strlen(disk_path);
239   struct evbuffer *buf = evbuffer_new();
240   if(buf == NULL) {
241     log_error("evbuffer_new() failed: %s", strerror(errno));
242     return 500;
243   }
244   DIR *dir = opendir(disk_path);
245   if(dir == NULL) {
246     log_error("opendir() failed: %s", strerror(errno));
247     return 500;
248   }
249   struct dirent *entryp = malloc(offsetof(struct dirent, d_name) +
250                                  pathconf(disk_path, _PC_NAME_MAX) + 1);
251   struct dirent *resultp = NULL;
252   if(entryp == NULL) {
253     log_error("malloc() failed while creating directory pointer: %s",
254               strerror(errno));
255     return 500;
256   }
257   struct stat file_stat_buf;
258   int entry_len;
259   int first = 1;
260   for(;;) {
261     readdir_r(dir, entryp, &resultp);
262     if(resultp == NULL) {
263       break;
264     }
265     if(strcmp(entryp->d_name, ".") == 0 ||
266        strcmp(entryp->d_name, "..") == 0) {
267       // skip.
268       continue;
269     }
270     if(first) {
271       evbuffer_add(buf, "{", 1);
272       first = 0;
273     } else {
274       evbuffer_add(buf, ",", 1);
275     }
276     entry_len = strlen(entryp->d_name);
277     char full_path[disk_path_len + entry_len + 1];
278     sprintf(full_path, "%s%s", disk_path, entryp->d_name);
279     stat(full_path, &file_stat_buf);
280
281     log_debug("Listing add entry %s", entryp->d_name);
282     
283     char *escaped_name = escape_name(entryp->d_name);
284     if(! escaped_name) {
285       // failed to allocate name
286       free(entryp);
287       free(dir);
288       return 500;
289     }
290     evbuffer_add_printf(buf, "\"%s%s\":%lld", escaped_name,
291                         S_ISDIR(file_stat_buf.st_mode) ? "/" : "",
292                         ((long long)file_stat_buf.st_mtime) * 1000);
293     free(escaped_name);
294   }
295   if(first) {
296     // empty directory.
297     evbuffer_add(buf, "{", 1);
298   }
299   evbuffer_add(buf, "}\n", 2);
300
301   char *etag = make_etag(stat_buf);
302   if(etag == NULL) {
303     log_error("make_etag() failed");
304     free(entryp);
305     closedir(dir);
306     return 500;
307   }
308
309   ADD_RESP_HEADER(request, "Content-Type", "application/json; charset=UTF-8");
310   ADD_RESP_HEADER_CP(request, "ETag", etag);
311
312   // FIXME: why do I need to use *_chunk_* here???
313   evhtp_send_reply_chunk_start(request, EVHTP_RES_OK);
314   evhtp_send_reply_chunk(request, buf);
315   evhtp_send_reply_chunk_end(request);
316   free(etag);
317   free(entryp);
318   closedir(dir);
319   return 0;
320 }
321
322 static char *get_xattr(const char *path, const char *key, int maxlen) {
323   int len = 32;
324   char *value = malloc(32);
325   for(value = malloc(len);len<=maxlen;value = realloc(value, len+=16)) {
326     if(value == NULL) {
327       log_error("malloc() / realloc() failed: %s", strerror(errno));
328       return NULL;
329     }
330     int actual_len = getxattr(path, key, value, len);
331     log_debug("getxattr() for key %s returned actual length of %d bytes", key, actual_len);
332     if(actual_len > 0) {
333       return value;
334     } else {
335       if(errno == ERANGE) {
336         // buffer too small.
337         continue;
338       } else if(errno == ENOATTR) {
339         // attribute not set
340         free(value);
341         return NULL;
342       } else if(errno == ENOTSUP) {
343         // xattr not supported
344         log_error("File system doesn't support extended attributes! You may want to use another one.");
345         free(value);
346         return NULL;
347       } else {
348         log_error("Unexpected error while getting %s attribute: %s", key, strerror(errno));
349         free(value);
350         return NULL;
351       }
352     }
353   }
354   log_error("%s attribute seems to be longer than %d bytes. That is simply unreasonable.", key, maxlen);
355   free(value);
356   return NULL;
357 }
358
359 static char *content_type_from_xattr(const char *path) {
360   char *mime_type = get_xattr(path, "user.mime_type", 128);
361   if(mime_type == NULL) {
362     return NULL;
363   }
364   char *charset = get_xattr(path, "user.charset", 64);
365   if(charset == NULL) {
366     return mime_type;
367   }
368   int mt_len = strlen(mime_type);
369   char *content_type = realloc(mime_type, mt_len + strlen(charset) + 10 + 1);
370   if(content_type == NULL) {
371     log_error("realloc() failed: %s", strerror(errno));
372     free(charset);
373     return mime_type;
374   }
375   log_debug("content_type now: %s (also charset is \"%s\")", content_type, charset);
376   sprintf(content_type + mt_len, "; charset=%s", charset);
377   free(charset);
378   return content_type;
379 }
380
381 static int content_type_to_xattr(int fd, const char *content_type) {
382   char *content_type_copy = strdup(content_type), *saveptr = NULL;
383   if(content_type_copy == NULL) {
384     log_error("strdup() failed: %s", strerror(errno));
385     return -1;
386   }
387   char *mime_type = strtok_r(content_type_copy, ";", &saveptr);
388   log_debug("extracted mime type: %s", mime_type);
389   char *rest = strtok_r(NULL, "", &saveptr);
390   char *charset_begin = NULL, *charset = NULL;
391   if(rest) {
392     charset_begin = strstr(rest, "charset=");
393     if(charset_begin) {
394       charset = charset_begin + 8;
395       log_debug("extracted charset: %s", charset);
396     }
397   }
398   if(charset == NULL) {
399     // FIXME: should this rather be binary or us-ascii?
400     charset = "UTF-8";
401     log_debug("guessed charset: %s", charset);
402   }
403   if(fsetxattr(fd, "user.mime_type", mime_type, strlen(mime_type) + 1, 0) != 0) {
404     log_error("fsetxattr() failed: %s", strerror(errno));
405     free(content_type_copy);
406     return -1;
407   }
408   log_debug("fsetxattr (mime_type) done");
409   if(fsetxattr(fd, "user.charset", mime_type, strlen(charset) + 1, 0) != 0) {
410     log_error("fsetxattr() failed: %s", strerror(errno));
411     free(content_type_copy);
412     return -1;
413   }
414   log_debug("fsetxattr (charset) done");
415   free(content_type_copy);
416   return 0;
417 }
418
419
420 static int serve_file_head(evhtp_request_t *request, char *disk_path, struct stat *stat_buf, const char *mime_type) {
421   log_debug("serve_file_head for path %s", disk_path);
422   int free_mime_type = 0;
423   // mime type is either passed in ... (such as for directory listings)
424   if(mime_type == NULL) {
425     // ... or detected based on xattr
426     mime_type = content_type_from_xattr(disk_path);
427     if(mime_type == NULL) {
428       // ... or guessed by libmagic
429       log_debug("mime type not given, detecting...");
430       mime_type = magic_file(magic_cookie, disk_path);
431       if(mime_type == NULL) {
432         // ... or defaulted to "application/octet-stream"
433         log_error("magic failed: %s", magic_error(magic_cookie));
434         mime_type = "application/octet-stream; charset=binary";
435       }
436     } else {
437       // xattr detected mime type and allocated memory for it
438       free_mime_type = 1;
439     }
440   }
441   struct rs_header content_type_header = {
442     .key = "Content-Type",
443     // header won't be freed (it's completely stack based / constant),
444     // so cast is valid here to satisfy type check.
445     // (struct rs_header.value cannot be constant, because in the case of a request
446     //  header it will be free()'d at some point)
447     .value = (char*)mime_type,
448     .next = &RS_DEFAULT_HEADERS
449   };
450   char *length_string = malloc(24);
451   if(length_string == NULL) {
452     log_error("malloc() failed: %s", strerror(errno));
453     return 500;
454   }
455   snprintf(length_string, 24, "%ld", stat_buf->st_size);
456   char *etag_string = make_etag(stat_buf);
457   if(etag_string == NULL) {
458     log_error("make_etag() failed");
459     free(length_string);
460     return 500;
461   }
462
463   ADD_RESP_HEADER_CP(request, "Content-Length", length_string);
464   ADD_RESP_HEADER_CP(request, "ETag", etag_string);
465
466   log_debug("HAVE SET CONTENT LENGTH: %s", length_string);
467
468   evhtp_send_reply_chunk_start(request, 200);
469
470   free(etag_string);
471   free(length_string);
472   if(free_mime_type) {
473     free((char*)mime_type);
474   }
475   return 0;
476 }
477
478 // serve a file body for the given request
479 static int serve_file(evhtp_request_t *request, const char *disk_path, struct stat *stat_buf) {
480   int fd = open(disk_path, O_RDONLY | O_NONBLOCK);
481   if(fd < 0) {
482     log_error("open() failed: %s", strerror(errno));
483     return 500;
484   }
485   struct evbuffer *buf = evbuffer_new();
486   while(evbuffer_read(buf, fd, 4096) != 0) {
487     evhtp_send_reply_chunk(request, buf);
488   }
489   evbuffer_free(buf);
490   evhtp_send_reply_chunk_end(request);
491   return 0;
492 }
493
494 static char *make_disk_path(char *user, char *path, char **storage_root) {
495
496   // FIXME: use passwd->pwdir instead of /home/{user}/
497
498   // calculate maximum length of path
499   int pathlen = ( strlen(user) + strlen(path) +
500                   6 + // "/home/"
501                   1 + // another slash
502                   RS_HOME_SERVE_ROOT_LEN );
503   char *disk_path = malloc(pathlen);
504   if(disk_path == NULL) {
505     log_error("malloc() failed: %s", strerror(errno));
506     return NULL;
507   }
508   if(storage_root) {
509     *storage_root = malloc( 6 + RS_HOME_SERVE_ROOT_LEN + 1 + strlen(user) );
510     if(*storage_root == NULL) {
511       log_error("malloc() failed: %s", strerror(errno));
512       free(disk_path);
513       return NULL;
514     }
515     sprintf(*storage_root, "/home/%s/%s", user, RS_HOME_SERVE_ROOT);
516   }
517   // remove all /.. segments
518   // (we don't try to resolve them, but instead treat them as garbage)
519   char *traverse_pos = NULL;
520   while((traverse_pos = strstr(path, "/..")) != NULL) {
521     int restlen = strlen(traverse_pos + 3);
522     memmove(traverse_pos, traverse_pos + 3, restlen);
523     traverse_pos[restlen] = 0;
524   }
525   // build path
526   sprintf(disk_path, "/home/%s/%s%s", user, RS_HOME_SERVE_ROOT, path);
527   return disk_path;
528 }
529                       
530
531 static evhtp_res handle_get_or_head(evhtp_request_t *request, int include_body) {
532   char *disk_path = make_disk_path(request->uri->path->match_start,
533                                    request->uri->path->match_end, NULL);
534   if(disk_path == NULL) {
535     return 500;
536   }
537
538   log_debug("HANDLE GET OR HEAD, DISK PATH: %s", disk_path);
539
540   // stat
541   struct stat stat_buf;
542   if(stat(disk_path, &stat_buf) != 0) {
543     if(errno != ENOENT) {
544       log_error("stat() failed for path \"%s\": %s", disk_path, strerror(errno));
545       return 500;
546     } else {
547       return 404;
548     }
549   }
550   // check for directory
551   if(request->uri->path->file == NULL) {
552     // directory requested
553     if(! S_ISDIR(stat_buf.st_mode)) {
554       // not a directory.
555       return 404;
556     }
557     // directory found
558     if(include_body) {
559       return serve_directory(request, disk_path, &stat_buf);
560     } else {
561       return serve_file_head(request, disk_path, &stat_buf, "application/json");
562     }
563   } else {
564     // file requested
565     if(S_ISDIR(stat_buf.st_mode)) {
566       // found, but is a directory
567       return 404;
568     }
569     // file found
570     int head_result = serve_file_head(request, disk_path, &stat_buf, NULL);
571     if(head_result != 0) {
572       return head_result;
573     }
574     if(include_body) {
575       return serve_file(request, disk_path, &stat_buf);
576     } else {
577       // ?
578     }
579   }
580 }