piso/main.cc


Home Back

#include <libgen.h>

#ifdef _WIN32 
    #define _UNICODE = 1
    #define UNICODE  = 1
    #include <winsock2.h>    
    #include <windows.h>
    #include <strings.h>
#endif

#include <wx/wxprec.h>
#ifndef WX_PRECOMP
    #include <wx/wx.h>
#endif


#include "wx/xrc/xmlres.h"
#include "gui.cpp" 

#include <wx/listctrl.h>
#include <wx/spinctrl.h>
#include <wx/hyperlink.h>
#include <wx/filepicker.h>
#include <wx/utils.h> 
#include <wx/filefn.h>
#include <wx/platinfo.h>
#include <wx/stdpaths.h>
#include <wx/socket.h>
#include <wx/protocol/http.h>
#include <wx/fileconf.h>
#include <wx/aboutdlg.h> 

#include "crow_all.h"

#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;
#include <wx/base64.h> 

#include "miniupnpc/miniupnpc.h"
#include "miniupnpc/upnpcommands.h"

#include "icon.xpm"
#include "html.h"

const wxString version("0.1");
const wxString mainAuthor("Fabio Di Matteo (fadimatteo@gmail.com)");


class simple_upnpc : public wxThread
{
    public:
        char iaddr[30];
        char eport[20];
        char iport[20];
        char leaseDuration[20];
        char description[200];
        std::string getGateway();
        int addPortMapping();
        int deletePortMapping();
        int op=0;
    private:
        virtual void *Entry();    

};
void *simple_upnpc::Entry()
{
    if (op==0)    addPortMapping();
    if (op==1)    deletePortMapping();
    
    return (wxThread::ExitCode)0; 
}

int simple_upnpc::addPortMapping()
{
    struct UPNPDev *devlist ;
    int err;
    devlist = upnpDiscover(2000, NULL, NULL,
                            UPNP_LOCAL_PORT_SAME, 0, 2, &err); 
    
    if (devlist==NULL) return -1;
    
    
    struct UPNPUrls  urls;
    struct IGDdatas  data;
    char lanaddr[20]={0};
    sprintf(lanaddr,"%s",getGateway().c_str());
    
    
    UPNP_GetValidIGD(devlist, &urls, &data, lanaddr, sizeof(lanaddr), nullptr, 0); 
    char proto[]="TCP";
    int res;
    res=UPNP_AddPortMapping(devlist->descURL, data.first.servicetype,
           eport, iport, iaddr, description,
           proto, 0, leaseDuration); 

     return res;
}


int simple_upnpc::deletePortMapping()
{
    struct UPNPDev *devlist ;
    int err;
    devlist = upnpDiscover(2000, NULL, NULL,
                            UPNP_LOCAL_PORT_SAME, 0, 2, &err); 
    
    if (devlist==NULL) return -1;                        

    struct UPNPUrls  urls;
    struct IGDdatas  data;
    char lanaddr[20]={0};
    sprintf(lanaddr,"%s",getGateway().c_str());
    
    
    UPNP_GetValidIGD(devlist, &urls, &data, lanaddr, sizeof(lanaddr),nullptr, 0); 
    

    char proto[]="TCP";
    int res;
    res=UPNP_DeletePortMapping(devlist->descURL, data.first.servicetype,
                       eport, proto, iaddr);
    return res;                   
}

std::string simple_upnpc::getGateway()
{
    struct UPNPDev *devlist ;
    int err;
    devlist = upnpDiscover(2000, NULL, NULL, UPNP_LOCAL_PORT_SAME, 0, 2, &err);
    
    struct UPNPUrls  urls;
    struct IGDdatas  data;
    char lanaddr[]={0};
    UPNP_GetValidIGD(devlist, &urls, &data, lanaddr, sizeof(lanaddr),nullptr, 0);             
 
    std::string sub ;
    std::string fullUrl(urls.ipcondescURL);
    
    std::string gw("192.168.1.1");
    if (fullUrl.length()>4)
    {
    
        int pos = fullUrl.find(":");
        sub = fullUrl.substr(pos + 3);
     
        pos = sub.find(":");
        sub = sub.substr(0, pos);
     
        gw=sub;
    }
    
    return gw;
}



class CustomLogger : public crow::ILogHandler 
{
 public:
    wxListCtrl *weblog;
    CustomLogger() {}
  
  void log(std::string message, crow::LogLevel /*level*/) 
  {
        
        std::cerr << message << std::endl;
        wxTheApp->CallAfter( [=] {
            if (this->weblog)
            {
                int n=this->weblog->GetItemCount();     
                this->weblog->InsertItem(n+1,wxNow() +" "+ message);
                this->weblog->EnsureVisible(n);
            }
        });    
    
    
  }
};


class webServer : public wxThread
{
    public:
        crow::SimpleApp app;
        CustomLogger logger;
        wxListCtrl *log;
        int port;
        std::string root_folder;
        std::string prefixUrl;
        bool upload=false;
        webServer();
 
 
   private:
        virtual void *Entry();
};
 
webServer::webServer()
{
 
}

 
void *webServer::Entry()
{
    extern const char* p;
    
    logger.weblog=log;
    crow::logger::setHandler(&logger);
    
    if (prefixUrl!="/") prefixUrl=prefixUrl+"/";
    const char* route=prefixUrl.c_str();
    app.route_dynamic(route)([=](const crow::request& req){
        
        std::string curr_folder;
        char *f;
        f=req.url_params.get("f");
        auto page = crow::mustache::compile(p);
        crow::mustache::context ctx;
        

        
        if (f!=nullptr)
        {
            curr_folder=root_folder+"/"+f;
            
            std::string t=f;
            if (t.find("../")!=std::string::npos)
            {
                printf("ATTACK\n");
                ctx["rows"] = "<li>Attack!</li>";
                return page.render(ctx);
            }
        }else{
            curr_folder=root_folder;
        }
        
        std::string files;
        std::string e;
        for (const auto & entry : fs::directory_iterator(curr_folder))
        {
            if (is_directory(entry.path()) )
            {
                e=entry.path().filename().u8string();
                if (f!=nullptr)
                {
                    files=files+"<li>[+]&#128193; <a href=\""+route+"?f="+f+"/"+e+"\">"+e+"</a></li>";
                }else{
                    files=files+"<li>[+]&#128193; <a href=\""+route+"?f="+e+"\">"+e+"</a></li>";
                }
            }
        }
        
        std::string relPath;
        for (const auto & entry : fs::directory_iterator(curr_folder))
        {
            if (!is_directory(entry.path()) )
            {
                if (f!=nullptr)
                {
                    std::string sep="/";
                    relPath=f+sep+entry.path().filename().u8string();
                }else{
                    relPath=entry.path().filename().u8string();
                }
                files=files+"<li>&#128196; <a href=\""+route+"download?f="+relPath+"\">"+entry.path().filename().u8string()+"</a></li>";
            }
        }      
        
        const char* rows=files.c_str();
        
        if (f!=nullptr)
        {
            ctx["curr_folder"] = f;
        }else{
            ctx["curr_folder"] ="";
        }
        
        ctx["rows"] = rows;
        
        char homeUrl[200];
        char parentUrl[200];
        
        
        
        sprintf(homeUrl,"<a href=\"%s\" >&#127974; Home</a>",route);
        sprintf(parentUrl,"<a href=\"%s?f=%s\" >&uarr; UP</a>",route,dirname(f));
        ctx["home"] = homeUrl;
        ctx["parent"] = parentUrl;
        if (upload==true)
        {
            ctx["upload"] ="<a href=\"upload\">&#128190; Upload</a>";
        }
        
        return page.render(ctx);
    });
    
    
    
    
    std::string droute=prefixUrl+"download";
    app.route_dynamic(droute.c_str())([=]( const crow::request& req,crow::response& res){
        chdir(root_folder.c_str());
        const char *f=req.url_params.get("f");
        char myfile[512];
        sprintf(myfile,"%s",f);
        res.set_static_file_info(myfile);
        std::string basname=basename(myfile);
        std::string header="filename=\""+basname+"\"";
        res.set_header("Content-Disposition",header);
        res.end();
        
        
        
    });
    
    
    
    
    CROW_ROUTE(app, "/ping")([=](const crow::request& req){
        const char* r="1";
        return r;
    });
    
    if (upload==true)
    {
        std::string drouteFormUpload=prefixUrl+"upload";
        app.route_dynamic(drouteFormUpload.c_str())([=](const crow::request& req ){
            extern const char* pu;
            auto page = crow::mustache::compile(pu);
            crow::mustache::context ctx;
            std::string u(prefixUrl+"save");
            ctx["action"] = u.c_str();;
            return page.render(ctx);
        
        });
        
        std::string drouteSaveUpload=prefixUrl+"save";
        app.route_dynamic(drouteSaveUpload.c_str()).methods("POST"_method,"GET"_method)([=](const crow::request& req ){
             
            crow::multipart::message msg(req);
            crow::multipart::header myheader = msg.parts[0].get_header_object("Content-Disposition");
            //std::string myheader_value=myheader.value;
            std::string  outfile_name=myheader.params["filename"];
            CROW_LOG_ERROR << "File Name ->"+outfile_name;
            
            std::ofstream out_file(root_folder+"/"+outfile_name, std::ios::binary);
            if (!out_file)
            {
              CROW_LOG_ERROR << " Write to file failed\n";
              return "Write to file failed!";
            }
              
            out_file << msg.parts[0].body;
            out_file.close();
            
            extern const char* uploadSuccess;
            return uploadSuccess;
        
        });
    } //if (upload)
    

    app.server_name("Piso web server");
    app.port(port).multithreaded().run();
    return (wxThread::ExitCode)0; 
}


 
class MainFrame{
    public:
        wxFrame frame0;
        wxMenuBar *menubar;
        wxButton   *btStart, *btStop, *btSettings;
        wxSpinCtrl *spinPort;
        wxListCtrl *log;
        wxDirPickerCtrl *dirPckr;
        wxTextCtrl *txtPrefixUrl;
        wxHyperlinkCtrl *lblLocalIp, *lblPublicIp;
        wxCheckBox *chkExpose;
        wxCheckBox *chkPermitUpload;
        void ShowFrame();
        void OnClickStart(wxCommandEvent& event);
        void OnClickStop(wxCommandEvent& event);
        void OnCheckExpose(wxCommandEvent& event);
        void OnCheckPermitUpload(wxCommandEvent& event);
        void OnMenuItemExitSelection(wxCommandEvent& event);
        void OnMenuItemAboutSelection(wxCommandEvent& event);
        void ClosePort();
        void ExposePort();
        void Quit(wxCommandEvent& event);
        void OnClose(wxCloseEvent& event);
        wxString GetPublicIp();
        std::string GetLocalIp();
    private:
        bool upload=false;
        webServer *thWebServer;
        void LoadIni();
        void SaveIni();
        void CreateMenuBar();
        void TerminateApp();
} ;


void MainFrame::TerminateApp()
{
    int dialog_return_value =  wxID_NO;
    wxMessageDialog* dial = new wxMessageDialog(NULL, _("Close Piso?"), _("Exit"), wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION);
    dialog_return_value = dial->ShowModal();

    if (dialog_return_value== wxID_YES)
    {
        frame0.Destroy();
    }

}

void MainFrame::OnClose(wxCloseEvent& event)
{
    TerminateApp();
}

void MainFrame::Quit(wxCommandEvent& event)
{
    TerminateApp();
}

void MainFrame::OnMenuItemAboutSelection(wxCommandEvent& event)
{
    wxAboutDialogInfo info;
    info.SetName(_("Piso Web Server"));
    info.SetVersion(version);
    info.SetDescription(_("Piso allows you to share files across the network."));
    info.SetCopyright(mainAuthor);
    info.SetWebSite("https://www.freemedialab.org/code/project.php?p=piso");
    info.SetIcon(frame0_icon);
    wxAboutBox(info);
}

void MainFrame::LoadIni()
{
    #ifdef __linux__
        const wxString homefolder=wxStandardPaths::Get().GetUserConfigDir();
        const wxString inifilepath=homefolder+"/.config/piso/piso.conf";
    #endif
    #ifdef _WIN32
        const wxString homefolder=wxStandardPaths::Get().GetUserConfigDir();
        const wxString inifilepath=homefolder+"\\piso\\piso.conf";
    #endif 
    
    
    
    if (wxFileExists(inifilepath))
    {
        wxFileConfig *ConfigINI = new wxFileConfig(wxEmptyString,wxEmptyString,inifilepath);
        ConfigINI->SetPath("/settings");
        wxString folder=ConfigINI->Read("folder",homefolder);
        int port=ConfigINI->Read("port",1024);
        upload=ConfigINI->Read("upload",false);
        wxString webroot=ConfigINI->Read("webroot","/");
        
        dirPckr->SetPath(folder);
        spinPort->SetValue(port);
        chkPermitUpload->SetValue(upload);
        txtPrefixUrl->SetValue(webroot);
    }

}

void MainFrame::SaveIni()
{
    #ifdef __linux__
    const wxString homefolder=wxStandardPaths::Get().GetUserConfigDir();
    const wxString configfolder=wxStandardPaths::Get().GetUserConfigDir()+"/.config/piso/";
    const wxString inifilepath=homefolder+"/.config/piso/piso.conf";
    #endif
    #ifdef _WIN32
    const wxString homefolder=wxStandardPaths::Get().GetUserConfigDir();
    const wxString configfolder=wxStandardPaths::Get().GetUserConfigDir()+"\\piso\\";
    const wxString inifilepath=homefolder+"\\piso\\piso.conf";
    #endif
    
    
    
    if (!wxDirExists(configfolder)) wxMkdir(configfolder);
    wxFileConfig *ConfigINI = new wxFileConfig(wxEmptyString,wxEmptyString,inifilepath);
    ConfigINI->SetPath("/settings");
    ConfigINI->Write("folder",dirPckr->GetPath());
    ConfigINI->Write("port",spinPort->GetValue());
    ConfigINI->Write("upload",chkPermitUpload->GetValue());
    ConfigINI->Write("webroot",txtPrefixUrl->GetValue());
    
    ConfigINI->Flush();


}

std::string MainFrame::GetLocalIp()
{
    unsigned short port = 53;
    const char* GoogleDnsIp = "8.8.8.8";
 
 
    /*Riempo le strutture dati per il server DNS*/
    int sd;
    struct sockaddr_in server;
    struct hostent *hp;
 
    hp = gethostbyname(GoogleDnsIp);
    //bzero(&server, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(port);
    server.sin_addr.s_addr =((struct in_addr*)(hp->h_addr))->s_addr;
 
    /*Riempo le strutture dati per il client*/
    struct sockaddr_in client;
    struct hostent *cp;
 
    cp = gethostbyname("127.0.0.1");
    //bzero(&client, sizeof(client));
    client.sin_family = AF_INET;
    client.sin_port = htons(port);
    client.sin_addr.s_addr =((struct in_addr*)(cp->h_addr))->s_addr;
 
 
    /* Creo il socket */
    if((sd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
    {
            printf("Errore nella creazione del socket!\n");
    }
 
 
    /* connettiamoci all'host */
    if (connect(sd, (struct sockaddr *)&server, sizeof(server)) < 0)
    {
        printf("Errore di connessione!\n");
    }
 
    /*Individuo il mio indirizzo ip pubblico*/
    socklen_t* clientlen = (socklen_t* )sizeof(client);
    getsockname(sd, (struct sockaddr *) &client, (socklen_t*) &clientlen);    
 
    /*Lo converto in stringa e lo scrivo sulla variabile*/
    char MyLocalAddress[50];
    inet_ntop(AF_INET, &client.sin_addr.s_addr, MyLocalAddress, 50);
 
    /*Chiudiamo il socket*/
    close(sd);
    
    
    std::string r= MyLocalAddress;
    return r;
}

wxString MainFrame::GetPublicIp()
{
    /*wxFileSystem* fileSystem = new wxFileSystem;
    wxFSFile* file = fileSystem->OpenFile("https://www.freemedialab.org/myip/rawip.php");
    wxString r="no";
 
    if (file)
    {
        wxInputStream* strm=file->GetStream();
        char buf[50];
        strm->ReadAll( buf,50);
        r=buf;
    }
    
    delete fileSystem;
    return r;*/
    
    wxArrayString output;
    wxArrayString errors;
    wxString r("127.0.0.1");
    int res=wxExecute ("curl https://www.freemedialab.org/myip/rawip.php", output, errors, wxEXEC_HIDE_CONSOLE);
    if (res==0)
    {
        return output[0];
    }else {
        return r;
    }
}


void MainFrame::CreateMenuBar()
{
    enum
    {
        ID_QUIT = 1,
        ID_ABOUT = 2
    };

    
    wxMenu *menuFile = new wxMenu;
    menuFile->Append(wxID_EXIT);
    
    wxMenu *menuInfo = new wxMenu;
    menuInfo->Append(ID_ABOUT, "About...","Authors");
    
    wxMenuBar *menuBar = new wxMenuBar;
    menuBar->Append(menuFile, "&File");
    menuBar->Append(menuInfo, "&Info");
    
    frame0.SetMenuBar(menuBar);
    
    frame0.Bind(wxEVT_MENU, &MainFrame::OnMenuItemAboutSelection, this, ID_ABOUT);
    frame0.Bind(wxEVT_MENU, &MainFrame::Quit, this, wxID_EXIT);
}
 
void MainFrame::ShowFrame()
{
    wxXmlResource::Get()->LoadFrame(&frame0, NULL,  wxT("frame0"));
    
    frame0.SetIcon(frame0_icon);
    frame0.Bind(wxEVT_CLOSE_WINDOW, &MainFrame::OnClose, this);
    
    spinPort = XRCCTRL(frame0, "spinPort", wxSpinCtrl);
    log = XRCCTRL(frame0, "log", wxListCtrl);
    dirPckr = XRCCTRL(frame0, "dirPckr", wxDirPickerCtrl);
    dirPckr->SetPath(wxStandardPaths::Get().GetUserConfigDir());
    
    txtPrefixUrl=XRCCTRL(frame0, "txtPrefixUrl", wxTextCtrl);
    
    btStart=XRCCTRL(frame0, "btStart", wxButton);
    btStart->Bind(wxEVT_BUTTON, &MainFrame::OnClickStart, this);
    
    btStop=XRCCTRL(frame0, "btStop", wxButton);
    btStop->Bind(wxEVT_BUTTON, &MainFrame::OnClickStop, this);
    
    
    chkExpose = XRCCTRL(frame0, "chkExpose", wxCheckBox);
    chkExpose->Bind(wxEVT_COMMAND_CHECKBOX_CLICKED, &MainFrame::OnCheckExpose, this);
    
    chkPermitUpload = XRCCTRL(frame0, "chkPermitUpload", wxCheckBox);
    chkPermitUpload->Bind(wxEVT_COMMAND_CHECKBOX_CLICKED, &MainFrame::OnCheckPermitUpload, this);
    
    
    lblLocalIp=XRCCTRL(frame0, "lblLocalIp", wxHyperlinkCtrl);
    lblPublicIp=XRCCTRL(frame0, "lblPubliclIp", wxHyperlinkCtrl);
    
    log->AppendColumn     (     "Log",wxLIST_FORMAT_LEFT, 550);
    //log->SetColumnWidth(0,2600);
    
    int n=log->GetItemCount();     
    log->InsertItem(n+1,"Piso ready.");
    
    lblLocalIp->Hide();
    lblPublicIp->Hide();

    this->LoadIni();
    CreateMenuBar();
    
    frame0.SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) );
    frame0.Show();
 
}

void MainFrame::ExposePort()
{
    /*int port = spinPort->GetValue();
    wxString cmd="upnpc -r "+wxString::Format(wxT("%i"),port)+ " TCP";
    wxArrayString output;
    wxArrayString errors;
    wxExecute (cmd, output, errors, wxEXEC_HIDE_CONSOLE);*/
    
    wxString publicIp=GetPublicIp();
    if (publicIp!="127.0.0.1")
    {
        auto piso_upnp = new simple_upnpc();
        sprintf(piso_upnp->eport,"%d",spinPort->GetValue());
        sprintf(piso_upnp->iport,"%d",spinPort->GetValue());
        sprintf(piso_upnp->iaddr,"%s",GetLocalIp().c_str());
        sprintf(piso_upnp->leaseDuration,"%s","86400");
        sprintf(piso_upnp->description,"%s","Piso web server");
        piso_upnp->op=0;
        piso_upnp->Create();
        piso_upnp->Run();
    }
    
}

void MainFrame::ClosePort()
{
    /*int port = spinPort->GetValue();
    wxString cmd="upnpc -d "+wxString::Format(wxT("%i"),port)+ " TCP";
    wxArrayString output;
    wxArrayString errors;
    wxExecute (cmd, output, errors, wxEXEC_HIDE_CONSOLE);*/
    wxString publicIp=GetPublicIp();
    if (publicIp!="127.0.0.1")
    {
        auto piso_upnp = new simple_upnpc();
        sprintf(piso_upnp->eport,"%d",spinPort->GetValue());
        sprintf(piso_upnp->iport,"%d",spinPort->GetValue());
        sprintf(piso_upnp->iaddr,"%s",GetLocalIp().c_str());
        sprintf(piso_upnp->leaseDuration,"%s","86400");
        sprintf(piso_upnp->description,"%s","Piso web server");
        piso_upnp->op=1;
        piso_upnp->Create();
        piso_upnp->Run();
    }
}


void MainFrame::OnCheckPermitUpload(wxCommandEvent& event)
{
    bool s=chkPermitUpload->GetValue();
    if (s==true)
    {
        upload=true;
    }else{
        upload=false;
    }
}


void MainFrame::OnCheckExpose(wxCommandEvent& event)
{
    bool s=chkExpose->GetValue();
    if (s==true)
    {
        lblPublicIp->Show();
        ExposePort();
        int n=log->GetItemCount();     
        log->InsertItem(n+1,"Try to expose on internet");
        log->EnsureVisible(n);
        wxString publicIp=GetPublicIp();
        if (publicIp!="no")
        { 
            wxString strPort = wxString::Format(wxT("%i"),spinPort->GetValue());
            lblPublicIp->SetURL("http://"+publicIp+":"+strPort+txtPrefixUrl->GetValue());
            lblPublicIp->SetLabel("http://"+publicIp+":"+strPort+txtPrefixUrl->GetValue());
        }else{
            lblPublicIp->SetLabel("No public ip!");
        }
        
    }else{
        int n=log->GetItemCount();     
        log->InsertItem(n+1,"Try to close expose on internet");
        log->EnsureVisible(n);
        
        ClosePort();
    }
}
 
void MainFrame::OnClickStart(wxCommandEvent& event)
{
    lblLocalIp->Show();
    
    
    dirPckr->Enable(false);
    spinPort->Enable(false);
    btStart->Enable(false);
    txtPrefixUrl->Enable(false);
    chkPermitUpload->Enable(false);
    
    int port = spinPort->GetValue();
    wxString ip="http://"+GetLocalIp()+":"+wxString::Format(wxT("%i"),port)+txtPrefixUrl->GetValue();
    lblLocalIp->SetLabel(ip);
    lblLocalIp->SetURL(ip);
    
    wxString publicIp=GetPublicIp();
    wxString strPort = wxString::Format(wxT("%i"),spinPort->GetValue());
    lblPublicIp->SetURL("http://"+publicIp+":"+strPort+txtPrefixUrl->GetValue());
    lblPublicIp->SetLabel("http://"+publicIp+":"+strPort+txtPrefixUrl->GetValue());
    
    int n=log->GetItemCount();
    log->InsertItem(n+1,"Starting webserver...");
    
    thWebServer = new webServer();
    thWebServer->port=spinPort->GetValue();
    thWebServer->log=log;
    thWebServer->root_folder=dirPckr->GetPath();
    thWebServer->prefixUrl=txtPrefixUrl->GetValue();
    thWebServer->upload=upload;
    thWebServer->Create();
    thWebServer->Run();
    
}
 

void MainFrame::OnClickStop(wxCommandEvent& event)
{
    wxHTTP pingHttp;
    int port = spinPort->GetValue();
    pingHttp.Connect("localhost",port);
    pingHttp.GetInputStream("/ping");
    int resp=pingHttp.GetResponse();
    if (resp==200)
    {
        wxTheApp->CallAfter( [=] {
            thWebServer->app.stop();
            thWebServer->app.signal_clear();
        });
        dirPckr->Enable(true);
        spinPort->Enable(true);
        btStart->Enable(true);
        txtPrefixUrl->Enable(true);
        chkPermitUpload->Enable(true);
        
        int n=log->GetItemCount();     
        log->InsertItem(n+1,"Stop webserver");
        log->EnsureVisible(n);
        SaveIni();
    }    
}

class MyApp: public wxApp
{
    virtual bool OnInit();
}; 
bool MyApp::OnInit()
{
    wxXmlResource::Get()->InitAllHandlers();  
    InitXmlResource(); 
 
    MainFrame *MainWin = new MainFrame(); 
    MainWin->ShowFrame();                   
 
    return true;
}
 
 
IMPLEMENT_APP(MyApp)

Powered by Code, a simple repository browser by Fabio Di Matteo