summaryrefslogtreecommitdiffstats
path: root/source/cWebAdmin.cpp
diff options
context:
space:
mode:
authorcedeel@gmail.com <cedeel@gmail.com@0a769ca7-a7f5-676a-18bf-c427514a06d6>2012-06-14 15:06:06 +0200
committercedeel@gmail.com <cedeel@gmail.com@0a769ca7-a7f5-676a-18bf-c427514a06d6>2012-06-14 15:06:06 +0200
commit92c59963f82f81aa3202657e7fdbb2592924ede3 (patch)
treeb7eb2474528a4998fa102e3ec9119b908cee08b4 /source/cWebAdmin.cpp
parentAdded HOOK_WEATHER_CHANGE. (diff)
downloadcuberite-92c59963f82f81aa3202657e7fdbb2592924ede3.tar
cuberite-92c59963f82f81aa3202657e7fdbb2592924ede3.tar.gz
cuberite-92c59963f82f81aa3202657e7fdbb2592924ede3.tar.bz2
cuberite-92c59963f82f81aa3202657e7fdbb2592924ede3.tar.lz
cuberite-92c59963f82f81aa3202657e7fdbb2592924ede3.tar.xz
cuberite-92c59963f82f81aa3202657e7fdbb2592924ede3.tar.zst
cuberite-92c59963f82f81aa3202657e7fdbb2592924ede3.zip
Diffstat (limited to '')
-rw-r--r--source/cWebAdmin.cpp766
1 files changed, 383 insertions, 383 deletions
diff --git a/source/cWebAdmin.cpp b/source/cWebAdmin.cpp
index b05eedb16..33068ec38 100644
--- a/source/cWebAdmin.cpp
+++ b/source/cWebAdmin.cpp
@@ -1,383 +1,383 @@
-
-#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
-
-#include "cWebAdmin.h"
-#include "cStringMap.h"
-
-#include "cWebPlugin.h"
-#include "cWebPlugin_Lua.h"
-
-#include "cPluginManager.h"
-#include "cPlugin.h"
-
-#include "cWorld.h"
-#include "cPlayer.h"
-#include "cServer.h"
-#include "cRoot.h"
-
-#include "../iniFile/iniFile.h"
-
-#ifdef _WIN32
- #include <psapi.h>
-#else
- #include <sys/resource.h>
-#endif
-
-
-
-
-
-/// Helper class - appends all player names together in a HTML list
-class cPlayerAccum :
- public cPlayerListCallback
-{
- virtual bool Item(cPlayer * a_Player) override
- {
- m_Contents.append("<li>");
- m_Contents.append(a_Player->GetName());
- m_Contents.append("</li>");
- return false;
- }
-
-public:
-
- AString m_Contents;
-} ;
-
-
-
-
-
-cWebAdmin * WebAdmin = NULL;
-
-
-
-
-
-cWebAdmin::cWebAdmin( int a_Port /* = 8080 */ )
- : m_Port( a_Port )
- , m_bConnected( false )
-{
- WebAdmin = this;
- m_Event = new cEvent();
- Init( m_Port );
-}
-
-cWebAdmin::~cWebAdmin()
-{
- WebAdmin = 0;
- m_WebServer->Stop();
-
- while( m_Plugins.begin() != m_Plugins.end() )
- {
- delete *m_Plugins.begin();
- //m_Plugins.remove( *m_Plugins.begin() );
- }
- delete m_WebServer;
- delete m_IniFile;
-
- m_Event->Wait();
- delete m_Event;
-}
-
-void cWebAdmin::AddPlugin( cWebPlugin* a_Plugin )
-{
- m_Plugins.remove( a_Plugin );
- m_Plugins.push_back( a_Plugin );
-}
-
-void cWebAdmin::RemovePlugin( cWebPlugin* a_Plugin )
-{
- m_Plugins.remove( a_Plugin );
-}
-
-
-
-
-
-void cWebAdmin::Request_Handler(webserver::http_request* r)
-{
- if( WebAdmin == 0 ) return;
- LOG("Path: %s", r->path_.c_str() );
-
- if (r->path_ == "/")
- {
- r->answer_ += "<h1>MCServer WebAdmin</h1>";
- r->answer_ += "<center>";
- r->answer_ += "<form method='get' action='webadmin/'>";
- r->answer_ += "<input type='submit' value='Log in'>";
- r->answer_ += "</form>";
- r->answer_ += "</center>";
- return;
- }
-
- if (r->path_.empty() || r->path_[0] != '/')
- {
- r->answer_ += "<h1>Bad request</h1>";
- r->answer_ += "<p>";
- r->answer_ = r->path_; // TODO: Shouldn't we sanitize this? Possible security issue.
- r->answer_ += "</p>";
- return;
- }
-
- AStringVector Split = StringSplit(r->path_.substr(1), "/");
-
- if (Split.empty() || (Split[0] != "webadmin"))
- {
- r->answer_ += "<h1>Bad request</h1>";
- return;
- }
-
- if (!r->authentication_given_)
- {
- r->answer_ += "no auth";
- r->auth_realm_ = "MCServer WebAdmin";
- }
-
- std::string UserPassword = WebAdmin->m_IniFile->GetValue( "User:"+r->username_, "Password", "");
- if ((UserPassword != "") && (r->password_ == UserPassword))
- {
- std::string BaseURL = "./";
- if (Split.size() > 1)
- {
- for (unsigned int i = 0; i < Split.size(); i++)
- {
- BaseURL += "../";
- }
- BaseURL += "webadmin/";
- }
-
- std::string Menu;
- std::string Content;
- std::string Template = WebAdmin->GetTemplate();
- std::string FoundPlugin;
-
- for (PluginList::iterator itr = WebAdmin->m_Plugins.begin(); itr != WebAdmin->m_Plugins.end(); ++itr)
- {
- cWebPlugin* WebPlugin = *itr;
- cWebPlugin_Lua* LuaPlugin = dynamic_cast< cWebPlugin_Lua* >( WebPlugin );
- if( LuaPlugin )
- {
- std::list< std::pair<std::string, std::string> > NameList = LuaPlugin->GetTabNames();
- for( std::list< std::pair<std::string, std::string> >::iterator Names = NameList.begin(); Names != NameList.end(); ++Names )
- {
- Menu += "<li><a href='" + BaseURL + WebPlugin->GetName() + "/" + (*Names).second + "'>" + (*Names).first + "</a></li>";
- }
- }
- else
- {
- Menu += "<li><a href='" + BaseURL + WebPlugin->GetName() + "'>" + WebPlugin->GetName() + "</a></li>";
- }
- }
-
- HTTPRequest Request;
- Request.Username = r->username_;
- Request.Method = r->method_;
- Request.Params = r->params_;
- Request.PostParams = r->params_post_;
- Request.Path = r->path_.substr(1);
-
- for( unsigned int i = 0; i < r->multipart_formdata_.size(); ++i )
- {
- webserver::formdata& fd = r->multipart_formdata_[i];
-
- HTTPFormData HTTPfd;//( fd.value_ );
- HTTPfd.Value = fd.value_;
- HTTPfd.Type = fd.content_type_;
- HTTPfd.Name = fd.name_;
- LOGINFO("Form data name: %s", fd.name_.c_str() );
- Request.FormData[ fd.name_ ] = HTTPfd;
- }
-
- if( Split.size() > 1 )
- {
- for( PluginList::iterator itr = WebAdmin->m_Plugins.begin(); itr != WebAdmin->m_Plugins.end(); ++itr )
- {
- if( (*itr)->GetName() == Split[1] )
- {
- Content = (*itr)->HandleRequest( &Request );
- cWebPlugin* WebPlugin = *itr;
- FoundPlugin = WebPlugin->GetName();
- cWebPlugin_Lua* LuaPlugin = dynamic_cast< cWebPlugin_Lua* >( WebPlugin );
- if( LuaPlugin )
- {
- FoundPlugin += " - " + LuaPlugin->GetTabNameForRequest( &Request ).first;
- }
- break;
- }
- }
- }
-
- if( FoundPlugin.empty() ) // Default page
- {
- Content.clear();
- FoundPlugin = "Current Game";
- Content += "<h4>Server Name:</h4>";
- Content += "<p>" + std::string( cRoot::Get()->GetServer()->GetServerID() ) + "</p>";
-
- Content += "<h4>Plugins:</h4><ul>";
- cPluginManager* PM = cRoot::Get()->GetPluginManager();
- if( PM )
- {
- const cPluginManager::PluginList & List = PM->GetAllPlugins();
- for( cPluginManager::PluginList::const_iterator itr = List.begin(); itr != List.end(); ++itr )
- {
- AString VersionNum;
- AppendPrintf(Content, "<li>%s V.%i</li>", (*itr)->GetName(), (*itr)->GetVersion());
- }
- }
- Content += "</ul>";
- Content += "<h4>Players:</h4><ul>";
-
- cPlayerAccum PlayerAccum;
- cWorld * World = cRoot::Get()->GetDefaultWorld(); // TODO - Create a list of worlds and players
- World->ForEachPlayer(PlayerAccum);
- Content.append(PlayerAccum.m_Contents);
- Content += "</ul><br>";
- }
-
-
-
- if( Split.size() > 1 )
- {
- Content += "\n<p><a href='" + BaseURL + "'>Go back</a></p>";
- }
-
- // mem usage
-#ifndef _WIN32
- rusage resource_usage;
- if (getrusage(RUSAGE_SELF, &resource_usage) != 0)
- {
- ReplaceString( Template, std::string("{MEM}"), "Error :(" );
- }
- else
- {
- AString MemUsage;
- Printf(MemUsage, "%0.2f", ((double)resource_usage.ru_maxrss / 1024 / 1024) );
- ReplaceString(Template, std::string("{MEM}"), MemUsage);
- }
-#else
- HANDLE hProcess = GetCurrentProcess();
- PROCESS_MEMORY_COUNTERS pmc;
- if( GetProcessMemoryInfo( hProcess, &pmc, sizeof(pmc) ) )
- {
- AString MemUsage;
- Printf(MemUsage, "%0.2f", (pmc.WorkingSetSize / 1024.f / 1024.f) );
- ReplaceString( Template, "{MEM}", MemUsage );
- }
-#endif
- // end mem usage
-
- ReplaceString( Template, "{USERNAME}", r->username_ );
- ReplaceString( Template, "{MENU}", Menu );
- ReplaceString( Template, "{PLUGIN_NAME}", FoundPlugin );
- ReplaceString( Template, "{CONTENT}", Content );
- ReplaceString( Template, "{TITLE}", "MCServer" );
-
- AString NumChunks;
- Printf(NumChunks, "%d", cRoot::Get()->GetTotalChunkCount());
- ReplaceString(Template, "{NUMCHUNKS}", NumChunks);
-
- r->answer_ = Template;
- }
- else
- {
- r->answer_ += "Wrong username/password";
- r->auth_realm_ = "MCServer WebAdmin";
- }
-}
-
-
-
-
-
-bool cWebAdmin::Init( int a_Port )
-{
- m_Port = a_Port;
-
- m_IniFile = new cIniFile("webadmin.ini");
- if( m_IniFile->ReadFile() )
- {
- m_Port = m_IniFile->GetValueI("WebAdmin", "Port", 8080 );
- }
-
- LOG("Starting WebAdmin on port %i", m_Port);
-
-#ifdef _WIN32
- HANDLE hThread = CreateThread(
- NULL, // default security
- 0, // default stack size
- ListenThread, // name of the thread function
- this, // thread parameters
- 0, // default startup flags
- NULL);
- CloseHandle( hThread ); // Just close the handle immediately
-#else
- pthread_t LstnThread;
- pthread_create( &LstnThread, 0, ListenThread, this);
-#endif
-
- return true;
-}
-
-
-
-
-
-#ifdef _WIN32
-DWORD WINAPI cWebAdmin::ListenThread(LPVOID lpParam)
-#else
-void *cWebAdmin::ListenThread( void *lpParam )
-#endif
-{
- cWebAdmin* self = (cWebAdmin*)lpParam;
-
- self->m_WebServer = new webserver(self->m_Port, Request_Handler );
- if (!self->m_WebServer->Begin())
- {
- LOGWARN("WebServer failed to start! WebAdmin is disabled");
- }
-
- self->m_Event->Set();
- return 0;
-}
-
-
-
-
-
-std::string cWebAdmin::GetTemplate()
-{
- std::string retVal = "";
-
- char SourceFile[] = "webadmin/template.html";
-
- cFile f;
- if (!f.Open(SourceFile, cFile::fmRead))
- {
- return "";
- }
-
- // copy the file into the buffer:
- f.ReadRestOfFile(retVal);
-
- return retVal;
-}
-
-
-
-
-
-void cWebAdmin::RemovePlugin( lua_State* L )
-{
- for( PluginList::iterator itr = m_Plugins.begin(); itr != m_Plugins.end(); )
- {
- if( (*itr)->GetLuaState() == L )
- {
- PluginList::iterator prev = itr++;
- delete *prev; // deleting a dereferenced iterator also takes it out of the list, so no need for erase()
- }
- else
- ++itr;
- }
-}
+
+#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
+
+#include "cWebAdmin.h"
+#include "cStringMap.h"
+
+#include "cWebPlugin.h"
+#include "cWebPlugin_Lua.h"
+
+#include "cPluginManager.h"
+#include "cPlugin.h"
+
+#include "cWorld.h"
+#include "cPlayer.h"
+#include "cServer.h"
+#include "cRoot.h"
+
+#include "../iniFile/iniFile.h"
+
+#ifdef _WIN32
+ #include <psapi.h>
+#else
+ #include <sys/resource.h>
+#endif
+
+
+
+
+
+/// Helper class - appends all player names together in a HTML list
+class cPlayerAccum :
+ public cPlayerListCallback
+{
+ virtual bool Item(cPlayer * a_Player) override
+ {
+ m_Contents.append("<li>");
+ m_Contents.append(a_Player->GetName());
+ m_Contents.append("</li>");
+ return false;
+ }
+
+public:
+
+ AString m_Contents;
+} ;
+
+
+
+
+
+cWebAdmin * WebAdmin = NULL;
+
+
+
+
+
+cWebAdmin::cWebAdmin( int a_Port /* = 8080 */ )
+ : m_Port( a_Port )
+ , m_bConnected( false )
+{
+ WebAdmin = this;
+ m_Event = new cEvent();
+ Init( m_Port );
+}
+
+cWebAdmin::~cWebAdmin()
+{
+ WebAdmin = 0;
+ m_WebServer->Stop();
+
+ while( m_Plugins.begin() != m_Plugins.end() )
+ {
+ delete *m_Plugins.begin();
+ //m_Plugins.remove( *m_Plugins.begin() );
+ }
+ delete m_WebServer;
+ delete m_IniFile;
+
+ m_Event->Wait();
+ delete m_Event;
+}
+
+void cWebAdmin::AddPlugin( cWebPlugin* a_Plugin )
+{
+ m_Plugins.remove( a_Plugin );
+ m_Plugins.push_back( a_Plugin );
+}
+
+void cWebAdmin::RemovePlugin( cWebPlugin* a_Plugin )
+{
+ m_Plugins.remove( a_Plugin );
+}
+
+
+
+
+
+void cWebAdmin::Request_Handler(webserver::http_request* r)
+{
+ if( WebAdmin == 0 ) return;
+ LOG("Path: %s", r->path_.c_str() );
+
+ if (r->path_ == "/")
+ {
+ r->answer_ += "<h1>MCServer WebAdmin</h1>";
+ r->answer_ += "<center>";
+ r->answer_ += "<form method='get' action='webadmin/'>";
+ r->answer_ += "<input type='submit' value='Log in'>";
+ r->answer_ += "</form>";
+ r->answer_ += "</center>";
+ return;
+ }
+
+ if (r->path_.empty() || r->path_[0] != '/')
+ {
+ r->answer_ += "<h1>Bad request</h1>";
+ r->answer_ += "<p>";
+ r->answer_ = r->path_; // TODO: Shouldn't we sanitize this? Possible security issue.
+ r->answer_ += "</p>";
+ return;
+ }
+
+ AStringVector Split = StringSplit(r->path_.substr(1), "/");
+
+ if (Split.empty() || (Split[0] != "webadmin"))
+ {
+ r->answer_ += "<h1>Bad request</h1>";
+ return;
+ }
+
+ if (!r->authentication_given_)
+ {
+ r->answer_ += "no auth";
+ r->auth_realm_ = "MCServer WebAdmin";
+ }
+
+ std::string UserPassword = WebAdmin->m_IniFile->GetValue( "User:"+r->username_, "Password", "");
+ if ((UserPassword != "") && (r->password_ == UserPassword))
+ {
+ std::string BaseURL = "./";
+ if (Split.size() > 1)
+ {
+ for (unsigned int i = 0; i < Split.size(); i++)
+ {
+ BaseURL += "../";
+ }
+ BaseURL += "webadmin/";
+ }
+
+ std::string Menu;
+ std::string Content;
+ std::string Template = WebAdmin->GetTemplate();
+ std::string FoundPlugin;
+
+ for (PluginList::iterator itr = WebAdmin->m_Plugins.begin(); itr != WebAdmin->m_Plugins.end(); ++itr)
+ {
+ cWebPlugin* WebPlugin = *itr;
+ cWebPlugin_Lua* LuaPlugin = dynamic_cast< cWebPlugin_Lua* >( WebPlugin );
+ if( LuaPlugin )
+ {
+ std::list< std::pair<std::string, std::string> > NameList = LuaPlugin->GetTabNames();
+ for( std::list< std::pair<std::string, std::string> >::iterator Names = NameList.begin(); Names != NameList.end(); ++Names )
+ {
+ Menu += "<li><a href='" + BaseURL + WebPlugin->GetName() + "/" + (*Names).second + "'>" + (*Names).first + "</a></li>";
+ }
+ }
+ else
+ {
+ Menu += "<li><a href='" + BaseURL + WebPlugin->GetName() + "'>" + WebPlugin->GetName() + "</a></li>";
+ }
+ }
+
+ HTTPRequest Request;
+ Request.Username = r->username_;
+ Request.Method = r->method_;
+ Request.Params = r->params_;
+ Request.PostParams = r->params_post_;
+ Request.Path = r->path_.substr(1);
+
+ for( unsigned int i = 0; i < r->multipart_formdata_.size(); ++i )
+ {
+ webserver::formdata& fd = r->multipart_formdata_[i];
+
+ HTTPFormData HTTPfd;//( fd.value_ );
+ HTTPfd.Value = fd.value_;
+ HTTPfd.Type = fd.content_type_;
+ HTTPfd.Name = fd.name_;
+ LOGINFO("Form data name: %s", fd.name_.c_str() );
+ Request.FormData[ fd.name_ ] = HTTPfd;
+ }
+
+ if( Split.size() > 1 )
+ {
+ for( PluginList::iterator itr = WebAdmin->m_Plugins.begin(); itr != WebAdmin->m_Plugins.end(); ++itr )
+ {
+ if( (*itr)->GetName() == Split[1] )
+ {
+ Content = (*itr)->HandleRequest( &Request );
+ cWebPlugin* WebPlugin = *itr;
+ FoundPlugin = WebPlugin->GetName();
+ cWebPlugin_Lua* LuaPlugin = dynamic_cast< cWebPlugin_Lua* >( WebPlugin );
+ if( LuaPlugin )
+ {
+ FoundPlugin += " - " + LuaPlugin->GetTabNameForRequest( &Request ).first;
+ }
+ break;
+ }
+ }
+ }
+
+ if( FoundPlugin.empty() ) // Default page
+ {
+ Content.clear();
+ FoundPlugin = "Current Game";
+ Content += "<h4>Server Name:</h4>";
+ Content += "<p>" + std::string( cRoot::Get()->GetServer()->GetServerID() ) + "</p>";
+
+ Content += "<h4>Plugins:</h4><ul>";
+ cPluginManager* PM = cRoot::Get()->GetPluginManager();
+ if( PM )
+ {
+ const cPluginManager::PluginList & List = PM->GetAllPlugins();
+ for( cPluginManager::PluginList::const_iterator itr = List.begin(); itr != List.end(); ++itr )
+ {
+ AString VersionNum;
+ AppendPrintf(Content, "<li>%s V.%i</li>", (*itr)->GetName(), (*itr)->GetVersion());
+ }
+ }
+ Content += "</ul>";
+ Content += "<h4>Players:</h4><ul>";
+
+ cPlayerAccum PlayerAccum;
+ cWorld * World = cRoot::Get()->GetDefaultWorld(); // TODO - Create a list of worlds and players
+ World->ForEachPlayer(PlayerAccum);
+ Content.append(PlayerAccum.m_Contents);
+ Content += "</ul><br>";
+ }
+
+
+
+ if( Split.size() > 1 )
+ {
+ Content += "\n<p><a href='" + BaseURL + "'>Go back</a></p>";
+ }
+
+ // mem usage
+#ifndef _WIN32
+ rusage resource_usage;
+ if (getrusage(RUSAGE_SELF, &resource_usage) != 0)
+ {
+ ReplaceString( Template, std::string("{MEM}"), "Error :(" );
+ }
+ else
+ {
+ AString MemUsage;
+ Printf(MemUsage, "%0.2f", ((double)resource_usage.ru_maxrss / 1024 / 1024) );
+ ReplaceString(Template, std::string("{MEM}"), MemUsage);
+ }
+#else
+ HANDLE hProcess = GetCurrentProcess();
+ PROCESS_MEMORY_COUNTERS pmc;
+ if( GetProcessMemoryInfo( hProcess, &pmc, sizeof(pmc) ) )
+ {
+ AString MemUsage;
+ Printf(MemUsage, "%0.2f", (pmc.WorkingSetSize / 1024.f / 1024.f) );
+ ReplaceString( Template, "{MEM}", MemUsage );
+ }
+#endif
+ // end mem usage
+
+ ReplaceString( Template, "{USERNAME}", r->username_ );
+ ReplaceString( Template, "{MENU}", Menu );
+ ReplaceString( Template, "{PLUGIN_NAME}", FoundPlugin );
+ ReplaceString( Template, "{CONTENT}", Content );
+ ReplaceString( Template, "{TITLE}", "MCServer" );
+
+ AString NumChunks;
+ Printf(NumChunks, "%d", cRoot::Get()->GetTotalChunkCount());
+ ReplaceString(Template, "{NUMCHUNKS}", NumChunks);
+
+ r->answer_ = Template;
+ }
+ else
+ {
+ r->answer_ += "Wrong username/password";
+ r->auth_realm_ = "MCServer WebAdmin";
+ }
+}
+
+
+
+
+
+bool cWebAdmin::Init( int a_Port )
+{
+ m_Port = a_Port;
+
+ m_IniFile = new cIniFile("webadmin.ini");
+ if( m_IniFile->ReadFile() )
+ {
+ m_Port = m_IniFile->GetValueI("WebAdmin", "Port", 8080 );
+ }
+
+ LOG("Starting WebAdmin on port %i", m_Port);
+
+#ifdef _WIN32
+ HANDLE hThread = CreateThread(
+ NULL, // default security
+ 0, // default stack size
+ ListenThread, // name of the thread function
+ this, // thread parameters
+ 0, // default startup flags
+ NULL);
+ CloseHandle( hThread ); // Just close the handle immediately
+#else
+ pthread_t LstnThread;
+ pthread_create( &LstnThread, 0, ListenThread, this);
+#endif
+
+ return true;
+}
+
+
+
+
+
+#ifdef _WIN32
+DWORD WINAPI cWebAdmin::ListenThread(LPVOID lpParam)
+#else
+void *cWebAdmin::ListenThread( void *lpParam )
+#endif
+{
+ cWebAdmin* self = (cWebAdmin*)lpParam;
+
+ self->m_WebServer = new webserver(self->m_Port, Request_Handler );
+ if (!self->m_WebServer->Begin())
+ {
+ LOGWARN("WebServer failed to start! WebAdmin is disabled");
+ }
+
+ self->m_Event->Set();
+ return 0;
+}
+
+
+
+
+
+std::string cWebAdmin::GetTemplate()
+{
+ std::string retVal = "";
+
+ char SourceFile[] = "webadmin/template.html";
+
+ cFile f;
+ if (!f.Open(SourceFile, cFile::fmRead))
+ {
+ return "";
+ }
+
+ // copy the file into the buffer:
+ f.ReadRestOfFile(retVal);
+
+ return retVal;
+}
+
+
+
+
+
+void cWebAdmin::RemovePlugin( lua_State* L )
+{
+ for( PluginList::iterator itr = m_Plugins.begin(); itr != m_Plugins.end(); )
+ {
+ if( (*itr)->GetLuaState() == L )
+ {
+ PluginList::iterator prev = itr++;
+ delete *prev; // deleting a dereferenced iterator also takes it out of the list, so no need for erase()
+ }
+ else
+ ++itr;
+ }
+}