]> git.mxchange.org Git - flightgear.git/blob - src/Network/httpd.cxx
Add error messages when unable to open HTTP port.
[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 }
135
136 bool FGHttpd::open() {
137     if ( is_enabled() ) {
138         SG_LOG( SG_IO, SG_ALERT, "This shouldn't happen, but the channel "
139                 << "is already in use, ignoring" );
140         return false;
141     }
142
143     server = new HttpdServer( port );
144     
145     set_hz( 15 );                // default to processing requests @ 15Hz
146     set_enabled( true );
147
148     return true;
149 }
150
151
152 bool FGHttpd::process() {
153     simgear::NetChannel::poll();
154
155     return true;
156 }
157
158
159 bool FGHttpd::close() {
160     if (!server)
161         return true;
162
163     SG_LOG( SG_IO, SG_INFO, "closing FGHttpd" );   
164
165     delete server;
166     server = NULL;
167     set_enabled( false );
168
169     return true;
170 }
171
172
173 class CompareNodes {
174 public:
175     bool operator() (const SGPropertyNode *a, const SGPropertyNode *b) const {
176         int r = strcmp(a->getName(), b->getName());
177         return r ? r < 0 : a->getIndex() < b->getIndex();
178     }
179 };
180
181
182 // Handle http GET requests
183 void HttpdChannel::foundTerminator (void) {
184     
185     closeWhenDone ();
186
187     const string s = buffer.getData();
188
189     if ( s.find( "GET " ) == 0 ) {
190         SG_LOG( SG_IO, SG_INFO, "echo: " << s );   
191
192         string rest = s.substr(4);
193         string request;
194         string tmp;
195
196         string::size_type pos = rest.find( " " );
197         if ( pos != string::npos ) {
198             request = rest.substr( 0, pos );
199         } else {
200             request = "/";
201         }
202
203         SGPropertyNode *node = NULL;
204         pos = request.find( "?" );
205         if ( pos != string::npos ) {
206             // request to update property value
207             string args = request.substr( pos + 1 );
208             request = request.substr( 0, pos );
209             SG_LOG( SG_IO, SG_INFO, "'" << request << "' '" << args << "'" );   
210             request = urlDecode(request);
211
212             // parse args looking for "value="
213             bool done = false;
214             while ( ! done ) {
215                 string arg;
216                 pos = args.find("&");
217                 if ( pos != string::npos ) {
218                     arg = args.substr( 0, pos );
219                     args = args.substr( pos + 1 );
220                 } else {
221                     arg = args;
222                     done = true;
223                 }
224
225                 SG_LOG( SG_IO, SG_INFO, "  arg = " << arg );   
226                 string::size_type apos = arg.find("=");
227                 if ( apos != string::npos ) {
228                     string a = arg.substr( 0, apos );
229                     string b = arg.substr( apos + 1 );
230                     SG_LOG( SG_IO, SG_INFO, "    a = " << a << "  b = " << b );
231                     if ( request == "/run.cgi" ) {
232                         // execute a command
233                         if ( a == "value" ) {
234                             SGPropertyNode args;
235                             if ( !globals->get_commands()
236                                  ->execute(urlDecode(b).c_str(), &args) )
237                             {
238                                 SG_LOG( SG_NETWORK, SG_ALERT,
239                                         "Command " << urlDecode(b)
240                                         << " failed.");
241                             }
242
243                         }
244                     } else {
245                         if ( a == "value" ) {
246                             // update a property value
247                             fgSetString( request.c_str(),
248                                          urlDecode(b).c_str() );
249                         }
250                     }
251                 }
252             }
253         } else {
254             request = urlDecode(request);
255         }
256
257         node = globals->get_props()->getNode(request.c_str());
258
259         string response = "";
260         response += "<HTML LANG=\"en\">";
261         response += getTerminator();
262
263         response += "<HEAD>";
264         response += getTerminator();
265
266         response += "<TITLE>FlightGear - ";
267         response += request;
268         response += "</TITLE>";
269         response += getTerminator();
270
271         response += "</HEAD>";
272         response += getTerminator();
273
274         response += "<BODY>";
275         response += getTerminator();
276
277         if (node == NULL) {
278             response += "<H3>Non-existent node requested!</H3>";
279             response += getTerminator();
280
281             response += "<B>";
282             response += request.c_str();
283             response += "</B> does not exist.";
284             response += getTerminator();
285         } else if ( node->nChildren() > 0 ) {
286             // request is a path with children
287             response += "<H3>Contents of \"";
288             response += request;
289             response += "\"</H3>";
290             response += getTerminator();
291
292
293             vector<SGPropertyNode *> children;
294             for (int i = 0; i < node->nChildren(); i++)
295                 children.push_back(node->getChild(i));
296             std::sort(children.begin(), children.end(), CompareNodes());
297
298             vector<SGPropertyNode *>::iterator it, end = children.end();
299             for (it = children.begin(); it != end; ++it) {
300                 SGPropertyNode *child = *it;
301                 string name = child->getDisplayName(true);
302                 string line = "";
303                 if ( child->nChildren() > 0 ) {
304                     line += "<B><A HREF=\"";
305                     line += request;
306                     if ( request.substr(request.length() - 1, 1) != "/" ) {
307                         line += "/";
308                     }
309                     line += urlEncode(name);
310                     line += "\">";
311                     line += name;
312                     line += "</A></B>";
313                     line += "/<BR>";
314                 } else {
315                     string value = node->getStringValue ( name.c_str(), "" );
316                     line += "<B>";
317                     line += name;
318                     line += "</B> <A HREF=\"";
319                     line += request;
320                     if ( request.substr(request.length() - 1, 1) != "/" ) {
321                         line += "/";
322                     }
323                     line += urlEncode(name);
324                     line += "\">(";
325                     line += value;
326                     line += ")</A><BR>";
327                 }
328                 response += line;
329                 response += getTerminator();
330             }
331         } else {
332             // request for an individual data member
333             string value = node->getStringValue();
334             
335             response += "<form method=\"GET\" action=\"";
336             response += urlEncode(request);
337             response += "\">";
338             response += "<B>";
339             response += request;
340             response += "</B> = ";
341             response += "<input type=text name=value size=\"15\" value=\"";
342             response += value;
343             response += "\" maxlength=2047>";
344             response += "<input type=submit value=\"update\" name=\"submit\">";
345             response += "</FORM>";
346         }
347         response += "</BODY>";
348         response += getTerminator();
349
350         response += "</HTML>";
351         response += getTerminator();
352
353         push( "HTTP/1.1 200 OK" );
354         push( getTerminator() );
355         
356         SG_LOG( SG_IO, SG_INFO, "size = " << response.length() );
357         char ctmp[256];
358         sprintf(ctmp, "Content-Length: %u", (unsigned)response.length());
359         push( ctmp );
360         push( getTerminator() );
361
362         push( "Connection: close" );
363         push( getTerminator() );
364
365         push( "Content-Type: text/html" );
366         push( getTerminator() );
367         push( getTerminator() );
368                 
369         push( response.c_str() );
370     }
371
372     buffer.remove();
373 }
374
375
376 // encode everything but "a-zA-Z0-9_.-/" (see RFC2396)
377 string HttpdChannel::urlEncode(string s) {
378     string r = "";
379     
380     for ( int i = 0; i < (int)s.length(); i++ ) {
381         if ( isalnum(s[i]) || s[i] == '_' || s[i] == '.'
382                 || s[i] == '-' || s[i] == '/' ) {
383             r += s[i];
384         } else {
385             char buf[16];
386             sprintf(buf, "%%%02X", (unsigned char)s[i]);
387             r += buf;
388         }
389     }
390     return r;
391 }
392
393
394 string HttpdChannel::urlDecode(string s) {
395     string r = "";
396     int max = s.length();
397     int a, b;
398
399     for ( int i = 0; i < max; i++ ) {
400         if ( s[i] == '+' ) {
401             r += ' ';
402         } else if ( s[i] == '%' && i + 2 < max
403                 && isxdigit(s[i + 1])
404                 && isxdigit(s[i + 2]) ) {
405             i++;
406             a = isdigit(s[i]) ? s[i] - '0' : toupper(s[i]) - 'A' + 10;
407             i++;
408             b = isdigit(s[i]) ? s[i] - '0' : toupper(s[i]) - 'A' + 10;
409             r += (char)(a * 16 + b);
410         } else {
411             r += s[i];
412         }
413     }
414     return r;
415 }