luodawei commited on
Commit
a37fc18
·
verified ·
1 Parent(s): 2bec03a

Upload 3 files

Browse files
Files changed (3) hide show
  1. Dockerfile +16 -0
  2. app.py +145 -0
  3. requirements.txt +3 -0
Dockerfile ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Read the doc: https://huggingface.co/docs/hub/spaces-sdks-docker
2
+ # you will also find guides on how best to write your Dockerfile
3
+
4
+ FROM python:3.9
5
+
6
+ RUN useradd -m -u 1000 user
7
+ USER user
8
+ ENV PATH="/home/user/.local/bin:$PATH"
9
+
10
+ WORKDIR /app
11
+
12
+ COPY --chown=user ./requirements.txt requirements.txt
13
+ RUN pip install --no-cache-dir --upgrade -r requirements.txt
14
+
15
+ COPY --chown=user . /app
16
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
app.py ADDED
@@ -0,0 +1,145 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import hashlib
2
+ import hmac
3
+ import os
4
+ import shutil
5
+ import tempfile
6
+ from pathlib import Path
7
+
8
+ from fastapi import Depends, FastAPI, Header, HTTPException, Query, Request
9
+ from fastapi.responses import FileResponse
10
+
11
+
12
+ # Replace this value with sha256(FS_PASSWORD). Example:
13
+ # python3 -c 'import hashlib; print(hashlib.sha256(b"your-password").hexdigest())'
14
+ key = '25c20cbbe2eaf5f6dbf349965a04597c51df059dcaec38e6dcaae4e96bdc7c3e'
15
+
16
+
17
+ app = FastAPI(docs_url=None, redoc_url=None, openapi_url=None)
18
+
19
+
20
+ def _storage_root() -> Path:
21
+ configured = os.environ.get("FS_ROOT")
22
+ if configured:
23
+ root = Path(configured)
24
+ elif Path("/data").exists():
25
+ root = Path("/data/fs")
26
+ else:
27
+ root = Path("/app/data")
28
+
29
+ root.mkdir(parents=True, exist_ok=True)
30
+ return root.resolve()
31
+
32
+
33
+ ROOT = _storage_root()
34
+
35
+
36
+ def _sha256(value: str) -> str:
37
+ return hashlib.sha256(value.encode("utf-8")).hexdigest()
38
+
39
+
40
+ def _require_password(x_fs_password: str = Header(default="")) -> None:
41
+ if key == "REPLACE_WITH_SHA256_OF_FS_PASSWORD":
42
+ raise HTTPException(status_code=503, detail="server password hash is not configured")
43
+
44
+ if not x_fs_password or not hmac.compare_digest(_sha256(x_fs_password), key):
45
+ raise HTTPException(status_code=401, detail="unauthorized")
46
+
47
+
48
+ def _safe_path(path: str) -> Path:
49
+ if not path or path == ".":
50
+ return ROOT
51
+
52
+ if "\x00" in path:
53
+ raise HTTPException(status_code=400, detail="invalid path")
54
+
55
+ raw = Path(path)
56
+ if raw.is_absolute() or any(part in ("..", "") for part in raw.parts):
57
+ raise HTTPException(status_code=400, detail="invalid path")
58
+
59
+ resolved = (ROOT / raw).resolve()
60
+ if ROOT != resolved and ROOT not in resolved.parents:
61
+ raise HTTPException(status_code=400, detail="invalid path")
62
+ return resolved
63
+
64
+
65
+ @app.get("/")
66
+ async def root() -> dict:
67
+ raise HTTPException(status_code=404, detail="not found")
68
+
69
+
70
+ @app.put("/file", dependencies=[Depends(_require_password)])
71
+ async def upload(request: Request, path: str = Query(..., min_length=1)) -> dict:
72
+ target = _safe_path(path)
73
+ if target == ROOT:
74
+ raise HTTPException(status_code=400, detail="path must be a file")
75
+
76
+ target.parent.mkdir(parents=True, exist_ok=True)
77
+ fd, tmp_name = tempfile.mkstemp(prefix=f".{target.name}.", suffix=".upload", dir=str(target.parent))
78
+ bytes_written = 0
79
+
80
+ try:
81
+ with os.fdopen(fd, "wb") as tmp_file:
82
+ async for chunk in request.stream():
83
+ if chunk:
84
+ tmp_file.write(chunk)
85
+ bytes_written += len(chunk)
86
+ tmp_file.flush()
87
+ os.fsync(tmp_file.fileno())
88
+
89
+ os.replace(tmp_name, target)
90
+ except Exception:
91
+ try:
92
+ os.unlink(tmp_name)
93
+ except FileNotFoundError:
94
+ pass
95
+ raise
96
+
97
+ return {"path": str(target.relative_to(ROOT)), "bytes": bytes_written}
98
+
99
+
100
+ @app.get("/file", dependencies=[Depends(_require_password)])
101
+ async def download(path: str = Query(..., min_length=1)) -> FileResponse:
102
+ target = _safe_path(path)
103
+ if not target.is_file():
104
+ raise HTTPException(status_code=404, detail="file not found")
105
+
106
+ return FileResponse(target, media_type="application/octet-stream", filename=target.name)
107
+
108
+
109
+ @app.get("/ls", dependencies=[Depends(_require_password)])
110
+ async def ls(path: str = ".") -> list[str]:
111
+ target = _safe_path(path)
112
+ if not target.exists():
113
+ raise HTTPException(status_code=404, detail="path not found")
114
+
115
+ if target.is_file():
116
+ return [target.name]
117
+
118
+ entries = []
119
+ for child in target.iterdir():
120
+ name = child.name + ("/" if child.is_dir() else "")
121
+ entries.append(name)
122
+ return sorted(entries)
123
+
124
+
125
+ @app.delete("/rm", dependencies=[Depends(_require_password)])
126
+ async def rm(path: str = ".") -> dict:
127
+ target = _safe_path(path)
128
+ if target == ROOT:
129
+ removed = 0
130
+ for child in ROOT.iterdir():
131
+ if child.is_dir():
132
+ shutil.rmtree(child)
133
+ else:
134
+ child.unlink()
135
+ removed += 1
136
+ return {"removed": removed}
137
+
138
+ if not target.exists():
139
+ return {"removed": 0}
140
+
141
+ if target.is_dir():
142
+ shutil.rmtree(target)
143
+ else:
144
+ target.unlink()
145
+ return {"removed": 1}
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ fastapi==0.111.0
2
+ uvicorn[standard]==0.30.1
3
+ requests==2.32.3