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
3 changes: 2 additions & 1 deletion gui/wxpython/modules/extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import wx

from grass.script import task as gtask
from grass.script import find_addon_name

from core import globalvar
from core.gcmd import GError, RunCommand, GException, GMessage
Expand Down Expand Up @@ -477,7 +478,7 @@ def _getSelectedExtensions(self):
GMessage(_("No extension selected. Operation canceled."), parent=self)
return []

return eList
return find_addon_name(addons=eList)

def OnUninstall(self, event):
"""Uninstall selected extensions"""
Expand Down
73 changes: 73 additions & 0 deletions python/grass/script/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"""

import os
import re
import sys
import atexit
import subprocess
Expand All @@ -32,10 +33,14 @@
import io
from tempfile import NamedTemporaryFile
from pathlib import Path
from urllib.parse import urlparse

import xml.etree.ElementTree as etree

Check notice

Code scanning / Bandit

Using xml.etree.ElementTree to parse untrusted XML data is known to be vulnerable to XML attacks. Replace xml.etree.ElementTree with the equivalent defusedxml package, or make sure defusedxml.defuse_stdlib() is called.

Using xml.etree.ElementTree to parse untrusted XML data is known to be vulnerable to XML attacks. Replace xml.etree.ElementTree with the equivalent defusedxml package, or make sure defusedxml.defuse_stdlib() is called.

from .utils import KeyValue, parse_key_val, basename, encode, decode, try_remove
from grass.exceptions import ScriptError, CalledModuleError
from grass.grassdb.manage import resolve_mapset_path
from grass.utils.download import download_file


# subprocess wrapper that uses shell on Windows
Expand Down Expand Up @@ -2054,6 +2059,74 @@ def create_environment(gisdbase, location, mapset, env=None):
return f.name, env


def find_addon_name(addons, url=None):
"""Find correct addon name if addon is a multi-addon
e.g. wx.metadata contains multiple modules g.gui.cswbrowser etc.

Examples:
- for the g.gui.cswbrowser module the wx.metadata addon name is
returned
- for the i.sentinel.download module the i.sentinel addon name is
returned
etc.

:param list addons: list of individual addon modules
:param str url: Addons modules.xml file URL

:return: list of unique simple and multi addons

>>> addons = find_addon_name(
... addons=[
... "g.gui.metadata",
... "g.gui.cswbrowser",
... "db.csw.run",
... "db.csw.harvest",
... "db.csw.admin",
... "v.info.iso",
... "r.info.iso",
... "t.info.iso",
... "db.join",
... ]
... )
>>> addons.sort()
>>> addons
['db.join', 'wx.metadata']
"""
Comment on lines +2078 to +2094
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I understand this example result, why only those 2 addons?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I understand this example result, why only those 2 addons?

wx.metadata is multi-addon (contains g.gui.metadata addon, g.gui.cswbrowser addon and etc.), we need identify multi-addon name which is in this example wx.metadata if user want uninstalling multi-addon (see GUI Manage installed extension dialog list of installed addon after you install some multi-addon e.g. wx.metadata)

if not url:
grass_version = os.getenv("GRASS_VERSION", "unknown")
if grass_version != "unknown":
major, minor, patch = grass_version.split(".")
else:
fatal(_("Unable to get GRASS GIS version."))
url = "https://grass.osgeo.org/addons/grass{major}/modules.xml".format(
major=major,
)
response = download_file(
url=url,
response_format="application/xml",
file_name=os.path.basename(urlparse(url).path),
)
tree = etree.fromstring(response.read())

Check warning

Code scanning / Bandit

Using xml.etree.ElementTree.fromstring to parse untrusted XML data is known to be vulnerable to XML attacks. Replace xml.etree.ElementTree.fromstring with its defusedxml equivalent function or make sure defusedxml.defuse_stdlib() is called

Using xml.etree.ElementTree.fromstring to parse untrusted XML data is known to be vulnerable to XML attacks. Replace xml.etree.ElementTree.fromstring with its defusedxml equivalent function or make sure defusedxml.defuse_stdlib() is called
result = []
for addon in addons:
found = False
addon_pattern = re.compile(r".*{}$".format(addon))
for i in tree:
for f in i.findall(".//binary/file"):
if re.match(addon_pattern, f.text):
result.append(i.attrib["name"])
found = True
break
if not found:
warning(
_(
"The addon <{}> was not found among the official"
" addons.".format(addon)
),
)
return list(set(result))


if __name__ == "__main__":
import doctest

Expand Down
105 changes: 105 additions & 0 deletions python/grass/utils/download.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

"""Download and extract various archives"""

import http
import os
import shutil
import tarfile
Expand All @@ -21,6 +22,12 @@
from urllib.error import HTTPError, URLError
from urllib.parse import urlparse
from urllib.request import urlretrieve
from urllib import request as urlrequest

HEADERS = {
"User-Agent": "Mozilla/5.0",
}
HTTP_STATUS_CODES = list(http.HTTPStatus)


def debug(*args, **kwargs):
Expand Down Expand Up @@ -206,3 +213,101 @@ def name_from_url(url):
# Special treatment of .tar.gz extension.
return os.path.splitext(name)[0]
return name


def urlopen(url, *args, **kwargs):
"""Wrapper around urlopen. Same function as 'urlopen', but with the
ability to define headers.

:param str url: URL

:return: urllib.request.urlopen response object
"""
proxy = kwargs.get("proxy")
if proxy:
del kwargs["proxy"]
PROXIES = {}
for ptype, purl in (p.split("=") for p in proxy.split(",")):
PROXIES[ptype] = purl
proxy = urlrequest.ProxyHandler(PROXIES)
opener = urlrequest.build_opener(proxy)
urlrequest.install_opener(opener)
request = urlrequest.Request(url, headers=HEADERS)
return urlrequest.urlopen(request, *args, **kwargs)

Check warning

Code scanning / Bandit

Audit url open for permitted schemes. Allowing use of file:/ or custom schemes is often unexpected.

Audit url open for permitted schemes. Allowing use of file:/ or custom schemes is often unexpected.


def download_file(url, response_format, file_name, *args, **kwargs):
"""Download file

:param str url: file URL address
:param str response_format: content type of downloaded file
:param str file_name: downloaded file name

:return: urllib.request.urlopen response object

>>> grass_version = os.getenv("GRASS_VERSION", "unknown")
>>> if grass_version != "unknown":
... major, minor, patch = grass_version.split(".")
... url = (
... "https://grass.osgeo.org/addons/grass{}/"
... "modules.xml".format(major)
... )
... response = download_file(
... url=url,
... response_format="application/xml",
... file_name=os.path.basename(urlparse(url).path),
... )
... response.code
200
"""
import grass.script as gs

try:
response = urlopen(url, *args, **kwargs)

if not response.code == 200:
index = HTTP_STATUS_CODES.index(response.code)
desc = HTTP_STATUS_CODES[index].description
gs.fatal(
_(
"The download of the <{file_name}> file "
" from the server <{url}> was not successful"
" return status code {code},"
" {desc}".format(
file_name=file_name,
url=url,
code=response.code,
desc=desc,
),
),
)
if response_format not in response.getheader("Content-Type"):
gs.fatal(
_(
"Wrong downloaded <{file_name}> format."
" Check url <{url}>. Allowed file format is"
" {response_format}.".format(
file_name=file_name,
url=url,
response_format=response_format,
),
),
)
return response
except (HTTPError, URLError) as err:
desc = ""
if hasattr(err, "code"):
index = HTTP_STATUS_CODES.index(err.code)
desc = ", {}".format(HTTP_STATUS_CODES[index].description)
gs.fatal(
_(
"The download of the <{file_name}> file"
" from the server <{url}> was not successful, "
" {err}{desc}.".format(
file_name=file_name,
url=url,
err=err,
desc=desc,
),
),
)
Loading