<commit_before>/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/

#include "registerwindow.h"
#include "registerhandler.h"

#include "debuggeractions.h"
#include "debuggeragents.h"
#include "debuggerconstants.h"

#include <utils/qtcassert.h>

#include <QtCore/QDebug>
#include <QtCore/QDir>
#include <QtCore/QFileInfo>
#include <QtCore/QFileInfoList>

#include <QtGui/QAction>
#include <QtGui/QHeaderView>
#include <QtGui/QItemDelegate>
#include <QtGui/QLineEdit>
#include <QtGui/QMenu>
#include <QtGui/QPainter>
#include <QtGui/QResizeEvent>
#include <QtGui/QToolButton>

namespace Debugger {
namespace Internal {

///////////////////////////////////////////////////////////////////////
//
// RegisterDelegate
//
///////////////////////////////////////////////////////////////////////

class RegisterDelegate : public QItemDelegate
{
public:
    RegisterDelegate(DebuggerManager *manager, QObject *parent)
        : QItemDelegate(parent), m_manager(manager)
    {}

    QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &,
        const QModelIndex &) const
    {
        QLineEdit *lineEdit = new QLineEdit(parent);
        lineEdit->setAlignment(Qt::AlignRight);
        return lineEdit;
    }

    void setEditorData(QWidget *editor, const QModelIndex &index) const
    {
        QLineEdit *lineEdit = qobject_cast<QLineEdit *>(editor);
        QTC_ASSERT(lineEdit, return);
        lineEdit->setText(index.model()->data(index, Qt::DisplayRole).toString());
    }

    void setModelData(QWidget *editor, QAbstractItemModel *model,
        const QModelIndex &index) const
    {
        Q_UNUSED(model);
        //qDebug() << "SET MODEL DATA";
        QLineEdit *lineEdit = qobject_cast<QLineEdit*>(editor);
        QTC_ASSERT(lineEdit, return);
        QString value = lineEdit->text();
        //model->setData(index, value, Qt::EditRole);
        if (index.column() == 1)
            m_manager->setRegisterValue(index.row(), value);
    }

    void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option,
        const QModelIndex &) const
    {
        editor->setGeometry(option.rect);
    }

    void paint(QPainter *painter, const QStyleOptionViewItem &option,
        const QModelIndex &index) const
    {
        if (index.column() == 1) {
            // FIXME: performance? this changes only on real font changes.
            QFontMetrics fm(option.font);
            int charWidth = fm.width(QLatin1Char('x'));
            for (int i = '1'; i <= '9'; ++i)
                charWidth = qMax(charWidth, fm.width(QLatin1Char(i)));
            for (int i = 'a'; i <= 'f'; ++i)
                charWidth = qMax(charWidth, fm.width(QLatin1Char(i)));
            QString str = index.model()->data(index, Qt::DisplayRole).toString();
            int x = option.rect.x();
            for (int i = 0; i < str.size(); ++i) {
                QRect r = option.rect;
                r.setX(x);
                r.setWidth(charWidth);
                x += charWidth;
                painter->drawText(r, Qt::AlignHCenter, QString(str.at(i)));
            }
        } else {
            QItemDelegate::paint(painter, option, index);
        }
    }

private:
    DebuggerManager *m_manager;
};

///////////////////////////////////////////////////////////////////////
//
// RegisterWindow
//
///////////////////////////////////////////////////////////////////////

RegisterWindow::RegisterWindow(DebuggerManager *manager)
  : m_manager(manager), m_alwaysResizeColumnsToContents(true),
    m_alwaysReloadContents(false)
{
    QAction *act = theDebuggerAction(UseAlternatingRowColors);
    setWindowTitle(tr("Registers"));
    setSortingEnabled(true);
    setAlternatingRowColors(act->isChecked());
    setRootIsDecorated(false);
    setItemDelegate(new RegisterDelegate(m_manager, this));

    connect(act, SIGNAL(toggled(bool)),
        this, SLOT(setAlternatingRowColorsHelper(bool)));
}

void RegisterWindow::resizeEvent(QResizeEvent *ev)
{
    QTreeView::resizeEvent(ev);
}

void RegisterWindow::contextMenuEvent(QContextMenuEvent *ev)
{
    QMenu menu;

    QAction *actReload = menu.addAction(tr("Reload register listing"));
    QAction *actAlwaysReload = menu.addAction(tr("Always reload register listing"));
    actAlwaysReload->setCheckable(true);
    actAlwaysReload->setChecked(m_alwaysReloadContents);
    menu.addSeparator();

    QModelIndex idx = indexAt(ev->pos());
    QString address = model()->data(idx, RegisterAddressRole).toString();
    QAction *actShowMemory = menu.addAction(QString());
    if (address.isEmpty()) {
        actShowMemory->setText(tr("Open memory editor"));
        actShowMemory->setEnabled(false);
    } else {
        actShowMemory->setText(tr("Open memory editor at %1").arg(address));
    }
    menu.addSeparator();

    int base = model()->data(QModelIndex(), RegisterNumberBaseRole).toInt();
    QAction *act16 = menu.addAction(tr("Hexadecimal"));
    act16->setCheckable(true);
    act16->setChecked(base == 16);
    QAction *act10 = menu.addAction(tr("Decimal"));
    act10->setCheckable(true);
    act10->setChecked(base == 10);
    QAction *act8 = menu.addAction(tr("Octal"));
    act8->setCheckable(true);
    act8->setChecked(base == 8);
    QAction *act2 = menu.addAction(tr("Binary"));
    act2->setCheckable(true);
    act2->setChecked(base == 2);
    menu.addSeparator();

    QAction *actAdjust = menu.addAction(tr("Adjust column widths to contents"));
    QAction *actAlwaysAdjust =
        menu.addAction(tr("Always adjust column widths to contents"));
    actAlwaysAdjust->setCheckable(true);
    actAlwaysAdjust->setChecked(m_alwaysResizeColumnsToContents);
    menu.addSeparator();

    menu.addAction(theDebuggerAction(SettingsDialog));

    QAction *act = menu.exec(ev->globalPos());

    if (act == actAdjust)
        resizeColumnsToContents();
    else if (act == actAlwaysAdjust)
        setAlwaysResizeColumnsToContents(!m_alwaysResizeColumnsToContents);
    else if (act == actReload)
        reloadContents();
    else if (act == actAlwaysReload)
        setAlwaysReloadContents(!m_alwaysReloadContents);
    else if (act == actShowMemory)
        (void) new MemoryViewAgent(m_manager, address);
    else if (act) {
        base = (act == act10 ? 10 : act == act8 ? 8 : act == act2 ? 2 : 16);
        QMetaObject::invokeMethod(model(), "setNumberBase", Q_ARG(int, base));
    }
}

void RegisterWindow::resizeColumnsToContents()
{
    resizeColumnToContents(0);
    resizeColumnToContents(1);
}

void RegisterWindow::setAlwaysResizeColumnsToContents(bool on)
{
    m_alwaysResizeColumnsToContents = on;
    QHeaderView::ResizeMode mode = on
        ? QHeaderView::ResizeToContents : QHeaderView::Interactive;
    header()->setResizeMode(0, mode);
    header()->setResizeMode(1, mode);
}

void RegisterWindow::setAlwaysReloadContents(bool on)
{
    m_alwaysReloadContents = on;
    if (m_alwaysReloadContents)
        reloadContents();
}

void RegisterWindow::reloadContents()
{
    emit reloadRegisterRequested();
}

void RegisterWindow::setModel(QAbstractItemModel *model)
{
    QTreeView::setModel(model);
    setAlwaysResizeColumnsToContents(true);
}

} // namespace Internal
} // namespace Debugger
<commit_msg>debugger: disable sorting of registers<commit_after>/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/

#include "registerwindow.h"
#include "registerhandler.h"

#include "debuggeractions.h"
#include "debuggeragents.h"
#include "debuggerconstants.h"

#include <utils/qtcassert.h>

#include <QtCore/QDebug>
#include <QtCore/QDir>
#include <QtCore/QFileInfo>
#include <QtCore/QFileInfoList>

#include <QtGui/QAction>
#include <QtGui/QHeaderView>
#include <QtGui/QItemDelegate>
#include <QtGui/QLineEdit>
#include <QtGui/QMenu>
#include <QtGui/QPainter>
#include <QtGui/QResizeEvent>
#include <QtGui/QToolButton>

namespace Debugger {
namespace Internal {

///////////////////////////////////////////////////////////////////////
//
// RegisterDelegate
//
///////////////////////////////////////////////////////////////////////

class RegisterDelegate : public QItemDelegate
{
public:
    RegisterDelegate(DebuggerManager *manager, QObject *parent)
        : QItemDelegate(parent), m_manager(manager)
    {}

    QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &,
        const QModelIndex &) const
    {
        QLineEdit *lineEdit = new QLineEdit(parent);
        lineEdit->setAlignment(Qt::AlignRight);
        return lineEdit;
    }

    void setEditorData(QWidget *editor, const QModelIndex &index) const
    {
        QLineEdit *lineEdit = qobject_cast<QLineEdit *>(editor);
        QTC_ASSERT(lineEdit, return);
        lineEdit->setText(index.model()->data(index, Qt::DisplayRole).toString());
    }

    void setModelData(QWidget *editor, QAbstractItemModel *model,
        const QModelIndex &index) const
    {
        Q_UNUSED(model);
        //qDebug() << "SET MODEL DATA";
        QLineEdit *lineEdit = qobject_cast<QLineEdit*>(editor);
        QTC_ASSERT(lineEdit, return);
        QString value = lineEdit->text();
        //model->setData(index, value, Qt::EditRole);
        if (index.column() == 1)
            m_manager->setRegisterValue(index.row(), value);
    }

    void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option,
        const QModelIndex &) const
    {
        editor->setGeometry(option.rect);
    }

    void paint(QPainter *painter, const QStyleOptionViewItem &option,
        const QModelIndex &index) const
    {
        if (index.column() == 1) {
            // FIXME: performance? this changes only on real font changes.
            QFontMetrics fm(option.font);
            int charWidth = fm.width(QLatin1Char('x'));
            for (int i = '1'; i <= '9'; ++i)
                charWidth = qMax(charWidth, fm.width(QLatin1Char(i)));
            for (int i = 'a'; i <= 'f'; ++i)
                charWidth = qMax(charWidth, fm.width(QLatin1Char(i)));
            QString str = index.model()->data(index, Qt::DisplayRole).toString();
            int x = option.rect.x();
            for (int i = 0; i < str.size(); ++i) {
                QRect r = option.rect;
                r.setX(x);
                r.setWidth(charWidth);
                x += charWidth;
                painter->drawText(r, Qt::AlignHCenter, QString(str.at(i)));
            }
        } else {
            QItemDelegate::paint(painter, option, index);
        }
    }

private:
    DebuggerManager *m_manager;
};

///////////////////////////////////////////////////////////////////////
//
// RegisterWindow
//
///////////////////////////////////////////////////////////////////////

RegisterWindow::RegisterWindow(DebuggerManager *manager)
  : m_manager(manager), m_alwaysResizeColumnsToContents(true),
    m_alwaysReloadContents(false)
{
    QAction *act = theDebuggerAction(UseAlternatingRowColors);
    setWindowTitle(tr("Registers"));
    setAlternatingRowColors(act->isChecked());
    setRootIsDecorated(false);
    setItemDelegate(new RegisterDelegate(m_manager, this));

    connect(act, SIGNAL(toggled(bool)),
        this, SLOT(setAlternatingRowColorsHelper(bool)));
}

void RegisterWindow::resizeEvent(QResizeEvent *ev)
{
    QTreeView::resizeEvent(ev);
}

void RegisterWindow::contextMenuEvent(QContextMenuEvent *ev)
{
    QMenu menu;

    QAction *actReload = menu.addAction(tr("Reload register listing"));
    QAction *actAlwaysReload = menu.addAction(tr("Always reload register listing"));
    actAlwaysReload->setCheckable(true);
    actAlwaysReload->setChecked(m_alwaysReloadContents);
    menu.addSeparator();

    QModelIndex idx = indexAt(ev->pos());
    QString address = model()->data(idx, RegisterAddressRole).toString();
    QAction *actShowMemory = menu.addAction(QString());
    if (address.isEmpty()) {
        actShowMemory->setText(tr("Open memory editor"));
        actShowMemory->setEnabled(false);
    } else {
        actShowMemory->setText(tr("Open memory editor at %1").arg(address));
    }
    menu.addSeparator();

    int base = model()->data(QModelIndex(), RegisterNumberBaseRole).toInt();
    QAction *act16 = menu.addAction(tr("Hexadecimal"));
    act16->setCheckable(true);
    act16->setChecked(base == 16);
    QAction *act10 = menu.addAction(tr("Decimal"));
    act10->setCheckable(true);
    act10->setChecked(base == 10);
    QAction *act8 = menu.addAction(tr("Octal"));
    act8->setCheckable(true);
    act8->setChecked(base == 8);
    QAction *act2 = menu.addAction(tr("Binary"));
    act2->setCheckable(true);
    act2->setChecked(base == 2);
    menu.addSeparator();

    QAction *actAdjust = menu.addAction(tr("Adjust column widths to contents"));
    QAction *actAlwaysAdjust =
        menu.addAction(tr("Always adjust column widths to contents"));
    actAlwaysAdjust->setCheckable(true);
    actAlwaysAdjust->setChecked(m_alwaysResizeColumnsToContents);
    menu.addSeparator();

    menu.addAction(theDebuggerAction(SettingsDialog));

    QAction *act = menu.exec(ev->globalPos());

    if (act == actAdjust)
        resizeColumnsToContents();
    else if (act == actAlwaysAdjust)
        setAlwaysResizeColumnsToContents(!m_alwaysResizeColumnsToContents);
    else if (act == actReload)
        reloadContents();
    else if (act == actAlwaysReload)
        setAlwaysReloadContents(!m_alwaysReloadContents);
    else if (act == actShowMemory)
        (void) new MemoryViewAgent(m_manager, address);
    else if (act) {
        base = (act == act10 ? 10 : act == act8 ? 8 : act == act2 ? 2 : 16);
        QMetaObject::invokeMethod(model(), "setNumberBase", Q_ARG(int, base));
    }
}

void RegisterWindow::resizeColumnsToContents()
{
    resizeColumnToContents(0);
    resizeColumnToContents(1);
}

void RegisterWindow::setAlwaysResizeColumnsToContents(bool on)
{
    m_alwaysResizeColumnsToContents = on;
    QHeaderView::ResizeMode mode = on
        ? QHeaderView::ResizeToContents : QHeaderView::Interactive;
    header()->setResizeMode(0, mode);
    header()->setResizeMode(1, mode);
}

void RegisterWindow::setAlwaysReloadContents(bool on)
{
    m_alwaysReloadContents = on;
    if (m_alwaysReloadContents)
        reloadContents();
}

void RegisterWindow::reloadContents()
{
    emit reloadRegisterRequested();
}

void RegisterWindow::setModel(QAbstractItemModel *model)
{
    QTreeView::setModel(model);
    setAlwaysResizeColumnsToContents(true);
}

} // namespace Internal
} // namespace Debugger
<|endoftext|>


<commit_before>/**
 * The Seeks proxy and plugin framework are part of the SEEKS project.
 * Copyright (C) 2010 Emmanuel Benazera, ebenazer@seeks-project.info
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "uri_capture.h"
#include "uc_configuration.h"
#include "db_uri_record.h"
#include "seeks_proxy.h" // for user_db.
#include "user_db.h"
#include "proxy_dts.h"
#include "urlmatch.h"
#include "miscutil.h"
#include "errlog.h"

#include <sys/time.h>
#include <sys/stat.h>

#include <algorithm>
#include <iterator>
#include <iostream>

using sp::seeks_proxy;
using sp::user_db;
using sp::db_record;
using sp::urlmatch;
using sp::miscutil;
using sp::errlog;

namespace seeks_plugins
{

   /*- uri_db_sweepable -*/
   uri_db_sweepable::uri_db_sweepable()
     :user_db_sweepable()
       {
	  // set last sweep to now.
	  // sweeping is done when plugin starts.
	  struct timeval tv_now;
	  gettimeofday(&tv_now,NULL);
	  _last_sweep = tv_now.tv_sec;
       }

   uri_db_sweepable::~uri_db_sweepable()
     {
     }

   bool uri_db_sweepable::sweep_me()
     {
	struct timeval tv_now;
	gettimeofday(&tv_now,NULL);
	if ((tv_now.tv_sec - _last_sweep)
	    > uc_configuration::_config->_sweep_cycle)
	  return true;
	else return false;
     }

   int uri_db_sweepable::sweep_records()
     {
	struct timeval tv_now;
	gettimeofday(&tv_now,NULL);
	time_t sweep_date = tv_now.tv_sec - uc_configuration::_config->_retention;
	seeks_proxy::_user_db->prune_db("uri-capture",sweep_date);
     }

   /*- uri_capture -*/
   uri_capture::uri_capture()
     : plugin(),_nr(0)
       {
	  _name = "uri-capture";
	  _version_major = "0";
	  _version_minor = "1";
	  _configuration = NULL;
	  _interceptor_plugin = new uri_capture_element(this);

	  if (seeks_proxy::_datadir.empty())
	    _config_filename = plugin_manager::_plugin_repository + "uri_capture/uri-capture-config";
	  else
	    _config_filename = seeks_proxy::_datadir + "/plugins/uri_capture/uri-capture-config";

#ifdef SEEKS_CONFIGDIR
	  struct stat stFileInfo;
	  if (!stat(_config_filename.c_str(), &stFileInfo)  == 0)
	    {
	       _config_filename = SEEKS_CONFIGDIR "/uri-capture-config";
	    }
#endif

	  if (uc_configuration::_config == NULL)
	    uc_configuration::_config = new uc_configuration(_config_filename);
	  _configuration = uc_configuration::_config;
       }

   uri_capture::~uri_capture()
     {
     }

   void uri_capture::start()
     {
	// check for user db.
	if (!seeks_proxy::_user_db || !seeks_proxy::_user_db->_opened)
	  {
	     errlog::log_error(LOG_LEVEL_ERROR,"user db is not opened for URI capture plugin to work with it");
	  }

	// preventive sweep of records.
	static_cast<uri_capture_element*>(_interceptor_plugin)->_uds.sweep_records();

	// get number of captured URI already in user_db.
	_nr = seeks_proxy::_user_db->number_records(_name);

	errlog::log_error(LOG_LEVEL_INFO,"uri_capture plugin: %u records",_nr);
     }

   void uri_capture::stop()
     {
     }

   sp::db_record* uri_capture::create_db_record()
     {
	return new db_uri_record();
     }

   int uri_capture::remove_all_uri_records()
     {
	seeks_proxy::_user_db->prune_db(_name);
     }

   /*- uri_capture_element -*/
   std::string uri_capture_element::_capt_filename = "uri_capture/uri-patterns";
   hash_map<const char*,bool,hash<const char*>,eqstr> uri_capture_element::_img_ext_list;
   std::string uri_capture_element::_cgi_site_host = CGI_SITE_1_HOST;

   uri_capture_element::uri_capture_element(plugin *parent)
     : interceptor_plugin((seeks_proxy::_datadir.empty() ? std::string(plugin_manager::_plugin_repository
								       + uri_capture_element::_capt_filename).c_str()
			   : std::string(seeks_proxy::_datadir + "/plugins/" + uri_capture_element::_capt_filename).c_str()),
			  parent)
       {
	  uri_capture_element::init_file_ext_list();
	  seeks_proxy::_user_db->register_sweeper(&_uds);
       }

   uri_capture_element::~uri_capture_element()
     {
     }

   http_response* uri_capture_element::plugin_response(client_state *csp)
     {
	// store domain names.
	/* std::cerr << "[uri_capture]: headers:\n";
	std::copy(csp->_headers.begin(),csp->_headers.end(),
		  std::ostream_iterator<const char*>(std::cout,"\n"));
	std::cerr << std::endl; */

	std::string host, referer, accept, get;
	bool connect = false;
	uri_capture_element::get_useful_headers(csp->_headers,
						host,referer,
						accept,get,connect);

	/**
	 * URI domain name is store in two cases:
	 * - there is no referer in the HTTP request headers.
	 * - the host domain is different than the referer, indicating a move
	 *   to a different website.
	 *
	 * We do not record:
	 * - 'CONNECT' requests.
	 * - paths to images.
	 * - TODO: calls to search engines.
	 */
	std::string uri;

	bool store = true;
	if (connect)
	  {
	     store = false;
	  }
	else if (store)
	  {
	     size_t p = accept.find("image");
	     if (p!=std::string::npos)
	       store = false;
	     else
	       {
		  p = miscutil::replace_in_string(get," HTTP/1.1","");
		  if (p == 0)
		    miscutil::replace_in_string(get," HTTP/1.0","");
		  if (uri_capture_element::is_path_to_no_page(get))
		    store = false;
	       }
	  }
	host = urlmatch::strip_url(host);
	std::transform(host.begin(),host.end(),host.begin(),tolower);
	std::transform(get.begin(),get.end(),get.begin(),tolower);

	//std::cerr << "****************** host: " << host << " -- ref_host: " << ref_host << std::endl;
	//std::cerr << "cgi host: " << uri_capture_element::_cgi_site_host << std::endl;
	if (host == uri_capture_element::_cgi_site_host) // if proxy domain.
	  store = false;

	if (store && referer.empty())
	  {
	     //std::cerr << "****************** no referer!\n";
	     if (get != "/")
	       uri = host + get;
	  }
	else if (store)
	  {
	     std::string ref_host, ref_path;
	     urlmatch::parse_url_host_and_path(referer,ref_host,ref_path);
	     ref_host = urlmatch::strip_url(ref_host);

	     if (get != "/")
	       uri = host + get;
	  }

	//std::cerr << "-----------------> store: " << store << std::endl;
	//std::cerr << std::endl;

	if (store)
	  {
	     // add record to user db.
	     db_uri_record dbur(_parent->get_name());
	     if (!uri.empty())
	       {
		  //TODO: uri cleaning ?

		  std::cerr << "adding URI: " << uri << std::endl;
		  seeks_proxy::_user_db->add_dbr(uri,dbur);
		  static_cast<uri_capture*>(_parent)->_nr++;
	       }
	     if (!host.empty() && uri != host)
	       {
		  std::cerr << "adding HOST: " << host << std::endl;
		  seeks_proxy::_user_db->add_dbr(host,dbur);
		  static_cast<uri_capture*>(_parent)->_nr++;
	       }
	  }

	return NULL; // no response, so the proxy does not crunch this HTTP request.
     }

   void uri_capture_element::get_useful_headers(const std::list<const char*> &headers,
						std::string &host, std::string &referer,
						std::string &accept, std::string &get,
						bool &connect)
     {
	std::list<const char*>::const_iterator lit = headers.begin();
	while(lit!=headers.end())
	  {
	     if (miscutil::strncmpic((*lit),"get ",4) == 0)
	       {
		  get = (*lit);
		  get = get.substr(4);
	       }
	     else if (miscutil::strncmpic((*lit),"host:",5) == 0)
	       {
		  host = (*lit);
		  host = host.substr(6);
	       }
	     else if (miscutil::strncmpic((*lit),"referer:",8) == 0)
	       {
		  referer = (*lit);
		  referer = referer.substr(9);
	       }
	     else if (miscutil::strncmpic((*lit),"accept:",7) == 0)
	       {
		  accept = (*lit);
		  accept = accept.substr(8);
	       }
	     else if (miscutil::strncmpic((*lit),"connect ",8) == 0) // XXX: could grab GET and negate the test.
	       connect = true;
	     ++lit;
	  }
     }

   void uri_capture_element::init_file_ext_list()
     {
	static std::string ext_list[32]
	  = { "jpg","jpeg","raw","png","gif","bmp","ppm","pgm","pbm","pnm","tga","pcx",
	      "ecw","img","sid","pgf","cgm","svg","eps","odg","pdf","pgml","swf",
	      "vml","wmf","emf","xps","ico","css","js","xml","pl"};

	if (!uri_capture_element::_img_ext_list.empty())
	  return;

	for (int i=0;i<32;i++)
	  uri_capture_element::_img_ext_list.insert(std::pair<const char*,bool>(strdup(ext_list[i].c_str()),true));
     }

   bool uri_capture_element::is_path_to_no_page(const std::string &path)
     {
	size_t pos = path.find_last_of(".");
	if (pos == std::string::npos || path.size() < pos+1)
	  return false;
	std::string ext = path.substr(pos+1);
	hash_map<const char*,bool,hash<const char*>,eqstr>::const_iterator hit;
	if ((hit=uri_capture_element::_img_ext_list.find(ext.c_str()))!=uri_capture_element::_img_ext_list.end())
	  return true;
	return false;
     }

   /* auto-registration */
#if defined(ON_OPENBSD) || defined(ON_OSX)
   extern "C"
     {
	plugin* maker()
	  {
	     return new uri_capture;
	  }
     }
#else
   plugin* makeruc()
     {
	return new uri_capture;
     }

   class proxy_autor_capture
     {
      public:
	proxy_autor_capture()
	  {
	     plugin_manager::_factory["uri-capture"] = makeruc; // beware: default plugin shell with no name.
	  }
     };

   proxy_autor_capture _p; // one instance, instanciated when dl-opening.
#endif

} /* end of namespace. */
<commit_msg>fixed sweeping call when user db does not exist and tiny refactoring of URI capture fcts<commit_after>/**
 * The Seeks proxy and plugin framework are part of the SEEKS project.
 * Copyright (C) 2010 Emmanuel Benazera, ebenazer@seeks-project.info
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "uri_capture.h"
#include "uc_configuration.h"
#include "db_uri_record.h"
#include "seeks_proxy.h" // for user_db.
#include "user_db.h"
#include "proxy_dts.h"
#include "urlmatch.h"
#include "miscutil.h"
#include "errlog.h"

#include <sys/time.h>
#include <sys/stat.h>

#include <algorithm>
#include <iterator>
#include <iostream>

using sp::seeks_proxy;
using sp::user_db;
using sp::db_record;
using sp::urlmatch;
using sp::miscutil;
using sp::errlog;

namespace seeks_plugins
{

   /*- uri_db_sweepable -*/
   uri_db_sweepable::uri_db_sweepable()
     :user_db_sweepable()
       {
	  // set last sweep to now.
	  // sweeping is done when plugin starts.
	  struct timeval tv_now;
	  gettimeofday(&tv_now,NULL);
	  _last_sweep = tv_now.tv_sec;
       }

   uri_db_sweepable::~uri_db_sweepable()
     {
     }

   bool uri_db_sweepable::sweep_me()
     {
	struct timeval tv_now;
	gettimeofday(&tv_now,NULL);
	if ((tv_now.tv_sec - _last_sweep)
	    > uc_configuration::_config->_sweep_cycle)
	  return true;
	else return false;
     }

   int uri_db_sweepable::sweep_records()
     {
	struct timeval tv_now;
	gettimeofday(&tv_now,NULL);
	time_t sweep_date = tv_now.tv_sec - uc_configuration::_config->_retention;
	seeks_proxy::_user_db->prune_db("uri-capture",sweep_date);
     }

   /*- uri_capture -*/
   uri_capture::uri_capture()
     : plugin(),_nr(0)
       {
	  _name = "uri-capture";
	  _version_major = "0";
	  _version_minor = "1";
	  _configuration = NULL;
	  _interceptor_plugin = new uri_capture_element(this);

	  if (seeks_proxy::_datadir.empty())
	    _config_filename = plugin_manager::_plugin_repository + "uri_capture/uri-capture-config";
	  else
	    _config_filename = seeks_proxy::_datadir + "/plugins/uri_capture/uri-capture-config";

#ifdef SEEKS_CONFIGDIR
	  struct stat stFileInfo;
	  if (!stat(_config_filename.c_str(), &stFileInfo)  == 0)
	    {
	       _config_filename = SEEKS_CONFIGDIR "/uri-capture-config";
	    }
#endif

	  if (uc_configuration::_config == NULL)
	    uc_configuration::_config = new uc_configuration(_config_filename);
	  _configuration = uc_configuration::_config;
       }

   uri_capture::~uri_capture()
     {
     }

   void uri_capture::start()
     {
	// check for user db.
	if (!seeks_proxy::_user_db || !seeks_proxy::_user_db->_opened)
	  {
	     errlog::log_error(LOG_LEVEL_ERROR,"user db is not opened for URI capture plugin to work with it");
	  }
	else
	  {
	     // preventive sweep of records.
	     static_cast<uri_capture_element*>(_interceptor_plugin)->_uds.sweep_records();

	     // get number of captured URI already in user_db.
	     _nr = seeks_proxy::_user_db->number_records(_name);

	     errlog::log_error(LOG_LEVEL_INFO,"uri_capture plugin: %u records",_nr);
	  }
     }

   void uri_capture::stop()
     {
     }

   sp::db_record* uri_capture::create_db_record()
     {
	return new db_uri_record();
     }

   int uri_capture::remove_all_uri_records()
     {
	seeks_proxy::_user_db->prune_db(_name);
     }

   /*- uri_capture_element -*/
   std::string uri_capture_element::_capt_filename = "uri_capture/uri-patterns";
   hash_map<const char*,bool,hash<const char*>,eqstr> uri_capture_element::_img_ext_list;
   std::string uri_capture_element::_cgi_site_host = CGI_SITE_1_HOST;

   uri_capture_element::uri_capture_element(plugin *parent)
     : interceptor_plugin((seeks_proxy::_datadir.empty() ? std::string(plugin_manager::_plugin_repository
								       + uri_capture_element::_capt_filename).c_str()
			   : std::string(seeks_proxy::_datadir + "/plugins/" + uri_capture_element::_capt_filename).c_str()),
			  parent)
       {
	  uri_capture_element::init_file_ext_list();
	  if (seeks_proxy::_user_db)
	    seeks_proxy::_user_db->register_sweeper(&_uds);
       }

   uri_capture_element::~uri_capture_element()
     {
     }

   http_response* uri_capture_element::plugin_response(client_state *csp)
     {
	// store domain names.
	/* std::cerr << "[uri_capture]: headers:\n";
	std::copy(csp->_headers.begin(),csp->_headers.end(),
		  std::ostream_iterator<const char*>(std::cout,"\n"));
	std::cerr << std::endl; */

	std::string host, referer, accept, get;
	bool connect = false;
	uri_capture_element::get_useful_headers(csp->_headers,
						host,referer,
						accept,get,connect);

	/**
	 * URI domain name is store in two cases:
	 * - there is no referer in the HTTP request headers.
	 * - the host domain is different than the referer, indicating a move
	 *   to a different website.
	 *
	 * We do not record:
	 * - 'CONNECT' requests.
	 * - paths to images.
	 * - TODO: calls to search engines.
	 */
	std::string uri;

	bool store = true;
	if (connect)
	  {
	     store = false;
	  }
	else if (store)
	  {
	     size_t p = accept.find("image");
	     if (p!=std::string::npos)
	       store = false;
	     else
	       {
		  p = miscutil::replace_in_string(get," HTTP/1.1","");
		  if (p == 0)
		    miscutil::replace_in_string(get," HTTP/1.0","");
		  if (uri_capture_element::is_path_to_no_page(get))
		    store = false;
	       }
	  }
	host = uri_capture_element::prepare_uri(host);
	std::transform(get.begin(),get.end(),get.begin(),tolower);

	//std::cerr << "****************** host: " << host << " -- ref_host: " << ref_host << std::endl;
	//std::cerr << "cgi host: " << uri_capture_element::_cgi_site_host << std::endl;
	if (host == uri_capture_element::_cgi_site_host) // if proxy domain.
	  store = false;

	if (store && referer.empty())
	  {
	     //std::cerr << "****************** no referer!\n";
	     if (get != "/")
	       uri = host + get;
	  }
	else if (store)
	  {
	     if (get != "/")
	       uri = host + get;
	  }

	//std::cerr << "-----------------> store: " << store << std::endl;
	//std::cerr << std::endl;

	if (store)
	  {
	     store_uri(uri,host);
	  }

	return NULL; // no response, so the proxy does not crunch this HTTP request.
     }

   void uri_capture_element::store_uri(const std::string &uri, const std::string &host) const
     {
	// add record to user db.
	db_uri_record dbur(_parent->get_name());
	if (!uri.empty())
	  {
	     //TODO: uri cleaning ?
	     std::cerr << "adding URI: " << uri << std::endl;
	     seeks_proxy::_user_db->add_dbr(uri,dbur);
	     static_cast<uri_capture*>(_parent)->_nr++;
	  }
	if (!host.empty() && uri != host)
	  {
	     std::cerr << "adding HOST: " << host << std::endl;
	     seeks_proxy::_user_db->add_dbr(host,dbur);
	     static_cast<uri_capture*>(_parent)->_nr++;
	  }
     }

   std::string uri_capture_element::prepare_uri(const std::string &uri)
     {
	std::string prep_uri = urlmatch::strip_url(uri);
	std::transform(prep_uri.begin(),prep_uri.end(),prep_uri.begin(),tolower);
	return prep_uri;
     }

   void uri_capture_element::get_useful_headers(const std::list<const char*> &headers,
						std::string &host, std::string &referer,
						std::string &accept, std::string &get,
						bool &connect)
     {
	std::list<const char*>::const_iterator lit = headers.begin();
	while(lit!=headers.end())
	  {
	     if (miscutil::strncmpic((*lit),"get ",4) == 0)
	       {
		  get = (*lit);
		  get = get.substr(4);
	       }
	     else if (miscutil::strncmpic((*lit),"host:",5) == 0)
	       {
		  host = (*lit);
		  host = host.substr(6);
	       }
	     else if (miscutil::strncmpic((*lit),"referer:",8) == 0)
	       {
		  referer = (*lit);
		  referer = referer.substr(9);
	       }
	     else if (miscutil::strncmpic((*lit),"accept:",7) == 0)
	       {
		  accept = (*lit);
		  accept = accept.substr(8);
	       }
	     else if (miscutil::strncmpic((*lit),"connect ",8) == 0) // XXX: could grab GET and negate the test.
	       connect = true;
	     ++lit;
	  }
     }

   void uri_capture_element::init_file_ext_list()
     {
	static std::string ext_list[32]
	  = { "jpg","jpeg","raw","png","gif","bmp","ppm","pgm","pbm","pnm","tga","pcx",
	      "ecw","img","sid","pgf","cgm","svg","eps","odg","pdf","pgml","swf",
	      "vml","wmf","emf","xps","ico","css","js","xml","pl"};

	if (!uri_capture_element::_img_ext_list.empty())
	  return;

	for (int i=0;i<32;i++)
	  uri_capture_element::_img_ext_list.insert(std::pair<const char*,bool>(strdup(ext_list[i].c_str()),true));
     }

   bool uri_capture_element::is_path_to_no_page(const std::string &path)
     {
	size_t pos = path.find_last_of(".");
	if (pos == std::string::npos || path.size() < pos+1)
	  return false;
	std::string ext = path.substr(pos+1);
	hash_map<const char*,bool,hash<const char*>,eqstr>::const_iterator hit;
	if ((hit=uri_capture_element::_img_ext_list.find(ext.c_str()))!=uri_capture_element::_img_ext_list.end())
	  return true;
	return false;
     }

   /* auto-registration */
#if defined(ON_OPENBSD) || defined(ON_OSX)
   extern "C"
     {
	plugin* maker()
	  {
	     return new uri_capture;
	  }
     }
#else
   plugin* makeruc()
     {
	return new uri_capture;
     }

   class proxy_autor_capture
     {
      public:
	proxy_autor_capture()
	  {
	     plugin_manager::_factory["uri-capture"] = makeruc; // beware: default plugin shell with no name.
	  }
     };

   proxy_autor_capture _p; // one instance, instanciated when dl-opening.
#endif

} /* end of namespace. */
<|endoftext|>


<commit_before>/* csvstream_test.cpp
 *
 * Andrew DeOrio <awdeorio@umich.edu>
 *
 * An easy-to-use CSV file parser for C++
 * https://github.com/awdeorio/csvstream
 */

#include "csvstream.h"
#include <iostream>
#include <fstream>
#include <string>
#include <map>
#include <vector>
using namespace std;

void test_filename_ctor();
void test_stream_ctor();
void test_getheader();
void test_emptyfields();
void test_tsv();
void test_too_few_cols_in_the_middle(bool strict);
void test_too_few_cols_at_the_end(bool strict);
void test_too_many_cols(bool strict);
void test_no_newline_at_the_end();
void test_quotes();
void test_escape_quotes();
void test_multiline_quotes();
void test_osx_line_endings();
void test_windows_line_endings();
void test_strict_notsctrict();
void test_notstrict_exceptions();

int main() {
  test_filename_ctor();
  test_stream_ctor();
  test_getheader();
  test_emptyfields();
  test_tsv();
  for (auto strict : { false, true }) {
    test_too_few_cols_in_the_middle(strict);
    test_too_few_cols_at_the_end(strict);
    test_too_many_cols(strict);
  }
  test_no_newline_at_the_end();
  test_quotes();
  test_escape_quotes();
  test_multiline_quotes();
  test_osx_line_endings();
  test_windows_line_endings();
  test_strict_notsctrict();
  test_notstrict_exceptions();
  cout << "csvstream_test PASSED\n";
  return 0;
}

// data for next few unit tests
const string input_filename_animals = "csvstream_example.csv";
const vector<string> header_correct_animals = {"name", "animal"};
const vector<map<string, string>> output_correct_animals =
  {
    {{"name","Fergie"},{"animal","horse"}},
    {{"name","Myrtle II"},{"animal","chicken"}},
    {{"name","Oscar"},{"animal","cat"}},
  }
  ;

void test_filename_ctor() {
  // Test creating a csvstream object from a filename string

  // Save actual output
  vector<map<string, string>> output_observed;

  // Read file
  csvstream csvin(input_filename_animals);
  map<string, string> row;
  while (csvin >> row) {
    output_observed.push_back(row);
  }

  // Check output
  assert(output_observed == output_correct_animals);
}

void test_stream_ctor() {
  // Test creating a csvstream object from a stream

  // Save actual output
  vector<map<string, string>> output_observed;

  // Open file
  ifstream fin(input_filename_animals.c_str());
  assert(fin.is_open());

  // Read file from stream
  csvstream csvin(fin);
  map<string, string> row;
  while (csvin >> row) {
    output_observed.push_back(row);
  }

  // Check output
  assert(output_observed == output_correct_animals);

  // Clean up
  fin.close();
}

void test_getheader() {
  // Test reading header from first line of CSV file

  csvstream csvin(input_filename_animals);
  auto header = csvin.getheader();
  assert(header == header_correct_animals);
}

void test_emptyfields() {
  // Test with input data containing empty fields (consecutive commas)

  // Input
  stringstream iss("a,b,c\n,,\n,,\n");

  // Correct answer
  const vector<map<string, string>> output_correct =
    {
      {{"a",""},{"b",""},{"c",""}},
      {{"a",""},{"b",""},{"c",""}},
    }
  ;

  // Save actual output
  vector<map<string, string>> output_observed;

  // Read stream
  csvstream csvin(iss);
  map<string, string> row;
  try {
    while (csvin >> row) {
      output_observed.push_back(row);
    }
  } catch(csvstream_exception e) {
    cout << e.what() << endl;
    assert(0);
  }

  // Check output
  assert(output_observed == output_correct);
}

void test_tsv() {
  // Test with tab separated data

  // Input
  stringstream iss("a\tb\tc\nd\te\tf\n\t\t\n");

  // Correct answer
  const vector<map<string, string>> output_correct =
    {
      {{"a","d"},{"b","e"},{"c","f"}},
      {{"a",""},{"b",""},{"c",""}},
    }
  ;

  // Save actual output
  vector<map<string, string>> output_observed;

  // Read file
  csvstream csvin(iss, '\t');
  map<string, string> row;
  try {
    while (csvin >> row) {
      output_observed.push_back(row);
    }
  } catch(csvstream_exception e) {
    cout << e.what() << endl;
    assert(0);
  }

  // Check output
  assert(output_observed == output_correct);
}

void test_too_few_cols_in_the_middle(bool strict) {
  // Test error condition: a row in the middle of the file that doesn't have
  // enough fields

  // Input
  stringstream iss("a,b,c\n,\nd,e,f");

  // Create object
  csvstream csvin(iss, ',', strict);

  // Read file
  map<string, string> row;
  try {
    while (csvin >> row); // throw away data
  } catch(csvstream_exception e) {
    // exception must be caught for strict mode
    if (strict) return;
    cout << e.what() << endl;
    assert(0);
  }

  // if exception wasn't caught for strict mode, then it didn't work
  if (strict) assert(0);
}

void test_too_few_cols_at_the_end(bool strict) {
  // Test error condition: last row doesn't have enough fields

  // Input
  stringstream iss("a,b,c\n,");

  // Create object
  csvstream csvin(iss, ',', strict);

  // Read file
  map<string, string> row;
  try {
    while (csvin >> row); // throw away data
  } catch(csvstream_exception e) {
    // exception must be caught for strict mode
    if (strict) return;
    cout << e.what() << endl;
    assert(0);
  }

    // if exception wasn't caught for strict mode, then it didn't work
  if (strict) assert(0);
}

void test_too_many_cols(bool strict) {
  // Test error condition: row with too many fields

  // Input
  stringstream iss("a,b,c\n,,,");

  // Create object
  csvstream csvin(iss, ',', strict);

  // Read file
  map<string, string> row;
  try {
    while (csvin >> row); // throw away data
  } catch(csvstream_exception e) {
    // exception must be caught for strict mode
    if (strict) return;
    cout << e.what() << endl;
    assert(0);
  }

  // if exception wasn't caught for strict mode, then it didn't work
  if (strict) assert(0);
}

void test_no_newline_at_the_end() {
  // Test with input that has no newline at the end

  // Input
  stringstream iss("a,b,c\n,,");

  // Create object
  csvstream csvin(iss);

  // Correct answer
  const vector<map<string, string>> output_correct =
    {
      {{"a",""},{"b",""},{"c",""}},
    }
  ;

  // Save actual output
  vector<map<string, string>> output_observed;

  // Read stream
  map<string, string> row;
  try {
    while (csvin >> row) {
      output_observed.push_back(row);
    }
  } catch(csvstream_exception e) {
    cout << e.what() << endl;
    assert(0);
  }

  // Check output
  assert(output_observed == output_correct);

}

void test_quotes() {
  // Input
  stringstream iss("\"a\",b,c\n\"1\",2,3\n\"4,44\",5,6\n");

  // Correct answer
  const vector<map<string, string>> output_correct =
    {
      {{"a","1"},{"b","2"},{"c","3"}},
      {{"a","4,44"},{"b","5"},{"c","6"}}, //quoted comma
    }
  ;

  // Save actual output
  vector<map<string, string>> output_observed;

  // Read stream
  csvstream csvin(iss);
  map<string, string> row;
  try {
    while (csvin >> row) {
      output_observed.push_back(row);
    }
  } catch(csvstream_exception e) {
    cout << e.what() << endl;
    assert(0);
  }

  // Check output
  assert(output_observed == output_correct);
}

void test_escape_quotes() {
  // Test with input data containing escaped quotes

  // Input
  stringstream iss("\\\"a\\\",b,c\n\\\"1\\\",2,3\n\"4,\\\"44\",5,6");

  // Correct answer
  const vector<map<string, string>> output_correct =
    {
      {{"\\\"a\\\"","\\\"1\\\""},{"b","2"},{"c","3"}},  //escaped quote
      {{"\\\"a\\\"","4,\\\"44"},{"b","5"},{"c","6"}},  //escaped quote
    }
  ;

  // Save actual output
  vector<map<string, string>> output_observed;

  // Read stream
  csvstream csvin(iss);
  map<string, string> row;
  try {
    while (csvin >> row) {
      output_observed.push_back(row);
    }
  } catch(csvstream_exception e) {
    cout << e.what() << endl;
    assert(0);
  }

  // Check output
  assert(output_observed == output_correct);
}

void test_multiline_quotes() {
  // Test with input data containing a quoted field containing newlines

  stringstream iss("a,b\n\"hello\nworld\",\"b\"\n");

  const vector<map<string, string>> output_correct =
    {
      {{"a", "hello\nworld"},{"b", "b"}}
    }
  ;

  vector<map<string, string>> output_observed;

  csvstream csvin(iss);
  map<string, string> row;
  try {
    while (csvin >> row) {
      output_observed.push_back(row);
    }
  } catch(csvstream_exception e) {
    cout << e.what() << endl;
    assert(0);
  }

  // Check output
  assert(output_observed == output_correct);
}

void test_osx_line_endings() {
  // Test with input data containing OSX line endings: \r

  // Input
  stringstream iss("a,b,c\r1,2,3\r,,\r");

  // Correct answer
  const vector<map<string, string>> output_correct =
    {
      {{"a","1"},{"b","2"},{"c","3"}},
      {{"a",""},{"b",""},{"c",""}},
    }
  ;

  // Save actual output
  vector<map<string, string>> output_observed;

  // Read stream
  csvstream csvin(iss);
  map<string, string> row;
  try {
    while (csvin >> row) {
      output_observed.push_back(row);
    }
  } catch(csvstream_exception e) {
    cout << e.what() << endl;
    assert(0);
  }

  // Check output
  assert(output_observed == output_correct);
}

void test_windows_line_endings() {
  // Test with input data containing Windows line endings: \r\n

  // Input
  stringstream iss("a,b,c\r\n1,2,3\r\n,,\r\n");

  // Correct answer
  const vector<map<string, string>> output_correct =
    {
      {{"a","1"},{"b","2"},{"c","3"}},
      {{"a",""},{"b",""},{"c",""}},
    }
  ;

  // Save actual output
  vector<map<string, string>> output_observed;

  // Read stream
  csvstream csvin(iss);
  map<string, string> row;
  try {
    while (csvin >> row) {
      output_observed.push_back(row);
    }
  } catch(csvstream_exception e) {
    cout << e.what() << endl;
    assert(0);
  }

  // Check output
  assert(output_observed == output_correct);
}

void test_ordered() {
  // Test ordered stream extraction operator.  Store data in a vector instead
  // of a map.

  // Input
  stringstream iss("b,a,c\n2,1,3\n");

  // Correct answer
  const vector<vector<pair<string, string>>> output_correct =
    {
      {{"b","2"},{"a","1"},{"c","3"}},
    }
  ;

  // Save actual output
  vector<vector<pair<string, string>>> output_observed;

  // Read stream
  csvstream csvin(iss);
  vector<pair<string, string>> row;
  try {
    while (csvin >> row) {
      output_observed.push_back(row);
    }
  } catch(csvstream_exception e) {
    cout << e.what() << endl;
    assert(0);
  }

  // Check output
  assert(output_observed == output_correct);
}

void test_strict_notsctrict() {
  // Test with same input for strict and not strict modes.

  // Correct answer
  const vector<vector<pair<string, string>>> output_correct =
    {
      {{"a","1"},{"b","2"},{"c","3"}},
      {{"a","4"},{"b","5"},{"c","6"}},
    }
  ;
  for (auto strict : { false, true }) {
    // Input
    stringstream iss("a,b,c\n1,2,3\n4,5,6");

    // Save actual output
    vector<vector<pair<string, string>>> output_observed;

    // Read stream
    csvstream csvin(iss, ',', strict);
    vector<pair<string, string>> row;
    try {
      while (csvin >> row) {
        output_observed.push_back(row);
      }
    } catch(csvstream_exception e) {
      cout << e.what() << endl;
      assert(0);
    }

    // Check output
    assert(output_observed == output_correct);
  }
}

void test_notstrict_exceptions() {
  // Test for exceptions in not strict mode.

  // Invalid input strings
  vector<string> input_strings = {
      "\"a,b,c\n1,2,3", // no closing bracket
      ",,,\n1,2,3", // empty headers
      "\n\n\n\n" // string with endings only
      "a,b\n1,2,3\n1", // lines have different size
      "a,b,c\n,,,,,\n,,\n,\n\n", // empty values
    }
  ;

  for (auto &input_string : input_strings) {
    // Input
    stringstream iss(input_string);

    // Read stream
    csvstream csvin(iss, ',', false);
    vector<pair<string, string>> row;
    try {
      while (csvin >> row);
    } catch(csvstream_exception e) {
      cout << e.what() << endl;
      // If exception was caught, it didn't work
      assert(0);
    }
  }
}
<commit_msg>testcase nit: replace parametrized tests with copy pasta.  Easier to read for a unit test.<commit_after>/* csvstream_test.cpp
 *
 * Andrew DeOrio <awdeorio@umich.edu>
 *
 * An easy-to-use CSV file parser for C++
 * https://github.com/awdeorio/csvstream
 */

#include "csvstream.h"
#include <iostream>
#include <fstream>
#include <string>
#include <map>
#include <vector>
using namespace std;

void test_filename_ctor();
void test_stream_ctor();
void test_getheader();
void test_emptyfields();
void test_tsv();
void test_too_few_cols_in_the_middle_strict();
void test_too_few_cols_in_the_middle_notstrict();
void test_too_few_cols_at_the_end_strict();
void test_too_few_cols_at_the_end_notstrict();
void test_too_many_cols_strict();
void test_too_many_cols_notstrict();
void test_no_newline_at_the_end();
void test_quotes();
void test_escape_quotes();
void test_multiline_quotes();
void test_osx_line_endings();
void test_windows_line_endings();
void test_strict_notsctrict();
void test_notstrict_exceptions();

int main() {
  test_filename_ctor();
  test_stream_ctor();
  test_getheader();
  test_emptyfields();
  test_tsv();
  test_too_few_cols_in_the_middle_strict();
  test_too_few_cols_in_the_middle_notstrict();
  test_too_few_cols_at_the_end_strict();
  test_too_few_cols_at_the_end_notstrict();
  test_too_many_cols_strict();
  test_too_many_cols_notstrict();
  test_no_newline_at_the_end();
  test_quotes();
  test_escape_quotes();
  test_multiline_quotes();
  test_osx_line_endings();
  test_windows_line_endings();
  test_strict_notsctrict();
  test_notstrict_exceptions();
  cout << "csvstream_test PASSED\n";
  return 0;
}

// data for next few unit tests
const string input_filename_animals = "csvstream_example.csv";
const vector<string> header_correct_animals = {"name", "animal"};
const vector<map<string, string>> output_correct_animals =
  {
    {{"name","Fergie"},{"animal","horse"}},
    {{"name","Myrtle II"},{"animal","chicken"}},
    {{"name","Oscar"},{"animal","cat"}},
  }
  ;

void test_filename_ctor() {
  // Test creating a csvstream object from a filename string

  // Save actual output
  vector<map<string, string>> output_observed;

  // Read file
  csvstream csvin(input_filename_animals);
  map<string, string> row;
  while (csvin >> row) {
    output_observed.push_back(row);
  }

  // Check output
  assert(output_observed == output_correct_animals);
}

void test_stream_ctor() {
  // Test creating a csvstream object from a stream

  // Save actual output
  vector<map<string, string>> output_observed;

  // Open file
  ifstream fin(input_filename_animals.c_str());
  assert(fin.is_open());

  // Read file from stream
  csvstream csvin(fin);
  map<string, string> row;
  while (csvin >> row) {
    output_observed.push_back(row);
  }

  // Check output
  assert(output_observed == output_correct_animals);

  // Clean up
  fin.close();
}

void test_getheader() {
  // Test reading header from first line of CSV file

  csvstream csvin(input_filename_animals);
  auto header = csvin.getheader();
  assert(header == header_correct_animals);
}

void test_emptyfields() {
  // Test with input data containing empty fields (consecutive commas)

  // Input
  stringstream iss("a,b,c\n,,\n,,\n");

  // Correct answer
  const vector<map<string, string>> output_correct =
    {
      {{"a",""},{"b",""},{"c",""}},
      {{"a",""},{"b",""},{"c",""}},
    }
  ;

  // Save actual output
  vector<map<string, string>> output_observed;

  // Read stream
  csvstream csvin(iss);
  map<string, string> row;
  try {
    while (csvin >> row) {
      output_observed.push_back(row);
    }
  } catch(csvstream_exception e) {
    cout << e.what() << endl;
    assert(0);
  }

  // Check output
  assert(output_observed == output_correct);
}

void test_tsv() {
  // Test with tab separated data

  // Input
  stringstream iss("a\tb\tc\nd\te\tf\n\t\t\n");

  // Correct answer
  const vector<map<string, string>> output_correct =
    {
      {{"a","d"},{"b","e"},{"c","f"}},
      {{"a",""},{"b",""},{"c",""}},
    }
  ;

  // Save actual output
  vector<map<string, string>> output_observed;

  // Read file
  csvstream csvin(iss, '\t');
  map<string, string> row;
  try {
    while (csvin >> row) {
      output_observed.push_back(row);
    }
  } catch(csvstream_exception e) {
    cout << e.what() << endl;
    assert(0);
  }

  // Check output
  assert(output_observed == output_correct);
}

void test_too_few_cols_in_the_middle_strict() {
  // Test error condition: a row in the middle of the file that doesn't have
  // enough fields

  // Input
  stringstream iss("a,b,c\n,\nd,e,f");

  // Create object
  csvstream csvin(iss);

  // Read file
  map<string, string> row;
  try {
    while (csvin >> row); // throw away data
  } catch(csvstream_exception e) {
    //if we caught an exception, then it worked
    return;
  }

  // if we made it this far, then it didn't work
  assert(0);
}

void test_too_few_cols_in_the_middle_notstrict() {
  // Test error condition: a row in the middle of the file that doesn't have
  // enough fields.  When strict=false, this should *not* cause an error.

  // Input
  stringstream iss("a,b,c\n,\nd,e,f");

  // Create object with strict=false
  csvstream csvin(iss, ',', false);

  // Read file
  map<string, string> row;
  try {
    while (csvin >> row); // throw away data
  } catch(csvstream_exception e) {
    // If we caught an exception, then strict=false failed to coerce the number
    // of values.
    assert(0);
  }
}

void test_too_few_cols_at_the_end_strict() {
  // Test error condition: last row doesn't have enough fields

  // Input
  stringstream iss("a,b,c\n,");

  // Create object
  csvstream csvin(iss);

  // Read file
  map<string, string> row;
  try {
    while (csvin >> row); // throw away data
  } catch(csvstream_exception e) {
    //if we caught an exception, then it worked
    return;
  }

  // if we made it this far, then it didn't work
  assert(0);
}

void test_too_few_cols_at_the_end_notstrict() {
  // Test error condition: last row doesn't have enough fields

  // Input
  stringstream iss("a,b,c\n,");

  // Create object with strict=false
  csvstream csvin(iss, ',', false);

  // Read file
  map<string, string> row;
  try {
    while (csvin >> row); // throw away data
  } catch(csvstream_exception e) {
    // If we caught an exception, then strict=false failed to coerce the number
    // of values.
    assert(0);
  }
}

void test_too_many_cols_strict() {
  // Test error condition: row with too many fields

  // Input
  stringstream iss("a,b,c\n,,,");

  // Create object
  csvstream csvin(iss);

  // Read file
  map<string, string> row;
  try {
    while (csvin >> row); // throw away data
  } catch(csvstream_exception e) {
    //if we caught an exception, then it worked
    return;
  }

  // if we made it this far, then it didn't work
  assert(0);
}

void test_too_many_cols_notstrict() {
  // Test error condition: row with too many fields.  When strict=false,
  // this should *not* cause an error.

  // Input
  stringstream iss("a,b,c\n,,,");

  // Create object, with strict=false
  csvstream csvin(iss, ',', false);

  // Read file
  map<string, string> row;
  try {
    while (csvin >> row); // throw away data
  } catch(csvstream_exception e) {
    // If we caught an exception, then strict=false failed to coerce the number
    // of values.
    assert(0);
  }
}

void test_no_newline_at_the_end() {
  // Test with input that has no newline at the end

  // Input
  stringstream iss("a,b,c\n,,");

  // Create object
  csvstream csvin(iss);

  // Correct answer
  const vector<map<string, string>> output_correct =
    {
      {{"a",""},{"b",""},{"c",""}},
    }
  ;

  // Save actual output
  vector<map<string, string>> output_observed;

  // Read stream
  map<string, string> row;
  try {
    while (csvin >> row) {
      output_observed.push_back(row);
    }
  } catch(csvstream_exception e) {
    cout << e.what() << endl;
    assert(0);
  }

  // Check output
  assert(output_observed == output_correct);

}

void test_quotes() {
  // Input
  stringstream iss("\"a\",b,c\n\"1\",2,3\n\"4,44\",5,6\n");

  // Correct answer
  const vector<map<string, string>> output_correct =
    {
      {{"a","1"},{"b","2"},{"c","3"}},
      {{"a","4,44"},{"b","5"},{"c","6"}}, //quoted comma
    }
  ;

  // Save actual output
  vector<map<string, string>> output_observed;

  // Read stream
  csvstream csvin(iss);
  map<string, string> row;
  try {
    while (csvin >> row) {
      output_observed.push_back(row);
    }
  } catch(csvstream_exception e) {
    cout << e.what() << endl;
    assert(0);
  }

  // Check output
  assert(output_observed == output_correct);
}

void test_escape_quotes() {
  // Test with input data containing escaped quotes

  // Input
  stringstream iss("\\\"a\\\",b,c\n\\\"1\\\",2,3\n\"4,\\\"44\",5,6");

  // Correct answer
  const vector<map<string, string>> output_correct =
    {
      {{"\\\"a\\\"","\\\"1\\\""},{"b","2"},{"c","3"}},  //escaped quote
      {{"\\\"a\\\"","4,\\\"44"},{"b","5"},{"c","6"}},  //escaped quote
    }
  ;

  // Save actual output
  vector<map<string, string>> output_observed;

  // Read stream
  csvstream csvin(iss);
  map<string, string> row;
  try {
    while (csvin >> row) {
      output_observed.push_back(row);
    }
  } catch(csvstream_exception e) {
    cout << e.what() << endl;
    assert(0);
  }

  // Check output
  assert(output_observed == output_correct);
}

void test_multiline_quotes() {
  // Test with input data containing a quoted field containing newlines

  stringstream iss("a,b\n\"hello\nworld\",\"b\"\n");

  const vector<map<string, string>> output_correct =
    {
      {{"a", "hello\nworld"},{"b", "b"}}
    }
  ;

  vector<map<string, string>> output_observed;

  csvstream csvin(iss);
  map<string, string> row;
  try {
    while (csvin >> row) {
      output_observed.push_back(row);
    }
  } catch(csvstream_exception e) {
    cout << e.what() << endl;
    assert(0);
  }

  // Check output
  assert(output_observed == output_correct);
}

void test_osx_line_endings() {
  // Test with input data containing OSX line endings: \r

  // Input
  stringstream iss("a,b,c\r1,2,3\r,,\r");

  // Correct answer
  const vector<map<string, string>> output_correct =
    {
      {{"a","1"},{"b","2"},{"c","3"}},
      {{"a",""},{"b",""},{"c",""}},
    }
  ;

  // Save actual output
  vector<map<string, string>> output_observed;

  // Read stream
  csvstream csvin(iss);
  map<string, string> row;
  try {
    while (csvin >> row) {
      output_observed.push_back(row);
    }
  } catch(csvstream_exception e) {
    cout << e.what() << endl;
    assert(0);
  }

  // Check output
  assert(output_observed == output_correct);
}

void test_windows_line_endings() {
  // Test with input data containing Windows line endings: \r\n

  // Input
  stringstream iss("a,b,c\r\n1,2,3\r\n,,\r\n");

  // Correct answer
  const vector<map<string, string>> output_correct =
    {
      {{"a","1"},{"b","2"},{"c","3"}},
      {{"a",""},{"b",""},{"c",""}},
    }
  ;

  // Save actual output
  vector<map<string, string>> output_observed;

  // Read stream
  csvstream csvin(iss);
  map<string, string> row;
  try {
    while (csvin >> row) {
      output_observed.push_back(row);
    }
  } catch(csvstream_exception e) {
    cout << e.what() << endl;
    assert(0);
  }

  // Check output
  assert(output_observed == output_correct);
}

void test_ordered() {
  // Test ordered stream extraction operator.  Store data in a vector instead
  // of a map.

  // Input
  stringstream iss("b,a,c\n2,1,3\n");

  // Correct answer
  const vector<vector<pair<string, string>>> output_correct =
    {
      {{"b","2"},{"a","1"},{"c","3"}},
    }
  ;

  // Save actual output
  vector<vector<pair<string, string>>> output_observed;

  // Read stream
  csvstream csvin(iss);
  vector<pair<string, string>> row;
  try {
    while (csvin >> row) {
      output_observed.push_back(row);
    }
  } catch(csvstream_exception e) {
    cout << e.what() << endl;
    assert(0);
  }

  // Check output
  assert(output_observed == output_correct);
}

void test_strict_notsctrict() {
  // Test with same input for strict and not strict modes.

  // Correct answer
  const vector<vector<pair<string, string>>> output_correct =
    {
      {{"a","1"},{"b","2"},{"c","3"}},
      {{"a","4"},{"b","5"},{"c","6"}},
    }
  ;
  for (auto strict : { false, true }) {
    // Input
    stringstream iss("a,b,c\n1,2,3\n4,5,6");

    // Save actual output
    vector<vector<pair<string, string>>> output_observed;

    // Read stream
    csvstream csvin(iss, ',', strict);
    vector<pair<string, string>> row;
    try {
      while (csvin >> row) {
        output_observed.push_back(row);
      }
    } catch(csvstream_exception e) {
      cout << e.what() << endl;
      assert(0);
    }

    // Check output
    assert(output_observed == output_correct);
  }
}

void test_notstrict_exceptions() {
  // Test for exceptions in not strict mode.

  // Invalid input strings
  vector<string> input_strings = {
      "\"a,b,c\n1,2,3", // no closing bracket
      ",,,\n1,2,3", // empty headers
      "\n\n\n\n" // string with endings only
      "a,b\n1,2,3\n1", // lines have different size
      "a,b,c\n,,,,,\n,,\n,\n\n", // empty values
    }
  ;

  for (auto &input_string : input_strings) {
    // Input
    stringstream iss(input_string);

    // Read stream
    csvstream csvin(iss, ',', false);
    vector<pair<string, string>> row;
    try {
      while (csvin >> row);
    } catch(csvstream_exception e) {
      cout << e.what() << endl;
      // If exception was caught, it didn't work
      assert(0);
    }
  }
}
<|endoftext|>


<commit_before>/*
  Q Light Controller Plus
  webaccessauth.cpp

  Copyright (c) Bartosz Grabias

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0.txt

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

#include <iostream>
#include <QDebug>
#include <QByteArray>
#include <QCryptographicHash>
#include <QFile>
#include <QTextStream>

#include "webaccessauth.h"

#include "qhttprequest.h"
#include "qhttpresponse.h"

#define PASSWORD_HASH_ALGORITHM QCryptographicHash::Algorithm::Sha256

WebAccessAuth::WebAccessAuth(const QString& realm)
    : m_passwords()
    , m_realm(realm)
{
}

bool WebAccessAuth::loadPasswordsFile(const QString& filePath)
{
    QFile file(filePath);

    if(! file.open(QIODevice::OpenModeFlag::ReadOnly))
        return false;

    QTextStream stream(&file);
    QString line;

    while(! (line = stream.readLine()).isNull())
    {
        int colonIndex = line.indexOf(':');

        if(colonIndex == -1)
        {
            qDebug() << "WebAccessAuth::loadPasswordsFile: Skipping invalid line '" << line << "'";
            continue;
        }

        auto username = line.left(colonIndex);
        auto passwordHash = line.mid(colonIndex + 1);

        // Silently skips duplicate usernames due to unique keys in maps
        this->m_passwords.insert(username, passwordHash);
    }

    return true;
}

bool WebAccessAuth::authenticateRequest(const QHttpRequest* req, QHttpResponse* res) const
{
    // Disable authentication when no passwords were added
    if(this->m_passwords.empty())
        return true;

    QString header = QString("Basic realm=\"") + m_realm + QString("\"");
    res->setHeader("WWW-Authenticate", header);

    QString auth = req->header("Authorization");
    // Tell the browser that authorization is required
    if(! auth.startsWith("Basic "))
    {
        this->sendUnauthorizedResponse(res);
        return false;
    }

    auto authentication = QString(QByteArray::fromBase64(auth.right(auth.size() - 6).toUtf8()));
    std::cout << "Got authentication header: " << authentication.toStdString().c_str() << std::endl;
    int colonIndex = authentication.indexOf(':');

    if(colonIndex == -1)
    {   // Technically this should be 400 Bad Request, but 401 Unauthorized is fine
        this->sendUnauthorizedResponse(res);
        return false;
    }

    auto username = authentication.left(colonIndex);
    auto passwordHash = this->hashPassword(authentication.mid(colonIndex + 1));

    auto passwordHashIterator = m_passwords.find(username);
    if(passwordHashIterator == m_passwords.end() || *passwordHashIterator != passwordHash)
    {
        this->sendUnauthorizedResponse(res);
        return false;
    }

    return true;
}

void WebAccessAuth::sendUnauthorizedResponse(QHttpResponse* res) const
{
    const static QByteArray text = QString(
        ""
            ""
                "<meta charset=\"utf-8\">"
                "<title>Unauthorized</title>"
            ""
            ""
                "
401 Unauthorized
"
                "
Access to this resource requires proper authorization"
                "and you have failed to authenticate.
"
            ""
        ""
    ).toUtf8();

    res->setHeader("Content-Type", "text/html");
    res->setHeader("Content-Length", QString::number(text.size()));
    res->writeHead(401);
    res->end(text);
}

QString WebAccessAuth::hashPassword(const QString& password) const
{
    std::cout << "Hashing password: '" << password.toStdString().c_str() << "'" << std::endl;
    return QCryptographicHash::hash(password.toUtf8(), PASSWORD_HASH_ALGORITHM).toHex();
}<commit_msg>Tidying up debug stuff.<commit_after>/*
  Q Light Controller Plus
  webaccessauth.cpp

  Copyright (c) Bartosz Grabias

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0.txt

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

#include <QDebug>
#include <QByteArray>
#include <QCryptographicHash>
#include <QFile>
#include <QTextStream>

#include "webaccessauth.h"

#include "qhttprequest.h"
#include "qhttpresponse.h"

#define PASSWORD_HASH_ALGORITHM QCryptographicHash::Algorithm::Sha256

WebAccessAuth::WebAccessAuth(const QString& realm)
    : m_passwords()
    , m_realm(realm)
{
}

bool WebAccessAuth::loadPasswordsFile(const QString& filePath)
{
    QFile file(filePath);

    if(! file.open(QIODevice::OpenModeFlag::ReadOnly))
        return false;

    QTextStream stream(&file);
    QString line;

    while(! (line = stream.readLine()).isNull())
    {
        int colonIndex = line.indexOf(':');

        if(colonIndex == -1)
        {
            qDebug() << "Skipping invalid line '" << line << "'";
            continue;
        }

        auto username = line.left(colonIndex);
        auto passwordHash = line.mid(colonIndex + 1);

        // Silently skips duplicate usernames due to unique keys in maps
        this->m_passwords.insert(username, passwordHash);
    }

    return true;
}

bool WebAccessAuth::authenticateRequest(const QHttpRequest* req, QHttpResponse* res) const
{
    // Disable authentication when no passwords were added
    if(this->m_passwords.empty())
        return true;

    QString header = QString("Basic realm=\"") + m_realm + QString("\"");
    res->setHeader("WWW-Authenticate", header);

    QString auth = req->header("Authorization");
    // Tell the browser that authorization is required
    if(! auth.startsWith("Basic "))
    {
        this->sendUnauthorizedResponse(res);
        return false;
    }

    auto authentication = QString(QByteArray::fromBase64(auth.right(auth.size() - 6).toUtf8()));
    std::cout << "Got authentication header: " << authentication.toStdString().c_str() << std::endl;
    int colonIndex = authentication.indexOf(':');

    // Disallow empty passwords
    if(colonIndex == -1)
    {
        this->sendUnauthorizedResponse(res);
        return false;
    }

    auto username = authentication.left(colonIndex);
    auto passwordHash = this->hashPassword(authentication.mid(colonIndex + 1));

    auto passwordHashIterator = m_passwords.find(username);
    if(passwordHashIterator == m_passwords.end() || *passwordHashIterator != passwordHash)
    {
        this->sendUnauthorizedResponse(res);
        return false;
    }

    return true;
}

void WebAccessAuth::sendUnauthorizedResponse(QHttpResponse* res) const
{
    const static QByteArray text = QString(
        ""
            ""
                "<meta charset=\"utf-8\">"
                "<title>Unauthorized</title>"
            ""
            ""
                "
401 Unauthorized
"
                "
Access to this resource requires proper authorization"
                "and you have failed to authenticate.
"
            ""
        ""
    ).toUtf8();

    res->setHeader("Content-Type", "text/html");
    res->setHeader("Content-Length", QString::number(text.size()));
    res->writeHead(401);
    res->end(text);
}

QString WebAccessAuth::hashPassword(const QString& password) const
{
    return QCryptographicHash::hash(password.toUtf8(), PASSWORD_HASH_ALGORITHM).toHex();
}<|endoftext|>


<commit_before>/**
 * \file
 *
 * \brief Plugin which acts as proxy and calls other plugins written in python
 *
 * \copyright BSD License (see doc/COPYING or http://www.libelektra.org)
 *
 */

#ifndef SWIG_TYPE_TABLE
# error Build system error, SWIG_TYPE_TABLE is not defined
#endif

#include <Python.h>

#ifndef HAVE_KDBCONFIG
# include <kdbconfig.h>
#endif
#include <kdbhelper.h>
#include SWIG_RUNTIME
#include "python.hpp"

#include <key.hpp>
#include <keyset.hpp>
#include <libgen.h>
#include <pthread.h>
#include <iostream>

using namespace ckdb;
#include <kdberrors.h>

#define __stringify(x) __stringify2(x)
#define __stringify2(x) #x
#define PYTHON_PLUGIN_NAME_STR __stringify(PYTHON_PLUGIN_NAME)

static PyObject *Python_fromSWIG(ckdb::Key *key)
{
	swig_type_info *ti = SWIG_TypeQuery("kdb::Key *");
	if (key == NULL || ti == NULL)
		return Py_None;
	return SWIG_NewPointerObj(new kdb::Key(key), ti, 0);
}

static PyObject *Python_fromSWIG(ckdb::KeySet *keyset)
{
	swig_type_info *ti = SWIG_TypeQuery("kdb::KeySet *");
	if (keyset == NULL || ti == NULL)
		return Py_None;
	return SWIG_NewPointerObj(new kdb::KeySet(keyset), ti, 0);
}

extern "C"
{

typedef struct
{
	PyObject *instance;
	int printError;
	int shutdown;
} moduleData;

/* pythons repr() - a little debug helper - misses all Py_DECREF calls! */
#define Python_Repr(obj) \
	PyBytes_AS_STRING( \
		PyUnicode_AsEncodedString(PyObject_Repr(obj), "utf-8", "Error ~") \
	)

static PyObject *Python_ImportModule(const char *name)
{
	if (!name)
		return NULL;

	PyGILState_STATE gstate = PyGILState_Ensure();
	PyObject *module = PyImport_ImportModule(name);
	PyGILState_Release(gstate);
	return module;
}

static PyObject *Python_CallFunction(PyObject *object, PyObject *args)
{
	PyGILState_STATE gstate = PyGILState_Ensure();

	if (!PyCallable_Check(object))
	{
		PyGILState_Release(gstate);
		return NULL;
	}

	PyObject *res = PyObject_CallObject(object, args ? args : PyTuple_New (0));
	PyGILState_Release(gstate);
	Py_XINCREF(res);
	return res;
}

static int Python_CallFunction_Int(moduleData *data, PyObject *object,
		PyObject *args, ckdb::Key *errorKey)
{
	int ret = -1;
	PyGILState_STATE gstate = PyGILState_Ensure();

	PyObject *res = Python_CallFunction(object, args);
	if (!res)
	{
		ELEKTRA_SET_ERROR(111, errorKey,
				"Error while calling python function");
		if (data->printError)
			PyErr_Print();
	}
	else
	{
#if PY_MAJOR_VERSION >= 3
		if (!PyLong_Check(res))
#else
		if (!PyInt_Check(res))
#endif
			ELEKTRA_SET_ERROR(111, errorKey,
					"Error: return value is no integer");
		else
			ret = PyLong_AsLong(res);
	}

	PyGILState_Release(gstate);
	Py_XDECREF(res);
	return ret;
}

static int Python_CallFunction_Helper1(moduleData *data, const char *funcName,
	ckdb::Key *errorKey)
{
	int ret = 0;
	PyGILState_STATE gstate = PyGILState_Ensure();
	PyObject *func = PyObject_GetAttrString(data->instance, funcName);
	if (func)
	{
		PyObject *arg0 = Python_fromSWIG(errorKey);
		PyObject *args = Py_BuildValue("(O)", arg0);
		ret = Python_CallFunction_Int(data, func, args, errorKey);
		Py_DECREF(arg0);
		Py_DECREF(args);
		Py_DECREF(func);
	}
	PyGILState_Release(gstate);
	return ret;
}

static int Python_CallFunction_Helper2(moduleData *data, const char *funcName,
	ckdb::KeySet *returned, ckdb::Key *parentKey)
{
	int ret = 0;
	PyGILState_STATE gstate = PyGILState_Ensure();
	PyObject *func = PyObject_GetAttrString(data->instance, funcName);
	if (func)
	{
		PyObject *arg0 = Python_fromSWIG(returned);
		PyObject *arg1 = Python_fromSWIG(parentKey);
		PyObject *args = Py_BuildValue("(OO)", arg0, arg1);
		ret = Python_CallFunction_Int(data, func, args, parentKey);
		Py_DECREF(arg0);
		Py_DECREF(arg1);
		Py_DECREF(args);
		Py_DECREF(func);
	}
	PyGILState_Release(gstate);
	return ret;
}

static int Python_AppendToSysPath(const char *path)
{
	if (path == NULL)
		return 0;

	PyGILState_STATE gstate = PyGILState_Ensure();
	PyObject *sysPath = PySys_GetObject((char *)"path");
	PyObject *pyPath  = PyUnicode_FromString(path);
	PyList_Append(sysPath, pyPath);
	Py_DECREF(pyPath);
	PyGILState_Release(gstate);
	return 1;
}

static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static unsigned open_cnt = 0;

int PYTHON_PLUGIN_FUNCTION(Open)(ckdb::Plugin *handle, ckdb::Key *errorKey)
{
	/* store modules */
	moduleData *data = new moduleData;
	data->instance   = NULL;
	data->printError = 0;
	data->shutdown   = 0;
	elektraPluginSetData(handle, data);

	KeySet *config = elektraPluginGetConfig(handle);
	data->printError = (ksLookupByName(config, "/print", 0) != NULL);
	data->shutdown   = (ksLookupByName(config, "/shutdown", 0) != NULL);

	Key *script = ksLookupByName(config, "/script", 0);
	if (script == NULL)
		return 0; // success if no script to execute
	if (keyString(script) == NULL)
	{
		ELEKTRA_SET_ERROR(111, errorKey, "No python script set");
		return -1;
	}

	/* initialize python interpreter - only once */
	pthread_mutex_lock(&mutex);
	if (!Py_IsInitialized())
	{
		Py_Initialize();
		PyEval_InitThreads();
		if (!Py_IsInitialized())
		{
			pthread_mutex_unlock(&mutex);
			return -1;
		}
	}
	open_cnt++;
	pthread_mutex_unlock(&mutex);

	/* import kdb */
	PyObject *kdbModule = Python_ImportModule("kdb");
	if (kdbModule == NULL)
	{
		ELEKTRA_SET_ERROR(111, errorKey, "Unable to import kdb module");
		if (data->printError)
			PyErr_Print();
		return -1;
	}
	Py_XDECREF(kdbModule);

	/* extend sys path */
	char *tmpScript = elektraStrDup(keyString(script));
	const char *dname = dirname(tmpScript);
	if (!Python_AppendToSysPath(dname))
	{
		ELEKTRA_SET_ERROR(111, errorKey, "Unable to extend sys.path");
		elektraFree(tmpScript);
		return -1;
	}
	elektraFree(tmpScript);

	/* import module/script */
	tmpScript = elektraStrDup(keyString(script));
	char *bname = basename(tmpScript);
	size_t bname_len = strlen(bname);
	if (bname_len >= 4 && strcmp(bname + bname_len - 3, ".py") == 0)
		bname[bname_len - 3] = '\0';

	PyObject *pModule = Python_ImportModule(bname);
	if (pModule == NULL)
	{
		ELEKTRA_SET_ERRORF(111, errorKey,"Unable to import python script %s",
				keyString(script));
		if (data->printError)
			PyErr_Print();
		elektraFree(tmpScript);
		return -1;
	}
	elektraFree(tmpScript);

	/* get class */
	PyGILState_STATE gstate = PyGILState_Ensure();
	PyObject *klass = PyObject_GetAttrString(pModule, "ElektraPlugin");
	Py_DECREF(pModule);
	PyGILState_Release(gstate);
	if (klass == NULL)
	{
		ELEKTRA_SET_ERROR(111, errorKey,
				"Module doesn't provide a ElektraPlugin class");
		if (data->printError)
			PyErr_Print();
		return -1;
	}

	/* create instance of class */
	gstate = PyGILState_Ensure();
	PyObject *inst_args = Py_BuildValue("()");
	PyObject *inst = PyEval_CallObject(klass, inst_args);
	Py_DECREF(klass);
	Py_DECREF(inst_args);
	PyGILState_Release(gstate);
	if (inst == NULL)
	{
		ELEKTRA_SET_ERROR(111, errorKey,
				"Unable to create instance of ElektraPlugin");
		if (data->printError)
			PyErr_Print();
		return -1;
	}
	data->instance = inst;

	/* call python function */
	return Python_CallFunction_Helper1(data, "open", errorKey);
}

int PYTHON_PLUGIN_FUNCTION(Close)(ckdb::Plugin *handle, ckdb::Key *errorKey)
{
	moduleData *data = static_cast<moduleData *>(elektraPluginGetData(handle));
	if (data == NULL)
		return 0;

	/* call python function */
	int ret = 0;
	if (data->instance != NULL)
	{
		PyGILState_STATE gstate = PyGILState_Ensure();
		ret = Python_CallFunction_Helper1(data, "close", errorKey);

		/* clean up references */
		Py_DECREF(data->instance);
		data->instance = NULL;
		PyGILState_Release(gstate);
	}

	/* destroy python if plugin isn't used anymore */
	//FIXME python reinitialization is known to be buggy
	pthread_mutex_lock(&mutex);
	open_cnt--;
	if (!open_cnt && data->shutdown && Py_IsInitialized())
		Py_Finalize();
	pthread_mutex_unlock(&mutex);

	delete data;
	return ret;
}

int PYTHON_PLUGIN_FUNCTION(Get)(ckdb::Plugin *handle, ckdb::KeySet *returned,
	ckdb::Key *parentKey)
{
#define _MODULE_CONFIG_PATH "system/elektra/modules/" PYTHON_PLUGIN_NAME_STR
	if (!strcmp(keyName(parentKey), _MODULE_CONFIG_PATH))
	{
		KeySet *n;
		ksAppend(returned, n = ksNew(30,
			keyNew(_MODULE_CONFIG_PATH,
				KEY_VALUE, "python interpreter waits for your orders", KEY_END),
			keyNew(_MODULE_CONFIG_PATH "/exports", KEY_END),
			keyNew(_MODULE_CONFIG_PATH "/exports/get",
				KEY_FUNC, PYTHON_PLUGIN_FUNCTION(Get),
				KEY_END),
			keyNew(_MODULE_CONFIG_PATH "/exports/set",
				KEY_FUNC, PYTHON_PLUGIN_FUNCTION(Set),
				KEY_END),
			keyNew(_MODULE_CONFIG_PATH "/exports/error",
				KEY_FUNC, PYTHON_PLUGIN_FUNCTION(Error),
				KEY_END),
			keyNew(_MODULE_CONFIG_PATH "/exports/open",
				KEY_FUNC, PYTHON_PLUGIN_FUNCTION(Open),
				KEY_END),
			keyNew(_MODULE_CONFIG_PATH "/exports/close",
				KEY_FUNC, PYTHON_PLUGIN_FUNCTION(Close),
				KEY_END),
#define __readme_header(x) __readme_header2(x)
#define __readme_header2(x) __stringify(readme_##x.c)
#include __readme_header(PYTHON_PLUGIN_NAME)
			keyNew(_MODULE_CONFIG_PATH "/infos/version",
				KEY_VALUE, PLUGINVERSION, KEY_END),
			KS_END));
		ksDel(n);
	}

	moduleData *data = static_cast<moduleData *>(elektraPluginGetData(handle));
	if (data != NULL && data->instance != NULL)
		return Python_CallFunction_Helper2(data, "get", returned,
				parentKey);
	return 0;
}

int PYTHON_PLUGIN_FUNCTION(Set)(ckdb::Plugin *handle, ckdb::KeySet *returned,
	ckdb::Key *parentKey)
{
	moduleData *data = static_cast<moduleData *>(elektraPluginGetData(handle));
	if (data != NULL && data->instance != NULL)
		return Python_CallFunction_Helper2(data, "set", returned,
				parentKey);
	return 0;
}

int PYTHON_PLUGIN_FUNCTION(Error)(ckdb::Plugin *handle, ckdb::KeySet *returned,
	ckdb::Key *parentKey)
{
	moduleData *data = static_cast<moduleData *>(elektraPluginGetData(handle));
	if (data != NULL && data->instance != NULL)
		return Python_CallFunction_Helper2(data, "error", returned,
				parentKey);
	return 0;
}

ckdb::Plugin *PYTHON_PLUGIN_EXPORT(PYTHON_PLUGIN_NAME)
{
	return elektraPluginExport(PYTHON_PLUGIN_NAME_STR,
		ELEKTRA_PLUGIN_OPEN,  &PYTHON_PLUGIN_FUNCTION(Open),
		ELEKTRA_PLUGIN_CLOSE, &PYTHON_PLUGIN_FUNCTION(Close),
		ELEKTRA_PLUGIN_GET,   &PYTHON_PLUGIN_FUNCTION(Get),
		ELEKTRA_PLUGIN_SET,   &PYTHON_PLUGIN_FUNCTION(Set),
		ELEKTRA_PLUGIN_ERROR, &PYTHON_PLUGIN_FUNCTION(Error),
		ELEKTRA_PLUGIN_END);
}
}
<commit_msg>python: fix small memleak if plugin cannot be initialized<commit_after>/**
 * \file
 *
 * \brief Plugin which acts as proxy and calls other plugins written in python
 *
 * \copyright BSD License (see doc/COPYING or http://www.libelektra.org)
 *
 */

#ifndef SWIG_TYPE_TABLE
# error Build system error, SWIG_TYPE_TABLE is not defined
#endif

#include <Python.h>

#ifndef HAVE_KDBCONFIG
# include <kdbconfig.h>
#endif
#include <kdbhelper.h>
#include SWIG_RUNTIME
#include "python.hpp"

#include <key.hpp>
#include <keyset.hpp>
#include <libgen.h>
#include <pthread.h>
#include <iostream>

using namespace ckdb;
#include <kdberrors.h>

#define __stringify(x) __stringify2(x)
#define __stringify2(x) #x
#define PYTHON_PLUGIN_NAME_STR __stringify(PYTHON_PLUGIN_NAME)

static PyObject *Python_fromSWIG(ckdb::Key *key)
{
	swig_type_info *ti = SWIG_TypeQuery("kdb::Key *");
	if (key == NULL || ti == NULL)
		return Py_None;
	return SWIG_NewPointerObj(new kdb::Key(key), ti, 0);
}

static PyObject *Python_fromSWIG(ckdb::KeySet *keyset)
{
	swig_type_info *ti = SWIG_TypeQuery("kdb::KeySet *");
	if (keyset == NULL || ti == NULL)
		return Py_None;
	return SWIG_NewPointerObj(new kdb::KeySet(keyset), ti, 0);
}

extern "C"
{

typedef struct
{
	PyObject *instance;
	int printError;
	int shutdown;
} moduleData;

/* pythons repr() - a little debug helper - misses all Py_DECREF calls! */
#define Python_Repr(obj) \
	PyBytes_AS_STRING( \
		PyUnicode_AsEncodedString(PyObject_Repr(obj), "utf-8", "Error ~") \
	)

static PyObject *Python_ImportModule(const char *name)
{
	if (!name)
		return NULL;

	PyGILState_STATE gstate = PyGILState_Ensure();
	PyObject *module = PyImport_ImportModule(name);
	PyGILState_Release(gstate);
	return module;
}

static PyObject *Python_CallFunction(PyObject *object, PyObject *args)
{
	PyGILState_STATE gstate = PyGILState_Ensure();

	if (!PyCallable_Check(object))
	{
		PyGILState_Release(gstate);
		return NULL;
	}

	PyObject *res = PyObject_CallObject(object, args ? args : PyTuple_New (0));
	PyGILState_Release(gstate);
	Py_XINCREF(res);
	return res;
}

static int Python_CallFunction_Int(moduleData *data, PyObject *object,
		PyObject *args, ckdb::Key *errorKey)
{
	int ret = -1;
	PyGILState_STATE gstate = PyGILState_Ensure();

	PyObject *res = Python_CallFunction(object, args);
	if (!res)
	{
		ELEKTRA_SET_ERROR(111, errorKey,
				"Error while calling python function");
		if (data->printError)
			PyErr_Print();
	}
	else
	{
#if PY_MAJOR_VERSION >= 3
		if (!PyLong_Check(res))
#else
		if (!PyInt_Check(res))
#endif
			ELEKTRA_SET_ERROR(111, errorKey,
					"Error: return value is no integer");
		else
			ret = PyLong_AsLong(res);
	}

	PyGILState_Release(gstate);
	Py_XDECREF(res);
	return ret;
}

static int Python_CallFunction_Helper1(moduleData *data, const char *funcName,
	ckdb::Key *errorKey)
{
	int ret = 0;
	PyGILState_STATE gstate = PyGILState_Ensure();
	PyObject *func = PyObject_GetAttrString(data->instance, funcName);
	if (func)
	{
		PyObject *arg0 = Python_fromSWIG(errorKey);
		PyObject *args = Py_BuildValue("(O)", arg0);
		ret = Python_CallFunction_Int(data, func, args, errorKey);
		Py_DECREF(arg0);
		Py_DECREF(args);
		Py_DECREF(func);
	}
	PyGILState_Release(gstate);
	return ret;
}

static int Python_CallFunction_Helper2(moduleData *data, const char *funcName,
	ckdb::KeySet *returned, ckdb::Key *parentKey)
{
	int ret = 0;
	PyGILState_STATE gstate = PyGILState_Ensure();
	PyObject *func = PyObject_GetAttrString(data->instance, funcName);
	if (func)
	{
		PyObject *arg0 = Python_fromSWIG(returned);
		PyObject *arg1 = Python_fromSWIG(parentKey);
		PyObject *args = Py_BuildValue("(OO)", arg0, arg1);
		ret = Python_CallFunction_Int(data, func, args, parentKey);
		Py_DECREF(arg0);
		Py_DECREF(arg1);
		Py_DECREF(args);
		Py_DECREF(func);
	}
	PyGILState_Release(gstate);
	return ret;
}

static int Python_AppendToSysPath(const char *path)
{
	if (path == NULL)
		return 0;

	PyGILState_STATE gstate = PyGILState_Ensure();
	PyObject *sysPath = PySys_GetObject((char *)"path");
	PyObject *pyPath  = PyUnicode_FromString(path);
	PyList_Append(sysPath, pyPath);
	Py_DECREF(pyPath);
	PyGILState_Release(gstate);
	return 1;
}

static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static unsigned open_cnt = 0;

static void Python_Shutdown(moduleData *data)
{
	/* destroy python if plugin isn't used anymore */
	//FIXME python reinitialization is known to be buggy
	pthread_mutex_lock(&mutex);
	if (data->shutdown && Py_IsInitialized())
	{
		if (!--open_cnt)
			Py_Finalize();
	}
	pthread_mutex_unlock(&mutex);
}

int PYTHON_PLUGIN_FUNCTION(Open)(ckdb::Plugin *handle, ckdb::Key *errorKey)
{
	KeySet *config = elektraPluginGetConfig(handle);
	Key *script = ksLookupByName(config, "/script", 0);
	if (script == NULL)
		return 0; // success if no script to execute

	if (keyString(script) == NULL)
	{
		ELEKTRA_SET_ERROR(111, errorKey, "No python script set");
		return -1;
	}

	/* create module data */
	moduleData *data = new moduleData;
	data->instance   = NULL;
	data->printError = (ksLookupByName(config, "/print", 0) != NULL);
	data->shutdown   = (ksLookupByName(config, "/shutdown", 0) != NULL);

	{
		/* initialize python interpreter - only once */
		pthread_mutex_lock(&mutex);
		if (!Py_IsInitialized())
		{
			Py_Initialize();
			PyEval_InitThreads();
			if (!Py_IsInitialized())
			{
				pthread_mutex_unlock(&mutex);
				goto error;
			}
		}
		open_cnt++;
		pthread_mutex_unlock(&mutex);

		/* import kdb */
		PyObject *kdbModule = Python_ImportModule("kdb");
		if (kdbModule == NULL)
		{
			ELEKTRA_SET_ERROR(111, errorKey, "Unable to import kdb module");
			goto error_print;
		}
		Py_XDECREF(kdbModule);

		/* extend sys path */
		char *tmpScript = elektraStrDup(keyString(script));
		const char *dname = dirname(tmpScript);
		if (!Python_AppendToSysPath(dname))
		{
			ELEKTRA_SET_ERROR(111, errorKey, "Unable to extend sys.path");
			elektraFree(tmpScript);
			goto error;
		}
		elektraFree(tmpScript);

		/* import module/script */
		tmpScript = elektraStrDup(keyString(script));
		char *bname = basename(tmpScript);
		size_t bname_len = strlen(bname);
		if (bname_len >= 4 && strcmp(bname + bname_len - 3, ".py") == 0)
			bname[bname_len - 3] = '\0';

		PyObject *pModule = Python_ImportModule(bname);
		if (pModule == NULL)
		{
			ELEKTRA_SET_ERRORF(111, errorKey,"Unable to import python script %s",
					keyString(script));
			elektraFree(tmpScript);
			goto error_print;
		}
		elektraFree(tmpScript);

		/* get class */
		PyGILState_STATE gstate = PyGILState_Ensure();
		PyObject *klass = PyObject_GetAttrString(pModule, "ElektraPlugin");
		Py_DECREF(pModule);
		PyGILState_Release(gstate);
		if (klass == NULL)
		{
			ELEKTRA_SET_ERROR(111, errorKey,
					"Module doesn't provide a ElektraPlugin class");
			goto error_print;
		}

		/* create instance of class */
		gstate = PyGILState_Ensure();
		PyObject *inst_args = Py_BuildValue("()");
		PyObject *inst = PyEval_CallObject(klass, inst_args);
		Py_DECREF(klass);
		Py_DECREF(inst_args);
		PyGILState_Release(gstate);
		if (inst == NULL)
		{
			ELEKTRA_SET_ERROR(111, errorKey,
					"Unable to create instance of ElektraPlugin");
			goto error_print;
		}
		data->instance = inst;
	}

	/* store module data after everything is set up */
	elektraPluginSetData(handle, data);

	/* call python function */
	return Python_CallFunction_Helper1(data, "open", errorKey);

error_print:
	if (data->printError)
		PyErr_Print();
error:
	/* destroy python */
	Python_Shutdown(data);
	delete data;
	return -1;
}

int PYTHON_PLUGIN_FUNCTION(Close)(ckdb::Plugin *handle, ckdb::Key *errorKey)
{
	moduleData *data = static_cast<moduleData *>(elektraPluginGetData(handle));
	if (data == NULL)
		return 0;

	/* call python function */
	int ret = 0;
	if (data != NULL)
	{
		PyGILState_STATE gstate = PyGILState_Ensure();
		ret = Python_CallFunction_Helper1(data, "close", errorKey);

		/* clean up references */
		Py_DECREF(data->instance);
		data->instance = NULL;
		PyGILState_Release(gstate);
	}

	/* destroy python */
	Python_Shutdown(data);
	delete data;
	return ret;
}

int PYTHON_PLUGIN_FUNCTION(Get)(ckdb::Plugin *handle, ckdb::KeySet *returned,
	ckdb::Key *parentKey)
{
#define _MODULE_CONFIG_PATH "system/elektra/modules/" PYTHON_PLUGIN_NAME_STR
	if (!strcmp(keyName(parentKey), _MODULE_CONFIG_PATH))
	{
		KeySet *n;
		ksAppend(returned, n = ksNew(30,
			keyNew(_MODULE_CONFIG_PATH,
				KEY_VALUE, "python interpreter waits for your orders", KEY_END),
			keyNew(_MODULE_CONFIG_PATH "/exports", KEY_END),
			keyNew(_MODULE_CONFIG_PATH "/exports/get",
				KEY_FUNC, PYTHON_PLUGIN_FUNCTION(Get),
				KEY_END),
			keyNew(_MODULE_CONFIG_PATH "/exports/set",
				KEY_FUNC, PYTHON_PLUGIN_FUNCTION(Set),
				KEY_END),
			keyNew(_MODULE_CONFIG_PATH "/exports/error",
				KEY_FUNC, PYTHON_PLUGIN_FUNCTION(Error),
				KEY_END),
			keyNew(_MODULE_CONFIG_PATH "/exports/open",
				KEY_FUNC, PYTHON_PLUGIN_FUNCTION(Open),
				KEY_END),
			keyNew(_MODULE_CONFIG_PATH "/exports/close",
				KEY_FUNC, PYTHON_PLUGIN_FUNCTION(Close),
				KEY_END),
#define __readme_header(x) __readme_header2(x)
#define __readme_header2(x) __stringify(readme_##x.c)
#include __readme_header(PYTHON_PLUGIN_NAME)
			keyNew(_MODULE_CONFIG_PATH "/infos/version",
				KEY_VALUE, PLUGINVERSION, KEY_END),
			KS_END));
		ksDel(n);
	}

	moduleData *data = static_cast<moduleData *>(elektraPluginGetData(handle));
	if (data != NULL)
		return Python_CallFunction_Helper2(data, "get", returned,
				parentKey);
	return 0;
}

int PYTHON_PLUGIN_FUNCTION(Set)(ckdb::Plugin *handle, ckdb::KeySet *returned,
	ckdb::Key *parentKey)
{
	moduleData *data = static_cast<moduleData *>(elektraPluginGetData(handle));
	if (data != NULL)
		return Python_CallFunction_Helper2(data, "set", returned,
				parentKey);
	return 0;
}

int PYTHON_PLUGIN_FUNCTION(Error)(ckdb::Plugin *handle, ckdb::KeySet *returned,
	ckdb::Key *parentKey)
{
	moduleData *data = static_cast<moduleData *>(elektraPluginGetData(handle));
	if (data != NULL)
		return Python_CallFunction_Helper2(data, "error", returned,
				parentKey);
	return 0;
}

ckdb::Plugin *PYTHON_PLUGIN_EXPORT(PYTHON_PLUGIN_NAME)
{
	return elektraPluginExport(PYTHON_PLUGIN_NAME_STR,
		ELEKTRA_PLUGIN_OPEN,  &PYTHON_PLUGIN_FUNCTION(Open),
		ELEKTRA_PLUGIN_CLOSE, &PYTHON_PLUGIN_FUNCTION(Close),
		ELEKTRA_PLUGIN_GET,   &PYTHON_PLUGIN_FUNCTION(Get),
		ELEKTRA_PLUGIN_SET,   &PYTHON_PLUGIN_FUNCTION(Set),
		ELEKTRA_PLUGIN_ERROR, &PYTHON_PLUGIN_FUNCTION(Error),
		ELEKTRA_PLUGIN_END);
}
}
<|endoftext|>