pgadmin4/runtime/Server.cpp
Ashesh Vashi 7767c085c3 Resolved an issue finding the python interpreter on *nix systems, and
Windows 2008 R2 (32 bit), while running the pgAdmin 4 as runtime for
the PostgreSQL one click installers.

- Found a typo in runtime code, we were appending the path using ';' on
  *nix systems too. We should have used ':', and that did not allow the
  os.environ['PATH'] to identify the correct path of the python
  interpreter under the 'venv' directory.

- On Windows 2008, it was not honouring the environment variables, set
  under the Qt application (e.g. pgAdmin4.exe runtime), in the python
  application. (e.g. pgAdmin4.py). We will need to assume that - the
  python interpreter resides under the 'venv' directory outside the
  'bin' directory.

- Also, on windows 2008, it was setting PYTHONHOME environment variable
  to the full path of the pgAdmin4.exe, we need to reset it to 'venv'
  directory, if we find the python interpreter under it.

Thanks Murtuza Zabuawala for tips, and help.
2017-03-10 21:19:06 +05:30

308 lines
10 KiB
C++

//////////////////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2017, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
// Server.cpp - Thread in which the web server will run.
//
//////////////////////////////////////////////////////////////////////////
#include "pgAdmin4.h"
// Must be before QT
#include <Python.h>
// QT headers
#include <QDebug>
#include <QDir>
#include <QMessageBox>
// 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;
// Initialise Python
Py_NoSiteFlag=1;
Py_DontWriteBytecodeFlag=1;
// Python3 requires conversion of char * to wchar_t *, so...
#ifdef PYTHON2
Py_SetProgramName(PGA_APP_NAME.toUtf8().data());
#else
char *appName = PGA_APP_NAME.toUtf8().data();
const size_t cSize = strlen(appName)+1;
m_wcAppName = new wchar_t[cSize];
mbstowcs (m_wcAppName, appName, cSize);
Py_SetProgramName(m_wcAppName);
#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())
{
#ifdef PYTHON2
Py_SetPythonHome(pythonHome.toUtf8().data());
#else
char *python_home = pythonHome.toUtf8().data();
const size_t cSize = strlen(python_home) + 1;
wchar_t* wcPythonHome = new wchar_t[cSize];
mbstowcs (wcPythonHome, python_home, cSize);
Py_SetPythonHome(wcPythonHome);
#endif
}
Py_Initialize();
// Get the current path
PyObject* sysPath = PySys_GetObject((char*)"path");
// Add new additional path elements
for (i = path_list.size() - 1; i >= 0 ; --i)
{
#ifdef PYTHON2
PyList_Append(sysPath, PyString_FromString(path_list.at(i).toUtf8().data()));
#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()));
#endif
#endif
}
}
Server::~Server()
{
if (m_wcAppName)
delete m_wcAppName;
// Shutdown Python
Py_Finalize();
}
bool Server::Init()
{
QSettings settings;
// Find the webapp
QStringList paths;
paths.append("../web/"); // Linux source tree
paths.append("../../web/"); // Windows source tree
paths.append("../../../../web/"); // Mac source tree (in a dev env)
#ifdef Q_OS_MAC
paths.append("../Resources/web/"); // Mac source tree (in a release app bundle)
#endif
paths.append(settings.value("ApplicationPath").toString()); // System configured value
paths.append(""); // Should be last!
for (int i = 0; i < paths.size(); ++i)
{
QDir dir;
if (paths[i].startsWith('/'))
dir = paths[i];
else
dir = QCoreApplication::applicationDirPath() + "/" + paths[i];
m_appfile = dir.canonicalPath() + "/pgAdmin4.py";
if (QFile::exists(m_appfile))
{
qDebug() << "Webapp path: " << m_appfile;
break;
}
}
if (!QFile::exists(m_appfile))
{
setError(tr("Failed to locate pgAdmin4.py, terminating server thread."));
return false;
}
return true;
}
void Server::run()
{
// Open the application code and run it.
FILE *cp = fopen(m_appfile.toUtf8().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!
#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.toUtf8().data() };
PySys_SetArgv(1, n_argv);
PyObject* PyFileObject = PyFile_FromString(m_appfile.toUtf8().data(), (char *)"r");
int ret = PyRun_SimpleFile(PyFile_AsFile(PyFileObject), m_appfile.toUtf8().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.toUtf8().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.toUtf8().data(), (char *)"r", -1, NULL, NULL,NULL,1);
if (PyRun_SimpleFile(fdopen(PyObject_AsFileDescriptor(PyFileObject),"r"), m_appfile.toUtf8().data()) != 0)
setError(tr("Failed to launch the application server, server thread exiting."));
#endif
fclose(cp);
}