Skip to content

Commit 2bf435e

Browse files
authored
URL Builder Support added + URL linking privacy improvements (#305)
1 parent a836864 commit 2bf435e

20 files changed

Lines changed: 311 additions & 47 deletions

apprise_api/api/payload_mapper.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ def remap_fields(rules, payload, form=None):
6464
payload[value] = payload[key]
6565
del payload[key]
6666

67-
elif value in payload:
67+
else:
6868
# swap
6969
_tmp = payload[value]
7070
payload[value] = payload[key]

apprise_api/api/templates/base.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,20 +104,25 @@
104104
<ul class="collection z-depth-1">
105105
<a class="collection-item"
106106
target="_blank"
107+
rel="noopener noreferrer"
107108
href="https://appriseit.com/services/"><i class="material-icons" style="color: #38967c;">style</i>{% trans "Notification Services" %}</a>
108109
<a class="collection-item"
109110
target="_blank"
111+
rel="noopener noreferrer"
110112
href="https://appriseit.com/config/"><i class="material-icons">local_library</i> {% trans "Configuration Help" %}</a>
111113
<a class="collection-item"
112114
target="_blank"
115+
rel="noopener noreferrer"
113116
href="https://appriseit.com/getting-started/"><i class="material-icons">library_books</i> {% trans "Documentation" %}</a>
114117
</ul>
115118
<ul class="collection z-depth-1">
116119
<a class="collection-item"
117120
target="_blank"
121+
rel="noopener noreferrer"
118122
href="https://www.paypal.com/donate/?hosted_button_id=CR6YF7KLQWQ5E"><i class="material-icons" style="color: maroon;">free_breakfast</i> <strong>{% trans "Buy Developer A Coffee" %}</strong></a>
119123
<a class="collection-item"
120124
target="_blank"
125+
rel="noopener noreferrer"
121126
href="https://github.com/sponsors/caronc"><i class="material-icons" style="color: red;">favorite</i> <strong>{% trans "Sponsor Apprise Development" %}</strong></a>
122127
</ul>
123128
{% block menu %}

apprise_api/api/templates/config.html

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ <h5>{% trans "Getting Started" %}</h5>
9090
<div>
9191
{% blocktrans %}
9292
<i class="material-icons">lightbulb_outline</i>You can always refer to the
93-
<a href="https://appriseit.com/services/">Apprise Wiki</a> if you're having troubles assembling your URL(s).
93+
<a href="https://appriseit.com/services/" target="_blank" rel="noopener noreferrer">Apprise Wiki</a> if you're having troubles assembling your URL(s).
9494
{% endblocktrans %}
9595
</div>
9696
</li>
@@ -157,7 +157,7 @@ <h6>{% trans "Using The Apprise CLI" %}</h6>
157157
<pre><code class="bash">apprise --body=&quot;Test Message&quot; --tag=all \<br />
158158
&nbsp;&nbsp;&nbsp;&nbsp;--config={{request.scheme}}://{{request.META.HTTP_HOST}}{{BASE_URL}}/cfg/<em>{{key}}</em></code></pre>
159159
</div>
160-
{% blocktrans %}You may also create an <a href="https://appriseit.com/config/#cli" target="_blank">Apprise configuration file</a> that contains this line somewhere in it:{% endblocktrans %}
160+
{% blocktrans %}You may also create an <a href="https://appriseit.com/config/#cli" target="_blank" rel="noopener noreferrer">Apprise configuration file</a> that contains this line somewhere in it:{% endblocktrans %}
161161
<div class="code-snippet"
162162
data-copy-text='include {{ request.scheme }}://{{ request.META.HTTP_HOST }}{{ BASE_URL }}/cfg/{{ key }}'
163163
data-copy-label='{% trans "Apprise configuration 'include' entry copied to clipboard" %}'></div>
@@ -222,10 +222,20 @@ <h6>{% trans "Using CURL" %}</h6>
222222
</div>
223223
{{ field.errors }}
224224
{% elif field.name == 'format' %}
225-
<div class="input-field config-format-field">
226-
{{ field.label_tag }}
227-
{{ field }}
228-
{{ field.errors }}
225+
<div class="config-toolbar" style="justify-content: space-between;">
226+
<div class="input-field config-format-field">
227+
{{ field.label_tag }}
228+
{{ field }}
229+
{{ field.errors }}
230+
</div>
231+
<a href="https://appriseit.com/tools/url-builder/"
232+
target="_blank"
233+
rel="noopener noreferrer"
234+
class="btn waves-effect waves-light config-url-builder-btn"
235+
style="margin-bottom: 0.75rem">
236+
{% trans "Apprise URL Builder" %}
237+
<i class="material-icons right">build</i>
238+
</a>
229239
</div>
230240
{% else %}
231241
<div class="input-field">

apprise_api/api/templates/details.html

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<h4>{% trans "Apprise Details" %}</h4>
1010
<p>
1111
{% blocktrans %}The following services are supported by this
12-
<a target="_blank" href="https://github.com/caronc/apprise">Apprise</a>
12+
<a target="_blank" href="https://github.com/caronc/apprise" rel="noopener noreferrer">Apprise</a>
1313
instance.{% endblocktrans %}
1414
</p>
1515
<p>
@@ -76,13 +76,27 @@ <h5>{{ entry.service_name }}</h5>
7676
{% if entry.protocols %}
7777
<li>
7878
<strong>{% blocktrans %}Insecure Schema(s){% endblocktrans %}:</strong>
79-
<code>{{ entry.protocols|join:", " }}</code>
79+
<code>
80+
{% for schema in entry.protocols %}
81+
<a href="https://appriseit.com/tools/url-builder/?schema={{ schema }}"
82+
target="_blank"
83+
rel="noopener noreferrer">{{ schema }}</a>
84+
{% if not forloop.last %},{% endif %}
85+
{% endfor %}
86+
</code>
8087
</li>
8188
{% endif %}
8289
{% if entry.secure_protocols %}
8390
<li>
8491
<strong>{% blocktrans %}Secure Schema(s){% endblocktrans %}:</strong>
85-
<code>{{ entry.secure_protocols|join:", " }}</code>
92+
<code>
93+
{% for schema in entry.secure_protocols %}
94+
<a href="https://appriseit.com/tools/url-builder/?schema={{ schema }}"
95+
target="_blank"
96+
rel="noopener noreferrer">{{ schema }}</a>
97+
{% if not forloop.last %},{% endif %}
98+
{% endfor %}
99+
</code>
86100
</li>
87101
{% endif %}
88102
<li>
@@ -165,7 +179,7 @@ <h4>{% trans "API Endpoints" %}</h4>
165179
</li>
166180
</ul>
167181
<p>
168-
{% blocktrans %}More details on the JSON format can be found <a href="https://appriseit.com/dev/apprise_details/" target="_blank">here</a>.{% endblocktrans %}
182+
{% blocktrans %}More details on the JSON format can be found <a href="https://appriseit.com/dev/apprise_details/" target="_blank" rel="noopener noreferrer">here</a>.{% endblocktrans %}
169183
</p>
170184
</div>
171185
</div>

apprise_api/api/templates/welcome.html

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<h4>{% trans "The Apprise API" %}</h4>
99
<p>
1010
{% blocktrans %}
11-
<a target="_blank" href="https://github.com/caronc/apprise">Apprise</a> allows you to send a notification to almost
11+
<a target="_blank" href="https://github.com/caronc/apprise" rel="noopener noreferrer">Apprise</a> allows you to send a notification to almost
1212
all of the most popular notification services available to us today such as: <em>Telegram</em>, <em>Discord</em>,
1313
<em>Slack</em>, <em>Amazon SNS</em>, <em>Gotify</em>, etc.
1414
This API provides a simple gateway to directly access it via an HTTP interface.{% endblocktrans %}
@@ -27,7 +27,7 @@ <h4>{% trans "The Apprise API" %}</h4>
2727
<div class="section">
2828
<h4>{% trans "Stateless Endpoints" %}</h4>
2929
<p>
30-
{% blocktrans %}Those who wish to treat this API as nothing but a <a href="https://docs.microsoft.com/en-us/azure/architecture/patterns/sidecar" target="_blank">sidecar</a> and/or
30+
{% blocktrans %}Those who wish to treat this API as nothing but a <a href="https://docs.microsoft.com/en-us/azure/architecture/patterns/sidecar" target="_blank" rel="noopener noreferrer">sidecar</a> and/or
3131
microservice to their project only need to use the following URL.{% endblocktrans %}
3232
</p>
3333
<div class="table-wrapper">
@@ -44,7 +44,7 @@ <h4>{% trans "Stateless Endpoints" %}</h4>
4444
<code>{{ BASE_URL }}/notify/</code>
4545
</td>
4646
<td>
47-
{% blocktrans %}Used to notify one one or more specified Apprise URLs. See the <a target="_blank" href="https://appriseit.com/services/">Apprise Wiki</a> if you need help
47+
{% blocktrans %}Used to notify one one or more specified Apprise URLs. See the <a target="_blank" href="https://appriseit.com/services/" rel="noopener noreferrer">Apprise Wiki</a> if you need help
4848
constructing your URL(s).{% endblocktrans %}
4949
<div class="section">
5050
<table>
@@ -120,7 +120,7 @@ <h4>{% trans "Stateless Endpoints" %}</h4>
120120
&nbsp;&nbsp;&nbsp;&nbsp;"{{request.scheme}}://{{request.META.HTTP_HOST}}{{BASE_URL}}/notify/"
121121
</code></pre>
122122
<pre><code class="bash">
123-
# {% blocktrans %}Send an web based file attachment to a <a href="https://appriseit.com/services/discord/" target="_blank">Discord</a> server:{% endblocktrans %}<br />
123+
# {% blocktrans %}Send an web based file attachment to a <a href="https://appriseit.com/services/discord/" target="_blank" rel="noopener noreferrer">Discord</a> server:{% endblocktrans %}<br />
124124
curl -X POST -F 'urls=discord://credentials' \<br />
125125
&nbsp;&nbsp;&nbsp;&nbsp;-F "attach=https://raw.githubusercontent.com/caronc/apprise/master/apprise/assets/themes/default/apprise-logo.png" \ <br />
126126
&nbsp;&nbsp;&nbsp;&nbsp;"{{request.scheme}}://{{request.META.HTTP_HOST}}{{BASE_URL}}/notify/"
@@ -280,7 +280,7 @@ <h4>{% trans "Persistent Store Endpoints" %}</h4>
280280
</td>
281281
<td>
282282
{% blocktrans %}Used to add a new Apprise configuration or a set of URLs and associates them with configuration
283-
identified with the id of <em>{{key}}</em>. See the <a target="_blank" href="https://appriseit.com/services/">Apprise Wiki</a> if you need help
283+
identified with the id of <em>{{key}}</em>. See the <a target="_blank" href="https://appriseit.com/services/" rel="noopener noreferrer">Apprise Wiki</a> if you need help
284284
constructing your URLs.{% endblocktrans %}
285285
<div class="section">
286286
<table>

apprise_api/api/tests/test_add.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,15 @@ def test_save_config_by_urls(self):
133133
response = self.client.post("/add/{}".format(key), {"urls": "slack://-/-/-"})
134134
assert response.status_code == 400
135135

136+
# Submit with an invalid config format choice — AddByConfigForm
137+
# fails validation (False branch of 'if form.is_valid()') while
138+
# AddByUrlForm still succeeds via the valid 'urls' field.
139+
response = self.client.post(
140+
"/add/{}".format(key),
141+
{"format": "invalid_format_xyz", "urls": "mailto://user:pass@yahoo.ca"},
142+
)
143+
assert response.status_code == 200
144+
136145
# Test with JSON
137146
response = self.client.post(
138147
"/add/{}".format(key),

apprise_api/api/tests/test_attachment.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
# THE SOFTWARE.
2424
import base64
2525
from contextlib import suppress
26+
import os
2627
from os.path import dirname, getsize, join
2728
from shutil import rmtree
2829
from tempfile import TemporaryDirectory
@@ -89,6 +90,15 @@ def test_attachment_initialization(self):
8990
a = Attachment("file")
9091
assert a.filename
9192

93+
# Test with an explicit path already provided (covers the
94+
# 'if not path:' False branch — mkstemp is skipped)
95+
import tempfile
96+
97+
fd, explicit_path = tempfile.mkstemp(dir=self.tmp_dir.name)
98+
os.close(fd)
99+
a = Attachment("explicit.txt", path=explicit_path, delete=False)
100+
assert a._path == explicit_path
101+
92102
def test_form_file_attachment_parsing(self):
93103
"""
94104
Test the parsing of file attachments
@@ -270,6 +280,18 @@ def test(*args, **kwargs):
270280
assert isinstance(result, list)
271281
assert len(result) == 0
272282

283+
# dict with a valid non-empty filename — exercises the False branch
284+
# of 'elif not filename:' (filename is provided and non-empty, so the
285+
# fallback-to-default branch is not taken)
286+
attachment_payload = {
287+
"base64": base64.b64encode(b"data to be encoded").decode("utf-8"),
288+
"filename": "myfile.bin",
289+
}
290+
result = parse_attachments(attachment_payload, {})
291+
assert isinstance(result, list)
292+
assert len(result) == 1
293+
assert result[0].name == "myfile.bin"
294+
273295
# filename provided, but its empty (and/or contains whitespace)
274296
attachment_payload = {
275297
"base64": base64.b64encode(b"data to be encoded").decode("utf-8"),

apprise_api/api/tests/test_config_cache.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,13 @@ def test_apprise_config_list_simple_mode(tmpdir):
149149
assert len(keys) == 10
150150
assert sorted(keys) == sorted(text_keys + yaml_keys)
151151

152+
# Add a subdirectory — keys() must skip non-file entries (covers the
153+
# 'if os.path.isfile(path): False' branch)
154+
subdir = os.path.join(str(tmpdir), "subdir")
155+
os.makedirs(subdir)
156+
keys = acc_obj.keys()
157+
assert len(keys) == 10
158+
152159

153160
def test_apprise_config_list_hash_mode(tmpdir):
154161
"""

apprise_api/api/tests/test_emoji.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class NotifyWithEmojiTests(SimpleTestCase):
3939
Test notifications with Emoji Settings
4040
"""
4141

42-
@mock.patch("requests.post")
42+
@mock.patch("requests.request")
4343
def test_stateful_notify_with_emoji(self, mock_post):
4444
"""
4545
Test adding a simple stateful notification with emoji flags
@@ -85,7 +85,7 @@ def test_stateful_notify_with_emoji(self, mock_post):
8585
assert mock_post.call_count == 1
8686

8787
details = mock_post.call_args_list[0]
88-
assert details[0][0].startswith("http://localhost")
88+
assert details[0][1].startswith("http://localhost")
8989

9090
payload = json.loads(details[1]["data"])
9191
assert payload["title"] == "😃"
@@ -101,7 +101,7 @@ def test_stateful_notify_with_emoji(self, mock_post):
101101
assert mock_post.call_count == 1
102102

103103
details = mock_post.call_args_list[0]
104-
assert details[0][0].startswith("http://localhost")
104+
assert details[0][1].startswith("http://localhost")
105105

106106
payload = json.loads(details[1]["data"])
107107
assert payload["title"] == ":grin:"
@@ -110,7 +110,7 @@ def test_stateful_notify_with_emoji(self, mock_post):
110110
# Reset our mock object
111111
mock_post.reset_mock()
112112

113-
@mock.patch("requests.post")
113+
@mock.patch("requests.request")
114114
def test_stateless_notify_with_emoji(self, mock_post):
115115
"""
116116
Test adding a simple stateless notification with emoji flags
@@ -141,7 +141,7 @@ def test_stateless_notify_with_emoji(self, mock_post):
141141
assert mock_post.call_count == 1
142142

143143
details = mock_post.call_args_list[0]
144-
assert details[0][0].startswith("http://localhost")
144+
assert details[0][1].startswith("http://localhost")
145145

146146
payload = json.loads(details[1]["data"])
147147
assert payload["title"] == "😃"
@@ -161,7 +161,7 @@ def test_stateless_notify_with_emoji(self, mock_post):
161161
assert mock_post.call_count == 1
162162

163163
details = mock_post.call_args_list[0]
164-
assert details[0][0].startswith("http://localhost")
164+
assert details[0][1].startswith("http://localhost")
165165

166166
payload = json.loads(details[1]["data"])
167167
assert payload["title"] == ":grin:"

apprise_api/api/tests/test_healthecheck.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
2323
# THE SOFTWARE.
2424
from json import loads
25+
import time
2526
from unittest import mock
2627

2728
from django.test import SimpleTestCase
@@ -313,3 +314,32 @@ def test_healthcheck_library(self):
313314
"CONFIG_PERMISSION_ISSUE",
314315
],
315316
}
317+
318+
def test_healthcheck_lazy_stale_mtime(self):
319+
"""
320+
Test that lazy healthcheck re-checks when the cached .tmp_hc file
321+
exists but is older than 30 seconds (covers the delta > 30 False
322+
branch at the 'if delta <= 30.00' guards).
323+
"""
324+
# First call: creates .tmp_hc files and primes can_write_config
325+
result = healthcheck(lazy=False)
326+
assert result["can_write_config"] is True
327+
assert result["persistent_storage"] is True
328+
329+
# Second call with mocked mtime returning 60 seconds in the past —
330+
# delta > 30 so we skip the early-return branch and re-verify.
331+
old_time = time.time() - 60
332+
with mock.patch("apprise_api.api.utils.os.path.getmtime", return_value=old_time):
333+
result = healthcheck(lazy=True)
334+
assert result["can_write_config"] is True
335+
assert result["persistent_storage"] is True
336+
337+
def test_healthcheck_memory_storage_mode(self):
338+
"""
339+
Test that when APPRISE_STORAGE_MODE is 'memory' the persistent-storage
340+
elif branch is skipped (store.mode == MEMORY so the elif is False).
341+
"""
342+
with override_settings(APPRISE_STORAGE_MODE="memory"):
343+
result = healthcheck(lazy=False)
344+
assert result["persistent_storage"] is False
345+
assert "STORE_PERMISSION_ISSUE" not in result["details"]

0 commit comments

Comments
 (0)