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