]> git.mxchange.org Git - flightgear.git/blob - src/Network/httpd.cxx
Fix crash starting at heliport.
[flightgear.git] / src / Network / httpd.cxx
1 // httpd.cxx -- FGFS http property manager interface / external script
2 //              and control class
3 //
4 // Written by Curtis Olson, started June 2001.
5 //
6 // Copyright (C) 2001  Curtis L. Olson - http://www.flightgear.org/~curt
7 //
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.
12 //
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.
17 //
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.
21 //
22 // $Id$
23
24
25 #ifdef HAVE_CONFIG_H
26 #  include <config.h>
27 #endif
28
29 #include <simgear/compiler.h>
30
31 #include <algorithm>            // sort()
32 #include <cstdlib>              // atoi() atof()
33 #include <cstring>
34 #include <cstdio>
35 #include <string>
36
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>
43
44 #include <Main/fg_props.hxx>
45 #include <Main/globals.hxx>
46
47 #include "httpd.hxx"
48
49 using std::string;
50
51 /* simple httpd server that makes an hasty stab at following the http
52    1.1 rfc.  */
53
54 //////////////////////////////////////////////////////////////
55 // class HttpdChannel
56 //////////////////////////////////////////////////////////////
57
58 class HttpdChannel : public simgear::NetChat
59 {
60     simgear::NetBuffer buffer;
61
62     string urlEncode(string);
63     string urlDecode(string);
64
65 public:
66
67     HttpdChannel() : buffer(512) { setTerminator("\r\n"); }
68
69     virtual void collectIncomingData (const char* s, int n) {
70         buffer.append(s,n);
71     }
72
73     // Handle the actual http request
74     virtual void foundTerminator(void);
75 };
76
77
78 //////////////////////////////////////////////////////////////
79 // class HttpdServer
80 //////////////////////////////////////////////////////////////
81
82 class HttpdServer : private simgear::NetChannel
83 {
84     virtual bool writable (void) { return false; }
85
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" );
90
91         HttpdChannel *hc = new HttpdChannel;
92         hc->setHandle ( handle );
93     }
94
95 public:
96
97     HttpdServer ( int port );
98 };
99
100 HttpdServer::HttpdServer(int port)
101 {
102     if (!open())
103     {
104         SG_LOG( SG_IO, SG_ALERT, "Failed to open HTTP port.");
105         return;
106     }
107
108     if (0 != bind( "", port ))
109     {
110         SG_LOG( SG_IO, SG_ALERT, "Failed to bind HTTP port.");
111         return;
112     }
113
114     if (0 != listen( 5 ))
115     {
116         SG_LOG( SG_IO, SG_ALERT, "Failed to listen on HTTP port.");
117         return;
118     }
119
120     SG_LOG(SG_IO, SG_ALERT, "Httpd server started on port " << port);
121 }
122
123 //////////////////////////////////////////////////////////////
124 // class FGHttpd
125 //////////////////////////////////////////////////////////////
126
127 FGHttpd::FGHttpd(int p) :
128     port(p),
129     server(NULL)
130 {
131 }
132
133 FGHttpd::~FGHttpd()
134 {
135     delete server;
136 }
137
138 bool FGHttpd::open() {
139     if ( is_enabled() ) {
140         SG_LOG( SG_IO, SG_ALERT, "This shouldn't happen, but the channel "
141                 << "is already in use, ignoring" );
142         return false;
143     }
144
145     server = new HttpdServer( port );
146     
147     set_hz( 15 );                // default to processing requests @ 15Hz
148     set_enabled( true );
149
150     return true;
151 }
152
153
154 bool FGHttpd::process() {
155     simgear::NetChannel::poll();
156
157     return true;
158 }
159
160
161 bool FGHttpd::close() {
162     if (!server)
163         return true;
164
165     SG_LOG( SG_IO, SG_INFO, "closing FGHttpd" );   
166
167     delete server;
168     server = NULL;
169     set_enabled( false );
170
171     return true;
172 }
173
174
175 class CompareNodes {
176 public:
177     bool operator() (const SGPropertyNode *a, const SGPropertyNode *b) const {
178         int r = strcmp(a->getName(), b->getName());
179         return r ? r < 0 : a->getIndex() < b->getIndex();
180     }
181 };
182
183
184 // Handle http GET requests
185 void HttpdChannel::foundTerminator (void) {
186     
187     closeWhenDone ();
188
189     const string s = buffer.getData();
190
191     if ( s.find( "GET " ) == 0 ) {
192         SG_LOG( SG_IO, SG_INFO, "echo: " << s );   
193
194         string rest = s.substr(4);
195         string request;
196         string tmp;
197
198         string::size_type pos = rest.find( " " );
199         if ( pos != string::npos ) {
200             request = rest.substr( 0, pos );
201         } else {
202             request = "/";
203         }
204
205         SGPropertyNode *node = NULL;
206         pos = request.find( "?" );
207         if ( pos != string::npos ) {
208             // request to update property value
209             string args = request.substr( pos + 1 );
210             request = request.substr( 0, pos );
211             SG_LOG( SG_IO, SG_INFO, "'" << request << "' '" << args << "'" );   
212             request = urlDecode(request);
213
214             // parse args looking for "value="
215             bool done = false;
216             while ( ! done ) {
217                 string arg;
218                 pos = args.find("&");
219                 if ( pos != string::npos ) {
220                     arg = args.substr( 0, pos );
221                     args = args.substr( pos + 1 );
222                 } else {
223                     arg = args;
224                     done = true;
225                 }
226
227                 SG_LOG( SG_IO, SG_INFO, "  arg = " << arg );   
228                 string::size_type apos = arg.find("=");
229                 if ( apos != string::npos ) {
230                     string a = arg.substr( 0, apos );
231                     string b = arg.substr( apos + 1 );
232                     SG_LOG( SG_IO, SG_INFO, "    a = " << a << "  b = " << b );
233                     if ( request == "/run.cgi" ) {
234                         // execute a command
235                         if ( a == "value" ) {
236                             SGPropertyNode args;
237                             if ( !globals->get_commands()
238                                  ->execute(urlDecode(b).c_str(), &args) )
239                             {
240                                 SG_LOG( SG_NETWORK, SG_ALERT,
241                                         "Command " << urlDecode(b)
242                                         << " failed.");
243                             }
244
245                         }
246                     } else {
247                         if ( a == "value" ) {
248                             // update a property value
249                             fgSetString( request.c_str(),
250                                          urlDecode(b).c_str() );
251                         }
252                     }
253                 }
254             }
255         } else {
256             request = urlDecode(request);
257         }
258
259         node = globals->get_props()->getNode(request.c_str());
260
261         string response = "";
262         response += "<HTML LANG=\"en\">";
263         response += getTerminator();
264
265         response += "<HEAD>";
266         response += getTerminator();
267
268         response += "<TITLE>FlightGear - ";
269         response += request;
270         response += "</TITLE>";
271         response += getTerminator();
272
273         response += "</HEAD>";
274         response += getTerminator();
275
276         response += "<BODY>";
277         response += getTerminator();
278
279         if (node == NULL) {
280             response += "<H3>Non-existent node requested!</H3>";
281             response += getTerminator();
282
283             response += "<B>";
284             response += request.c_str();
285             response += "</B> does not exist.";
286             response += getTerminator();
287         } else if ( node->nChildren() > 0 ) {
288             // request is a path with children
289             response += "<H3>Contents of \"";
290             response += request;
291             response += "\"</H3>";
292             response += getTerminator();
293
294
295             vector<SGPropertyNode *> children;
296             for (int i = 0; i < node->nChildren(); i++)
297                 children.push_back(node->getChild(i));
298             std::sort(children.begin(), children.end(), CompareNodes());
299
300             vector<SGPropertyNode *>::iterator it, end = children.end();
301             for (it = children.begin(); it != end; ++it) {
302                 SGPropertyNode *child = *it;
303                 string name = child->getDisplayName(true);
304                 string line = "";
305                 if ( child->nChildren() > 0 ) {
306                     line += "<B><A HREF=\"";
307                     line += request;
308                     if ( request.substr(request.length() - 1, 1) != "/" ) {
309                         line += "/";
310                     }
311                     line += urlEncode(name);
312                     line += "\">";
313                     line += name;
314                     line += "</A></B>";
315                     line += "/<BR>";
316                 } else {
317                     string value = node->getStringValue ( name.c_str(), "" );
318                     line += "<B>";
319                     line += name;
320                     line += "</B> <A HREF=\"";
321                     line += request;
322                     if ( request.substr(request.length() - 1, 1) != "/" ) {
323                         line += "/";
324                     }
325                     line += urlEncode(name);
326                     line += "\">(";
327                     line += value;
328                     line += ")</A><BR>";
329                 }
330                 response += line;
331                 response += getTerminator();
332             }
333         } else {
334             // request for an individual data member
335             string value = node->getStringValue();
336             
337             response += "<form method=\"GET\" action=\"";
338             response += urlEncode(request);
339             response += "\">";
340             response += "<B>";
341             response += request;
342             response += "</B> = ";
343             response += "<input type=text name=value size=\"15\" value=\"";
344             response += value;
345             response += "\" maxlength=2047>";
346             response += "<input type=submit value=\"update\" name=\"submit\">";
347             response += "</FORM>";
348         }
349         response += "</BODY>";
350         response += getTerminator();
351
352         response += "</HTML>";
353         response += getTerminator();
354
355         push( "HTTP/1.1 200 OK" );
356         push( getTerminator() );
357         
358         SG_LOG( SG_IO, SG_INFO, "size = " << response.length() );
359         char ctmp[256];
360         sprintf(ctmp, "Content-Length: %u", (unsigned)response.length());
361         push( ctmp );
362         push( getTerminator() );
363
364         push( "Connection: close" );
365         push( getTerminator() );
366
367         push( "Content-Type: text/html" );
368         push( getTerminator() );
369         push( getTerminator() );
370                 
371         push( response.c_str() );
372     }
373
374     buffer.remove();
375 }
376
377
378 // encode everything but "a-zA-Z0-9_.-/" (see RFC2396)
379 string HttpdChannel::urlEncode(string s) {
380     string r = "";
381     
382     for ( int i = 0; i < (int)s.length(); i++ ) {
383         if ( isalnum(s[i]) || s[i] == '_' || s[i] == '.'
384                 || s[i] == '-' || s[i] == '/' ) {
385             r += s[i];
386         } else {
387             char buf[16];
388             sprintf(buf, "%%%02X", (unsigned char)s[i]);
389             r += buf;
390         }
391     }
392     return r;
393 }
394
395
396 string HttpdChannel::urlDecode(string s) {
397     string r = "";
398     int max = s.length();
399     int a, b;
400
401     for ( int i = 0; i < max; i++ ) {
402         if ( s[i] == '+' ) {
403             r += ' ';
404         } else if ( s[i] == '%' && i + 2 < max
405                 && isxdigit(s[i + 1])
406                 && isxdigit(s[i + 2]) ) {
407             i++;
408             a = isdigit(s[i]) ? s[i] - '0' : toupper(s[i]) - 'A' + 10;
409             i++;
410             b = isdigit(s[i]) ? s[i] - '0' : toupper(s[i]) - 'A' + 10;
411             r += (char)(a * 16 + b);
412         } else {
413             r += s[i];
414         }
415     }
416     return r;
417 }