]> git.saurik.com Git - apt.git/blame - test/interactive-helper/aptwebserver.cc
implement directory listing in your webserver
[apt.git] / test / interactive-helper / aptwebserver.cc
CommitLineData
e6cd40dc
DK
1#include <apt-pkg/strutl.h>
2#include <apt-pkg/fileutl.h>
3#include <apt-pkg/error.h>
4#include <apt-pkg/cmndline.h>
90d1d54e
MV
5#include <apt-pkg/configuration.h>
6#include <apt-pkg/init.h>
e6cd40dc
DK
7
8#include <vector>
9#include <string>
10#include <list>
11#include <sstream>
12
13#include <sys/socket.h>
14#include <sys/types.h>
d6405329 15#include <sys/stat.h>
e6cd40dc
DK
16#include <netinet/in.h>
17#include <unistd.h>
18#include <errno.h>
19#include <time.h>
20#include <stdlib.h>
d6405329 21#include <dirent.h>
e6cd40dc 22
dc57a59b 23char const * const httpcodeToStr(int const httpcode) { /*{{{*/
e6cd40dc
DK
24 switch (httpcode) {
25 // Informational 1xx
26 case 100: return "100 Continue";
27 case 101: return "101 Switching Protocols";
28 // Successful 2xx
29 case 200: return "200 OK";
30 case 201: return "201 Created";
31 case 202: return "202 Accepted";
32 case 203: return "203 Non-Authoritative Information";
33 case 204: return "204 No Content";
34 case 205: return "205 Reset Content";
35 case 206: return "206 Partial Conent";
36 // Redirections 3xx
37 case 300: return "300 Multiple Choices";
38 case 301: return "301 Moved Permanently";
39 case 302: return "302 Found";
40 case 303: return "303 See Other";
41 case 304: return "304 Not Modified";
42 case 305: return "304 Use Proxy";
43 case 307: return "307 Temporary Redirect";
44 // Client errors 4xx
45 case 400: return "400 Bad Request";
46 case 401: return "401 Unauthorized";
47 case 402: return "402 Payment Required";
48 case 403: return "403 Forbidden";
49 case 404: return "404 Not Found";
50 case 405: return "405 Method Not Allowed";
51 case 406: return "406 Not Acceptable";
52 case 407: return "Proxy Authentication Required";
53 case 408: return "Request Time-out";
54 case 409: return "Conflict";
55 case 410: return "Gone";
56 case 411: return "Length Required";
57 case 412: return "Precondition Failed";
58 case 413: return "Request Entity Too Large";
59 case 414: return "Request-URI Too Large";
60 case 415: return "Unsupported Media Type";
61 case 416: return "Requested range not satisfiable";
62 case 417: return "Expectation Failed";
63 // Server error 5xx
64 case 500: return "Internal Server Error";
65 case 501: return "Not Implemented";
66 case 502: return "Bad Gateway";
67 case 503: return "Service Unavailable";
68 case 504: return "Gateway Time-out";
69 case 505: return "HTTP Version not supported";
70 }
71 return NULL;
dc57a59b
DK
72}
73 /*}}}*/
74void addFileHeaders(std::list<std::string> &headers, FileFd &data) { /*{{{*/
e6cd40dc
DK
75 std::ostringstream contentlength;
76 contentlength << "Content-Length: " << data.FileSize();
77 headers.push_back(contentlength.str());
78
79 std::string lastmodified("Last-Modified: ");
80 lastmodified.append(TimeRFC1123(data.ModificationTime()));
81 headers.push_back(lastmodified);
dc57a59b
DK
82}
83 /*}}}*/
a38a00b9
MV
84void addDataHeaders(std::list<std::string> &headers, std::string &data) {/*{{{*/
85 std::ostringstream contentlength;
86 contentlength << "Content-Length: " << data.size();
87 headers.push_back(contentlength.str());
dc57a59b
DK
88}
89 /*}}}*/
90bool sendHead(int const client, int const httpcode, std::list<std::string> &headers) { /*{{{*/
e6cd40dc
DK
91 string response("HTTP/1.1 ");
92 response.append(httpcodeToStr(httpcode));
93 headers.push_front(response);
94
95 headers.push_back("Server: APT webserver");
96
97 std::string date("Date: ");
98 date.append(TimeRFC1123(time(NULL)));
99 headers.push_back(date);
100
101 std::clog << ">>> RESPONSE >>>" << std::endl;
102 bool Success = true;
103 for (std::list<std::string>::const_iterator h = headers.begin();
104 Success == true && h != headers.end(); ++h) {
105 Success &= FileFd::Write(client, h->c_str(), h->size());
106 Success &= FileFd::Write(client, "\r\n", 2);
107 std::clog << *h << std::endl;
108 }
109 Success &= FileFd::Write(client, "\r\n", 2);
110 std::clog << "<<<<<<<<<<<<<<<<" << std::endl;
111 return Success;
dc57a59b
DK
112}
113 /*}}}*/
114bool sendFile(int const client, FileFd &data) { /*{{{*/
e6cd40dc
DK
115 bool Success = true;
116 char buffer[500];
117 unsigned long long actual = 0;
118 while ((Success &= data.Read(buffer, sizeof(buffer), &actual)) == true) {
119 if (actual == 0)
120 break;
121 Success &= FileFd::Write(client, buffer, actual);
122 }
123 Success &= FileFd::Write(client, "\r\n", 2);
124 return Success;
dc57a59b
DK
125}
126 /*}}}*/
127bool sendData(int const client, std::string const &data) { /*{{{*/
e6cd40dc
DK
128 bool Success = true;
129 Success &= FileFd::Write(client, data.c_str(), data.size());
130 Success &= FileFd::Write(client, "\r\n", 2);
131 return Success;
dc57a59b
DK
132}
133 /*}}}*/
134void sendError(int const client, int const httpcode, string const &request, bool content) { /*{{{*/
e6cd40dc 135 std::list<std::string> headers;
dc57a59b
DK
136 string response("<html><head><title>");
137 response.append(httpcodeToStr(httpcode)).append("</title></head>");
138 response.append("<body><h1>").append(httpcodeToStr(httpcode)).append("</h1");
139 response.append("This error is a result of the request: <pre>");
140 response.append(request).append("</pre></body></html>");
141 addDataHeaders(headers, response);
e6cd40dc 142 sendHead(client, httpcode, headers);
dc57a59b
DK
143 if (content == true)
144 sendData(client, response);
145}
146 /*}}}*/
d6405329
DK
147// sendDirectoryLisiting /*{{{*/
148int filter_hidden_files(const struct dirent *a) {
149 if (a->d_name[0] == '.')
150 return 0;
151#ifdef _DIRENT_HAVE_D_TYPE
152 // if we have the d_type check that only files and dirs will be included
153 if (a->d_type != DT_UNKNOWN &&
154 a->d_type != DT_REG &&
155 a->d_type != DT_LNK && // this includes links to regular files
156 a->d_type != DT_DIR)
157 return 0;
158#endif
159 return 1;
160}
161int grouped_alpha_case_sort(const struct dirent **a, const struct dirent **b) {
162#ifdef _DIRENT_HAVE_D_TYPE
163 if ((*a)->d_type == DT_DIR && (*b)->d_type == DT_DIR);
164 else if ((*a)->d_type == DT_DIR && (*b)->d_type == DT_REG)
165 return -1;
166 else if ((*b)->d_type == DT_DIR && (*a)->d_type == DT_REG)
167 return 1;
168 else
169#endif
170 {
171 struct stat f_prop; //File's property
172 stat((*a)->d_name, &f_prop);
173 int const amode = f_prop.st_mode;
174 stat((*b)->d_name, &f_prop);
175 int const bmode = f_prop.st_mode;
176 if (S_ISDIR(amode) && S_ISDIR(bmode));
177 else if (S_ISDIR(amode))
178 return -1;
179 else if (S_ISDIR(bmode))
180 return 1;
181 }
182 return strcasecmp((*a)->d_name, (*b)->d_name);
183}
184void sendDirectoryListing(int const client, string const &dir, string const &request, bool content) {
185 std::list<std::string> headers;
186 std::ostringstream listing;
187
188 struct dirent **namelist;
189 int const counter = scandir(dir.c_str(), &namelist, filter_hidden_files, grouped_alpha_case_sort);
190 if (counter == -1) {
191 sendError(client, 500, request, content);
192 return;
193 }
194
195 listing << "<html><head><title>Index of " << dir << "</title>"
196 << "<style type=\"text/css\"><!-- td {padding: 0.02em 0.5em 0.02em 0.5em;}"
197 << "tr:nth-child(even){background-color:#dfdfdf;}"
198 << "h1, td:nth-child(3){text-align:center;}"
199 << "table {margin-left:auto;margin-right:auto;} --></style>"
200 << "</head>" << std::endl
201 << "<body><h1>Index of " << dir << "</h1>" << std::endl
202 << "<table><tr><th>#</th><th>Name</th><th>Size</th><th>Last-Modified</th></tr>" << std::endl;
203 if (dir != ".")
204 listing << "<tr><td>d</td><td><a href=\"..\">Parent Directory</a></td><td>-</td><td>-</td></tr>";
205 for (int i = 0; i < counter; ++i) {
206 struct stat fs;
207 std::string filename(dir);
208 filename.append("/").append(namelist[i]->d_name);
209 stat(filename.c_str(), &fs);
210 listing << "<tr><td>" << ((S_ISDIR(fs.st_mode)) ? 'd' : 'f') << "</td>"
211 << "<td><a href=\"" << namelist[i]->d_name << "\">" << namelist[i]->d_name << "</a></td>";
212 if (S_ISDIR(fs.st_mode))
213 listing << "<td>-</td>";
214 else
215 listing << "<td>" << SizeToStr(fs.st_size) << "B</td>";
216 listing << "<td>" << TimeRFC1123(fs.st_mtime) << "</td></tr>" << std::endl;
217 }
218 listing << "</table></body></html>" << std::endl;
219
220 std::string response(listing.str());
221 addDataHeaders(headers, response);
222 sendHead(client, 200, headers);
223 if (content == true)
224 sendData(client, response);
225}
226 /*}}}*/
dc57a59b 227int main(int const argc, const char * argv[])
e6cd40dc 228{
90d1d54e 229 CommandLine::Args Args[] = {
dc57a59b 230 {0, "simulate-paywall", "aptwebserver::Simulate-Paywall",
90d1d54e
MV
231 CommandLine::Boolean},
232 {0, "port", "aptwebserver::port", CommandLine::HasArg},
233 {0,0,0,0}
234 };
235
236 CommandLine CmdL(Args, _config);
3ce22d4f 237 if(CmdL.Parse(argc,argv) == false) {
90d1d54e
MV
238 _error->DumpErrors();
239 exit(1);
240 }
241
e6cd40dc
DK
242 // create socket, bind and listen to it {{{
243 int sock = socket(AF_INET6, SOCK_STREAM, 0);
244 if(sock < 0 ) {
245 _error->Errno("aptwerbserver", "Couldn't create socket");
246 _error->DumpErrors(std::cerr);
247 return 1;
248 }
249
90d1d54e
MV
250 // get the port
251 int const port = _config->FindI("aptwebserver::port", 8080);
252 bool const simulate_broken_server = _config->FindB("aptwebserver::Simulate-Paywall", false);
253
e6cd40dc
DK
254 // ensure that we accept all connections: v4 or v6
255 int const iponly = 0;
256 setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &iponly, sizeof(iponly));
257 // to not linger to an address
258 int const enable = 1;
259 setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable));
260
261 struct sockaddr_in6 locAddr;
262 memset(&locAddr, 0, sizeof(locAddr));
263 locAddr.sin6_family = AF_INET6;
90d1d54e 264 locAddr.sin6_port = htons(port);
e6cd40dc
DK
265 locAddr.sin6_addr = in6addr_any;
266
267 if (bind(sock, (struct sockaddr*) &locAddr, sizeof(locAddr)) < 0) {
268 _error->Errno("aptwerbserver", "Couldn't bind");
269 _error->DumpErrors(std::cerr);
270 return 2;
271 }
272
90d1d54e
MV
273 if (simulate_broken_server) {
274 std::clog << "Simulating a broken web server that return nonsense "
275 "for all querries" << std::endl;
276 } else {
277 std::clog << "Serving ANY file on port: " << port << std::endl;
278 }
279
e6cd40dc 280 listen(sock, 1);
dc57a59b 281 /*}}}*/
e6cd40dc
DK
282
283 std::vector<std::string> messages;
284 int client;
285 while ((client = accept(sock, NULL, NULL)) != -1) {
90d1d54e 286 std::clog << "ACCEPT client " << client
dc57a59b 287 << " on socket " << sock << std::endl;
e6cd40dc
DK
288
289 while (ReadMessages(client, messages)) {
290 for (std::vector<std::string>::const_iterator m = messages.begin();
291 m != messages.end(); ++m) {
dc57a59b
DK
292 std::clog << ">>> REQUEST >>>>" << std::endl << *m
293 << std::endl << "<<<<<<<<<<<<<<<<" << std::endl;
e6cd40dc
DK
294 std::list<std::string> headers;
295 bool sendContent = true;
296 if (strncmp(m->c_str(), "HEAD ", 5) == 0)
297 sendContent = false;
298 if (strncmp(m->c_str(), "GET ", 4) != 0)
299 sendError(client, 501, *m, true);
300
301 std::string host = LookupTag(*m, "Host", "");
302 if (host.empty() == true) {
303 // RFC 2616 ยง14.23 Host
304 sendError(client, 400, *m, sendContent);
305 continue;
306 }
307
308 size_t const filestart = m->find(' ', 5);
309 string filename = m->substr(5, filestart - 5);
d6405329
DK
310 if (filename.empty() == true)
311 filename = ".";
e6cd40dc 312
dc57a59b
DK
313 if (simulate_broken_server == true) {
314 string data("ni ni ni\n");
315 addDataHeaders(headers, data);
316 sendHead(client, 200, headers);
317 sendData(client, data);
318 }
d6405329 319 else if (RealFileExists(filename) == true) {
e6cd40dc
DK
320 FileFd data(filename, FileFd::ReadOnly);
321 std::string condition = LookupTag(*m, "If-Modified-Since", "");
322 if (condition.empty() == false) {
323 time_t cache;
dc57a59b
DK
324 if (RFC1123StrToTime(condition.c_str(), cache) == true &&
325 cache >= data.ModificationTime()) {
d6405329 326 sendHead(client, 304, headers);
e6cd40dc
DK
327 continue;
328 }
329 }
330 addFileHeaders(headers, data);
331 sendHead(client, 200, headers);
332 if (sendContent == true)
333 sendFile(client, data);
334 }
d6405329
DK
335 else if (DirectoryExists(filename) == true) {
336 sendDirectoryListing(client, filename, *m, sendContent);
337 }
338 else
339 sendError(client, 404, *m, false);
e6cd40dc
DK
340 }
341 _error->DumpErrors(std::cerr);
342 messages.clear();
da3ebfe7 343 }
a38a00b9 344
dc57a59b
DK
345 std::clog << "CLOSE client " << client
346 << " on socket " << sock << std::endl;
a38a00b9 347 close(client);
e6cd40dc
DK
348 }
349 return 0;
350}