Permalink
Browse files

BUG: Fix passing module paths containing single quote

When generating executable Python code, special characters (such as backslash and quote) must be escaped otherwise the generated code may be invalid.

Added qSlicerCorePythonManager::toPythonStringLiteral method to create Python string literals from string values.

Fixes https://issues.slicer.org/view.php?id=4592

git-svn-id: http://svn.slicer.org/Slicer4/trunk@27424 3bd1e089-480b-0410-8dfb-8563597acbee
  • Loading branch information...
lassoan
lassoan committed Sep 22, 2018
1 parent 4fe5569 commit e40af17987ff03931930d3c9ba198a0738f37f86
@@ -247,6 +247,12 @@ slicer_add_python_unittest(
TESTNAME_PREFIX nomainwindow_
)
slicer_add_python_unittest(
SCRIPT ${Slicer_SOURCE_DIR}/Base/Python/tests/test_PythonManager.py
SLICER_ARGS --no-main-window --disable-modules
TESTNAME_PREFIX nomainwindow_
)
slicer_add_python_unittest(
SCRIPT ${Slicer_SOURCE_DIR}/Base/Python/slicer/tests/test_slicer_util_save.py
SLICER_ARGS --no-main-window --disable-cli-modules --disable-scripted-loadable-modules DATA{${INPUT}/MR-head.nrrd}
@@ -0,0 +1,32 @@
import slicer
import unittest
class PythonManagerTests(unittest.TestCase):
def setUp(self):
pass
def test_toPythonStringLiteral(self):
"""toPythonStringLiteral method is used for creating string literals
that can be used in constructing executable Python code. Test here
that the method works well for special characters (quotes and backslashes)
by using the generated string literal to set a variable.
"""
test_strings = [
"test with a ' single quote",
'test with a " double quote',
'test with both single \' and double " quotes',
'test backslash \\ and \'single\' and "double" quotes'
"'test string in single quotes'"
'"test string in double quotes"' ]
for test_string in test_strings:
test_string_literal = slicer.app.pythonManager().toPythonStringLiteral(test_string)
exec("test_string_literal_value = "+test_string_literal)
print("Test: "+test_string+" -> "+test_string_literal+" -> "+test_string_literal_value)
self.assertEqual(test_string, test_string_literal_value)
def tearDown(self):
pass
@@ -34,6 +34,7 @@ class qSlicerCorePythonManagerWithoutApplicationTester: public QObject
private slots:
void testInitialize();
void toPythonStringLiteral();
};
// ----------------------------------------------------------------------------
@@ -42,6 +43,18 @@ void qSlicerCorePythonManagerWithoutApplicationTester::testInitialize()
this->PythonManager.initialize();
}
// ----------------------------------------------------------------------------
void qSlicerCorePythonManagerWithoutApplicationTester::toPythonStringLiteral()
{
QCOMPARE(this->PythonManager.toPythonStringLiteral("simple string"), "'simple string'");
QCOMPARE(this->PythonManager.toPythonStringLiteral("C:\\folder1\\folder2"), "'C:\\\\folder1\\\\folder2'");
QCOMPARE(this->PythonManager.toPythonStringLiteral("C:/folder1/folder2"), "'C:/folder1/folder2'");
QCOMPARE(this->PythonManager.toPythonStringLiteral("this \"special\" string contains double-quotes"), "'this \"special\" string contains double-quotes'");
QCOMPARE(this->PythonManager.toPythonStringLiteral("this name O'Neil contains a single-quote"), "'this name O\\'Neil contains a single-quote'");
QCOMPARE(this->PythonManager.toPythonStringLiteral("'single-quoted string'"), "'\\\'single-quoted string\\\''");
QCOMPARE(this->PythonManager.toPythonStringLiteral("\"double-quoted string\""), "'\"double-quoted string\"'");
}
// ----------------------------------------------------------------------------
CTK_TEST_MAIN(qSlicerCorePythonManagerWithoutApplicationTest)
#include "moc_qSlicerCorePythonManagerWithoutApplicationTest.cxx"
@@ -110,7 +110,7 @@ void qSlicerCorePythonManager::addVTKObjectToPythonMain(const QString& name, vtk
void qSlicerCorePythonManager::appendPythonPath(const QString& path)
{
// TODO Make sure PYTHONPATH is updated
this->executeString(QString("import sys; sys.path.append('%1'); del sys").arg(path));
this->executeString(QString("import sys; sys.path.append(%1); del sys").arg(this->toPythonStringLiteral(path)));
}
//-----------------------------------------------------------------------------
@@ -121,3 +121,12 @@ void qSlicerCorePythonManager::appendPythonPaths(const QStringList& paths)
this->appendPythonPath(path);
}
}
//-----------------------------------------------------------------------------
QString qSlicerCorePythonManager::toPythonStringLiteral(QString path)
{
path = path.replace("\\", "\\\\");
path = path.replace("'", "\\'");
// since we enclose string in single quotes, double-quotes do not require escaping
return "'" + path + "'";
}
@@ -34,25 +34,39 @@ class Q_SLICER_BASE_QTCORE_EXPORT qSlicerCorePythonManager : public ctkAbstractP
{
Q_OBJECT
/// List of directories containing Python modules.
Q_PROPERTY(QStringList pythonPaths READ pythonPaths)
public:
typedef ctkAbstractPythonManager Superclass;
qSlicerCorePythonManager(QObject* parent=0);
~qSlicerCorePythonManager();
/// Convenient function allowing to add a VTK object to the interpreter main module
void addVTKObjectToPythonMain(const QString& name, vtkObject * object);
Q_INVOKABLE void addVTKObjectToPythonMain(const QString& name, vtkObject * object);
/// Append \a path to \a sys.path
/// \todo Add these methods to ctkAbstractPythonManager
/// \sa appendPythonPaths
void appendPythonPath(const QString& path);
Q_INVOKABLE void appendPythonPath(const QString& path);
/// Append \a paths to \a sys.path
void appendPythonPaths(const QStringList& paths);
Q_INVOKABLE void appendPythonPaths(const QStringList& paths);
/// List of directories containing Python modules.
virtual QStringList pythonPaths();
/// Convert a string to a safe python string literal.
/// Backslash, single-quote characters are escaped
/// and the string is enclosed between single quotes.
///
/// Examples:
/// some simple string => 'some simple string'
/// some " string => 'some " string'
/// some other ' string => 'some other \' string'
/// some backslash \ str => 'some backslash \\ str'
Q_INVOKABLE virtual QString toPythonStringLiteral(QString path);
protected:
virtual void preInitialization();
@@ -26,25 +26,32 @@
// PythonQt includes
#include <PythonQt.h>
// SlicerQt includes
#include "qSlicerCoreApplication.h"
#include "qSlicerCorePythonManager.h"
//-----------------------------------------------------------------------------
bool qSlicerScriptedUtils::loadSourceAsModule(const QString& moduleName,
const QString& fileName,
PyObject * global_dict,
PyObject * local_dict)
{
qSlicerCorePythonManager * pythonManager = qSlicerCoreApplication::application()->corePythonManager();
PyObject* pyRes = 0;
if (fileName.endsWith(".py"))
{
pyRes = PyRun_String(
QString("import imp;imp.load_source('%2', '%1');del imp;")
.arg(fileName).arg(moduleName).toLatin1(),
QString("import imp;imp.load_source(%2, %1);del imp;")
.arg(pythonManager->toPythonStringLiteral(fileName))
.arg(pythonManager->toPythonStringLiteral(moduleName)).toLatin1(),
Py_file_input, global_dict, local_dict);
}
else if (fileName.endsWith(".pyc"))
{
pyRes = PyRun_String(
QString("with open('%1', 'rb') as f:import imp;imp.load_module('%2', f, '%1', ('.pyc', 'rb', 2));del imp")
.arg(fileName).arg(moduleName).toLatin1(),
QString("with open(%1, 'rb') as f:import imp;imp.load_module(%2, f, %1, ('.pyc', 'rb', 2));del imp")
.arg(pythonManager->toPythonStringLiteral(fileName))
.arg(pythonManager->toPythonStringLiteral(moduleName)).toLatin1(),
Py_file_input, global_dict, local_dict);
}
if (!pyRes)
@@ -87,14 +87,14 @@ bool qSlicerLoadableModule::importModulePythonExtensions(
pythonManager->executeString(QString(
"from slicer.util import importVTKClassesFromDirectory;"
"importVTKClassesFromDirectory('%1', 'slicer', filematch='vtkSlicer*ModuleLogicPython.*');"
"importVTKClassesFromDirectory('%1', 'slicer', filematch='vtkSlicer*ModuleMRMLPython.*');"
"importVTKClassesFromDirectory('%1', 'slicer', filematch='vtkSlicer*ModuleMRMLDisplayableManagerPython.*');"
).arg(scopedCurrentDir.currentPath()));
"importVTKClassesFromDirectory(%1, 'slicer', filematch='vtkSlicer*ModuleLogicPython.*');"
"importVTKClassesFromDirectory(%1, 'slicer', filematch='vtkSlicer*ModuleMRMLPython.*');"
"importVTKClassesFromDirectory(%1, 'slicer', filematch='vtkSlicer*ModuleMRMLDisplayableManagerPython.*');"
).arg(pythonManager->toPythonStringLiteral(scopedCurrentDir.currentPath())));
pythonManager->executeString(QString(
"from slicer.util import importQtClassesFromDirectory;"
"importQtClassesFromDirectory('%1', 'slicer', filematch='qSlicer*PythonQt.*');"
).arg(scopedCurrentDir.currentPath()));
"importQtClassesFromDirectory(%1, 'slicer', filematch='qSlicer*PythonQt.*');"
).arg(pythonManager->toPythonStringLiteral(scopedCurrentDir.currentPath())));
return !pythonManager->pythonErrorOccured();
#else
Q_UNUSED(isEmbedded);
@@ -40,7 +40,8 @@ function(add_slicer_extensions_index_test testname)
set(test_binary_dir ${CMAKE_CURRENT_BINARY_DIR}/${testname}-build)
set(testcase "SlicerExtensionBuildSystemTest.SlicerExtensionBuildSystemTest.test_index_${testname}")
set(code "import sys; sys.path.append('${Slicer_BINARY_DIR}/bin/Python/slicer');")
# single quotes around append were replaced from ' to \" because path may contain a single quote character
set(code "import sys; sys.path.append(\"${Slicer_BINARY_DIR}/bin/Python/slicer\");")
set(code "${code}import testing;")
set(code "${code}testing.runUnitTest(['${CMAKE_CURRENT_BINARY_DIR}', '${CMAKE_CURRENT_SOURCE_DIR}'], '${testcase}')")

0 comments on commit e40af17

Please sign in to comment.