From 83c6c308c5c0eb94461be8fa7bd41d70c66faad9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rapha=C3=ABl=20Vinot?=
Date: Mon, 1 Aug 2022 17:51:43 +0200
Subject: [PATCH] new: Initial listing of all resources loaded on the capture
---
lookyloo/lookyloo.py | 12 ++++
poetry.lock | 47 +++++++++------
pyproject.toml | 6 +-
website/web/__init__.py | 66 +++++++++++---------
website/web/sri.txt | 2 +-
website/web/static/generic.css | 1 -
website/web/templates/hostname_popup.html | 7 ++-
website/web/templates/macros.html | 65 +++++++++++---------
website/web/templates/tree.html | 28 +++++++++
website/web/templates/tree_body_hashes.html | 67 +++++++++++++++++++++
10 files changed, 220 insertions(+), 81 deletions(-)
create mode 100644 website/web/templates/tree_body_hashes.html
diff --git a/lookyloo/lookyloo.py b/lookyloo/lookyloo.py
index 28623927..2974f82c 100644
--- a/lookyloo/lookyloo.py
+++ b/lookyloo/lookyloo.py
@@ -570,6 +570,18 @@ class Lookyloo():
break
return details, body_content
+ def get_all_body_hashes(self, capture_uuid: str, /) -> Dict[str, Dict[str, Union[URLNode, int]]]:
+ ct = self.get_crawled_tree(capture_uuid)
+ to_return: Dict[str, Dict[str, Union[URLNode, int]]] = defaultdict()
+ for node in ct.root_hartree.url_tree.traverse():
+ if node.empty_response or node.body_hash in to_return:
+ # If we have the same hash more than once, skip
+ continue
+ total_captures, details = self.indexing.get_body_hash_captures(node.body_hash, limit=-1)
+ # Note for future: mayeb get url, capture title, something better than just the hash to show to the user
+ to_return[node.body_hash] = {'node': node, 'total_captures': total_captures}
+ return to_return
+
def get_latest_url_capture(self, url: str, /) -> Optional[CaptureCache]:
'''Get the most recent capture with this URL'''
captures = self.sorted_capture_cache(self.indexing.get_captures_url(url))
diff --git a/poetry.lock b/poetry.lock
index 3a37e10d..12b20b45 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -403,7 +403,7 @@ tornado = ["tornado (>=0.2)"]
[[package]]
name = "har2tree"
-version = "1.13.2"
+version = "1.13.3"
description = "HTTP Archive (HAR) to ETE Toolkit generator"
category = "main"
optional = false
@@ -421,7 +421,7 @@ six = ">=1.16.0,<2.0.0"
w3lib = ">=1.22.0,<2.0.0"
[package.extras]
-docs = ["Sphinx (>=5.0.2,<6.0.0)"]
+docs = ["Sphinx (>=5.1.1,<6.0.0)"]
[[package]]
name = "hiredis"
@@ -544,7 +544,7 @@ i18n = ["Babel (>=2.7)"]
[[package]]
name = "jsonschema"
-version = "4.8.0"
+version = "4.9.0"
description = "An implementation of JSON Schema validation for Python"
category = "main"
optional = false
@@ -553,6 +553,7 @@ python-versions = ">=3.7"
[package.dependencies]
attrs = ">=17.4.0"
importlib-resources = {version = ">=1.4.0", markers = "python_version < \"3.9\""}
+pkgutil-resolve-name = {version = ">=1.3.10", markers = "python_version < \"3.9\""}
pyrsistent = ">=0.14.0,<0.17.0 || >0.17.0,<0.17.1 || >0.17.1,<0.17.2 || >0.17.2"
[package.extras]
@@ -713,6 +714,14 @@ python-versions = ">=3.7"
docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-issues (>=3.0.1)", "sphinx-removed-in", "sphinxext-opengraph"]
tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"]
+[[package]]
+name = "pkgutil-resolve-name"
+version = "1.3.10"
+description = "Resolve a name to an object."
+category = "main"
+optional = false
+python-versions = ">=3.6"
+
[[package]]
name = "playwright"
version = "1.24.0"
@@ -1192,7 +1201,7 @@ python-versions = "*"
[[package]]
name = "types-redis"
-version = "4.3.11"
+version = "4.3.13"
description = "Typing stubs for redis"
category = "dev"
optional = false
@@ -1200,7 +1209,7 @@ python-versions = "*"
[[package]]
name = "types-requests"
-version = "2.28.5"
+version = "2.28.6"
description = "Typing stubs for requests"
category = "dev"
optional = false
@@ -1219,7 +1228,7 @@ python-versions = "*"
[[package]]
name = "types-urllib3"
-version = "1.26.17"
+version = "1.26.20"
description = "Typing stubs for urllib3"
category = "dev"
optional = false
@@ -1391,7 +1400,7 @@ misp = ["python-magic", "pydeep2"]
[metadata]
lock-version = "1.1"
python-versions = ">=3.8,<3.11"
-content-hash = "73b3a7e37ff853a7edf8632744c175dc933552cfcb7b6fcd9c4bd5ebc1194801"
+content-hash = "44380fea67a49952b9e376628a50dcbe383929de3c6e174ce8b1c1b7c59f2525"
[metadata.files]
aiohttp = [
@@ -1753,8 +1762,8 @@ gunicorn = [
{file = "gunicorn-20.1.0.tar.gz", hash = "sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8"},
]
har2tree = [
- {file = "har2tree-1.13.2-py3-none-any.whl", hash = "sha256:e96db03800fe67b352c0c564c3f58ac6ce2e261fe64d15ec7f4e0b81c3a8edaa"},
- {file = "har2tree-1.13.2.tar.gz", hash = "sha256:c05c0952ae5c9af7d9d01c7362aa23c4060e8a48b8d6daa1f44ce70a1a8cfff3"},
+ {file = "har2tree-1.13.3-py3-none-any.whl", hash = "sha256:1cb921d3dfd9048244edcfc1aa01e5f0884248bf0cc39aafa18165eaed393db9"},
+ {file = "har2tree-1.13.3.tar.gz", hash = "sha256:7481696e9c4f2907d4df391027e4c4ee01b03f4a588a726dc1904035c0d959af"},
]
hiredis = [
{file = "hiredis-2.0.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b4c8b0bc5841e578d5fb32a16e0c305359b987b850a06964bd5a62739d688048"},
@@ -1828,8 +1837,8 @@ jinja2 = [
{file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"},
]
jsonschema = [
- {file = "jsonschema-4.8.0-py3-none-any.whl", hash = "sha256:58bb77251318cef5e1179e33dd6e7a008a3c6c638487ab4d943c2f370cc31a1a"},
- {file = "jsonschema-4.8.0.tar.gz", hash = "sha256:c1d410e379b210ba903bee6adf3fce6d5204cea4c2b622d63f914d2dbfef0993"},
+ {file = "jsonschema-4.9.0-py3-none-any.whl", hash = "sha256:5d0be0cd1b670438b71c3d3145b2abba1f9d197e3e91adc4c4bae4c0e114e252"},
+ {file = "jsonschema-4.9.0.tar.gz", hash = "sha256:df10e65c8f3687a48e93d0d348ce0ce5f897b5a28e9bbcbbe8f7c7eaf019e850"},
]
lief = [
{file = "lief-0.12.1-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:4fbbc9d520de87ac22210c62d22a9b088e5460f9a028741311e6f68ef8877ddd"},
@@ -2147,6 +2156,10 @@ pillow = [
{file = "Pillow-9.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:0030fdbd926fb85844b8b92e2f9449ba89607231d3dd597a21ae72dc7fe26927"},
{file = "Pillow-9.2.0.tar.gz", hash = "sha256:75e636fd3e0fb872693f23ccb8a5ff2cd578801251f3a4f6854c6a5d437d3c04"},
]
+pkgutil-resolve-name = [
+ {file = "pkgutil_resolve_name-1.3.10-py3-none-any.whl", hash = "sha256:ca27cc078d25c5ad71a9de0a7a330146c4e014c2462d9af19c6b828280649c5e"},
+ {file = "pkgutil_resolve_name-1.3.10.tar.gz", hash = "sha256:357d6c9e6a755653cfd78893817c0853af365dd51ec97f3d358a819373bbd174"},
+]
playwright = [
{file = "playwright-1.24.0-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:942c938a8fc8d5daa01cf4e2ca7dfd4cad9216e3e0af88992c083dd324f9210e"},
{file = "playwright-1.24.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fbc74dd91c0c00d282d340b6d08d22010279cbe84052afe0f0854dc1e3a51315"},
@@ -2425,20 +2438,20 @@ types-python-dateutil = [
{file = "types_python_dateutil-2.8.19-py3-none-any.whl", hash = "sha256:6284df1e4783d8fc6e587f0317a81333856b872a6669a282f8a325342bce7fa8"},
]
types-redis = [
- {file = "types-redis-4.3.11.tar.gz", hash = "sha256:83e5633f216c729fb39c5d3f71dd3b0080c651f75b5d4128733878512409c744"},
- {file = "types_redis-4.3.11-py3-none-any.whl", hash = "sha256:ba4612407a758353aa4fe5e1e4b804126e1160dbd6797840223958b1b5dda34d"},
+ {file = "types-redis-4.3.13.tar.gz", hash = "sha256:b8334a96a2f431521bfa72205b343129acdc5a646ffcfb304d80a1cd0deff548"},
+ {file = "types_redis-4.3.13-py3-none-any.whl", hash = "sha256:cc2209ecfab2ad6df1e3eec730c06f9b2dec77f4164eb86e04dad455a651b394"},
]
types-requests = [
- {file = "types-requests-2.28.5.tar.gz", hash = "sha256:ac618bfefcb3742eaf97c961e13e9e5a226e545eda4a3dbe293b898d40933ad1"},
- {file = "types_requests-2.28.5-py3-none-any.whl", hash = "sha256:98ab647ae88b5e2c41d6d20cfcb5117da1bea561110000b6fdeeea07b3e89877"},
+ {file = "types-requests-2.28.6.tar.gz", hash = "sha256:cf3383bbd79394bf051a0a9202d6831fa962f186f923c178f7c059e3424bd00e"},
+ {file = "types_requests-2.28.6-py3-none-any.whl", hash = "sha256:d8d7607419cd4b41a7b9497e15e8c0bad78d50df43c48ad25bc526a11518c3a9"},
]
types-setuptools = [
{file = "types-setuptools-63.2.2.tar.gz", hash = "sha256:a9aa0c01d5f3443cd544026d5ffc97b95ddadf731dab13419c393d43fd8617c0"},
{file = "types_setuptools-63.2.2-py3-none-any.whl", hash = "sha256:a370df7a1e0dc856af9d998234f6e2ab04f30f25b8e1410f6db65910979f6252"},
]
types-urllib3 = [
- {file = "types-urllib3-1.26.17.tar.gz", hash = "sha256:73fd274524c3fc7cd8cd9ceb0cb67ed99b45f9cb2831013e46d50c1451044800"},
- {file = "types_urllib3-1.26.17-py3-none-any.whl", hash = "sha256:0d027fcd27dbb3cb532453b4d977e05bc1e13aefd70519866af211b3003d895d"},
+ {file = "types-urllib3-1.26.20.tar.gz", hash = "sha256:1fb6e2af519a7216a19dd6be8cd2ee787b402a754ccb4a13ca1c0e5b202aea5a"},
+ {file = "types_urllib3-1.26.20-py3-none-any.whl", hash = "sha256:6249b6223226cb2012db3b4ff6945c9cb0e12ece9b24f5e29787c4f05028a979"},
]
types-werkzeug = [
{file = "types-Werkzeug-1.0.9.tar.gz", hash = "sha256:5cc269604c400133d452a40cee6397655f878fc460e03fde291b9e3a5eaa518c"},
diff --git a/pyproject.toml b/pyproject.toml
index 5c03fef3..59ec2edf 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -63,7 +63,7 @@ pyhashlookup = "^1.2.0"
lief = "^0.12.1"
ua-parser = "^0.15.0"
Flask-Login = "^0.6.2"
-har2tree = "^1.13.2"
+har2tree = "^1.13.3"
playwrightcapture = "^1.13.4"
passivetotal = "^2.5.9"
werkzeug = "2.1.2"
@@ -74,8 +74,8 @@ misp = ['python-magic', 'pydeep2']
[tool.poetry.dev-dependencies]
mypy = "^0.971"
ipython = "^8.4.0"
-types-redis = "^4.3.11"
-types-requests = "^2.28.5"
+types-redis = "^4.3.13"
+types-requests = "^2.28.6"
types-Flask = "^1.1.6"
types-pkg-resources = "^0.1.3"
types-Deprecated = "^1.2.9"
diff --git a/website/web/__init__.py b/website/web/__init__.py
index 9a3dc34c..5af59b16 100644
--- a/website/web/__init__.py
+++ b/website/web/__init__.py
@@ -8,7 +8,7 @@ import os
import time
from datetime import date, datetime, timedelta, timezone
from io import BytesIO, StringIO
-from typing import Any, Dict, List, Optional, Union
+from typing import Any, Dict, List, Optional, Union, TypedDict
from urllib.parse import quote_plus, unquote_plus, urlparse
import flask_login # type: ignore
@@ -160,6 +160,38 @@ def get_sri(directory: str, filename: str) -> str:
app.jinja_env.globals.update(get_sri=get_sri)
+class Icon(TypedDict):
+ icon: str
+ tooltip: str
+
+
+def get_icon(icon_id: str) -> Optional[Icon]:
+ available_icons: Dict[str, Icon] = {
+ 'js': {'icon': "javascript.png", 'tooltip': 'The content of the response is a javascript'},
+ 'exe': {'icon': "exe.png", 'tooltip': 'The content of the response is an executable'},
+ 'css': {'icon': "css.png", 'tooltip': 'The content of the response is a CSS'},
+ 'font': {'icon': "font.png", 'tooltip': 'The content of the response is a font'},
+ 'html': {'icon': "html.png", 'tooltip': 'The content of the response is a HTML document'},
+ 'json': {'icon': "json.png", 'tooltip': 'The content of the response is a Json'},
+ 'text': {'icon': "json.png", 'tooltip': 'The content of the response is a text'}, # FIXME: Need new icon
+ 'iframe': {'icon': "ifr.png", 'tooltip': 'This content is loaded from an Iframe'},
+ 'image': {'icon': "img.png", 'tooltip': 'The content of the response is an image'},
+ 'unset_mimetype': {'icon': "wtf.png", 'tooltip': 'The type of content of the response is not set'},
+ 'octet-stream': {'icon': "wtf.png", 'tooltip': 'The type of content of the response is a binary blob'},
+ 'unknown_mimetype': {'icon': "wtf.png", 'tooltip': 'The type of content of the response is of an unknown type'},
+ 'video': {'icon': "video.png", 'tooltip': 'The content of the response is a video'},
+ 'livestream': {'icon': "video.png", 'tooltip': 'The content of the response is a livestream'},
+ 'response_cookie': {'icon': "cookie_received.png", 'tooltip': 'There are cookies in the response'},
+ 'request_cookie': {'icon': "cookie_read.png", 'tooltip': 'There are cookies in the request'},
+ 'redirect': {'icon': "redirect.png", 'tooltip': 'The request is redirected'},
+ 'redirect_to_nothing': {'icon': "cookie_in_url.png", 'tooltip': 'The request is redirected to an URL we do not have in the capture'}
+ }
+ return available_icons.get(icon_id)
+
+
+app.jinja_env.globals.update(get_icon=get_icon)
+
+
# ##### Generic/configuration methods #####
@app.after_request
@@ -201,30 +233,6 @@ def urls_hostnode(tree_uuid: str, node_uuid: str):
@app.route('/tree//host/', methods=['GET'])
def hostnode_popup(tree_uuid: str, node_uuid: str):
- keys_response = {
- 'js': {'icon': "javascript.png", 'tooltip': 'The content of the response is a javascript'},
- 'exe': {'icon': "exe.png", 'tooltip': 'The content of the response is an executable'},
- 'css': {'icon': "css.png", 'tooltip': 'The content of the response is a CSS'},
- 'font': {'icon': "font.png", 'tooltip': 'The content of the response is a font'},
- 'html': {'icon': "html.png", 'tooltip': 'The content of the response is a HTML document'},
- 'json': {'icon': "json.png", 'tooltip': 'The content of the response is a Json'},
- 'text': {'icon': "json.png", 'tooltip': 'The content of the response is a text'}, # FIXME: Need new icon
- 'iframe': {'icon': "ifr.png", 'tooltip': 'This content is loaded from an Iframe'},
- 'image': {'icon': "img.png", 'tooltip': 'The content of the response is an image'},
- 'unset_mimetype': {'icon': "wtf.png", 'tooltip': 'The type of content of the response is not set'},
- 'octet-stream': {'icon': "wtf.png", 'tooltip': 'The type of content of the response is a binary blob'},
- 'unknown_mimetype': {'icon': "wtf.png", 'tooltip': 'The type of content of the response is of an unknown type'},
- 'video': {'icon': "video.png", 'tooltip': 'The content of the response is a video'},
- 'livestream': {'icon': "video.png", 'tooltip': 'The content of the response is a livestream'},
- 'response_cookie': {'icon': "cookie_received.png", 'tooltip': 'There are cookies in the response'},
- # redirect has to be last
- 'redirect': {'icon': "redirect.png", 'tooltip': 'The request is redirected'},
- 'redirect_to_nothing': {'icon': "cookie_in_url.png", 'tooltip': 'The request is redirected to an URL we do not have in the capture'}
- }
- keys_request = {
- 'request_cookie': {'icon': "cookie_read.png", 'tooltip': 'There are cookies in the request'}
- }
-
try:
hostnode, urls = lookyloo.get_hostnode_investigator(tree_uuid, node_uuid)
except IndexError:
@@ -235,8 +243,6 @@ def hostnode_popup(tree_uuid: str, node_uuid: str):
hostnode_uuid=node_uuid,
hostnode=hostnode,
urls=urls,
- keys_response=keys_response,
- keys_request=keys_request,
enable_context_by_users=enable_context_by_users,
uwhois_available=lookyloo.uwhois.available)
@@ -683,6 +689,12 @@ def mark_as_legitimate(tree_uuid: str):
return jsonify({'message': 'Legitimate entry added.'})
+@app.route('/tree//body_hashes', methods=['GET'])
+def tree_body_hashes(tree_uuid: str):
+ body_hashes = lookyloo.get_all_body_hashes(tree_uuid)
+ return render_template('tree_body_hashes.html', tree_uuid=tree_uuid, body_hashes=body_hashes)
+
+
# ##### helpers #####
def index_generic(show_hidden: bool=False, show_error: bool=True, category: Optional[str]=None):
diff --git a/website/web/sri.txt b/website/web/sri.txt
index d8ae2c87..1d25e9e8 100644
--- a/website/web/sri.txt
+++ b/website/web/sri.txt
@@ -17,7 +17,7 @@
"exe.png": "pWwo9nBLtEss/UJ173zHa6/RpySUyz/XMdNhWc6aRIvwwHMO6a+fLmu2K6TbvO3Jbg4VYL2Af4yhHPyhH3ZeTw==",
"favicon.ico": "KOmrfwRbOQqhhwSeBkNpMRAxSVMmmLg+2kRMg9iSv7OWjE9spJc7x4MKB4AE/hi0knaV7UBVctAU6XZ7AC72ZA==",
"font.png": "RwoQkj9dT9SLUL2F7cAA16Nat9t2hDb58eQlHF9ThUar829p0INUXG+5XuDaFOC8SsmCZK5vw2f+YAQ6mLC1Qw==",
- "generic.css": "6pYUMp7DzXI/O531PJ2PIB0/ce0TdIWEOEC4RfpcbMK2SRgKkZfGn12aixsTJEzAkF8Ao0/Bz405v3Bl0f7RUQ==",
+ "generic.css": "Sh/BcxFMLYYaLdCluVt9efGvJ9CF5d+YJ7lkL2M24PRGu8VZHI9lJiUlFObIocjQgwss3Ve2U5cUAE5WiAdpQQ==",
"generic.js": "UmFl4fHmB/UjMdUuYdFy9BfzQlJTyeImNHCFyBO4SdLxBCwCGxkF3NQvel1PKqW8JTnoPlPpq/n9d+vCfPeegA==",
"html.png": "T7pZrb8MMDsA/JV/51hu+TOglTqlxySuEVY0rpDjTuAEyhzk2v+W4kYrj7vX+Tp3n2d2lvVD08PwhCG62Yfbzg==",
"ifr.png": "rI5YJypmz1QcULRf9UaOYSqV4tPUSxUdLAycoYzCwywt4Pw4eWzBg9SUr769VyIimoiIyJR+aNuoIA4p5WO2fQ==",
diff --git a/website/web/static/generic.css b/website/web/static/generic.css
index c7e80c21..70be9227 100644
--- a/website/web/static/generic.css
+++ b/website/web/static/generic.css
@@ -59,7 +59,6 @@ table td p {
font-smooth: auto;
color: white;
background: black;
- z-index: 1;
border: 2px solid;
border-color: white;
padding-top: 2px;
diff --git a/website/web/templates/hostname_popup.html b/website/web/templates/hostname_popup.html
index 53956042..7fa2b52a 100644
--- a/website/web/templates/hostname_popup.html
+++ b/website/web/templates/hostname_popup.html
@@ -3,7 +3,8 @@
{% from "macros.html" import ressource_legitimacy_details %}
{% from "macros.html" import indexed_hash %}
{% from "macros.html" import indexed_cookies %}
-{% from "macros.html" import popup_icons %}
+{% from "macros.html" import popup_icons_request %}
+{% from "macros.html" import popup_icons_response %}
{% from "macros.html" import shorten_string %}
{% from "macros.html" import other_captures_table %}
{% from "macros.html" import get_ressource_button %}
@@ -142,7 +143,7 @@
Request
- {{ popup_icons(keys_request, url['url_object'], tree_uuid) }}
+ {{ popup_icons_request(url['url_object'], tree_uuid) }}
{% if url['url_object'].posted_data %}
@@ -179,7 +180,7 @@
Load time: {{ url['url_object'].time.total_seconds() }}s
- {{ popup_icons(keys_response, url['url_object'], tree_uuid) }}
+ {{ popup_icons_response(url['url_object'], tree_uuid) }}
{% if url['url_object'].rendered_html %}
diff --git a/website/web/templates/macros.html b/website/web/templates/macros.html
index ca6f8f70..e90ef41c 100644
--- a/website/web/templates/macros.html
+++ b/website/web/templates/macros.html
@@ -258,55 +258,62 @@
{% endif %}
{% endmacro %}
-{% macro popup_icons(lookup_dict, urlnode, tree_uuid) %}
+{% macro popup_icons_request(urlnode, tree_uuid) %}
-{% for key, icon_info in lookup_dict.items() %}
- {% if urlnode[key] %}
- {% if key == "request_cookie" %}
+ {% if urlnode.request_cookie %}
+ {% set icon_info = get_icon("request_cookie") %}
-
+
- {% elif key == "response_cookie"%}
+ {% endif %}
+
+{% endmacro %}
+
+{% macro popup_icons_response(urlnode, tree_uuid) %}
+
+ {% if urlnode.response_cookie %}
+ {% set icon_info = get_icon("response_cookie") %}
- {% elif key in ["js", "exe", "css", "font", "html", "json", "image", "video",
- "unknown_mimetype", "text", "unset_mimetype", "octet-stream", "livestream"]
- and not urlnode.empty_response %}
-
-
Click to download.'
- {% else %}
- data-bs-toggle="tooltip" data-bs-placement="bottom" data-bs-html="true" title="{{icon_info['tooltip']}}
Click to download."
- {% endif %}
- />
-
- {% elif key != "redirect" %}
-
![{{ icon_info['tooltip'] }} {{ icon_info['tooltip'] }}]({{ url_for('static', filename=icon_info['icon']) }})
- {%endif%}
+ {% endif %}
+
+ {% if urlnode.generic_type in ["js", "exe", "css", "font", "html", "json", "image", "video",
+ "unknown_mimetype", "text", "unset_mimetype", "octet-stream",
+ "livestream"] %}
+ {% set icon_info = get_icon(urlnode.generic_type) %}
+
+
Click to download.'
+ {% else %}
+ data-bs-toggle="tooltip" data-bs-placement="bottom" data-bs-html="true"
+ title="{{icon_info['tooltip']}}
Click to download."
+ {% endif %}
+ />
+
{%endif%}
-{% endfor %}
-{% if "redirect" in lookup_dict and urlnode["redirect"] %}
+{% if urlnode["redirect"] %}
+ {% set icon_info = get_icon('redirect') %}
{% for child in urlnode.children if child.name == urlnode.redirect_url %}
Redirect to: {{ shorten_string(urlnode.redirect_url, 50) }}
-
+
{% else %}
-
![{{ lookup_dict['redirect']['tooltip'] }} {{ lookup_dict['redirect']['tooltip'] }}]({{ url_for('static', filename=lookup_dict['redirect']['icon']) }})
{% endfor %}
{%endif%}
diff --git a/website/web/templates/tree.html b/website/web/templates/tree.html
index 2f5028de..1231ef99 100644
--- a/website/web/templates/tree.html
+++ b/website/web/templates/tree.html
@@ -82,6 +82,13 @@
});
+
+
+{% if from_popup %}
+
+{% endif %}
+
+