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.
Files changed (50) hide show
  1. {lfss-0.11.3 → lfss-0.11.4}/PKG-INFO +1 -1
  2. {lfss-0.11.3 → lfss-0.11.4}/lfss/eng/database.py +54 -22
  3. {lfss-0.11.3 → lfss-0.11.4}/pyproject.toml +1 -1
  4. {lfss-0.11.3 → lfss-0.11.4}/Readme.md +0 -0
  5. {lfss-0.11.3 → lfss-0.11.4}/docs/Enviroment_variables.md +0 -0
  6. {lfss-0.11.3 → lfss-0.11.4}/docs/Known_issues.md +0 -0
  7. {lfss-0.11.3 → lfss-0.11.4}/docs/Permission.md +0 -0
  8. {lfss-0.11.3 → lfss-0.11.4}/docs/Webdav.md +0 -0
  9. {lfss-0.11.3 → lfss-0.11.4}/docs/changelog.md +0 -0
  10. {lfss-0.11.3 → lfss-0.11.4}/frontend/api.js +0 -0
  11. {lfss-0.11.3 → lfss-0.11.4}/frontend/index.html +0 -0
  12. {lfss-0.11.3 → lfss-0.11.4}/frontend/info.css +0 -0
  13. {lfss-0.11.3 → lfss-0.11.4}/frontend/info.js +0 -0
  14. {lfss-0.11.3 → lfss-0.11.4}/frontend/login.css +0 -0
  15. {lfss-0.11.3 → lfss-0.11.4}/frontend/login.js +0 -0
  16. {lfss-0.11.3 → lfss-0.11.4}/frontend/popup.css +0 -0
  17. {lfss-0.11.3 → lfss-0.11.4}/frontend/popup.js +0 -0
  18. {lfss-0.11.3 → lfss-0.11.4}/frontend/scripts.js +0 -0
  19. {lfss-0.11.3 → lfss-0.11.4}/frontend/state.js +0 -0
  20. {lfss-0.11.3 → lfss-0.11.4}/frontend/styles.css +0 -0
  21. {lfss-0.11.3 → lfss-0.11.4}/frontend/thumb.css +0 -0
  22. {lfss-0.11.3 → lfss-0.11.4}/frontend/thumb.js +0 -0
  23. {lfss-0.11.3 → lfss-0.11.4}/frontend/utils.js +0 -0
  24. {lfss-0.11.3 → lfss-0.11.4}/lfss/api/__init__.py +0 -0
  25. {lfss-0.11.3 → lfss-0.11.4}/lfss/api/connector.py +0 -0
  26. {lfss-0.11.3 → lfss-0.11.4}/lfss/cli/__init__.py +0 -0
  27. {lfss-0.11.3 → lfss-0.11.4}/lfss/cli/balance.py +0 -0
  28. {lfss-0.11.3 → lfss-0.11.4}/lfss/cli/cli.py +0 -0
  29. {lfss-0.11.3 → lfss-0.11.4}/lfss/cli/log.py +0 -0
  30. {lfss-0.11.3 → lfss-0.11.4}/lfss/cli/panel.py +0 -0
  31. {lfss-0.11.3 → lfss-0.11.4}/lfss/cli/serve.py +0 -0
  32. {lfss-0.11.3 → lfss-0.11.4}/lfss/cli/user.py +0 -0
  33. {lfss-0.11.3 → lfss-0.11.4}/lfss/cli/vacuum.py +0 -0
  34. {lfss-0.11.3 → lfss-0.11.4}/lfss/eng/__init__.py +0 -0
  35. {lfss-0.11.3 → lfss-0.11.4}/lfss/eng/bounded_pool.py +0 -0
  36. {lfss-0.11.3 → lfss-0.11.4}/lfss/eng/config.py +0 -0
  37. {lfss-0.11.3 → lfss-0.11.4}/lfss/eng/connection_pool.py +0 -0
  38. {lfss-0.11.3 → lfss-0.11.4}/lfss/eng/datatype.py +0 -0
  39. {lfss-0.11.3 → lfss-0.11.4}/lfss/eng/error.py +0 -0
  40. {lfss-0.11.3 → lfss-0.11.4}/lfss/eng/log.py +0 -0
  41. {lfss-0.11.3 → lfss-0.11.4}/lfss/eng/thumb.py +0 -0
  42. {lfss-0.11.3 → lfss-0.11.4}/lfss/eng/utils.py +0 -0
  43. {lfss-0.11.3 → lfss-0.11.4}/lfss/sql/init.sql +0 -0
  44. {lfss-0.11.3 → lfss-0.11.4}/lfss/sql/pragma.sql +0 -0
  45. {lfss-0.11.3 → lfss-0.11.4}/lfss/svc/app.py +0 -0
  46. {lfss-0.11.3 → lfss-0.11.4}/lfss/svc/app_base.py +0 -0
  47. {lfss-0.11.3 → lfss-0.11.4}/lfss/svc/app_dav.py +0 -0
  48. {lfss-0.11.3 → lfss-0.11.4}/lfss/svc/app_native.py +0 -0
  49. {lfss-0.11.3 → lfss-0.11.4}/lfss/svc/common_impl.py +0 -0
  50. {lfss-0.11.3 → lfss-0.11.4}/lfss/svc/request_log.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: lfss
3
- Version: 0.11.3
3
+ Version: 0.11.4
4
4
  Summary: Lightweight file storage service
5
5
  Home-page: https://github.com/MenxLi/lfss
6
6
  Author: Li, Mengxun
@@ -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("SELECT COUNT(*) FROM fmeta WHERE url LIKE ?", (url + '%', ))
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("SELECT COUNT(*) FROM fmeta WHERE url LIKE ? AND url NOT LIKE ?", (url + '%', url + '%/%'))
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("SELECT SUM(file_size) FROM fmeta WHERE url LIKE ? AND url NOT LIKE ?", (url + '%', url + '%/%'))
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("SELECT SUM(file_size) FROM fmeta WHERE url LIKE ?", (url + '%', ))
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("SELECT * FROM fmeta WHERE url LIKE ?", (old_url + '%', ))
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("SELECT * FROM fmeta WHERE url LIKE ?", (old_url + '%', ))
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("SELECT * FROM fmeta WHERE url LIKE ? AND owner_id = ?", (old_url + '%', user_id))
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("SELECT DISTINCT owner_id FROM fmeta WHERE url LIKE ?", (path + '%', ))
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("SELECT SUM(file_size) FROM fmeta WHERE owner_id = ? AND url LIKE ?", (r[0], path + '%'))
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 ('/', "\\", "'", '"', "*", "%"))), # prohibited characters
727
+ "[{}]".format("".join(re.escape(c) for c in ('/', "\\", "'", '"', "*"))), # prohibited characters
696
728
  ])
697
729
  ),
698
730
  )
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "lfss"
3
- version = "0.11.3"
3
+ version = "0.11.4"
4
4
  description = "Lightweight file storage service"
5
5
  authors = ["Li, Mengxun <mengxunli@whu.edu.cn>"]
6
6
  readme = "Readme.md"
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