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