1 // httpd.cxx -- FGFS http property manager interface / external script
4 // Written by Curtis Olson, started June 2001.
6 // Copyright (C) 2001 Curtis L. Olson - http://www.flightgear.org/~curt
8 // This program is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU General Public License as
10 // published by the Free Software Foundation; either version 2 of the
11 // License, or (at your option) any later version.
13 // This program is distributed in the hope that it will be useful, but
14 // WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 // General Public License for more details.
18 // You should have received a copy of the GNU General Public License
19 // along with this program; if not, write to the Free Software
20 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
29 #include <simgear/compiler.h>
31 #include <algorithm> // sort()
32 #include <cstdlib> // atoi() atof()
37 #include <simgear/debug/logstream.hxx>
38 #include <simgear/io/sg_netChat.hxx>
39 #include <simgear/io/iochannel.hxx>
40 #include <simgear/math/sg_types.hxx>
41 #include <simgear/structure/commands.hxx>
42 #include <simgear/props/props.hxx>
44 #include <Main/fg_props.hxx>
45 #include <Main/globals.hxx>
51 /* simple httpd server that makes an hasty stab at following the http
54 //////////////////////////////////////////////////////////////
56 //////////////////////////////////////////////////////////////
58 class HttpdChannel : public simgear::NetChat
60 simgear::NetBuffer buffer;
62 string urlEncode(string);
63 string urlDecode(string);
67 HttpdChannel() : buffer(512) { setTerminator("\r\n"); }
69 virtual void collectIncomingData (const char* s, int n) {
73 // Handle the actual http request
74 virtual void foundTerminator(void);
78 //////////////////////////////////////////////////////////////
80 //////////////////////////////////////////////////////////////
82 class HttpdServer : private simgear::NetChannel
84 virtual bool writable (void) { return false; }
86 virtual void handleAccept (void) {
87 simgear::IPAddress addr;
88 int handle = accept ( &addr );
89 SG_LOG( SG_IO, SG_INFO, "Client " << addr.getHost() << ":" << addr.getPort() << " connected" );
91 HttpdChannel *hc = new HttpdChannel;
92 hc->setHandle ( handle );
93 poller.addChannel( hc );
96 simgear::NetChannelPoller poller;
99 HttpdServer ( int port );
107 HttpdServer::HttpdServer(int port)
111 SG_LOG( SG_IO, SG_ALERT, "Failed to open HTTP port.");
115 if (0 != bind( "", port ))
117 SG_LOG( SG_IO, SG_ALERT, "Failed to bind HTTP port.");
121 if (0 != listen( 5 ))
123 SG_LOG( SG_IO, SG_ALERT, "Failed to listen on HTTP port.");
127 poller.addChannel(this);
129 SG_LOG(SG_IO, SG_ALERT, "Httpd server started on port " << port);
132 //////////////////////////////////////////////////////////////
134 //////////////////////////////////////////////////////////////
136 FGHttpd::FGHttpd(int p) :
147 bool FGHttpd::open() {
148 if ( is_enabled() ) {
149 SG_LOG( SG_IO, SG_ALERT, "This shouldn't happen, but the channel "
150 << "is already in use, ignoring" );
154 server = new HttpdServer( port );
156 set_hz( 15 ); // default to processing requests @ 15Hz
163 bool FGHttpd::process() {
169 bool FGHttpd::close() {
173 SG_LOG( SG_IO, SG_INFO, "closing FGHttpd" );
177 set_enabled( false );
185 bool operator() (const SGPropertyNode *a, const SGPropertyNode *b) const {
186 int r = strcmp(a->getName(), b->getName());
187 return r ? r < 0 : a->getIndex() < b->getIndex();
192 // Handle http GET requests
193 void HttpdChannel::foundTerminator (void) {
197 const string s = buffer.getData();
199 if ( s.find( "GET " ) == 0 ) {
200 SG_LOG( SG_IO, SG_INFO, "echo: " << s );
202 string rest = s.substr(4);
206 string::size_type pos = rest.find( " " );
207 if ( pos != string::npos ) {
208 request = rest.substr( 0, pos );
213 SGPropertyNode *node = NULL;
214 pos = request.find( "?" );
215 if ( pos != string::npos ) {
216 // request to update property value
217 string args = request.substr( pos + 1 );
218 request = request.substr( 0, pos );
219 SG_LOG( SG_IO, SG_INFO, "'" << request << "' '" << args << "'" );
220 request = urlDecode(request);
222 // parse args looking for "value="
226 pos = args.find("&");
227 if ( pos != string::npos ) {
228 arg = args.substr( 0, pos );
229 args = args.substr( pos + 1 );
235 SG_LOG( SG_IO, SG_INFO, " arg = " << arg );
236 string::size_type apos = arg.find("=");
237 if ( apos != string::npos ) {
238 string a = arg.substr( 0, apos );
239 string b = arg.substr( apos + 1 );
240 SG_LOG( SG_IO, SG_INFO, " a = " << a << " b = " << b );
241 if ( request == "/run.cgi" ) {
243 if ( a == "value" ) {
245 if ( !globals->get_commands()
246 ->execute(urlDecode(b).c_str(), &args) )
248 SG_LOG( SG_NETWORK, SG_ALERT,
249 "Command " << urlDecode(b)
255 if ( a == "value" ) {
256 // update a property value
257 fgSetString( request.c_str(),
258 urlDecode(b).c_str() );
264 request = urlDecode(request);
267 node = globals->get_props()->getNode(request.c_str());
269 string response = "";
270 response += "<HTML LANG=\"en\">";
271 response += getTerminator();
273 response += "<HEAD>";
274 response += getTerminator();
276 response += "<TITLE>FlightGear - ";
278 response += "</TITLE>";
279 response += getTerminator();
281 response += "</HEAD>";
282 response += getTerminator();
284 response += "<BODY>";
285 response += getTerminator();
288 response += "<H3>Non-existent node requested!</H3>";
289 response += getTerminator();
292 response += request.c_str();
293 response += "</B> does not exist.";
294 response += getTerminator();
295 } else if ( node->nChildren() > 0 ) {
296 // request is a path with children
297 response += "<H3>Contents of \"";
299 response += "\"</H3>";
300 response += getTerminator();
303 vector<SGPropertyNode *> children;
304 for (int i = 0; i < node->nChildren(); i++)
305 children.push_back(node->getChild(i));
306 std::sort(children.begin(), children.end(), CompareNodes());
308 vector<SGPropertyNode *>::iterator it, end = children.end();
309 for (it = children.begin(); it != end; ++it) {
310 SGPropertyNode *child = *it;
311 string name = child->getDisplayName(true);
313 if ( child->nChildren() > 0 ) {
314 line += "<B><A HREF=\"";
316 if ( request.substr(request.length() - 1, 1) != "/" ) {
319 line += urlEncode(name);
325 string value = node->getStringValue ( name.c_str(), "" );
328 line += "</B> <A HREF=\"";
330 if ( request.substr(request.length() - 1, 1) != "/" ) {
333 line += urlEncode(name);
339 response += getTerminator();
342 // request for an individual data member
343 string value = node->getStringValue();
345 response += "<form method=\"GET\" action=\"";
346 response += urlEncode(request);
350 response += "</B> = ";
351 response += "<input type=text name=value size=\"15\" value=\"";
353 response += "\" maxlength=2047>";
354 response += "<input type=submit value=\"update\" name=\"submit\">";
355 response += "</FORM>";
357 response += "</BODY>";
358 response += getTerminator();
360 response += "</HTML>";
361 response += getTerminator();
363 push( "HTTP/1.1 200 OK" );
364 push( getTerminator() );
366 SG_LOG( SG_IO, SG_INFO, "size = " << response.length() );
368 sprintf(ctmp, "Content-Length: %u", (unsigned)response.length());
370 push( getTerminator() );
372 push( "Connection: close" );
373 push( getTerminator() );
375 push( "Content-Type: text/html" );
376 push( getTerminator() );
377 push( getTerminator() );
379 push( response.c_str() );
386 // encode everything but "a-zA-Z0-9_.-/" (see RFC2396)
387 string HttpdChannel::urlEncode(string s) {
390 for ( int i = 0; i < (int)s.length(); i++ ) {
391 if ( isalnum(s[i]) || s[i] == '_' || s[i] == '.'
392 || s[i] == '-' || s[i] == '/' ) {
396 sprintf(buf, "%%%02X", (unsigned char)s[i]);
404 string HttpdChannel::urlDecode(string s) {
406 int max = s.length();
409 for ( int i = 0; i < max; i++ ) {
412 } else if ( s[i] == '%' && i + 2 < max
413 && isxdigit(s[i + 1])
414 && isxdigit(s[i + 2]) ) {
416 a = isdigit(s[i]) ? s[i] - '0' : toupper(s[i]) - 'A' + 10;
418 b = isdigit(s[i]) ? s[i] - '0' : toupper(s[i]) - 'A' + 10;
419 r += (char)(a * 16 + b);