From a5a1c332e23a094e0c2a1c9513fb9f6b05cbbf05 Mon Sep 17 00:00:00 2001 From: "Jan Alexander Steffens (heftig)" Date: Sat, 28 Jan 2017 01:19:06 +0100 Subject: [PATCH] Simplify Server's python setup --- runtime/BrowserWindow.cpp | 6 +- runtime/ConfigWindow.cpp | 16 ++- runtime/ConfigWindow.h | 6 +- runtime/ConfigWindow.ui | 22 +++- runtime/Server.cpp | 253 +++++++++++----------------------------------- runtime/Server.h | 11 +- runtime/pgAdmin4.cpp | 106 ++++++++++--------- 7 files changed, 149 insertions(+), 271 deletions(-) diff --git a/runtime/BrowserWindow.cpp b/runtime/BrowserWindow.cpp index 516b8689818bddc9..a2ee4895f5351565 100644 --- a/runtime/BrowserWindow.cpp +++ b/runtime/BrowserWindow.cpp @@ -1286,17 +1286,17 @@ void BrowserWindow::preferences() ConfigWindow *dlg = new ConfigWindow(); dlg->setWindowTitle(QWidget::tr("Configuration")); - dlg->setPythonPath(settings.value("PythonPath").toString()); + dlg->setPythonExecutable(settings.value("PythonExecutable").toString()); dlg->setApplicationPath(settings.value("ApplicationPath").toString()); dlg->setModal(true); ok = dlg->exec(); - QString pythonpath = dlg->getPythonPath(); + QString pythonexecutable = dlg->getPythonExecutable(); QString applicationpath = dlg->getApplicationPath(); if (ok) { - settings.setValue("PythonPath", pythonpath); + settings.setValue("PythonExecutable", pythonexecutable); settings.setValue("ApplicationPath", applicationpath); } } diff --git a/runtime/ConfigWindow.cpp b/runtime/ConfigWindow.cpp index 3fb1a2738eb89ec0..c31345bf08d06d88 100644 --- a/runtime/ConfigWindow.cpp +++ b/runtime/ConfigWindow.cpp @@ -17,37 +17,45 @@ ConfigWindow::ConfigWindow(QWidget *parent) : ui(new Ui::ConfigWindow) { ui->setupUi(this); + +#ifdef PYTHON2 + ui->pythonExecutableLineEdit->setPlaceholderText( + QString(QWidget::tr("The Python 2 executable to use (may be within a virtual environment)"))); +#else + ui->pythonExecutableLineEdit->setPlaceholderText( + QString(QWidget::tr("The Python 3 executable to use (may be within a virtual environment)"))); +#endif } ConfigWindow::~ConfigWindow() { delete ui; } void ConfigWindow::on_buttonBox_accepted() { this->close(); } void ConfigWindow::on_buttonBox_rejected() { this->close(); } -QString ConfigWindow::getPythonPath() +QString ConfigWindow::getPythonExecutable() { - return ui->pythonPathLineEdit->text(); + return ui->pythonExecutableLineEdit->text(); } QString ConfigWindow::getApplicationPath() { return ui->applicationPathLineEdit->text(); } -void ConfigWindow::setPythonPath(QString path) +void ConfigWindow::setPythonExecutable(QString path) { - ui->pythonPathLineEdit->setText(path); + ui->pythonExecutableLineEdit->setText(path); } void ConfigWindow::setApplicationPath(QString path) diff --git a/runtime/ConfigWindow.h b/runtime/ConfigWindow.h index 0027a742aca7b762..c4487fcc6e97f354 100644 --- a/runtime/ConfigWindow.h +++ b/runtime/ConfigWindow.h @@ -26,19 +26,19 @@ public: explicit ConfigWindow(QWidget *parent = 0); ~ConfigWindow(); - QString getPythonPath(); + QString getPythonExecutable(); QString getApplicationPath(); - void setPythonPath(QString path); + void setPythonExecutable(QString path); void setApplicationPath(QString path); private slots: void on_buttonBox_accepted(); void on_buttonBox_rejected(); private: Ui::ConfigWindow *ui; - QString m_pythonpath, m_applicationpath; + QString m_pythonexecutable, m_applicationpath; }; #endif // CONFIGWINDOW_H diff --git a/runtime/ConfigWindow.ui b/runtime/ConfigWindow.ui index 40d87be43803204a..8b379052a1da8cd0 100644 --- a/runtime/ConfigWindow.ui +++ b/runtime/ConfigWindow.ui @@ -29,24 +29,38 @@ QFormLayout::ExpandingFieldsGrow - + + + + 0 + 1 + + - Python Path + Python Executable - + + + The python executable to use (may be within a virtual environment) + + Application Path - + + + The directory containing pgAdmin4.py (do not include the file name) + + diff --git a/runtime/Server.cpp b/runtime/Server.cpp index 33b43583480440b7..83bcdeedb09197c8 100644 --- a/runtime/Server.cpp +++ b/runtime/Server.cpp @@ -22,201 +22,85 @@ // App headers #include "Server.h" -static void add_to_path(QString &python_path, QString path, bool prepend=false) -{ - if (!python_path.contains(path)) - { - if (!prepend) - { -#if defined(Q_OS_WIN) - if (!python_path.isEmpty() && !python_path.endsWith(";")) - python_path.append(";"); -#else - if (!python_path.isEmpty() && !python_path.endsWith(":")) - python_path.append(":"); -#endif - - python_path.append(path); - } - else - { -#if defined(Q_OS_WIN) - if (!python_path.isEmpty() && !python_path.startsWith(";")) - python_path.prepend(";"); -#else - if (!python_path.isEmpty() && !python_path.startsWith(":")) - python_path.prepend(":"); -#endif - - python_path.prepend(path); - } - } -} - Server::Server(quint16 port, QString key) { // Appserver port etc m_port = port; m_key = key; - m_wcAppName = NULL; - m_wcPythonHome = NULL; // Initialise Python - Py_NoSiteFlag=1; - Py_DontWriteBytecodeFlag=1; + Py_DontWriteBytecodeFlag = 1; - PGA_APP_NAME_UTF8 = PGA_APP_NAME.toUtf8(); + // Get the application directory + QString appDir = qApp->applicationDirPath(); + + // Build (and canonicalise) the virtual environment path +#ifdef Q_OS_MAC + QFileInfo venvBinPath(appDir + "/../Resources/venv/bin"); +#elif defined(Q_OS_WIN) + QFileInfo venvBinPath(appDir + "/../venv"); +#else + QFileInfo venvBinPath(appDir + "/../venv/bin"); +#endif + QString binPath = venvBinPath.canonicalFilePath(); + + if (binPath.isEmpty()) + { + // Use default Python environment from the config, or lastly, PATH + QSettings settings; + binPath.append(settings.value("PythonExecutable").toString()); + if (binPath.isEmpty()) + binPath.append("python"); + } + else + { + binPath.append("/python"); + } // Python3 requires conversion of char * to wchar_t *, so... #ifdef PYTHON2 - Py_SetProgramName(PGA_APP_NAME_UTF8.data()); + QByteArray appName = binPath.toLocal8Bit(); + m_pyAppName = new char[strlen(appName.data()) + 1]; + strcpy(m_pyAppName, appName.data()); + qDebug() << "Python interpreter: " << QString::fromLocal8Bit(m_pyAppName); #else - char *appName = PGA_APP_NAME_UTF8.data(); - const size_t cSize = strlen(appName)+1; - m_wcAppName = new wchar_t[cSize]; - mbstowcs (m_wcAppName, appName, cSize); - Py_SetProgramName(m_wcAppName); + std::wstring appName = binPath.toStdWString(); + m_pyAppName = new wchar_t[wcslen(appName.c_str()) + 1]; + wcscpy(m_pyAppName, appName.c_str()); + qDebug() << "Python interpreter: " << QString::fromWCharArray(m_pyAppName); #endif - - // Setup the search path - QSettings settings; - QString python_path = settings.value("PythonPath").toString(); - - // Get the application directory - QString app_dir = qApp->applicationDirPath(), - path_env = qgetenv("PATH"), - pythonHome; - QStringList path_list; - int i; - -#ifdef Q_OS_MAC - // In the case we're running in a release appbundle, we need to ensure the - // bundled virtual env is included in the Python path. We include it at the - // end, so expert users can override the path, but we do not save it, because - // if users move the app bundle, we'll end up with dead entries - - // Build (and canonicalise) the virtual environment path - QFileInfo venvBinPath(app_dir + "/../Resources/venv/bin"); - QFileInfo venvLibPath(app_dir + "/../Resources/venv/lib/python"); - QFileInfo venvDynLibPath(app_dir + "/../Resources/venv/lib/python/lib-dynload"); - QFileInfo venvSitePackagesPath(app_dir + "/../Resources/venv/lib/python/site-packages"); - QFileInfo venvPath(app_dir + "/../Resources/venv"); - - // Prepend the bin directory to the path - add_to_path(path_env, venvBinPath.canonicalFilePath(), true); - // Append the path, if it's not already there - add_to_path(python_path, venvLibPath.canonicalFilePath()); - add_to_path(python_path, venvDynLibPath.canonicalFilePath()); - add_to_path(python_path, venvSitePackagesPath.canonicalFilePath()); - add_to_path(pythonHome, venvPath.canonicalFilePath()); -#elif defined(Q_OS_WIN) - - // In the case we're running in a release application, we need to ensure the - // bundled virtual env is included in the Python path. We include it at the - // end, so expert users can override the path, but we do not save it. - - // Build (and canonicalise) the virtual environment path - QFileInfo venvBinPath(app_dir + "/../venv"); - QFileInfo venvLibPath(app_dir + "/../venv/Lib"); - QFileInfo venvDLLsPath(app_dir + "/../venv/DLLs"); - QFileInfo venvSitePackagesPath(app_dir + "/../venv/Lib/site-packages"); - QFileInfo venvPath(app_dir + "/../venv"); - - // Prepend the bin directory to the path - add_to_path(path_env, venvBinPath.canonicalFilePath(), true); - // Append paths, if they're not already there - add_to_path(python_path, venvLibPath.canonicalFilePath()); - add_to_path(python_path, venvDLLsPath.canonicalFilePath()); - add_to_path(python_path, venvSitePackagesPath.canonicalFilePath()); - add_to_path(pythonHome, venvPath.canonicalFilePath()); -#else - // Build (and canonicalise) the virtual environment path - QFileInfo venvBinPath(app_dir + "/../venv/bin"); - QFileInfo venvLibPath(app_dir + "/../venv/lib/python"); - QFileInfo venvDynLibPath(app_dir + "/../venv/lib/python/lib-dynload"); - QFileInfo venvSitePackagesPath(app_dir + "/../venv/lib/python/site-packages"); - QFileInfo venvPath(app_dir + "/../venv"); - - // Prepend the bin directory to the path - add_to_path(path_env, venvBinPath.canonicalFilePath(), true); - // Append the path, if it's not already there - add_to_path(python_path, venvLibPath.canonicalFilePath()); - add_to_path(python_path, venvDynLibPath.canonicalFilePath()); - add_to_path(python_path, venvSitePackagesPath.canonicalFilePath()); - add_to_path(pythonHome, venvPath.canonicalFilePath()); -#endif - - qputenv("PATH", path_env.toUtf8().data()); - - if (python_path.length() > 0) - { - // Split the path setting into individual entries - path_list = python_path.split(";", QString::SkipEmptyParts); - python_path = QString(); - - // Add new additional path elements - for (i = path_list.size() - 1; i >= 0 ; --i) - { - python_path.append(path_list.at(i)); - if (i > 0) - { -#if defined(Q_OS_WIN) - python_path.append(";"); -#else - python_path.append(":"); -#endif - } - } - qputenv("PYTHONPATH", python_path.toUtf8().data()); - } - - qDebug() << "Python path: " << python_path - << "\nPython Home: " << pythonHome; - if (!pythonHome.isEmpty()) - { - pythonHome_utf8 = pythonHome.toUtf8(); -#ifdef PYTHON2 - Py_SetPythonHome(pythonHome_utf8.data()); -#else - char *python_home = pythonHome_utf8.data(); - const size_t cSize = strlen(python_home) + 1; - m_wcPythonHome = new wchar_t[cSize]; - mbstowcs (m_wcPythonHome, python_home, cSize); - - Py_SetPythonHome(m_wcPythonHome); -#endif - } + Py_SetProgramName(m_pyAppName); Py_Initialize(); + /* + * Untrusted search path vulnerability in the PySys_SetArgv API function in Python 2.6 and earlier, and possibly later + * versions, prepends an empty string to sys.path when the argv[0] argument does not contain a path separator, + * which might allow local users to execute arbitrary code via a Trojan horse Python file in the current working directory. + * Here we have to set arguments explicitly to python interpreter. Check more details in 'PySys_SetArgv' documentation. + */ + PySys_SetArgvEx(1, &m_pyAppName, 0); + // Get the current path PyObject* sysPath = PySys_GetObject((char*)"path"); - - // Add new additional path elements - for (i = path_list.size() - 1; i >= 0 ; --i) - { + PyObject* sysPathRepr = PyObject_Repr(sysPath); #ifdef PYTHON2 - PyList_Append(sysPath, PyString_FromString(path_list.at(i).toUtf8().data())); + qDebug() << "Python path: " << QString::fromLocal8Bit(PyString_AsString(sysPathRepr)); #else -#if PY_MINOR_VERSION > 2 - PyList_Append(sysPath, PyUnicode_DecodeFSDefault(path_list.at(i).toUtf8().data())); -#else - PyList_Append(sysPath, PyBytes_FromString(path_list.at(i).toUtf8().data())); + PyObject* sysPathBytes = PyUnicode_AsEncodedString(sysPathRepr, "utf8", "replace"); + qDebug() << "Python path: " << QString::fromUtf8(PyBytes_AsString(sysPathBytes)); + Py_DECREF(sysPathBytes); #endif -#endif - } + Py_DECREF(sysPathRepr); } Server::~Server() { - if (m_wcAppName) - delete m_wcAppName; - - if (m_wcPythonHome) - delete m_wcPythonHome; - // Shutdown Python Py_Finalize(); + + if (m_pyAppName) + delete [] m_pyAppName; } bool Server::Init() @@ -264,52 +148,29 @@ bool Server::Init() void Server::run() { // Open the application code and run it. - FILE *cp = fopen(m_appfile.toUtf8().data(), "r"); + QByteArray m_appfile_utf8 = m_appfile.toUtf8(); + FILE *cp = fopen(m_appfile_utf8.data(), "r"); if (!cp) { setError(QString(tr("Failed to open the application file: %1, server thread exiting.")).arg(m_appfile)); return; } // Set the port number PyRun_SimpleString(QString("PGADMIN_PORT = %1").arg(m_port).toLatin1()); PyRun_SimpleString(QString("PGADMIN_KEY = '%1'").arg(m_key).toLatin1()); // Run the app! - QByteArray m_appfile_utf8 = m_appfile.toUtf8(); #ifdef PYTHON2 - /* - * Untrusted search path vulnerability in the PySys_SetArgv API function in Python 2.6 and earlier, and possibly later - * versions, prepends an empty string to sys.path when the argv[0] argument does not contain a path separator, - * which might allow local users to execute arbitrary code via a Trojan horse Python file in the current working directory. - * Here we have to set arguments explicitly to python interpreter. Check more details in 'PySys_SetArgv' documentation. - */ - char* n_argv[] = { m_appfile_utf8.data() }; - PySys_SetArgv(1, n_argv); - PyObject* PyFileObject = PyFile_FromString(m_appfile_utf8.data(), (char *)"r"); int ret = PyRun_SimpleFile(PyFile_AsFile(PyFileObject), m_appfile_utf8.data()); - if (ret != 0) - setError(tr("Failed to launch the application server, server thread exiting.")); #else - /* - * Untrusted search path vulnerability in the PySys_SetArgv API function in Python 2.6 and earlier, and possibly later - * versions, prepends an empty string to sys.path when the argv[0] argument does not contain a path separator, - * which might allow local users to execute arbitrary code via a Trojan horse Python file in the current working directory. - * Here we have to set arguments explicitly to python interpreter. Check more details in 'PySys_SetArgv' documentation. - */ - char *appName = m_appfile_utf8.data(); - const size_t cSize = strlen(appName)+1; - wchar_t* wcAppName = new wchar_t[cSize]; - mbstowcs (wcAppName, appName, cSize); - wchar_t* n_argv[] = { wcAppName }; - PySys_SetArgv(1, n_argv); - int fd = fileno(cp); PyObject* PyFileObject = PyFile_FromFd(fd, m_appfile_utf8.data(), (char *)"r", -1, NULL, NULL,NULL,1); - if (PyRun_SimpleFile(fdopen(PyObject_AsFileDescriptor(PyFileObject),"r"), m_appfile_utf8.data()) != 0) - setError(tr("Failed to launch the application server, server thread exiting.")); + int ret = PyRun_SimpleFile(fdopen(PyObject_AsFileDescriptor(PyFileObject),"r"), m_appfile_utf8.data()); #endif + if (ret != 0) + setError(tr("Failed to launch the application server, server thread exiting.")); fclose(cp); } diff --git a/runtime/Server.h b/runtime/Server.h index 488f9fcedc6d7dc7..207e2f157db7d7a5 100644 --- a/runtime/Server.h +++ b/runtime/Server.h @@ -40,12 +40,11 @@ private: quint16 m_port; QString m_key; - // Application name in UTF-8 for Python - wchar_t *m_wcAppName; - QByteArray PGA_APP_NAME_UTF8; - // PythonHome for Python - wchar_t *m_wcPythonHome; - QByteArray pythonHome_utf8; +#ifdef PYTHON2 + char *m_pyAppName; +#else + wchar_t *m_pyAppName; +#endif }; #endif // SERVER_H diff --git a/runtime/pgAdmin4.cpp b/runtime/pgAdmin4.cpp index a2dbe7451557befc..ebcb4ff4d4899aaf 100644 --- a/runtime/pgAdmin4.cpp +++ b/runtime/pgAdmin4.cpp @@ -228,69 +228,65 @@ int main(int argc, char * argv[]) // Fire up the webserver Server *server; - bool done = false; + server = new Server(port, key); - while (done != true) + if (!server->Init()) { - server = new Server(port, key); + splash->finish(NULL); - if (!server->Init()) + qDebug() << server->getError(); + + QString error = QString(QWidget::tr("An error occurred initialising the application server:\n\n%1")).arg(server->getError()); + QMessageBox::critical(NULL, QString(QWidget::tr("Fatal Error")), error); + + exit(1); + } + + server->start(); + + // This is a hack. Wait a second and then check to see if the server thread + // is still running. If it's not, we probably had a startup error + delay(1000); + + // Any errors? + if (server->isFinished() || server->getError().length() > 0) + { + splash->finish(NULL); + + qDebug() << server->getError(); + + QString error = QString(QWidget::tr("An error occurred initialising the application server:\n\n%1")).arg(server->getError()); + QMessageBox::critical(NULL, QString(QWidget::tr("Fatal Error")), error); + + // Allow the user to tweak the Python Path if needed + QSettings settings; + bool ok; + + ConfigWindow *dlg = new ConfigWindow(); + dlg->setWindowTitle(QWidget::tr("Configuration")); + dlg->setPythonExecutable(settings.value("PythonExecutable").toString()); + dlg->setApplicationPath(settings.value("ApplicationPath").toString()); + dlg->setModal(true); + ok = dlg->exec(); + + QString pythonexecutable = dlg->getPythonExecutable(); + QString applicationpath = dlg->getApplicationPath(); + + if (ok) { - splash->finish(NULL); + settings.setValue("PythonExecutable", pythonexecutable); + settings.setValue("ApplicationPath", applicationpath); + settings.sync(); - qDebug() << server->getError(); + QString msg = QString(QWidget::tr("Please restart the application to allow the changes to take effect.")); + QMessageBox::information(NULL, QString(QWidget::tr("Restart")), msg); - QString error = QString(QWidget::tr("An error occurred initialising the application server:\n\n%1")).arg(server->getError()); - QMessageBox::critical(NULL, QString(QWidget::tr("Fatal Error")), error); - - exit(1); - } - - server->start(); - - // This is a hack. Wait a second and then check to see if the server thread - // is still running. If it's not, we probably had a startup error - delay(1000); - - // Any errors? - if (server->isFinished() || server->getError().length() > 0) - { - splash->finish(NULL); - - qDebug() << server->getError(); - - QString error = QString(QWidget::tr("An error occurred initialising the application server:\n\n%1")).arg(server->getError()); - QMessageBox::critical(NULL, QString(QWidget::tr("Fatal Error")), error); - - // Allow the user to tweak the Python Path if needed - QSettings settings; - bool ok; - - ConfigWindow *dlg = new ConfigWindow(); - dlg->setWindowTitle(QWidget::tr("Configuration")); - dlg->setPythonPath(settings.value("PythonPath").toString()); - dlg->setApplicationPath(settings.value("ApplicationPath").toString()); - dlg->setModal(true); - ok = dlg->exec(); - - QString pythonpath = dlg->getPythonPath(); - QString applicationpath = dlg->getApplicationPath(); - - if (ok) - { - settings.setValue("PythonPath", pythonpath); - settings.setValue("ApplicationPath", applicationpath); - settings.sync(); - } - else - { - exit(1); - } - - delete server; + exit(0); } else - done = true; + { + exit(1); + } } -- 2.13.0