// PropertyUriHandler.cxx -- a web form interface to the property tree // // Written by Torsten Dreyer, started April 2014. // // Copyright (C) 2014 Torsten Dreyer // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License as // published by the Free Software Foundation; either version 2 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "PropertyUriHandler.hxx" #include
#include #include #include #include #include #include #include using std::string; using std::map; using std::vector; namespace flightgear { namespace http { class DOMElement { public: virtual ~DOMElement() {} virtual std::string render() const = 0; }; class DOMTextElement : public DOMElement { public: DOMTextElement( const string & text ) : _text(text) {} virtual std::string render() const { return _text; } private: string _text; }; class DOMNode : public DOMElement { public: DOMNode( const string & name ) : _name(name) {} virtual ~DOMNode(); virtual std::string render() const; virtual void addChild( DOMElement * child ); virtual void setAttribute( const string & name, const string & value ); protected: string _name; typedef vector Children_t; typedef map Attributes_t; Children_t _children; Attributes_t _attributes; }; DOMNode::~DOMNode() { for( Children_t::const_iterator it = _children.begin(); it != _children.end(); ++it ) delete *it; } string DOMNode::render() const { string reply; reply.append( "<" ).append( _name ); for( Attributes_t::const_iterator it = _attributes.begin(); it != _attributes.end(); ++it ) { reply.append( " " ); reply.append( it->first ); reply.append( "=\"" ); reply.append( it->second ); reply.append( "\"" ); } if( _children.empty() ) { reply.append( " />\r" ); return reply; } else { reply.append( ">" ); } for( Children_t::const_iterator it = _children.begin(); it != _children.end(); ++it ) { reply.append( (*it)->render() ); } reply.append( "\r" ); return reply; } void DOMNode::addChild( DOMElement * child ) { _children.push_back( child ); } void DOMNode::setAttribute( const string & name, const string & value ) { _attributes[name] = value; } class SortedChilds : public simgear::PropertyList { public: SortedChilds( SGPropertyNode_ptr node ) { for (int i = 0; i < node->nChildren(); i++) push_back(node->getChild(i)); std::sort(begin(), end(), CompareNodes()); } private: class CompareNodes { public: bool operator() (const SGPropertyNode *a, const SGPropertyNode *b) const { int r = strcmp(a->getName(), b->getName()); return r ? r < 0 : a->getIndex() < b->getIndex(); } }; }; static const char * getPropertyTypeString( simgear::props::Type type ) { switch( type ) { case simgear::props::NONE: return ""; case simgear::props::ALIAS: return "alias"; case simgear::props::BOOL: return "bool"; case simgear::props::INT: return "int"; case simgear::props::LONG: return "long"; case simgear::props::FLOAT: return "float"; case simgear::props::DOUBLE: return "double"; case simgear::props::STRING: return "string"; case simgear::props::UNSPECIFIED: return "unspecified"; case simgear::props::EXTENDED: return "extended"; case simgear::props::VEC3D: return "vec3d"; case simgear::props::VEC4D: return "vec4d"; default: return "?"; } } DOMElement * createHeader( const string & prefix, const string & propertyPath ) { using namespace simgear::strutils; string path = prefix; DOMNode * root = new DOMNode( "div" ); root->setAttribute( "id", "breadcrumb" ); DOMNode * headline = new DOMNode( "h3" ); root->addChild( headline ); headline->addChild( new DOMTextElement("FlightGear Property Browser") ); DOMNode * breadcrumb = new DOMNode("ul"); root->addChild( breadcrumb ); DOMNode * li = new DOMNode("li"); breadcrumb->addChild( li ); DOMNode * a = new DOMNode("a"); li->addChild( a ); a->setAttribute( "href", path ); a->addChild( new DOMTextElement( "[root]" ) ); string_list items = split( propertyPath, "/" ); for( string_list::iterator it = items.begin(); it != items.end(); ++it ) { if( (*it).empty() ) continue; path.append( *it ).append( "/" ); li = new DOMNode("li"); breadcrumb->addChild( li ); a = new DOMNode("a"); li->addChild( a ); a->setAttribute( "href", path ); a->addChild( new DOMTextElement( (*it) ) ); } return root; } static DOMElement * renderPropertyValueElement( SGPropertyNode_ptr node ) { string value = node->getStringValue(); int len = value.length(); if( len < 15 ) len = 15; DOMNode * root; if( len < 60 ) { root = new DOMNode( "input" ); root->setAttribute( "type", "text" ); root->setAttribute( "name", node->getDisplayName() ); root->setAttribute( "value", value ); root->setAttribute( "size", boost::lexical_cast( len ) ); root->setAttribute( "maxlength", "2047" ); } else { int rows = (len / 60)+1; int cols = 60; root = new DOMNode( "textarea" ); root->setAttribute( "name", node->getDisplayName() ); root->setAttribute( "cols", boost::lexical_cast( cols ) ); root->setAttribute( "rows", boost::lexical_cast( rows ) ); root->setAttribute( "maxlength", "2047" ); root->addChild( new DOMTextElement( value ) ); } return root; } bool PropertyUriHandler::handleGetRequest( const HTTPRequest & request, HTTPResponse & response ) { string propertyPath = request.Uri; // strip the uri prefix of our handler propertyPath = propertyPath.substr( getUri().size() ); // strip the querystring size_t pos = propertyPath.find( '?' ); if( pos != string::npos ) { propertyPath = propertyPath.substr( 0, pos-1 ); } // skip trailing '/' - not very efficient but shouldn't happen too often while( false == propertyPath.empty() && propertyPath[ propertyPath.length()-1 ] == '/' ) propertyPath = propertyPath.substr(0,propertyPath.length()-1); if( request.RequestVariables.get("submit") == "update" ) { // update leaf string value = request.RequestVariables.get("value"); SG_LOG(SG_NETWORK,SG_INFO, "httpd: setting " << propertyPath << " to '" << value << "'" ); fgSetString( propertyPath.c_str(), value ); } if( request.RequestVariables.get("submit") == "set" ) { for( HTTPRequest::StringMap::const_iterator it = request.RequestVariables.begin(); it != request.RequestVariables.end(); ++it ) { if( it->first == "submit" ) continue; string pp = propertyPath + "/" + it->first; SG_LOG(SG_NETWORK,SG_INFO, "httpd: setting " << pp << " to '" << it->second << "'" ); fgSetString( pp, it->second ); } } // build the response DOMNode * html = new DOMNode( "html" ); html->setAttribute( "lang", "en" ); DOMNode * head = new DOMNode( "head" ); html->addChild( head ); DOMNode * e; e = new DOMNode( "title" ); head->addChild( e ); e->addChild( new DOMTextElement( string("FlightGear Property Browser - ") + propertyPath ) ); e = new DOMNode( "link" ); head->addChild( e ); e->setAttribute( "href", "/css/props.css" ); e->setAttribute( "rel", "stylesheet" ); e->setAttribute( "type", "text/css" ); DOMNode * body = new DOMNode( "body" ); html->addChild( body ); SGPropertyNode_ptr node = fgGetNode( string("/") + propertyPath ); if( false == node.valid() ) { DOMNode * headline = new DOMNode( "h3" ); body->addChild( headline ); headline->addChild( new DOMTextElement( "Non-existent node requested!" ) ); e = new DOMNode( "b" ); e->addChild( new DOMTextElement( propertyPath ) ); // does not exist body->addChild( e ); } else if( node->nChildren() > 0 ) { // Render the list of children body->addChild( createHeader( getUri(), propertyPath )); DOMNode * table = new DOMNode("table"); body->addChild( table ); DOMNode * tr = new DOMNode( "tr" ); table->addChild( tr ); DOMNode * th = new DOMNode( "th" ); tr->addChild( th ); th->addChild( new DOMTextElement( " " ) ); th = new DOMNode( "th" ); tr->addChild( th ); th->addChild( new DOMTextElement( "Property" ) ); th->setAttribute( "id", "property" ); th = new DOMNode( "th" ); tr->addChild( th ); th->addChild( new DOMTextElement( "Value" ) ); th->setAttribute( "id", "value" ); th = new DOMNode( "th" ); tr->addChild( th ); th->addChild( new DOMTextElement( "Type" ) ); th->setAttribute( "id", "type" ); SortedChilds sortedChilds( node ); for(SortedChilds::iterator it = sortedChilds.begin(); it != sortedChilds.end(); ++it ) { tr = new DOMNode( "tr" ); table->addChild( tr ); SGPropertyNode_ptr child = *it; string name = child->getDisplayName(true); DOMNode * td; // Expand Link td = new DOMNode("td"); tr->addChild( td ); td->setAttribute( "id", "expand" ); if ( child->nChildren() > 0 ) { DOMNode * a = new DOMNode("a"); td->addChild( a ); a->setAttribute( "href", getUri() + propertyPath + "/" + name ); a->addChild( new DOMTextElement( "(+)" )); } // Property Name td = new DOMNode("td"); tr->addChild( td ); td->setAttribute( "id", "property" ); DOMNode * a = new DOMNode("a"); td->addChild( a ); a->setAttribute( "href", getUri() + propertyPath + "/" + name ); a->addChild( new DOMTextElement( name ) ); // Value td = new DOMNode("td"); tr->addChild( td ); td->setAttribute( "id", "value" ); if ( child->nChildren() == 0 ) { DOMNode * form = new DOMNode("form"); td->addChild( form ); form->setAttribute( "method", "GET" ); form->setAttribute( "action", getUri() + propertyPath ); e = new DOMNode( "input" ); form->addChild( e ); e->setAttribute( "type", "submit" ); e->setAttribute( "value", "set" ); e->setAttribute( "name", "submit" ); form->addChild( renderPropertyValueElement( node->getNode( name ) ) ); } else { td->addChild( new DOMTextElement( " " ) ); } // Property Type td = new DOMNode("td"); tr->addChild( td ); td->setAttribute( "id", "type" ); td->addChild( new DOMTextElement( getPropertyTypeString(node->getNode( name )->getType()) ) ); } } else { // Render a single property body->addChild( createHeader( getUri(), propertyPath )); e = new DOMNode( "div" ); body->addChild( e ); e->setAttribute( "id", "currentvalue" ); e->addChild( new DOMTextElement( "Current Value: " ) ); e->addChild( new DOMTextElement( node->getStringValue() ) ); DOMNode * form = new DOMNode("form"); body->addChild( form ); form->setAttribute( "method", "GET" ); form->setAttribute( "action", getUri() + propertyPath ); e = new DOMNode( "input" ); form->addChild( e ); e->setAttribute( "type", "submit" ); e->setAttribute( "value", "update" ); e->setAttribute( "name", "submit" ); form->addChild( renderPropertyValueElement( node ) ); } // Send the response response.Content = ""; response.Content.append( html->render() ); delete html; response.Header["Content-Type"] = "text/html; charset=ISO-8859-1"; return true; } } // namespace http } // namespace flightgear