Commit 7ef0ab2e authored by jan.koester's avatar jan.koester
Browse files

test

parent ec58f1f2
Loading
Loading
Loading
Loading
+41 −0
Original line number Diff line number Diff line
@@ -45,3 +45,44 @@ BLOGI:
    CERT: ""
    KEY: ""
    PASSWORD: ""
  # Multi-domain support: each domain gets its own DB, template, settings, etc.
  # If DOMAINS is not present, single-domain mode is used (backward compatible).
  DOMAINS:
    - NAME: "blog.example.com"
      DATABASE:
        DRIVER: "sqlite"
        CONNECTION: "/var/lib/blogi/blog-example.db"
      TEMPLATE: "/usr/local/share/blogi/themes/default"
      STARTPAGE: "/blog/content/index"
      HTTP:
        URL: "https://blog.example.com"
        PREFIX: "/blog"
      PLUGINDIR:
        - "/usr/local/lib/blogi/plugins"
      AUTHDB:
        URL: "https://auth.example.com"
        CLIENTNAME: "blogiclient"
        CLIENTSECRET: "clientsecret"
      MEDIADB:
        URL: "https://127.0.0.1:8442"
      COOKIE:
        DOMAIN: ".example.com"
    - NAME: "blog.another.org"
      DATABASE:
        DRIVER: "sqlite"
        CONNECTION: "/var/lib/blogi/blog-another.db"
      TEMPLATE: "/usr/local/share/blogi/themes/dark"
      STARTPAGE: "/content/index"
      HTTP:
        URL: "https://blog.another.org"
        PREFIX: ""
      PLUGINDIR:
        - "/usr/local/lib/blogi/plugins"
      AUTHDB:
        URL: "https://auth.another.org"
        CLIENTNAME: "anotherclient"
        CLIENTSECRET: "anothersecret"
      MEDIADB:
        URL: "https://media.another.org:8442"
      COOKIE:
        DOMAIN: ".another.org"
+173 −97
Original line number Diff line number Diff line
@@ -98,30 +98,34 @@ static std::string cookieDomain(libhttppp::HttpRequest &req) {
std::unique_ptr<blogi::Config> blogi::Blogi::Cfg;

blogi::Blogi::Blogi(std::vector<netplus::socket*> serversocket,bool debug) : HttpEvent(serversocket){
    PlgArgs = std::make_unique<PluginArgs>();
    PlgArgs->debug=debug;
    PlgArgs->config=Blogi::Cfg.get();

	std::cerr << "Initializing Blogi with DB Driver: " << Blogi::Cfg->getdbdriver() << " on " << Blogi::Cfg->getdbopts() << std::endl;
    // Helper to initialize a DomainContext
    auto initCtx = [&](DomainContext &ctx, const std::string &dbDriver, const std::string &dbOpts,
                       const std::string &authUrl, const std::string &clientName, const std::string &clientSecret,
                       const std::vector<AuthSource> &authSources,
                       const std::string &templatePath, const std::vector<std::string> &plgDirs,
                       const std::string &siteUrl, const std::string &prefix, const std::string &startPage,
                       const std::string &domainName)
    {
        ctx.domainName = domainName;
        ctx.plgArgs = std::make_unique<PluginArgs>();
        ctx.plgArgs->debug = debug;
        ctx.plgArgs->config = Blogi::Cfg.get();

        std::cerr << "Initializing domain '" << domainName << "' with DB Driver: " << dbDriver << " on " << dbOpts << std::endl;

        try {
            for (int i = 0; i < threads; i++) {
            PlgArgs->database.emplace_back(
                std::make_unique<dbpp::Database>(
                    Blogi::Cfg->getdbdriver(),
                    Blogi::Cfg->getdbopts()
                )
                ctx.plgArgs->database.emplace_back(
                    std::make_unique<dbpp::Database>(dbDriver, dbOpts)
                );
            }
        } catch(std::runtime_error &e) {
         std::cerr << "can't connect db reason: " << e.what() << std::endl;
            std::cerr << "can't connect db for domain '" << domainName << "' reason: " << e.what() << std::endl;
            throw e;
        }

    std::unique_ptr<dbpp::Database> initdb = std::make_unique<dbpp::Database>(
            Blogi::Cfg->getdbdriver(),
            Blogi::Cfg->getdbopts()
    );
        std::unique_ptr<dbpp::Database> initdb = std::make_unique<dbpp::Database>(dbDriver, dbOpts);

        Session sess(*initdb);
        sess._initSession();
@@ -147,60 +151,112 @@ blogi::Blogi::Blogi(std::vector<netplus::socket*> serversocket,bool debug) : Htt
            return;
        }

    PlgArgs->smtp=std::make_unique<SmtpSettings>();
        ctx.plgArgs->smtp = std::make_unique<SmtpSettings>();

        try {
        getConfig(*initdb,"SMTPHOST",PlgArgs->smtp->_SHost);
        getConfig(*initdb,"SMTPPORT",PlgArgs->smtp->_SPort);
        getConfig(*initdb,"SMTPUSER",PlgArgs->smtp->_SUser);
        getConfig(*initdb,"SMTPSENDER",PlgArgs->smtp->_Sender);
        getConfig(*initdb,"SMTPPASSWORD",PlgArgs->smtp->_SPass);
            getConfig(*initdb, "SMTPHOST", ctx.plgArgs->smtp->_SHost);
            getConfig(*initdb, "SMTPPORT", ctx.plgArgs->smtp->_SPort);
            getConfig(*initdb, "SMTPUSER", ctx.plgArgs->smtp->_SUser);
            getConfig(*initdb, "SMTPSENDER", ctx.plgArgs->smtp->_Sender);
            getConfig(*initdb, "SMTPPASSWORD", ctx.plgArgs->smtp->_SPass);
        } catch(std::runtime_error &e) {
        std::cerr << "SMTP config not found, skipping: " << e.what() << std::endl;
            std::cerr << "SMTP config not found for domain '" << domainName << "', skipping: " << e.what() << std::endl;
        }

    PlgArgs->auth=std::make_unique<Auth>(PlgArgs->database,*PlgArgs->config);
        ctx.plgArgs->auth = std::make_unique<Auth>(ctx.plgArgs->database, *ctx.plgArgs->config);

    ThemeConfig tplcfg(
        Blogi::Cfg->gettemplate(),
        PlgArgs->database,
        *Blogi::Cfg
    );
        ThemeConfig tplcfg(templatePath, ctx.plgArgs->database, *Blogi::Cfg);

    PlgArgs->theme=std::make_unique<Theme>(tplcfg,debug);
        ctx.plgArgs->theme = std::make_unique<Theme>(tplcfg, debug);

        try {
        PlgArgs->theme->renderPage(0,"index.html",Page,Index);
        PlgArgs->theme->renderPage(0,"mobile.html",MPage,MIndex);
            ctx.plgArgs->theme->renderPage(0, "index.html", ctx.page, ctx.index);
            ctx.plgArgs->theme->renderPage(0, "mobile.html", ctx.mPage, ctx.mIndex);
        } catch(libhtmlpp::HTMLException &e) {
            std::cerr << e.what() << std::endl;
            return;
        }
    PlgArgs->maxthreads=threads;

    libhtmlpp::HtmlPage cookiehtml;
        ctx.plgArgs->maxthreads = threads;

    PlgArgs->theme->renderPage(0, "cookie.html", cookiehtml, CookieEl);
        libhtmlpp::HtmlPage cookiehtml;
        ctx.plgArgs->theme->renderPage(0, "cookie.html", cookiehtml, ctx.cookieEl);

        uuid::uuid id(cookieid);

        CookieBanner cban(*initdb);

        cban.init();

        cban.addRule(id, "Technical Required", "This Cookie is Required for Authentication and some Basic Funtions of this Blog", true);

    BlogiPlg = std::make_unique<Plugin>();
        ctx.plugins = std::make_unique<Plugin>();
        for (size_t i = 0; i < plgDirs.size(); ++i) {
            ctx.plugins->loadPlugins(plgDirs[i], ctx.plgArgs.get());
        }
    };

    for(size_t i=0; i<PlgArgs->config->getplgdirs(); ++i){
         BlogiPlg->loadPlugins(PlgArgs->config->getplgdir(i),PlgArgs.get());
    // Initialize multi-domain contexts if configured
    if (Blogi::Cfg->getDomainCount() > 0) {
        for (size_t d = 0; d < Blogi::Cfg->getDomainCount(); ++d) {
            const DomainConfig &dcfg = Blogi::Cfg->getDomainConfig(d);
            auto ctx = std::make_unique<DomainContext>();
            initCtx(*ctx, dcfg.dbDriver, dcfg.dbConnection,
                    dcfg.authUrl, dcfg.clientName, dcfg.clientSecret,
                    dcfg.authSources,
                    dcfg.templatePath, dcfg.plgDirs,
                    dcfg.siteUrl, dcfg.prefix, dcfg.startPage,
                    dcfg.name);
            std::string key = dcfg.name;
            for (auto &c : key) c = std::tolower(c);
            _domains[key] = std::move(ctx);
        }
    }

    // Always initialize default context from global config (backward compat / fallback)
    {
        _defaultCtx = std::make_unique<DomainContext>();
        initCtx(*_defaultCtx, Blogi::Cfg->getdbdriver(), Blogi::Cfg->getdbopts(),
                Blogi::Cfg->getAuthUrl(), Blogi::Cfg->getClientName(), Blogi::Cfg->getClientSecret(),
                std::vector<AuthSource>(),
                Blogi::Cfg->gettemplate(),
                [&]() -> std::vector<std::string> {
                    std::vector<std::string> dirs;
                    for (size_t i = 0; i < Blogi::Cfg->getplgdirs(); ++i)
                        dirs.push_back(Blogi::Cfg->getplgdir(i));
                    return dirs;
                }(),
                Blogi::Cfg->getsiteurl(), Blogi::Cfg->getprefix(), Blogi::Cfg->getstartpage(),
                Blogi::Cfg->getDomain());
    }
}

blogi::Blogi::~Blogi(){
}

void blogi::Blogi::api(libhttppp::HttpRequest& curreq, const int tid){
blogi::DomainContext *blogi::Blogi::resolveDomain(libhttppp::HttpRequest &curreq) {
    std::string host;
    auto *fwd = curreq.getHeaderData("x-forwarded-host");
    if (fwd)
        host = fwd->at(0).getvalue();
    else
        host = curreq.getHost();

    // Strip port
    auto colon = host.find(':');
    if (colon != std::string::npos) host = host.substr(0, colon);

    for (auto &c : host) c = std::tolower(c);

    // Look up exact match in domain map
    auto it = _domains.find(host);
    if (it != _domains.end())
        return it->second.get();

    // Fallback to default
    return _defaultCtx.get();
}

void blogi::Blogi::api(libhttppp::HttpRequest& curreq, const int tid, DomainContext &ctx){
    auto &PlgArgs = ctx.plgArgs;
    auto &BlogiPlg = ctx.plugins;
    uuid::uuid aid;
    size_t clen = curreq.getContentLength();
    libhttppp::HttpResponse resp;
@@ -492,7 +548,9 @@ void blogi::Blogi::api(libhttppp::HttpRequest& curreq, const int tid){
    json_object_put(jrequest);
}

void blogi::Blogi::loginPage(libhttppp::HttpRequest &curreq,const int tid,const std::string &sessionid,libhtmlpp::HtmlElement &index){
void blogi::Blogi::loginPage(libhttppp::HttpRequest &curreq,const int tid,const std::string &sessionid,libhtmlpp::HtmlElement &index, DomainContext &ctx){
    auto &PlgArgs = ctx.plgArgs;
    auto &BlogiPlg = ctx.plugins;
    libhttppp::HTTPException excep;
    if(PlgArgs->auth->isLoggedIn(tid,curreq,sessionid)){
        libhttppp::HTTPException err;
@@ -586,7 +644,8 @@ void blogi::Blogi::loginPage(libhttppp::HttpRequest &curreq,const int tid,const

}

void blogi::Blogi::logoutPage(libhttppp::HttpRequest &curreq,const int tid,const std::string &sessiondid){
void blogi::Blogi::logoutPage(libhttppp::HttpRequest &curreq,const int tid,const std::string &sessiondid, DomainContext &ctx){
    auto &PlgArgs = ctx.plgArgs;
    libhttppp::HttpCookie cook,cookclr;

    cook.parse(curreq);
@@ -1347,7 +1406,9 @@ static void renderSettingsJson(json_object *jroot, libhtmlpp::HtmlString &out, c
    }
}

void blogi::Blogi::settingsApi(libhttppp::HttpRequest& curreq,const int tid,const std::string &sessiondid){
void blogi::Blogi::settingsApi(libhttppp::HttpRequest& curreq,const int tid,const std::string &sessiondid, DomainContext &ctx){
    auto &PlgArgs = ctx.plgArgs;
    auto &BlogiPlg = ctx.plugins;
    if(!PlgArgs->auth->isLoggedIn(tid,curreq,sessiondid)){
        libhttppp::HttpResponse resp;
        // Clear stale auth cookie
@@ -1511,7 +1572,9 @@ static json_object* SettingsLanguage(dbpp::Database &db, libhttppp::HttpRequest
    return jroot;
}

void blogi::Blogi::Settings(libhttppp::HttpRequest& curreq,const int tid,const std::string &sessiondid,libhtmlpp::HtmlElement &index){
void blogi::Blogi::Settings(libhttppp::HttpRequest& curreq,const int tid,const std::string &sessiondid,libhtmlpp::HtmlElement &index, DomainContext &ctx){
    auto &PlgArgs = ctx.plgArgs;
    auto &BlogiPlg = ctx.plugins;
    libhttppp::HttpCookie cookie;
    cookie.parse(curreq);

@@ -1524,7 +1587,7 @@ void blogi::Blogi::Settings(libhttppp::HttpRequest& curreq,const int tid,const s
    // Route API requests to settingsApi
    if(curreq.getRequestURL().compare(0,PlgArgs->config->buildurl("settings/api/").length(),
       PlgArgs->config->buildurl("settings/api/"))==0){
        settingsApi(curreq,tid,sessiondid);
        settingsApi(curreq,tid,sessiondid,ctx);
        return;
    }

@@ -1944,7 +2007,9 @@ void blogi::Blogi::Settings(libhttppp::HttpRequest& curreq,const int tid,const s
    sendCompressed(curreq,curres,out.c_str(),out.length());
}

void blogi::Blogi::searchPage(libhttppp::HttpRequest& curreq, const int tid, const std::string &sessionid,libhtmlpp::HtmlElement &index){
void blogi::Blogi::searchPage(libhttppp::HttpRequest& curreq, const int tid, const std::string &sessionid,libhtmlpp::HtmlElement &index, DomainContext &ctx){
    auto &PlgArgs = ctx.plgArgs;
    auto &BlogiPlg = ctx.plugins;
    std::string word,searchstr;
    SearchRet sdat;

@@ -2010,7 +2075,9 @@ void blogi::Blogi::searchPage(libhttppp::HttpRequest& curreq, const int tid, con

}

void blogi::Blogi::sitemapXML(libhttppp::HttpRequest& curreq, const int tid, const std::string &sessionid){
void blogi::Blogi::sitemapXML(libhttppp::HttpRequest& curreq, const int tid, const std::string &sessionid, DomainContext &ctx){
    auto &PlgArgs = ctx.plgArgs;
    auto &BlogiPlg = ctx.plugins;
    std::map<libhttppp::HttpUrl,std::chrono::year_month_day> sitemap;
    for(const blogi::Plugin::PluginData *curplg=BlogiPlg->getFirstPlugin(); curplg; curplg=curplg->getNextPlg()){
            curplg->getInstace()->GetSiteMap(tid,sitemap);
@@ -2036,7 +2103,12 @@ void blogi::Blogi::sitemapXML(libhttppp::HttpRequest& curreq, const int tid, con
};

void blogi::Blogi::RequestEvent(libhttppp::HttpRequest &curreq,const int tid,ULONG_PTR args){
    // Resolve which domain context to use for this request
    DomainContext *ctx = resolveDomain(curreq);
    try{;
        auto &PlgArgs = ctx->plgArgs;
        auto &BlogiPlg = ctx->plugins;

        libhttppp::HttpHeader::HeaderData *hip=curreq.getHeaderData("x-real-ip");
        libhttppp::HttpHeader::HeaderData *useragent=curreq.getHeaderData("user-agent");

@@ -2068,9 +2140,9 @@ RETRY_REQUEST:
        Session sess(*PlgArgs->database[tid]);

        if(curreq.isMobile())
            index=MIndex;
            index=ctx->mIndex;
        else
            index=Index;
            index=ctx->index;

        try{
            cookie.parse(curreq);
@@ -2095,7 +2167,7 @@ RETRY_REQUEST:

            if(!found){
                std::string _clang = getLang(*PlgArgs->database[tid]);
                blogi::print(*PlgArgs->database[tid],_clang,curreq.getRequestURL(),index,CookieEl);
                blogi::print(*PlgArgs->database[tid],_clang,curreq.getRequestURL(),index,ctx->cookieEl);
            }else{
                std::string buf;

@@ -2135,22 +2207,22 @@ RETRY_REQUEST:
                curres.send(curreq,"", 0);
                return;
            }else if(curreq.getRequestURL().compare(0,PlgArgs->config->buildurl("api").length(),PlgArgs->config->buildurl("api"))==0){
                api(curreq,tid);
                api(curreq,tid,*ctx);
                return;
            }else if(curreq.getRequestURL().compare(0,PlgArgs->config->buildurl("logout").length(),PlgArgs->config->buildurl("logout"))==0){
                logoutPage(curreq,tid,sessid);
                logoutPage(curreq,tid,sessid,*ctx);
                return;
            }else if(curreq.getRequestURL().compare(0,PlgArgs->config->buildurl("login").length(),PlgArgs->config->buildurl("login"))==0){
                loginPage(curreq,tid,sessid,index);
                loginPage(curreq,tid,sessid,index,*ctx);
                return;
            }else if(curreq.getRequestURL().compare(0,PlgArgs->config->buildurl("settings").length(),PlgArgs->config->buildurl("settings"))==0){
                Settings(curreq,tid,sessid,index);
                Settings(curreq,tid,sessid,index,*ctx);
                return;
            }else if(curreq.getRequestURL().compare(0,PlgArgs->config->buildurl("search").length(),PlgArgs->config->buildurl("search"))==0){
                searchPage(curreq,tid,sessid,index);
                searchPage(curreq,tid,sessid,index,*ctx);
                return;
            }else if(curreq.getRequestURL().compare(0,PlgArgs->config->buildurl("sitemap.xml").length(),PlgArgs->config->buildurl("sitemap.xml"))==0){
                sitemapXML(curreq,tid,sessid);
                sitemapXML(curreq,tid,sessid,*ctx);
                return;
            }else if(curreq.getRequestURL().compare(0,PlgArgs->config->buildurl("render").length(),PlgArgs->config->buildurl("render"))==0){

@@ -2227,9 +2299,9 @@ RETRY_REQUEST:
                }

                if(mobile)
                    index=MIndex;
                    index=ctx->mIndex;
                else
                    index=Index;
                    index=ctx->index;

                {
                    libhtmlpp::HtmlString out;
@@ -2389,7 +2461,7 @@ RETRY_REQUEST:
        err << "<!DOCTYPE html><html><body style=\"color:rgb(238, 238, 238); background:rgb(35, 38, 39);\"><span>"
        << hreason
        << "</span><br/><a style=\"text-decoration: none; color: rgb(58,212, 58);\" href=\""
        <<  PlgArgs->config->getstartpage()
        <<  ctx->plgArgs->config->getstartpage()
        << "\" >Zur&uuml;ck zur Startseite</a></body></html>";
        libhtmlpp::print(err.parse(),output);
        libhttppp::HttpResponse resp;
@@ -2402,7 +2474,7 @@ RETRY_REQUEST:
        err << "<!DOCTYPE html><html><body style=\"color:rgb(238, 238, 238); background:rgb(35, 38, 39);\"><span>"
        << "unknown error"
        << "</span><br/><a style=\"text-decoration: none; color: rgb(58,212, 58);\" href=\""
        <<  PlgArgs->config->getstartpage()
        <<  ctx->plgArgs->config->getstartpage()
        << "\" >Zur&uuml;ck zur Startseite</a></body></html>";
        libhtmlpp::print(err.parse(),output);
        libhttppp::HttpResponse resp;
@@ -2413,6 +2485,10 @@ RETRY_REQUEST:
}

void blogi::Blogi::ResponseEvent(libhttppp::HttpRequest& curreq,const int tid,ULONG_PTR args){
    DomainContext *ctx = resolveDomain(curreq);
    auto &PlgArgs = ctx->plgArgs;
    auto &BlogiPlg = ctx->plugins;

    if(PlgArgs->theme->Response(tid,curreq))
        return;

+25 −12
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*******************************************************************************/

#include <memory>
#include <map>

#include <httppp//httpd.h>

@@ -36,26 +37,38 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#pragma once

namespace blogi {

    struct DomainContext {
        std::string                                     domainName;
        std::unique_ptr<PluginArgs>                     plgArgs;
        std::unique_ptr<blogi::Plugin>                  plugins;
        libhtmlpp::HtmlPage                             page, mPage;
        libhtmlpp::HtmlElement                          index, mIndex;
        libhtmlpp::HtmlElement                          cookieEl;
    };

    class Blogi : public libhttppp::HttpEvent{
    public:
        Blogi(std::vector<netplus::socket*> serversocket,bool debug);
        ~Blogi();
        void api(libhttppp::HttpRequest &curreq,const int tid);
        void loginPage(libhttppp::HttpRequest &curreq,const int tid,const std::string &sessionid,libhtmlpp::HtmlElement &index);
        void logoutPage(libhttppp::HttpRequest &curreq,const int tid,const std::string &sessionid);
        void Settings(libhttppp::HttpRequest &curreq,const int tid,const std::string &sessionid,libhtmlpp::HtmlElement &index);
        void settingsApi(libhttppp::HttpRequest &curreq,const int tid,const std::string &sessionid);
        void searchPage(libhttppp::HttpRequest &curreq,const int tid,const std::string &sessionid,libhtmlpp::HtmlElement &index);
        void sitemapXML(libhttppp::HttpRequest& curreq, const int tid, const std::string &sessionid);
        void api(libhttppp::HttpRequest &curreq,const int tid, DomainContext &ctx);
        void loginPage(libhttppp::HttpRequest &curreq,const int tid,const std::string &sessionid,libhtmlpp::HtmlElement &index, DomainContext &ctx);
        void logoutPage(libhttppp::HttpRequest &curreq,const int tid,const std::string &sessionid, DomainContext &ctx);
        void Settings(libhttppp::HttpRequest &curreq,const int tid,const std::string &sessionid,libhtmlpp::HtmlElement &index, DomainContext &ctx);
        void settingsApi(libhttppp::HttpRequest &curreq,const int tid,const std::string &sessionid, DomainContext &ctx);
        void searchPage(libhttppp::HttpRequest &curreq,const int tid,const std::string &sessionid,libhtmlpp::HtmlElement &index, DomainContext &ctx);
        void sitemapXML(libhttppp::HttpRequest& curreq, const int tid, const std::string &sessionid, DomainContext &ctx);
        void RequestEvent(libhttppp::HttpRequest &curreq,const int tid,ULONG_PTR args);
        void ResponseEvent(libhttppp::HttpRequest &curreq,const int tid,ULONG_PTR args);

        DomainContext *resolveDomain(libhttppp::HttpRequest &curreq);

        static std::unique_ptr<blogi::Config> Cfg;
    private:
        std::unique_ptr<PluginArgs>   PlgArgs;
        libhtmlpp::HtmlPage                 Page,MPage;
        libhtmlpp::HtmlElement            Index,MIndex;
        libhtmlpp::HtmlElement            CookieEl;
        std::unique_ptr<blogi::Plugin> BlogiPlg;
        void initDomainContext(DomainContext &ctx, const DomainConfig &dcfg, bool debug);
        // Default domain (single-domain backward compat or fallback)
        std::unique_ptr<DomainContext>  _defaultCtx;
        // Multi-domain contexts keyed by domain name (lowercase)
        std::map<std::string, std::unique_ptr<DomainContext>> _domains;
    };
};
+104 −0

File changed.

Preview size limit exceeded, changes collapsed.

+27 −0
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@
#include <stdexcept>
#include <string>
#include <vector>
#include <map>

#include <confplus/conf.h>

@@ -45,6 +46,26 @@ namespace blogi {
        std::string domain;
    };

    struct DomainConfig {
        std::string               name;
        std::string               dbDriver;
        std::string               dbConnection;
        std::string               authUrl;
        std::string               clientName;
        std::string               clientSecret;
        std::vector<AuthSource>   authSources;
        std::string               siteUrl;
        std::string               prefix;
        std::string               templatePath;
        std::string               startPage;
        std::vector<std::string>  plgDirs;
        std::string               cookieDomain;
        std::string               mediaDBUrl;
        std::string               tmpDir;

        const std::string buildurl(const std::string &url) const;
    };

    class Config : public confplus::Config {
    public:
        Config(const std::string &path);
@@ -89,6 +110,11 @@ namespace blogi {

        const std::string &getMediaDBUrl() const;

        // Multi-domain support
        size_t getDomainCount() const;
        const DomainConfig &getDomainConfig(size_t idx) const;
        const DomainConfig *findDomainConfig(const std::string &hostname) const;

    private:
        std::vector<std::string>  _PlgDir;
        std::string               _DBDriver;
@@ -112,6 +138,7 @@ namespace blogi {
        std::string               _CookieDomain;
        std::string               _TmpDir;
        std::string               _MediaDBUrl;
        std::vector<DomainConfig> _Domains;
    };

    inline void getConfig(dbpp::Database &db,const std::string &key,std::string &value){