lfss 0.11.3__tar.gz → 0.11.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {lfss-0.11.3 → lfss-0.11.4}/PKG-INFO +1 -1
- {lfss-0.11.3 → lfss-0.11.4}/lfss/eng/database.py +54 -22
- {lfss-0.11.3 → lfss-0.11.4}/pyproject.toml +1 -1
- {lfss-0.11.3 → lfss-0.11.4}/Readme.md +0 -0
- {lfss-0.11.3 → lfss-0.11.4}/docs/Enviroment_variables.md +0 -0
- {lfss-0.11.3 → lfss-0.11.4}/docs/Known_issues.md +0 -0
- {lfss-0.11.3 → lfss-0.11.4}/docs/Permission.md +0 -0
- {lfss-0.11.3 → lfss-0.11.4}/docs/Webdav.md +0 -0
- {lfss-0.11.3 → lfss-0.11.4}/docs/changelog.md +0 -0
- {lfss-0.11.3 → lfss-0.11.4}/frontend/api.js +0 -0
- {lfss-0.11.3 → lfss-0.11.4}/frontend/index.html +0 -0
- {lfss-0.11.3 → lfss-0.11.4}/frontend/info.css +0 -0
- {lfss-0.11.3 → lfss-0.11.4}/frontend/info.js +0 -0
- {lfss-0.11.3 → lfss-0.11.4}/frontend/login.css +0 -0
- {lfss-0.11.3 → lfss-0.11.4}/frontend/login.js +0 -0
- {lfss-0.11.3 → lfss-0.11.4}/frontend/popup.css +0 -0
- {lfss-0.11.3 → lfss-0.11.4}/frontend/popup.js +0 -0
- {lfss-0.11.3 → lfss-0.11.4}/frontend/scripts.js +0 -0
- {lfss-0.11.3 → lfss-0.11.4}/frontend/state.js +0 -0
- {lfss-0.11.3 → lfss-0.11.4}/frontend/styles.css +0 -0
- {lfss-0.11.3 → lfss-0.11.4}/frontend/thumb.css +0 -0
- {lfss-0.11.3 → lfss-0.11.4}/frontend/thumb.js +0 -0
- {lfss-0.11.3 → lfss-0.11.4}/frontend/utils.js +0 -0
- {lfss-0.11.3 → lfss-0.11.4}/lfss/api/__init__.py +0 -0
- {lfss-0.11.3 → lfss-0.11.4}/lfss/api/connector.py +0 -0
- {lfss-0.11.3 → lfss-0.11.4}/lfss/cli/__init__.py +0 -0
- {lfss-0.11.3 → lfss-0.11.4}/lfss/cli/balance.py +0 -0
- {lfss-0.11.3 → lfss-0.11.4}/lfss/cli/cli.py +0 -0
- {lfss-0.11.3 → lfss-0.11.4}/lfss/cli/log.py +0 -0
- {lfss-0.11.3 → lfss-0.11.4}/lfss/cli/panel.py +0 -0
- {lfss-0.11.3 → lfss-0.11.4}/lfss/cli/serve.py +0 -0
- {lfss-0.11.3 → lfss-0.11.4}/lfss/cli/user.py +0 -0
- {lfss-0.11.3 → lfss-0.11.4}/lfss/cli/vacuum.py +0 -0
- {lfss-0.11.3 → lfss-0.11.4}/lfss/eng/__init__.py +0 -0
- {lfss-0.11.3 → lfss-0.11.4}/lfss/eng/bounded_pool.py +0 -0
- {lfss-0.11.3 → lfss-0.11.4}/lfss/eng/config.py +0 -0
- {lfss-0.11.3 → lfss-0.11.4}/lfss/eng/connection_pool.py +0 -0
- {lfss-0.11.3 → lfss-0.11.4}/lfss/eng/datatype.py +0 -0
- {lfss-0.11.3 → lfss-0.11.4}/lfss/eng/error.py +0 -0
- {lfss-0.11.3 → lfss-0.11.4}/lfss/eng/log.py +0 -0
- {lfss-0.11.3 → lfss-0.11.4}/lfss/eng/thumb.py +0 -0
- {lfss-0.11.3 → lfss-0.11.4}/lfss/eng/utils.py +0 -0
- {lfss-0.11.3 → lfss-0.11.4}/lfss/sql/init.sql +0 -0
- {lfss-0.11.3 → lfss-0.11.4}/lfss/sql/pragma.sql +0 -0
- {lfss-0.11.3 → lfss-0.11.4}/lfss/svc/app.py +0 -0
- {lfss-0.11.3 → lfss-0.11.4}/lfss/svc/app_base.py +0 -0
- {lfss-0.11.3 → lfss-0.11.4}/lfss/svc/app_dav.py +0 -0
- {lfss-0.11.3 → lfss-0.11.4}/lfss/svc/app_native.py +0 -0
- {lfss-0.11.3 → lfss-0.11.4}/lfss/svc/common_impl.py +0 -0
- {lfss-0.11.3 → lfss-0.11.4}/lfss/svc/request_log.py +0 -0
@@ -197,6 +197,11 @@ class FileConn(DBObjectBase):
|
|
197
197
|
def parse_record(record) -> FileRecord:
|
198
198
|
return FileRecord(*record)
|
199
199
|
|
200
|
+
@staticmethod
|
201
|
+
def escape_sqlike(url: str) -> str:
|
202
|
+
""" Escape a url for use in SQL LIKE clause (The % and _ characters) """
|
203
|
+
return url.replace('%', r'\%').replace('_', r'\_')
|
204
|
+
|
200
205
|
@overload
|
201
206
|
async def get_file_record(self, url: str, throw: Literal[True]) -> FileRecord: ...
|
202
207
|
@overload
|
@@ -246,9 +251,9 @@ class FileConn(DBObjectBase):
|
|
246
251
|
url, LENGTH(?) + 1,
|
247
252
|
INSTR(SUBSTR(url, LENGTH(?) + 1), '/')
|
248
253
|
) AS dirname
|
249
|
-
FROM fmeta WHERE url LIKE ? AND dirname != ''
|
254
|
+
FROM fmeta WHERE url LIKE ? ESCAPE '\\' AND dirname != ''
|
250
255
|
)
|
251
|
-
""", (url, url, url + '%'))
|
256
|
+
""", (url, url, self.escape_sqlike(url) + '%'))
|
252
257
|
res = await cursor.fetchone()
|
253
258
|
assert res is not None, "Error: count_path_dirs"
|
254
259
|
return res[0]
|
@@ -271,11 +276,11 @@ class FileConn(DBObjectBase):
|
|
271
276
|
1 + LENGTH(?),
|
272
277
|
INSTR(SUBSTR(url, 1 + LENGTH(?)), '/')
|
273
278
|
) AS dirname
|
274
|
-
FROM fmeta WHERE url LIKE ? AND dirname != ''
|
279
|
+
FROM fmeta WHERE url LIKE ? ESCAPE '\\' AND dirname != ''
|
275
280
|
""" \
|
276
281
|
+ (f"ORDER BY {order_by} {'DESC' if order_desc else 'ASC'}" if order_by else '') \
|
277
282
|
+ " LIMIT ? OFFSET ?"
|
278
|
-
cursor = await self.cur.execute(sql_qury, (url, url, url + '%', limit, offset))
|
283
|
+
cursor = await self.cur.execute(sql_qury, (url, url, self.escape_sqlike(url) + '%', limit, offset))
|
279
284
|
res = await cursor.fetchall()
|
280
285
|
dirs_str = [r[0] for r in res]
|
281
286
|
async def get_dir(dir_url):
|
@@ -290,9 +295,15 @@ class FileConn(DBObjectBase):
|
|
290
295
|
if not url.endswith('/'): url += '/'
|
291
296
|
if url == '/': url = ''
|
292
297
|
if flat:
|
293
|
-
cursor = await self.cur.execute(
|
298
|
+
cursor = await self.cur.execute(
|
299
|
+
"SELECT COUNT(*) FROM fmeta WHERE url LIKE ? ESCAPE '\\'",
|
300
|
+
(self.escape_sqlike(url) + '%', )
|
301
|
+
)
|
294
302
|
else:
|
295
|
-
cursor = await self.cur.execute(
|
303
|
+
cursor = await self.cur.execute(
|
304
|
+
"SELECT COUNT(*) FROM fmeta WHERE url LIKE ? ESCAPE '\\' AND url NOT LIKE ? ESCAPE '\\'",
|
305
|
+
(self.escape_sqlike(url) + '%', self.escape_sqlike(url) + '%/%')
|
306
|
+
)
|
296
307
|
res = await cursor.fetchone()
|
297
308
|
assert res is not None, "Error: count_path_files"
|
298
309
|
return res[0]
|
@@ -309,14 +320,14 @@ class FileConn(DBObjectBase):
|
|
309
320
|
if not url.endswith('/'): url += '/'
|
310
321
|
if url == '/': url = ''
|
311
322
|
|
312
|
-
sql_query = "SELECT * FROM fmeta WHERE url LIKE ?"
|
313
|
-
if not flat: sql_query += " AND url NOT LIKE ?"
|
323
|
+
sql_query = "SELECT * FROM fmeta WHERE url LIKE ? ESCAPE '\\'"
|
324
|
+
if not flat: sql_query += " AND url NOT LIKE ? ESCAPE '\\'"
|
314
325
|
if order_by: sql_query += f" ORDER BY {order_by} {'DESC' if order_desc else 'ASC'}"
|
315
326
|
sql_query += " LIMIT ? OFFSET ?"
|
316
327
|
if flat:
|
317
|
-
cursor = await self.cur.execute(sql_query, (url + '%', limit, offset))
|
328
|
+
cursor = await self.cur.execute(sql_query, (self.escape_sqlike(url) + '%', limit, offset))
|
318
329
|
else:
|
319
|
-
cursor = await self.cur.execute(sql_query, (url + '%', url + '%/%', limit, offset))
|
330
|
+
cursor = await self.cur.execute(sql_query, (self.escape_sqlike(url) + '%', self.escape_sqlike(url) + '%/%', limit, offset))
|
320
331
|
res = await cursor.fetchall()
|
321
332
|
files = [self.parse_record(r) for r in res]
|
322
333
|
return files
|
@@ -351,8 +362,8 @@ class FileConn(DBObjectBase):
|
|
351
362
|
MAX(access_time) as access_time,
|
352
363
|
COUNT(*) as n_files
|
353
364
|
FROM fmeta
|
354
|
-
WHERE url LIKE ?
|
355
|
-
""", (url + '%', ))
|
365
|
+
WHERE url LIKE ? ESCAPE '\\'
|
366
|
+
""", (self.escape_sqlike(url) + '%', ))
|
356
367
|
result = await cursor.fetchone()
|
357
368
|
if result is None or any(val is None for val in result):
|
358
369
|
raise PathNotFoundError(f"Path {url} not found")
|
@@ -376,10 +387,16 @@ class FileConn(DBObjectBase):
|
|
376
387
|
if not url.endswith('/'):
|
377
388
|
url += '/'
|
378
389
|
if not include_subpath:
|
379
|
-
cursor = await self.cur.execute(
|
390
|
+
cursor = await self.cur.execute(
|
391
|
+
"SELECT SUM(file_size) FROM fmeta WHERE url LIKE ? ESCAPE '\\' AND url NOT LIKE ? ESCAPE '\\'",
|
392
|
+
(self.escape_sqlike(url) + '%', self.escape_sqlike(url) + '%/%')
|
393
|
+
)
|
380
394
|
res = await cursor.fetchone()
|
381
395
|
else:
|
382
|
-
cursor = await self.cur.execute(
|
396
|
+
cursor = await self.cur.execute(
|
397
|
+
"SELECT SUM(file_size) FROM fmeta WHERE url LIKE ? ESCAPE '\\'",
|
398
|
+
(self.escape_sqlike(url) + '%', )
|
399
|
+
)
|
383
400
|
res = await cursor.fetchone()
|
384
401
|
assert res is not None
|
385
402
|
return res[0] or 0
|
@@ -442,7 +459,10 @@ class FileConn(DBObjectBase):
|
|
442
459
|
"""
|
443
460
|
assert old_url.endswith('/'), "Old path must end with /"
|
444
461
|
assert new_url.endswith('/'), "New path must end with /"
|
445
|
-
cursor = await self.cur.execute(
|
462
|
+
cursor = await self.cur.execute(
|
463
|
+
"SELECT * FROM fmeta WHERE url LIKE ? ESCAPE '\\'",
|
464
|
+
(self.escape_sqlike(old_url) + '%', )
|
465
|
+
)
|
446
466
|
res = await cursor.fetchall()
|
447
467
|
for r in res:
|
448
468
|
old_record = FileRecord(*r)
|
@@ -472,10 +492,16 @@ class FileConn(DBObjectBase):
|
|
472
492
|
assert old_url.endswith('/'), "Old path must end with /"
|
473
493
|
assert new_url.endswith('/'), "New path must end with /"
|
474
494
|
if user_id is None:
|
475
|
-
cursor = await self.cur.execute(
|
495
|
+
cursor = await self.cur.execute(
|
496
|
+
"SELECT * FROM fmeta WHERE url LIKE ? ESCAPE '\\'",
|
497
|
+
(self.escape_sqlike(old_url) + '%', )
|
498
|
+
)
|
476
499
|
res = await cursor.fetchall()
|
477
500
|
else:
|
478
|
-
cursor = await self.cur.execute(
|
501
|
+
cursor = await self.cur.execute(
|
502
|
+
"SELECT * FROM fmeta WHERE url LIKE ? ESCAPE '\\' AND owner_id = ?",
|
503
|
+
(self.escape_sqlike(old_url) + '%', user_id)
|
504
|
+
)
|
479
505
|
res = await cursor.fetchall()
|
480
506
|
for r in res:
|
481
507
|
new_r = new_url + r[0][len(old_url):]
|
@@ -510,10 +536,16 @@ class FileConn(DBObjectBase):
|
|
510
536
|
async def delete_records_by_prefix(self, path: str, under_owner_id: Optional[int] = None) -> list[FileRecord]:
|
511
537
|
"""Delete all records with url starting with path"""
|
512
538
|
# update user size
|
513
|
-
cursor = await self.cur.execute(
|
539
|
+
cursor = await self.cur.execute(
|
540
|
+
"SELECT DISTINCT owner_id FROM fmeta WHERE url LIKE ? ESCAPE '\\'",
|
541
|
+
(self.escape_sqlike(path) + '%', )
|
542
|
+
)
|
514
543
|
res = await cursor.fetchall()
|
515
544
|
for r in res:
|
516
|
-
cursor = await self.cur.execute(
|
545
|
+
cursor = await self.cur.execute(
|
546
|
+
"SELECT SUM(file_size) FROM fmeta WHERE owner_id = ? AND url LIKE ? ESCAPE '\\'",
|
547
|
+
(r[0], self.escape_sqlike(path) + '%')
|
548
|
+
)
|
517
549
|
size = await cursor.fetchone()
|
518
550
|
if size is not None:
|
519
551
|
await self._user_size_dec(r[0], size[0])
|
@@ -522,9 +554,9 @@ class FileConn(DBObjectBase):
|
|
522
554
|
# but it's not a big deal... we should have only one writer
|
523
555
|
|
524
556
|
if under_owner_id is None:
|
525
|
-
res = await self.cur.execute("DELETE FROM fmeta WHERE url LIKE ? RETURNING *", (path + '%', ))
|
557
|
+
res = await self.cur.execute("DELETE FROM fmeta WHERE url LIKE ? ESCAPE '\\' RETURNING *", (self.escape_sqlike(path) + '%', ))
|
526
558
|
else:
|
527
|
-
res = await self.cur.execute("DELETE FROM fmeta WHERE url LIKE ? AND owner_id = ? RETURNING *", (path + '%', under_owner_id))
|
559
|
+
res = await self.cur.execute("DELETE FROM fmeta WHERE url LIKE ? ESCAPE '\\' AND owner_id = ? RETURNING *", (self.escape_sqlike(path) + '%', under_owner_id))
|
528
560
|
all_f_rec = await res.fetchall()
|
529
561
|
self.logger.info(f"Deleted {len(all_f_rec)} file(s) for path {path}") # type: ignore
|
530
562
|
return [self.parse_record(r) for r in all_f_rec]
|
@@ -692,7 +724,7 @@ async def delayed_log_access(url: str):
|
|
692
724
|
prohibited_part_regex = re.compile(
|
693
725
|
"|".join([
|
694
726
|
r"^\s*\.+\s*$", # dot path
|
695
|
-
"[{}]".format("".join(re.escape(c) for c in ('/', "\\", "'", '"', "*"
|
727
|
+
"[{}]".format("".join(re.escape(c) for c in ('/', "\\", "'", '"', "*"))), # prohibited characters
|
696
728
|
])
|
697
729
|
),
|
698
730
|
)
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|