Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 26 additions & 3 deletions pyp2rpm/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def module_to_path(name, module):
if name == module:
return "%{pypi_name}"
else:
return module
return rpm_escape(module)


def package_to_path(package, module):
Expand All @@ -62,7 +62,7 @@ def package_to_path(package, module):
if package == module:
return "%{pypi_name}"
else:
return package
return rpm_escape(package)


def macroed_url(url):
Expand Down Expand Up @@ -126,6 +126,28 @@ def rpm_version(version, use_macro=True):
return '{}~{}'.format(rpm_version, rpm_suffix)


def rpm_escape(text):
"""Escapes RPM directives and macros in text to prevent code injection.

RPM spec files interpret percent signs (%) as the start of macros,
directives, or Lua scriptlets. To prevent malicious package metadata
from injecting arbitrary commands, all percent signs must be escaped
by doubling them (%%).

Args:
text: String that may contain user-controlled content

Returns:
String with all percent signs escaped (% becomes %%)
"""
if text is None:
return ''
if not isinstance(text, str):
text = str(text)
# Escape all percent signs by doubling them
return text.replace('%', '%%')


__all__ = [name_for_python_version,
script_name_for_python_version,
sitedir_for_python_version,
Expand All @@ -135,4 +157,5 @@ def rpm_version(version, use_macro=True):
package_to_path,
macroed_url,
rpm_version_410,
rpm_version]
rpm_version,
rpm_escape]
1 change: 1 addition & 0 deletions pyp2rpm/package_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from pyp2rpm import version
from pyp2rpm import utils
from pyp2rpm import filters

logger = logging.getLogger(__name__)

Expand Down
32 changes: 16 additions & 16 deletions pyp2rpm/templates/epel6.spec
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{{ data.credit_line }}
{% from 'macros.spec' import dependencies, for_python_versions, underscored_or_pypi -%}
%global pypi_name {{ data.name }}
%global pypi_version {{ data.version }}
%global pypi_name {{ data.name|rpm_escape }}
%global pypi_version {{ data.version|rpm_escape }}
{%- if data.srcname %}
%global srcname {{ data.srcname }}
%global srcname {{ data.srcname|rpm_escape }}
{%- endif %}
{%- for pv in data.python_versions %}
%global with_python{{ pv }} 1
Expand All @@ -12,11 +12,11 @@
Name: {{ data.pkg_name|macroed_pkg_name(data.srcname) }}
Version: {{ data.version|rpm_version_410 }}
Release: 1%{?dist}
Summary: {{ data.summary }}
Summary: {{ data.summary|rpm_escape }}

License: {{ data.license }}
URL: {{ data.home_page }}
Source0: {{ data.source0|replace(data.name, '%{pypi_name}')|replace(data.version, '%{pypi_version}') }}
License: {{ data.license|rpm_escape }}
URL: {{ data.home_page|rpm_escape }}
Source0: {{ data.source0|rpm_escape|replace(data.name|rpm_escape, '%{pypi_name}')|replace(data.version|rpm_escape, '%{pypi_version}') }}

{%- if not data.has_extension %}
BuildArch: noarch
Expand All @@ -27,20 +27,20 @@ BuildArch: noarch
{{ dependencies(data.runtime_deps, True, data.base_python_version, data.base_python_version) }}

%description
{{ data.description|truncate(400)|wordwrap }}
{{ data.description|truncate(400)|wordwrap|rpm_escape }}
{% call(pv) for_python_versions(data.python_versions) -%}
%package -n {{ data.pkg_name|macroed_pkg_name(data.srcname)|name_for_python_version(pv) }}
Summary: {{ data.summary }}
Summary: {{ data.summary|rpm_escape }}
{{ dependencies(data.runtime_deps, True, pv, pv) }}

%description -n {{ data.pkg_name|macroed_pkg_name(data.srcname)|name_for_python_version(pv) }}
{{ data.description|truncate(400)|wordwrap }}
{{ data.description|truncate(400)|wordwrap|rpm_escape }}
{%- endcall %}
{%- if data.sphinx_dir %}
%package -n {{ data.pkg_name|macroed_pkg_name(data.srcname)|name_for_python_version(None, True) }}-doc
Summary: {{ data.name }} documentation
Summary: {{ data.name|rpm_escape }} documentation
%description -n {{ data.pkg_name|macroed_pkg_name(data.srcname)|name_for_python_version(None, True) }}-doc
Documentation for {{ data.name }}
Documentation for {{ data.name|rpm_escape }}
{%- endif %}

%prep
Expand All @@ -58,7 +58,7 @@ find python{{pv}} -name '*.py' | xargs sed -i '1s|^#!python|#!%{__python{{pv}}}|
{%- if data.sphinx_dir %}
# generate html docs {# TODO: generate properly for other versions (pushd/popd into their dirs...)
# #}
PYTHONPATH=${PWD} {{ "sphinx-build"|script_name_for_python_version(data.base_python_version, False, False) }} {{ data.sphinx_dir }} html
PYTHONPATH=${PWD} {{ "sphinx-build"|script_name_for_python_version(data.base_python_version, False, False) }} {{ data.sphinx_dir|rpm_escape }} html
# remove the sphinx-build leftovers
rm -rf html/.{doctrees,buildinfo}
{%- endif %}
Expand Down Expand Up @@ -109,11 +109,11 @@ popd
{% call(pv) for_python_versions(data.sorted_python_versions, data.base_python_version) -%}
%files{% if pv != data.base_python_version %} -n {{ data.pkg_name|macroed_pkg_name(data.srcname)|name_for_python_version(pv) }}{% endif %}
{%- if data.doc_files %}
%doc {{ data.doc_files|join(' ') }}
%doc {{ data.doc_files|map('rpm_escape')|join(' ') }}
{%- endif %}
{%- if pv == data.base_python_version %}
{%- for script in data.scripts %}
%{_bindir}/{{ script }}
%{_bindir}/{{ script|rpm_escape }}
{%- endfor %}
{%- endif %}
{%- if data.py_modules %}
Expand Down Expand Up @@ -151,7 +151,7 @@ popd
%files -n {{ data.pkg_name|macroed_pkg_name(data.srcname)|name_for_python_version(pv, True) }}-doc
%doc html
{%- if data.doc_license %}
%license {{data.doc_license|join(' ')}}
%license {{data.doc_license|map('rpm_escape')|join(' ')}}
{%- endif %}
{% endif %}
%changelog
Expand Down
34 changes: 17 additions & 17 deletions pyp2rpm/templates/epel7.spec
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
{{ data.credit_line }}
{% from 'macros.spec' import dependencies, for_python_versions, underscored_or_pypi -%}
%global pypi_name {{ data.name }}
%global pypi_version {{ data.version }}
%global pypi_name {{ data.name|rpm_escape }}
%global pypi_version {{ data.version|rpm_escape }}
{%- if data.srcname %}
%global srcname {{ data.srcname }}
%global srcname {{ data.srcname|rpm_escape }}
{%- endif %}

Name: {{ data.pkg_name|macroed_pkg_name(data.srcname) }}
Version: {{ data.version|rpm_version_410 }}
Release: 1%{?dist}
Summary: {{ data.summary }}
Summary: {{ data.summary|rpm_escape }}

License: {{ data.license }}
URL: {{ data.home_page }}
Source0: {{ data.source0|replace(data.name, '%{pypi_name}')|replace(data.version, '%{pypi_version}') }}
License: {{ data.license|rpm_escape }}
URL: {{ data.home_page|rpm_escape }}
Source0: {{ data.source0|rpm_escape|replace(data.name|rpm_escape, '%{pypi_name}')|replace(data.version|rpm_escape, '%{pypi_version}') }}

{%- if not data.has_extension %}
BuildArch: noarch
Expand All @@ -23,23 +23,23 @@ BuildArch: noarch
{%- endfor %}

%description
{{ data.description|truncate(400)|wordwrap }}
{{ data.description|truncate(400)|wordwrap|rpm_escape }}
{% for pv in data.sorted_python_versions %}
%package -n {{data.pkg_name|macroed_pkg_name(data.srcname)|name_for_python_version(pv, True)}}
Summary: {{ data.summary }}
Summary: {{ data.summary|rpm_escape }}
{{ dependencies(data.runtime_deps, True, pv, pv, use_with=False) }}
%description -n {{data.pkg_name|macroed_pkg_name(data.srcname)|name_for_python_version(pv, True)}}
{{ data.description|truncate(400)|wordwrap }}
{{ data.description|truncate(400)|wordwrap|rpm_escape }}
{% endfor -%}
{%- if data.sphinx_dir %}
%package -n python-%{pypi_name}-doc
Summary: {{ data.name }} documentation
Summary: {{ data.name|rpm_escape }} documentation
%description -n python-%{pypi_name}-doc
Documentation for {{ data.name }}
Documentation for {{ data.name|rpm_escape }}
{%- endif %}

%prep
%autosetup -n {{ data.dirname|replace(data.name, '%{pypi_name}')|replace(data.version, '%{pypi_version}')|default('%{pypi_name}-%{pypi_version}', true) }}
%autosetup -n {{ data.dirname|rpm_escape|replace(data.name|rpm_escape, '%{pypi_name}')|replace(data.version|rpm_escape, '%{pypi_version}')|default('%{pypi_name}-%{pypi_version}', true) }}
{%- if data.has_bundled_egg_info %}
# Remove bundled egg-info
rm -rf %{pypi_name}.egg-info
Expand All @@ -51,7 +51,7 @@ rm -rf %{pypi_name}.egg-info
{%- endfor %}
{%- if data.sphinx_dir %}
# generate html docs
PYTHONPATH=${PWD} {{ "sphinx-build"|script_name_for_python_version(data.base_python_version, True, False) }} {{ data.sphinx_dir }} html
PYTHONPATH=${PWD} {{ "sphinx-build"|script_name_for_python_version(data.base_python_version, True, False) }} {{ data.sphinx_dir|rpm_escape }} html
# remove the sphinx-build leftovers
rm -rf html/.{doctrees,buildinfo}
{%- endif %}
Expand All @@ -77,11 +77,11 @@ rm -rf %{buildroot}%{_bindir}/*
{% for pv in data.sorted_python_versions %}
%files -n {{ data.pkg_name|macroed_pkg_name(data.srcname)|name_for_python_version(pv, True) }}
{%- if data.doc_files %}
%doc {{data.doc_files|join(' ') }}
%doc {{data.doc_files|map('rpm_escape')|join(' ') }}
{%- endif %}
{%- if pv == data.base_python_version %}
{%- for script in data.scripts %}
%{_bindir}/{{ script }}
%{_bindir}/{{ script|rpm_escape }}
{%- endfor %}
{%- endif %}
{%- if data.py_modules %}
Expand Down Expand Up @@ -119,7 +119,7 @@ rm -rf %{buildroot}%{_bindir}/*
%files -n python-%{pypi_name}-doc
%doc html
{%- if data.doc_license %}
%license {{data.doc_license|join(' ')}}
%license {{data.doc_license|map('rpm_escape')|join(' ')}}
{%- endif %}
{% endif %}
%changelog
Expand Down
34 changes: 17 additions & 17 deletions pyp2rpm/templates/fedora.spec
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
{{ data.credit_line }}
{% from 'macros.spec' import dependencies, for_python_versions, underscored_or_pypi, macroed_url -%}
%global pypi_name {{ data.name }}
%global pypi_version {{ data.version }}
%global pypi_name {{ data.name|rpm_escape }}
%global pypi_version {{ data.version|rpm_escape }}
{%- if data.srcname %}
%global srcname {{ data.srcname }}
%global srcname {{ data.srcname|rpm_escape }}
{%- endif %}

Name: {{ data.pkg_name|macroed_pkg_name(data.srcname) }}
Version: {{ data.version|rpm_version }}
Release: 1%{?dist}
Summary: {{ data.summary }}
Summary: {{ data.summary|rpm_escape }}

License: {{ data.license }}
URL: {{ data.home_page }}
Source0: {{ data.source0|replace(data.name, '%{pypi_name}')|replace(data.version, '%{pypi_version}')|macroed_url }}
License: {{ data.license|rpm_escape }}
URL: {{ data.home_page|rpm_escape }}
Source0: {{ data.source0|rpm_escape|replace(data.name|rpm_escape, '%{pypi_name}')|replace(data.version|rpm_escape, '%{pypi_version}')|macroed_url }}

{%- if not data.has_extension %}
BuildArch: noarch
Expand All @@ -23,24 +23,24 @@ BuildArch: noarch
{%- endfor %}

%description
{{ data.description|truncate(400)|wordwrap }}
{{ data.description|truncate(400)|wordwrap|rpm_escape }}
{% for pv in data.sorted_python_versions %}
%package -n {{data.pkg_name|macroed_pkg_name(data.srcname)|name_for_python_version(pv, True) }}
Summary: %{summary}
%{?python_provide:%python_provide {{data.pkg_name|macroed_pkg_name(data.srcname)|name_for_python_version(pv, True)}}}
{{ dependencies(data.runtime_deps, True, pv, pv) }}
%description -n {{data.pkg_name|macroed_pkg_name(data.srcname)|name_for_python_version(pv, True) }}
{{ data.description|truncate(400)|wordwrap }}
{{ data.description|truncate(400)|wordwrap|rpm_escape }}
{% endfor -%}
{%- if data.sphinx_dir %}
%package -n {{ data.pkg_name|macroed_pkg_name(data.srcname)|name_for_python_version(None, True) }}-doc
Summary: {{ data.name }} documentation
Summary: {{ data.name|rpm_escape }} documentation
%description -n {{ data.pkg_name|macroed_pkg_name(data.srcname)|name_for_python_version(None, True) }}-doc
Documentation for {{ data.name }}
Documentation for {{ data.name|rpm_escape }}
{%- endif %}

%prep
%autosetup -n {{ data.dirname|replace(data.name, '%{pypi_name}')|replace(data.version, '%{pypi_version}')|default('%{pypi_name}-%{pypi_version}', true) }}
%autosetup -n {{ data.dirname|rpm_escape|replace(data.name|rpm_escape, '%{pypi_name}')|replace(data.version|rpm_escape, '%{pypi_version}')|default('%{pypi_name}-%{pypi_version}', true) }}
{%- if data.has_bundled_egg_info %}
# Remove bundled egg-info
rm -rf %{pypi_name}.egg-info
Expand All @@ -52,7 +52,7 @@ rm -rf %{pypi_name}.egg-info
{%- endfor %}
{%- if data.sphinx_dir %}
# generate html docs
PYTHONPATH=${PWD} {{ "sphinx-build"|script_name_for_python_version(data.base_python_version, False, True) }} {{ data.sphinx_dir }} html
PYTHONPATH=${PWD} {{ "sphinx-build"|script_name_for_python_version(data.base_python_version, False, True) }} {{ data.sphinx_dir|rpm_escape }} html
# remove the sphinx-build leftovers
rm -rf html/.{doctrees,buildinfo}
{%- endif %}
Expand All @@ -78,14 +78,14 @@ rm -rf %{buildroot}%{_bindir}/*
{% for pv in data.sorted_python_versions %}
%files -n {{ data.pkg_name|macroed_pkg_name(data.srcname)|name_for_python_version(pv, True) }}
{%- if data.doc_license %}
%license {{data.doc_license|join(' ')}}
%license {{data.doc_license|map('rpm_escape')|join(' ')}}
{%- endif %}
{%- if data.doc_files %}
%doc {{data.doc_files|join(' ') }}
%doc {{data.doc_files|map('rpm_escape')|join(' ') }}
{%- endif %}
{%- if pv == data.base_python_version %}
{%- for script in data.scripts %}
%{_bindir}/{{ script }}
%{_bindir}/{{ script|rpm_escape }}
{%- endfor %}
{%- endif %}
{%- if data.py_modules %}
Expand Down Expand Up @@ -122,7 +122,7 @@ rm -rf %{buildroot}%{_bindir}/*
%files -n {{ data.pkg_name|macroed_pkg_name(data.srcname)|name_for_python_version(None, True) }}-doc
%doc html
{%- if data.doc_license %}
%license {{data.doc_license|join(' ')}}
%license {{data.doc_license|map('rpm_escape')|join(' ')}}
{%- endif %}
{% endif %}
%changelog
Expand Down
2 changes: 1 addition & 1 deletion pyp2rpm/templates/macros.spec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{# prints a single dependency for a specific python version #}
{%- macro one_dep(dep, python_version) %}
{{ dep[0] }}:{{ ' ' * (15 - dep[0]|length) }}{{ dep[2].format(name=dep[1]|name_for_python_version(python_version, True)) }}
{{ dep[0] }}:{{ ' ' * (15 - dep[0]|length) }}{{ dep[2].format(name=dep[1]|rpm_escape|name_for_python_version(python_version, True)) }}
{%- endmacro %}

{# Prints given deps (runtime or buildtime for given python_version,
Expand Down
Loading
Loading