Skip to content

Commit 87199b0

Browse files
committed
Allow attachment default expiry to be unlimited
Setting the expiry to 0 days is now interpreted as a default that makes attachments never expire. (This only applies once attached to a post; temporary uploads still have a 1-hour expiry).
1 parent 044e111 commit 87199b0

File tree

7 files changed

+80
-11
lines changed

7 files changed

+80
-11
lines changed

sogs.ini.sample

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959

6060
; How long newly uploaded files should be stored before being cleaned up, in days. Note that
6161
; changing this only affects new files. This limit does not apply to room images and attachments in
62-
; pinned messages, both of which do not expire.
62+
; pinned messages, both of which do not expire. Can be set to 0 to never expire new uploads.
6363
;
6464
;expiry = 15
6565

sogs/config.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
OMQ_INTERNAL = 'ipc://./omq.sock'
2020
LOG_LEVEL = 'WARNING'
2121
DM_EXPIRY = 15 * 86400.0 # Seconds, but specified in config file as days
22-
UPLOAD_DEFAULT_EXPIRY = 15 * 86400.0 # Seconds, but specified in config file as days
22+
UPLOAD_DEFAULT_EXPIRY = 15 * 86400.0 # Seconds (or None), but specified in config file as days
2323
UPLOAD_FILENAME_MAX = 60
2424
UPLOAD_FILENAME_KEEP_PREFIX = 40
2525
UPLOAD_FILENAME_KEEP_SUFFIX = 17
@@ -80,6 +80,9 @@ def val_or_none(v):
8080
def days_to_seconds(v):
8181
return float(v) * 86400.0
8282

83+
def days_to_seconds_or_none(v):
84+
return days_to_seconds(v) if v else None
85+
8386
truthy = ('y', 'yes', 'Y', 'Yes', 'true', 'True', 'on', 'On', '1')
8487
falsey = ('n', 'no', 'N', 'No', 'false', 'False', 'off', 'Off', '0')
8588
booly = truthy + falsey
@@ -108,7 +111,7 @@ def bool_opt(name):
108111
'http_show_recent': bool_opt('HTTP_SHOW_RECENT'),
109112
},
110113
'files': {
111-
'expiry': ('UPLOAD_DEFAULT_EXPIRY', None, days_to_seconds),
114+
'expiry': ('UPLOAD_DEFAULT_EXPIRY', None, days_to_seconds_or_none),
112115
'max_size': ('UPLOAD_FILE_MAX_SIZE', None, int),
113116
'uploads_dir': ('UPLOAD_PATH', path_exists, val_or_none),
114117
},

sogs/migrations/v_0_1_x.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,10 @@ def ins_user(session_id):
359359
)
360360
timestamp = time.time()
361361

362+
exp = None
363+
if config.UPLOAD_DEFAULT_EXPIRY:
364+
exp = timestamp + config.UPLOAD_DEFAULT_EXPIRY
365+
362366
new_id = db.insert_and_get_pk(
363367
"""
364368
INSERT INTO files (room, size, uploaded, expiry, path)
@@ -368,7 +372,7 @@ def ins_user(session_id):
368372
r=room_id,
369373
size=size,
370374
uploaded=timestamp,
371-
expiry=timestamp + config.UPLOAD_DEFAULT_EXPIRY,
375+
expiry=exp,
372376
path=path,
373377
dbconn=conn,
374378
)

sogs/model/file.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -127,12 +127,14 @@ def set_expiry(self, duration=None, forever=False):
127127
True. If duration is None (and not using forever) then the default expiry (relative to the
128128
current time) will be used.
129129
"""
130-
expiry = (
131-
None
132-
if forever
133-
else time.time()
134-
+ (duration if duration is not None else config.UPLOAD_DEFAULT_EXPIRY)
135-
)
130+
if forever:
131+
expiry = None
132+
elif duration is not None:
133+
expiry = time.time() + duration
134+
elif config.UPLOAD_DEFAULT_EXPIRY:
135+
expiry = time.time() + config.UPLOAD_DEFAULT_EXPIRY
136+
else:
137+
expiry = None
136138
query("UPDATE files SET expiry = :when WHERE id = :f", when=expiry, f=self.id)
137139
self.expiry = expiry
138140

sogs/model/room.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,10 @@ def _own_files(self, msg_id: int, files: List[int], user):
656656
Associated any of the given file ids with the given message id. Only files that are recent,
657657
expiring, unowned uploads by the same user are actually updated.
658658
"""
659+
expiry = None
660+
if config.UPLOAD_DEFAULT_EXPIRY:
661+
expiry = time.time() + config.UPLOAD_DEFAULT_EXPIRY
662+
659663
return db.query(
660664
"""
661665
UPDATE files SET
@@ -669,7 +673,7 @@ def _own_files(self, msg_id: int, files: List[int], user):
669673
AND expiry IS NOT NULL
670674
""",
671675
m=msg_id,
672-
exp=time.time() + config.UPLOAD_DEFAULT_EXPIRY,
676+
exp=expiry,
673677
ids=files,
674678
r=self.id,
675679
u=user.id,

tests/test_files.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from nacl.utils import random
2+
from request import sogs_post, sogs_post_raw
3+
from util import config_override, from_now, pad64
4+
from sogs.model.file import File
5+
from sogs import utils
6+
7+
8+
def _make_file_upload(filename):
9+
return random(1024), {"Content-Disposition": ('attachment', {'filename': filename})}
10+
11+
12+
def test_file_unexpiring(client, room, user):
13+
with config_override(UPLOAD_DEFAULT_EXPIRY=None):
14+
filedata, headers = _make_file_upload('up1.txt')
15+
r = sogs_post_raw(client, f'/room/{room.token}/file', filedata, user, extra_headers=headers)
16+
assert r.status_code == 201
17+
assert 'id' in r.json
18+
f = File(id=r.json.get('id'))
19+
# - verify that the file expiry is 1h from now (±1s)
20+
assert f.expiry == from_now.hours(1)
21+
# - add a post that references the file
22+
d, s = (utils.encode_base64(x) for x in (b"post data", pad64("sig")))
23+
post_info = {'data': d, 'signature': s, 'files': [f.id]}
24+
r = sogs_post(client, f'/room/{room.token}/message', post_info, user)
25+
assert r.status_code == 201
26+
assert 'id' in r.json
27+
post_id = r.json.get('id')
28+
f = File(id=f.id)
29+
assert f.post_id == post_id
30+
assert f.expiry is None

tests/util.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from typing import Union
22
import time
3+
from contextlib import contextmanager
4+
import sogs.config
35

46

57
def pad64(data: Union[bytes, str]):
@@ -47,3 +49,27 @@ def hours(n):
4749
@staticmethod
4850
def days(n):
4951
return from_now.hours(24) * n
52+
53+
54+
@contextmanager
55+
def config_override(**kwargs):
56+
"""
57+
Context manager that locally overrides one or more sogs.config.XXX values for all given XXX keys
58+
in kwargs. The original config values are restored when leaving the context.
59+
60+
e.g.
61+
62+
with config_override(UPLOAD_FILE_MAX_SIZE=1024):
63+
...
64+
"""
65+
66+
restore = {}
67+
for k, v in kwargs.items():
68+
restore[k] = getattr(sogs.config, k)
69+
setattr(sogs.config, k, v)
70+
71+
try:
72+
yield None
73+
finally:
74+
for k, v in restore.items():
75+
setattr(sogs.config, k, v)

0 commit comments

Comments
 (0)