blob: b59393816e9f79d5b7ce1f5c5cc34dbe8d28e92a [file] [log] [blame]
brians343bc112013-02-10 01:53:46 +00001#include "aos/atom_code/output/HTTPServer.h"
2
3#include <inttypes.h>
4#include <sys/types.h>
5#include <sys/stat.h>
6#include <fcntl.h>
Brian Silvermanf665d692013-02-17 22:11:39 -08007#include <string.h>
brians343bc112013-02-10 01:53:46 +00008
9#include <memory>
10
11#include "event2/event.h"
12
brians343bc112013-02-10 01:53:46 +000013#include "aos/common/scoped_fd.h"
14#include "aos/common/unique_malloc_ptr.h"
15
16namespace aos {
17namespace http {
18
19HTTPServer::HTTPServer(const char *directory, uint16_t port) :
20 directory_(directory), base_(event_base_new()), http_(evhttp_new(base_)) {
21 if (base_ == NULL) {
22 LOG(FATAL, "couldn't create an event_base\n");
23 }
24 if (http_ == NULL) {
25 LOG(FATAL, "couldn't create an evhttp\n");
26 }
27 if (evhttp_bind_socket(http_, "0.0.0.0", port) != 0) {
28 LOG(FATAL, "evhttp_bind_socket(%p, \"0.0.0.0\", %"PRIu16") failed\n",
29 http_, port);
30 }
31 evhttp_set_gencb(http_, StaticServeFile, this);
32}
33
34void HTTPServer::AddPage(const std::string &path,
35 void (*handler)(evhttp_request *, void *), void *data) {
36 switch (evhttp_set_cb(http_, path.c_str(), handler, data)) {
37 case 0:
38 LOG(DEBUG, "set callback handler for '%s'\n", path.c_str());
39 break;
40 case -1:
41 LOG(INFO, "changed callback handler for '%s'\n", path.c_str());
42 break;
43 default:
44 LOG(WARNING, "evhttp_set_cb(%p, %s, %p, %p) failed\n", http_, path.c_str(),
45 handler, data);
46 break;
47 }
48}
49
50void HTTPServer::AddStandardHeaders(evhttp_request *request) {
51 if (evhttp_add_header(evhttp_request_get_output_headers(request),
52 "Server", "aos::HTTPServer/0.0") == -1) {
53 LOG(WARNING, "adding Server header failed\n");
54 }
55}
56
57namespace {
58// All of these functions return false, NULL, or -1 if they fail (and send back
59// an error).
60
61// Returns the path of the file that is being requested.
62const char *GetPath(evhttp_request *request) {
63 // Docs are unclear whether this needs freeing, but it looks like it just
64 // returns an internal field of the request.
65 // Running valgrind with no freeing of uri or path doesn't report anything
66 // related to this code.
67 const evhttp_uri *uri = evhttp_request_get_evhttp_uri(request);
68 const char *path = evhttp_uri_get_path(uri);
69 if (path == NULL) {
70 evhttp_send_error(request, HTTP_BADREQUEST, "need a path");
71 return NULL;
72 }
73 if (strstr(path, "..") != NULL) {
74 evhttp_send_error(request, HTTP_NOTFOUND, "no .. allowed!!");
75 return NULL;
76 }
77 return path;
78}
79// Returns an fd open for reading for the file at "directory/path".
80int OpenFile(evhttp_request *request, const char *path,
81 const char *directory) {
82 char *temp;
83 if (asprintf(&temp, "%s/%s", directory, path) == -1) {
84 LOG(WARNING, "asprintf(%p, \"%%s/%%s\", %p, %p) failed with %d: %s\n",
85 &temp, directory, path, errno, strerror(errno));
86 evhttp_send_error(request, HTTP_INTERNAL, NULL);
87 return -1;
88 }
89 const unique_c_ptr<char> filename(temp);
90 ScopedFD file(open(filename.get(), O_RDONLY));
91 if (!file) {
92 if (errno == ENOENT) {
93 evhttp_send_error(request, HTTP_NOTFOUND, NULL);
94 return -1;
95 }
96 LOG(ERROR, "open('%s', 0) failed with %d: %s\n", filename.get(),
97 errno, strerror(errno));
98 evhttp_send_error(request, HTTP_INTERNAL, NULL);
99 return -1;
100 }
101 return file.release();
102}
103// Returns the size of the file specified by the given fd.
104off_t GetSize(int file) {
105 struct stat info;
106 if (fstat(file, &info) == -1) {
107 LOG(ERROR, "stat(%d, %p) failed with %d: %s\n", file, &info,
108 errno, strerror(errno));
109 return -1;
110 }
111 return info.st_size;
112}
113bool SendFileResponse(evhttp_request *request, int file_num) {
114 ScopedFD file(file_num);
115 const off_t size = GetSize(file.get());
116 if (size == -1) {
117 evhttp_send_error(request, HTTP_INTERNAL, NULL);
118 return false;
119 }
120 evbuffer *const buf = evhttp_request_get_output_buffer(request);
121 if (evbuffer_add_file(buf, file.get(), 0, size) == -1) {
122 LOG(WARNING, "evbuffer_add_file(%p, %d, 0, %jd) failed\n", buf,
123 file.get(), static_cast<intmax_t>(size));
124 evhttp_send_error(request, HTTP_INTERNAL, NULL);
125 return false;
126 } else {
127 // it succeeded, so evhttp takes ownership
128 file.release();
129 }
130 evhttp_send_reply(request, HTTP_OK, NULL, NULL);
131 return true;
132}
133
134} // namespace
135void HTTPServer::ServeFile(evhttp_request *request) {
136 AddStandardHeaders(request);
137
138 const char *path = GetPath(request);
139 if (path == NULL) return;
140
141 ScopedFD file(OpenFile(request, path, directory_));
142 if (!file) return;
143
144 if (!SendFileResponse(request, file.release())) return;
145}
146
147void HTTPServer::Run() {
148 event_base_dispatch(base_);
149 LOG(FATAL, "event_base_dispatch returned\n");
150}
151
152} // namespace http
153} // namespace aos