]> git.mxchange.org Git - flightgear.git/blob - src/Network/http/PropertyUriHandler.cxx
Interim windows build fix
[flightgear.git] / src / Network / http / PropertyUriHandler.cxx
1 // PropertyUriHandler.cxx -- a web form interface to the property tree
2 //
3 // Written by Torsten Dreyer, started April 2014.
4 //
5 // Copyright (C) 2014  Torsten Dreyer
6 //
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
11 //
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 // General Public License for more details.
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20
21
22 #include "PropertyUriHandler.hxx"
23 #include "SimpleDOM.hxx"
24 #include <Main/fg_props.hxx>
25 #include <simgear/debug/logstream.hxx>
26 #include <simgear/misc/strutils.hxx>
27 #include <boost/lexical_cast.hpp>
28
29 #include <vector>
30 #include <map>
31 #include <algorithm> 
32 #include <cstring>
33
34 using std::string;
35 using std::map;
36 using std::vector;
37
38 namespace flightgear {
39 namespace http {
40
41 // copied from http://stackoverflow.com/a/24315631
42 static void ReplaceAll(std::string & str, const std::string & from, const std::string & to) 
43 {
44     size_t start_pos = 0;
45     while((start_pos = str.find(from, start_pos)) != std::string::npos) {
46         str.replace(start_pos, from.length(), to);
47         start_pos += to.length(); // Handles case where 'to' is a substring of 'from'
48     }
49 }
50
51 static const std::string specialChars[][2] = {
52   { "&",  "&amp;" },
53   { "\"", "&quot;" },
54   { "'", "&#039;" },
55   { "<", "&lt;" },
56   { ">", "&gt;" },
57 };
58
59 static inline std::string htmlSpecialChars( const std::string & s )
60 {
61   string reply = s;
62   for( size_t i = 0; i < sizeof(specialChars)/sizeof(specialChars[0]); ++i )
63     ReplaceAll( reply, specialChars[i][0], specialChars[i][1] );
64   return reply;
65 }
66
67 class SortedChilds : public simgear::PropertyList {
68 public:
69   SortedChilds( SGPropertyNode_ptr node ) {
70     for (int i = 0; i < node->nChildren(); i++)
71       push_back(node->getChild(i));
72     std::sort(begin(), end(), CompareNodes());
73   }
74 private:
75   class CompareNodes {
76   public:
77     bool operator() (const SGPropertyNode *a, const SGPropertyNode *b) const {
78         int r = strcmp(a->getName(), b->getName());
79         return r ? r < 0 : a->getIndex() < b->getIndex();
80     }
81   };
82 };
83
84 static const char * getPropertyTypeString( simgear::props::Type type )
85 {
86   switch( type ) {
87     case simgear::props::NONE: return "";
88     case simgear::props::ALIAS: return "alias";
89     case simgear::props::BOOL: return "bool";
90     case simgear::props::INT: return "int";
91     case simgear::props::LONG: return "long";
92     case simgear::props::FLOAT: return "float";
93     case simgear::props::DOUBLE: return "double";
94     case simgear::props::STRING: return "string";
95     case simgear::props::UNSPECIFIED: return "unspecified";
96     case simgear::props::EXTENDED: return "extended";
97     case simgear::props::VEC3D: return "vec3d";
98     case simgear::props::VEC4D: return "vec4d";
99     default: return "?";
100   }
101 }
102
103 DOMElement * createHeader( const string & prefix, const string & propertyPath )
104 {
105   using namespace simgear::strutils;
106
107   string path = prefix;
108
109   DOMNode * root = new DOMNode( "div" );
110   root->setAttribute( "id", "breadcrumb" );
111
112   DOMNode * headline = new DOMNode( "h3" );
113   root->addChild( headline );
114   headline->addChild( new DOMTextElement("FlightGear Property Browser") );
115
116   {
117     DOMNode * div = new DOMNode("div");
118     root->addChild(div);
119     div->addChild( new DOMNode("span"))->addChild( new DOMTextElement("Path:"));
120     div->addChild( new DOMNode("span"))->addChild( new DOMTextElement( propertyPath ) );
121     div->addChild( new DOMNode("a"))->
122          setAttribute("href",string("/json/")+propertyPath+"?i=y")->
123          addChild( new DOMTextElement( "As JSON" ) );
124   }
125
126   DOMNode * breadcrumb = new DOMNode("ul");
127   root->addChild( breadcrumb );
128
129   DOMNode * li = new DOMNode("li");
130   breadcrumb->addChild( li );
131   DOMNode * a = new DOMNode("a");
132   li->addChild( a );
133   a->setAttribute( "href", path );
134   a->addChild( new DOMTextElement( "[root]" ) );
135
136   string_list items = split( propertyPath, "/" );
137   for( string_list::iterator it = items.begin(); it != items.end(); ++it ) {
138     if( (*it).empty() ) continue;
139     path.append( *it ).append( "/" );
140
141     li = new DOMNode("li");
142     breadcrumb->addChild( li );
143     a = new DOMNode("a");
144     li->addChild( a );
145     a->setAttribute( "href", path );
146     a->addChild( new DOMTextElement( (*it)  ) );
147   }
148
149   return root;
150 }
151
152 static DOMNode * createLabeledRadioButton( const char * label, const std::string & name, bool checked )
153 {
154     DOMNode * root =  new DOMNode("span");
155     root->setAttribute( "class", "radiobutton-container" );
156
157     root->addChild( new DOMNode("span"))->addChild( new DOMTextElement(label) );
158     DOMNode * radio = root->addChild(new DOMNode( "input" ))
159       ->setAttribute( "type", "radio" )
160       ->setAttribute( "name", name )
161       ->setAttribute( "value", label );
162
163     if( checked )
164       radio->setAttribute( "checked", "checked" );
165
166     return root;
167 }
168
169 static DOMElement * renderPropertyValueElement( SGPropertyNode_ptr node )
170 {
171     string value = node->getStringValue();
172     int len = value.length();
173
174     if( len < 15 ) len = 15;
175
176     DOMNode * root;
177
178     if( node->getType() == simgear::props::BOOL ) {
179         root = new DOMNode( "span" );
180
181         root->addChild( createLabeledRadioButton( "true", node->getDisplayName(), node->getBoolValue() ));
182         root->addChild( createLabeledRadioButton( "false", node->getDisplayName(), !node->getBoolValue() ));
183
184     } else if( len < 60 ) {
185         root = new DOMNode( "input" );
186         root->setAttribute( "type", "text" );
187         root->setAttribute( "name", node->getDisplayName() );
188         root->setAttribute( "value", htmlSpecialChars(value) );
189         root->setAttribute( "size", boost::lexical_cast<std::string>( len ) );
190         root->setAttribute( "maxlength", "2047" );
191     } else {
192         int rows = (len / 60)+1;
193         int cols = 60;
194         root = new DOMNode( "textarea" );
195         root->setAttribute( "name", node->getDisplayName() );
196         root->setAttribute( "cols", boost::lexical_cast<std::string>( cols ) );
197         root->setAttribute( "rows", boost::lexical_cast<std::string>( rows ) );
198         root->setAttribute( "maxlength", "2047" );
199         root->addChild( new DOMTextElement( htmlSpecialChars(value) ) );
200     }
201
202     return root;
203 }
204
205 bool PropertyUriHandler::handleGetRequest( const HTTPRequest & request, HTTPResponse & response, Connection * connection )
206 {
207
208   string propertyPath = request.Uri;
209
210   // strip the uri prefix of our handler
211   propertyPath = propertyPath.substr( getUri().size() );
212
213   // strip the querystring
214   size_t pos = propertyPath.find( '?' );
215   if( pos != string::npos ) {
216     propertyPath = propertyPath.substr( 0, pos-1 );
217   }
218
219   // skip trailing '/' - not very efficient but shouldn't happen too often
220   while( false == propertyPath.empty() && propertyPath[ propertyPath.length()-1 ] == '/' )
221     propertyPath = propertyPath.substr(0,propertyPath.length()-1);
222
223   if( request.RequestVariables.get("submit") == "update" ) {
224     // update leaf
225     string value = request.RequestVariables.get("value");
226     SG_LOG(SG_NETWORK,SG_INFO, "httpd: setting " << propertyPath << " to '" << value << "'" );
227     try {
228       fgSetString( propertyPath.c_str(), value );
229     }
230     catch( string & s ) { 
231       SG_LOG(SG_NETWORK,SG_WARN, "httpd: setting " << propertyPath << " to '" << value << "' failed: " << s );
232     }
233   }
234
235   if( request.RequestVariables.get("submit") == "set" ) {
236     for( HTTPRequest::StringMap::const_iterator it = request.RequestVariables.begin(); it != request.RequestVariables.end(); ++it ) {
237       if( it->first == "submit" ) continue;
238       string pp = propertyPath + "/" + it->first;
239       SG_LOG(SG_NETWORK,SG_INFO, "httpd: setting " << pp << " to '" << it->second << "'" );
240       try {
241         fgSetString( pp, it->second );
242       }
243       catch( string & s ) { 
244         SG_LOG(SG_NETWORK,SG_WARN, "httpd: setting " << pp << " to '" << it->second << "' failed: " << s );
245       }
246     }
247   }
248   
249   // build the response
250   DOMNode * html = new DOMNode( "html" );
251   html->setAttribute( "lang", "en" );
252
253   DOMNode * head = new DOMNode( "head" );
254   html->addChild( head );
255
256   DOMNode * e;
257   e = new DOMNode( "title" );
258   head->addChild( e );
259   e->addChild( new DOMTextElement( string("FlightGear Property Browser - ") + propertyPath ) );
260
261   e = new DOMNode( "link" );
262   head->addChild( e );
263   e->setAttribute( "href", "/css/props.css" );
264   e->setAttribute( "rel", "stylesheet" );
265   e->setAttribute( "type", "text/css" );
266
267   DOMNode * body = new DOMNode( "body" );
268   html->addChild( body );
269
270   SGPropertyNode_ptr node;
271   try {
272     node = fgGetNode( string("/") + propertyPath );
273   }
274   catch( string & s ) { 
275     SG_LOG(SG_NETWORK,SG_WARN, "httpd: reading '" << propertyPath  << "' failed: " << s );
276   }
277   if( false == node.valid() ) {
278     DOMNode * headline = new DOMNode( "h3" );
279     body->addChild( headline );
280     headline->addChild( new DOMTextElement( "Non-existent node requested!" ) );
281     e = new DOMNode( "b" );
282     e->addChild( new DOMTextElement( propertyPath ) );
283     // does not exist
284     body->addChild( e );
285     response.StatusCode = 404;
286
287   } else if( node->nChildren() > 0 ) {
288     // Render the list of children
289     body->addChild( createHeader( getUri(), propertyPath ));
290
291     DOMNode * table = new DOMNode("table");
292     body->addChild( table );
293
294     DOMNode * tr = new DOMNode( "tr" );
295     table->addChild( tr );
296
297     DOMNode * th = new DOMNode( "th" );
298     tr->addChild( th );
299     th->addChild( new DOMTextElement( "&nbsp;" ) );
300
301     th = new DOMNode( "th" );
302     tr->addChild( th );
303     th->addChild( new DOMTextElement( "Property" ) );
304     th->setAttribute( "id", "property" );
305
306     th = new DOMNode( "th" );
307     tr->addChild( th );
308     th->addChild( new DOMTextElement( "Value" ) );
309     th->setAttribute( "id", "value" );
310
311     th = new DOMNode( "th" );
312     tr->addChild( th );
313     th->addChild( new DOMTextElement( "Type" ) );
314     th->setAttribute( "id", "type" );
315
316     SortedChilds sortedChilds( node );
317     for(SortedChilds::iterator it = sortedChilds.begin(); it != sortedChilds.end(); ++it ) {
318       tr = new DOMNode( "tr" );
319       table->addChild( tr );
320
321       SGPropertyNode_ptr child = *it;
322       string name = child->getDisplayName(true);
323
324       DOMNode * td;
325
326       // Expand Link
327       td = new DOMNode("td");
328       tr->addChild( td );
329       td->setAttribute( "id", "expand" );
330       if ( child->nChildren() > 0 ) {
331         DOMNode * a = new DOMNode("a");
332         td->addChild( a );
333         a->setAttribute( "href", getUri() + propertyPath + "/" + name );
334         a->addChild( new DOMTextElement( "(+)" ));
335       }
336
337       // Property Name
338       td = new DOMNode("td");
339       tr->addChild( td );
340       td->setAttribute( "id", "property" );
341       DOMNode * a = new DOMNode("a");
342       td->addChild( a );
343       a->setAttribute( "href", getUri() + propertyPath + "/" + name );
344       a->addChild( new DOMTextElement( name ) );
345
346       // Value
347       td = new DOMNode("td");
348       tr->addChild( td );
349       td->setAttribute( "id", "value" );
350       if ( child->nChildren() == 0 ) {
351         DOMNode * form = new DOMNode("form");
352         td->addChild( form );
353         form->setAttribute( "method", "GET" );
354         form->setAttribute( "action", getUri() + propertyPath );
355
356         e = new DOMNode( "input" );
357         form->addChild( e );
358         e->setAttribute( "type", "submit" );
359         e->setAttribute( "value", "set" );
360         e->setAttribute( "name", "submit" );
361
362         form->addChild( renderPropertyValueElement( node->getNode( name ) ) );
363
364       } else {
365         td->addChild( new DOMTextElement( "&nbsp;" ) );
366       }
367
368       // Property Type
369       td = new DOMNode("td");
370       tr->addChild( td );
371       td->setAttribute( "id", "type" );
372       td->addChild( 
373         new DOMTextElement( getPropertyTypeString(node->getNode( name )->getType()) ) );
374
375     }
376   } else {
377     // Render a single property
378     body->addChild( createHeader( getUri(), propertyPath ));
379     e = new DOMNode( "div" );
380     body->addChild( e );
381
382     e->setAttribute( "id", "currentvalue" );
383     e->addChild( new DOMTextElement( "Current Value: " ) );
384     e->addChild( new DOMTextElement( htmlSpecialChars(node->getStringValue()) ) );
385
386     DOMNode * form = new DOMNode("form");
387     body->addChild( form );
388     form->setAttribute( "method", "GET" );
389     form->setAttribute( "action", getUri() + propertyPath );
390
391     e = new DOMNode( "input" );
392     form->addChild( e );
393     e->setAttribute( "type", "submit" );
394     e->setAttribute( "value", "update" );
395     e->setAttribute( "name", "submit" );
396
397     form->addChild( renderPropertyValueElement( node ) );
398   }
399
400   // Send the response 
401   response.Content = "<!DOCTYPE html>";
402   response.Content.append( html->render() );
403   delete html;
404   response.Header["Content-Type"] = "text/html; charset=UTF-8";
405
406   return true;
407
408 }
409
410 } // namespace http
411 } // namespace flightgear
412