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