xiaozhian commited on
Commit
ca533c7
·
verified ·
1 Parent(s): 16a70bc

Upload 17 files

Browse files
Files changed (17) hide show
  1. .gitignore +8 -0
  2. .htaccess +17 -0
  3. .nginx +14 -0
  4. Dockerfile +72 -0
  5. FileShare.php +240 -0
  6. LICENSE +674 -0
  7. api.php +93 -0
  8. css/marked.css +132 -0
  9. css/style.css +892 -0
  10. docker-compose.yml +11 -0
  11. favicon.png +0 -0
  12. file_api.php +112 -0
  13. index.php +814 -0
  14. init.php +70 -0
  15. router.php +72 -0
  16. settings.php +116 -0
  17. upload.php +118 -0
.gitignore ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ /data/*.db
2
+ /data/*.sqlite
3
+ /uploads/files/*
4
+ /uploads/images/*
5
+ !uploads/files/.htaccess
6
+ !uploads/images/.htaccess
7
+ !uploads/files/index.html
8
+ !uploads/images/index.html
.htaccess ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ AddType application/x-httpd-php83 .php
2
+
3
+ RewriteEngine On
4
+
5
+ RewriteCond %{REQUEST_URI} ^/([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})$
6
+ RewriteRule ^(.*)$ /index.php?id=%1 [L]
7
+
8
+ <FilesMatch "\.(db|bak|sql)$">
9
+ Require all denied
10
+ </FilesMatch>
11
+
12
+ # 添加默认文档类型
13
+ DirectoryIndex index.php
14
+
15
+ # 添加错误处理
16
+ php_flag display_errors on
17
+ php_value error_reporting E_ALL
.nginx ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ location ~* \.(db|bak|sql)$ {
2
+ deny all;
3
+ return 403;
4
+ }
5
+
6
+ location / {
7
+ try_files $uri $uri/ @rewrite;
8
+ }
9
+
10
+ location @rewrite {
11
+ if ($uri ~* "^/([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})$") {
12
+ rewrite ^/(.*)$ /index.php?id=$1 last;
13
+ }
14
+ }
Dockerfile ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM ubuntu:22.04
2
+
3
+ # 设置非交互模式和时区
4
+ ENV DEBIAN_FRONTEND=noninteractive
5
+ ENV TZ=Asia/Shanghai
6
+
7
+ # 更新包列表并安装基础依赖
8
+ RUN apt-get update && apt-get install -y \
9
+ software-properties-common \
10
+ curl \
11
+ wget \
12
+ git \
13
+ unzip \
14
+ libsqlite3-dev \
15
+ && rm -rf /var/lib/apt/lists/*
16
+
17
+ # 添加 PHP 仓库并安装 PHP
18
+ RUN add-apt-repository ppa:ondrej/php && \
19
+ apt-get update && \
20
+ apt-get install -y \
21
+ php8.3-cli \
22
+ php8.3-fpm \
23
+ php8.3-common \
24
+ php8.3-sqlite3 \
25
+ php8.3-xml \
26
+ php8.3-curl \
27
+ php8.3-mbstring \
28
+ && rm -rf /var/lib/apt/lists/*
29
+
30
+ # 设置工作目录
31
+ WORKDIR /var/www/html
32
+
33
+ # 复制项目文件
34
+ COPY . /var/www/html/
35
+
36
+ # 创建必要的目录和文件
37
+ RUN mkdir -p /var/www/html/data \
38
+ /var/www/html/uploads/images \
39
+ /var/www/html/uploads/files \
40
+ && touch /var/www/html/data/notes.db \
41
+ && chown -R www-data:www-data /var/www/html \
42
+ && find /var/www/html -type d -exec chmod 755 {} \; \
43
+ && find /var/www/html -type f -exec chmod 644 {} \; \
44
+ && chmod -R 777 /var/www/html/data \
45
+ && chmod -R 777 /var/www/html/uploads \
46
+ && chmod 666 /var/www/html/data/notes.db
47
+
48
+ # 配置 PHP
49
+ RUN echo "display_errors = On" >> /etc/php/8.3/cli/conf.d/error-reporting.ini && \
50
+ echo "error_reporting = E_ALL" >> /etc/php/8.3/cli/conf.d/error-reporting.ini && \
51
+ echo "log_errors = On" >> /etc/php/8.3/cli/conf.d/error-reporting.ini && \
52
+ echo "error_log = /dev/stderr" >> /etc/php/8.3/cli/conf.d/error-reporting.ini && \
53
+ echo "upload_max_filesize = 10G" >> /etc/php/8.3/cli/conf.d/uploads.ini && \
54
+ echo "post_max_size = 10G" >> /etc/php/8.3/cli/conf.d/uploads.ini && \
55
+ echo "memory_limit = 12G" >> /etc/php/8.3/cli/conf.d/uploads.ini && \
56
+ echo "max_execution_time = 3600" >> /etc/php/8.3/cli/conf.d/uploads.ini && \
57
+ echo "max_input_time = 3600" >> /etc/php/8.3/cli/conf.d/uploads.ini
58
+
59
+ # 创建启动脚本
60
+ RUN echo '#!/bin/bash\n\
61
+ # 启动 PHP 服务器\n\
62
+ exec php -d upload_max_filesize=10G -d post_max_size=10G -d memory_limit=12G -d max_execution_time=3600 -d max_input_time=3600 -S 0.0.0.0:7860 -t /var/www/html /var/www/html/router.php 2>&1 | grep -v "Accepted\|Closing\|preconnection"' > /usr/local/bin/docker-entrypoint.sh \
63
+ && chmod +x /usr/local/bin/docker-entrypoint.sh
64
+
65
+ # 暴露端口
66
+ EXPOSE 7860
67
+
68
+ # 切换到 www-data 用户
69
+ USER www-data
70
+
71
+ # 启动 PHP 内置服务器
72
+ CMD ["/usr/local/bin/docker-entrypoint.sh"]
FileShare.php ADDED
@@ -0,0 +1,240 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ class FileShare {
3
+ private $db;
4
+ private $settings;
5
+ private $upload_dir;
6
+ private $cleanup_interval = 3600; // 清理间隔:1小时
7
+
8
+ public function __construct() {
9
+ $this->settings = new Settings();
10
+ $this->db = new SQLite3($this->settings->getSetting('db_path'));
11
+ $this->upload_dir = __DIR__ . '/uploads/files/';
12
+
13
+ // 确保上传目录存在并创建必要的保护文件
14
+ $this->initializeUploadDirectory();
15
+
16
+ // 检查是否需要清理过期文件
17
+ $this->checkAndCleanup();
18
+ }
19
+
20
+ // 初始化上传目录和保护文件
21
+ private function initializeUploadDirectory() {
22
+ // 创建上传目录
23
+ if (!is_dir($this->upload_dir)) {
24
+ if (!mkdir($this->upload_dir, 0755, true)) {
25
+ throw new Exception('无法创建上传目录,请检查权限');
26
+ }
27
+
28
+ // 创建 .htaccess 文件来保护上传目录
29
+ $htaccess = $this->upload_dir . '.htaccess';
30
+ if (!file_exists($htaccess)) {
31
+ $htaccess_content = "Options -Indexes\n";
32
+ $htaccess_content .= "DirectoryIndex 403.html\n";
33
+ $htaccess_content .= "AddType text/plain .php\n";
34
+ $htaccess_content .= "AddType text/plain .html\n";
35
+ $htaccess_content .= "AddType text/plain .htm\n";
36
+ $htaccess_content .= "AddType text/plain .htaccess\n";
37
+ file_put_contents($htaccess, $htaccess_content);
38
+ }
39
+
40
+ // 创建 index.html 文件来防止目录列表
41
+ $index_html = $this->upload_dir . 'index.html';
42
+ if (!file_exists($index_html)) {
43
+ file_put_contents($index_html, '<!DOCTYPE html><html><head><title>403 Forbidden</title></head><body><h1>403 Forbidden</h1></body></html>');
44
+ }
45
+
46
+ // 创建 403.html 文件
47
+ $forbidden_page = $this->upload_dir . '403.html';
48
+ if (!file_exists($forbidden_page)) {
49
+ file_put_contents($forbidden_page, '<!DOCTYPE html><html><head><title>403 Forbidden</title></head><body><h1>403 Forbidden</h1><p>Access to this directory is forbidden.</p></body></html>');
50
+ }
51
+ }
52
+
53
+ // 设置目录权限
54
+ chmod($this->upload_dir, 0755);
55
+ }
56
+
57
+ // 检查并清理过期文件
58
+ private function checkAndCleanup() {
59
+ $lastCleanup = $this->settings->getSetting('last_cleanup', 0);
60
+
61
+ // 使用中国时间
62
+ date_default_timezone_set('Asia/Shanghai');
63
+ $currentTime = time();
64
+
65
+ if ($currentTime - $lastCleanup > $this->cleanup_interval) {
66
+ $this->settings->setSetting('last_cleanup', $currentTime);
67
+
68
+ $stmt = $this->db->prepare('
69
+ SELECT code, type, filepath
70
+ FROM files
71
+ WHERE expires_at < :current_time
72
+ OR (max_downloads > 0 AND current_downloads >= max_downloads)
73
+ ');
74
+ $stmt->bindValue(':current_time', $currentTime, SQLITE3_INTEGER);
75
+ $result = $stmt->execute();
76
+
77
+ // 删除过期的文件和记录
78
+ while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
79
+ if ($row['type'] === 'file' && $row['filepath'] && file_exists($row['filepath'])) {
80
+ unlink($row['filepath']);
81
+ }
82
+
83
+ $deleteStmt = $this->db->prepare('DELETE FROM files WHERE code = :code');
84
+ $deleteStmt->bindValue(':code', $row['code'], SQLITE3_TEXT);
85
+ $deleteStmt->execute();
86
+ }
87
+
88
+ // 清理空目录
89
+ $this->cleanEmptyDirectories($this->upload_dir);
90
+ }
91
+ }
92
+
93
+ // 清理空目录
94
+ private function cleanEmptyDirectories($dir) {
95
+ if (!is_dir($dir)) {
96
+ return;
97
+ }
98
+
99
+ $files = scandir($dir);
100
+ foreach ($files as $file) {
101
+ if ($file === '.' || $file === '..' || $file === '.htaccess' ||
102
+ $file === 'index.html' || $file === '403.html') {
103
+ continue;
104
+ }
105
+
106
+ $path = $dir . '/' . $file;
107
+ if (is_dir($path)) {
108
+ $this->cleanEmptyDirectories($path);
109
+ // 如果目录为空(只包含 . 和 ..),则删除
110
+ $subFiles = scandir($path);
111
+ if (count($subFiles) <= 2) { // 只有 . 和 ..
112
+ rmdir($path);
113
+ }
114
+ }
115
+ }
116
+ }
117
+
118
+ // 生成随机提取码
119
+ private function generateCode($length = 6) {
120
+ $chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
121
+ $code = '';
122
+ for ($i = 0; $i < $length; $i++) {
123
+ $code .= $chars[random_int(0, strlen($chars) - 1)];
124
+ }
125
+ return $code;
126
+ }
127
+
128
+ // 保存文件或文���
129
+ public function save($data) {
130
+ $this->checkAndCleanup();
131
+
132
+ $code = $this->generateCode();
133
+
134
+ // 使用中国时间
135
+ date_default_timezone_set('Asia/Shanghai');
136
+ $created_at = time(); // 当前中国时间
137
+ $expires_at = $created_at + intval($data['expire_time']); // 过期时间也是中国时间
138
+
139
+ if (isset($_FILES['file'])) {
140
+ // 处理文件上传
141
+ $file = $_FILES['file'];
142
+ $filename = $file['name'];
143
+ $filepath = $this->upload_dir . uniqid() . '_' . bin2hex(random_bytes(4)) . '_' . $filename;
144
+
145
+ if (!move_uploaded_file($file['tmp_name'], $filepath)) {
146
+ throw new Exception('文件上传失败');
147
+ }
148
+
149
+ $type = 'file';
150
+ $content = null;
151
+ } else {
152
+ // 处理文本内容
153
+ $filename = null;
154
+ $filepath = null;
155
+ $type = 'text';
156
+ $content = $data['content'];
157
+ }
158
+
159
+ $stmt = $this->db->prepare('
160
+ INSERT INTO files (code, filename, filepath, content, type, created_at, expires_at, max_downloads)
161
+ VALUES (:code, :filename, :filepath, :content, :type, :created_at, :expires_at, :max_downloads)
162
+ ');
163
+
164
+ $stmt->bindValue(':code', $code, SQLITE3_TEXT);
165
+ $stmt->bindValue(':filename', $filename, SQLITE3_TEXT);
166
+ $stmt->bindValue(':filepath', $filepath, SQLITE3_TEXT);
167
+ $stmt->bindValue(':content', $content, SQLITE3_TEXT);
168
+ $stmt->bindValue(':type', $type, SQLITE3_TEXT);
169
+ $stmt->bindValue(':created_at', $created_at, SQLITE3_INTEGER);
170
+ $stmt->bindValue(':expires_at', $expires_at, SQLITE3_INTEGER);
171
+ $stmt->bindValue(':max_downloads', intval($data['max_downloads']), SQLITE3_INTEGER);
172
+
173
+ if ($stmt->execute()) {
174
+ return $code;
175
+ }
176
+ throw new Exception('保存失败');
177
+ }
178
+
179
+ // 获取文件或文本
180
+ public function get($code) {
181
+ $this->checkAndCleanup();
182
+
183
+ $stmt = $this->db->prepare('SELECT * FROM files WHERE code = :code');
184
+ $stmt->bindValue(':code', $code, SQLITE3_TEXT);
185
+ $result = $stmt->execute();
186
+
187
+ if ($row = $result->fetchArray(SQLITE3_ASSOC)) {
188
+ // 使用中国时间进行比较
189
+ date_default_timezone_set('Asia/Shanghai');
190
+ if (time() > $row['expires_at']) {
191
+ $this->delete($code);
192
+ return null;
193
+ }
194
+
195
+ // 检查下载次数
196
+ if ($row['max_downloads'] > 0 && $row['current_downloads'] >= $row['max_downloads']) {
197
+ $this->delete($code);
198
+ return null;
199
+ }
200
+
201
+ // 更新下载次数
202
+ $stmt = $this->db->prepare('
203
+ UPDATE files
204
+ SET current_downloads = current_downloads + 1
205
+ WHERE code = :code
206
+ ');
207
+ $stmt->bindValue(':code', $code, SQLITE3_TEXT);
208
+ $stmt->execute();
209
+
210
+ // 返回更多文件信息
211
+ return [
212
+ 'type' => $row['type'],
213
+ 'content' => $row['content'],
214
+ 'filename' => $row['filename'],
215
+ 'filepath' => $row['filepath'],
216
+ 'current_downloads' => $row['current_downloads'],
217
+ 'max_downloads' => $row['max_downloads'],
218
+ 'expires_at' => $row['expires_at']
219
+ ];
220
+ }
221
+ return null;
222
+ }
223
+
224
+ // 删除文件或文本
225
+ private function delete($code) {
226
+ $stmt = $this->db->prepare('SELECT * FROM files WHERE code = :code');
227
+ $stmt->bindValue(':code', $code, SQLITE3_TEXT);
228
+ $result = $stmt->execute();
229
+
230
+ if ($row = $result->fetchArray(SQLITE3_ASSOC)) {
231
+ if ($row['type'] === 'file' && $row['filepath'] && file_exists($row['filepath'])) {
232
+ unlink($row['filepath']);
233
+ }
234
+ }
235
+
236
+ $stmt = $this->db->prepare('DELETE FROM files WHERE code = :code');
237
+ $stmt->bindValue(':code', $code, SQLITE3_TEXT);
238
+ $stmt->execute();
239
+ }
240
+ }
LICENSE ADDED
@@ -0,0 +1,674 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ GNU GENERAL PUBLIC LICENSE
2
+ Version 3, 29 June 2007
3
+
4
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
5
+ Everyone is permitted to copy and distribute verbatim copies
6
+ of this license document, but changing it is not allowed.
7
+
8
+ Preamble
9
+
10
+ The GNU General Public License is a free, copyleft license for
11
+ software and other kinds of works.
12
+
13
+ The licenses for most software and other practical works are designed
14
+ to take away your freedom to share and change the works. By contrast,
15
+ the GNU General Public License is intended to guarantee your freedom to
16
+ share and change all versions of a program--to make sure it remains free
17
+ software for all its users. We, the Free Software Foundation, use the
18
+ GNU General Public License for most of our software; it applies also to
19
+ any other work released this way by its authors. You can apply it to
20
+ your programs, too.
21
+
22
+ When we speak of free software, we are referring to freedom, not
23
+ price. Our General Public Licenses are designed to make sure that you
24
+ have the freedom to distribute copies of free software (and charge for
25
+ them if you wish), that you receive source code or can get it if you
26
+ want it, that you can change the software or use pieces of it in new
27
+ free programs, and that you know you can do these things.
28
+
29
+ To protect your rights, we need to prevent others from denying you
30
+ these rights or asking you to surrender the rights. Therefore, you have
31
+ certain responsibilities if you distribute copies of the software, or if
32
+ you modify it: responsibilities to respect the freedom of others.
33
+
34
+ For example, if you distribute copies of such a program, whether
35
+ gratis or for a fee, you must pass on to the recipients the same
36
+ freedoms that you received. You must make sure that they, too, receive
37
+ or can get the source code. And you must show them these terms so they
38
+ know their rights.
39
+
40
+ Developers that use the GNU GPL protect your rights with two steps:
41
+ (1) assert copyright on the software, and (2) offer you this License
42
+ giving you legal permission to copy, distribute and/or modify it.
43
+
44
+ For the developers' and authors' protection, the GPL clearly explains
45
+ that there is no warranty for this free software. For both users' and
46
+ authors' sake, the GPL requires that modified versions be marked as
47
+ changed, so that their problems will not be attributed erroneously to
48
+ authors of previous versions.
49
+
50
+ Some devices are designed to deny users access to install or run
51
+ modified versions of the software inside them, although the manufacturer
52
+ can do so. This is fundamentally incompatible with the aim of
53
+ protecting users' freedom to change the software. The systematic
54
+ pattern of such abuse occurs in the area of products for individuals to
55
+ use, which is precisely where it is most unacceptable. Therefore, we
56
+ have designed this version of the GPL to prohibit the practice for those
57
+ products. If such problems arise substantially in other domains, we
58
+ stand ready to extend this provision to those domains in future versions
59
+ of the GPL, as needed to protect the freedom of users.
60
+
61
+ Finally, every program is threatened constantly by software patents.
62
+ States should not allow patents to restrict development and use of
63
+ software on general-purpose computers, but in those that do, we wish to
64
+ avoid the special danger that patents applied to a free program could
65
+ make it effectively proprietary. To prevent this, the GPL assures that
66
+ patents cannot be used to render the program non-free.
67
+
68
+ The precise terms and conditions for copying, distribution and
69
+ modification follow.
70
+
71
+ TERMS AND CONDITIONS
72
+
73
+ 0. Definitions.
74
+
75
+ "This License" refers to version 3 of the GNU General Public License.
76
+
77
+ "Copyright" also means copyright-like laws that apply to other kinds of
78
+ works, such as semiconductor masks.
79
+
80
+ "The Program" refers to any copyrightable work licensed under this
81
+ License. Each licensee is addressed as "you". "Licensees" and
82
+ "recipients" may be individuals or organizations.
83
+
84
+ To "modify" a work means to copy from or adapt all or part of the work
85
+ in a fashion requiring copyright permission, other than the making of an
86
+ exact copy. The resulting work is called a "modified version" of the
87
+ earlier work or a work "based on" the earlier work.
88
+
89
+ A "covered work" means either the unmodified Program or a work based
90
+ on the Program.
91
+
92
+ To "propagate" a work means to do anything with it that, without
93
+ permission, would make you directly or secondarily liable for
94
+ infringement under applicable copyright law, except executing it on a
95
+ computer or modifying a private copy. Propagation includes copying,
96
+ distribution (with or without modification), making available to the
97
+ public, and in some countries other activities as well.
98
+
99
+ To "convey" a work means any kind of propagation that enables other
100
+ parties to make or receive copies. Mere interaction with a user through
101
+ a computer network, with no transfer of a copy, is not conveying.
102
+
103
+ An interactive user interface displays "Appropriate Legal Notices"
104
+ to the extent that it includes a convenient and prominently visible
105
+ feature that (1) displays an appropriate copyright notice, and (2)
106
+ tells the user that there is no warranty for the work (except to the
107
+ extent that warranties are provided), that licensees may convey the
108
+ work under this License, and how to view a copy of this License. If
109
+ the interface presents a list of user commands or options, such as a
110
+ menu, a prominent item in the list meets this criterion.
111
+
112
+ 1. Source Code.
113
+
114
+ The "source code" for a work means the preferred form of the work
115
+ for making modifications to it. "Object code" means any non-source
116
+ form of a work.
117
+
118
+ A "Standard Interface" means an interface that either is an official
119
+ standard defined by a recognized standards body, or, in the case of
120
+ interfaces specified for a particular programming language, one that
121
+ is widely used among developers working in that language.
122
+
123
+ The "System Libraries" of an executable work include anything, other
124
+ than the work as a whole, that (a) is included in the normal form of
125
+ packaging a Major Component, but which is not part of that Major
126
+ Component, and (b) serves only to enable use of the work with that
127
+ Major Component, or to implement a Standard Interface for which an
128
+ implementation is available to the public in source code form. A
129
+ "Major Component", in this context, means a major essential component
130
+ (kernel, window system, and so on) of the specific operating system
131
+ (if any) on which the executable work runs, or a compiler used to
132
+ produce the work, or an object code interpreter used to run it.
133
+
134
+ The "Corresponding Source" for a work in object code form means all
135
+ the source code needed to generate, install, and (for an executable
136
+ work) run the object code and to modify the work, including scripts to
137
+ control those activities. However, it does not include the work's
138
+ System Libraries, or general-purpose tools or generally available free
139
+ programs which are used unmodified in performing those activities but
140
+ which are not part of the work. For example, Corresponding Source
141
+ includes interface definition files associated with source files for
142
+ the work, and the source code for shared libraries and dynamically
143
+ linked subprograms that the work is specifically designed to require,
144
+ such as by intimate data communication or control flow between those
145
+ subprograms and other parts of the work.
146
+
147
+ The Corresponding Source need not include anything that users
148
+ can regenerate automatically from other parts of the Corresponding
149
+ Source.
150
+
151
+ The Corresponding Source for a work in source code form is that
152
+ same work.
153
+
154
+ 2. Basic Permissions.
155
+
156
+ All rights granted under this License are granted for the term of
157
+ copyright on the Program, and are irrevocable provided the stated
158
+ conditions are met. This License explicitly affirms your unlimited
159
+ permission to run the unmodified Program. The output from running a
160
+ covered work is covered by this License only if the output, given its
161
+ content, constitutes a covered work. This License acknowledges your
162
+ rights of fair use or other equivalent, as provided by copyright law.
163
+
164
+ You may make, run and propagate covered works that you do not
165
+ convey, without conditions so long as your license otherwise remains
166
+ in force. You may convey covered works to others for the sole purpose
167
+ of having them make modifications exclusively for you, or provide you
168
+ with facilities for running those works, provided that you comply with
169
+ the terms of this License in conveying all material for which you do
170
+ not control copyright. Those thus making or running the covered works
171
+ for you must do so exclusively on your behalf, under your direction
172
+ and control, on terms that prohibit them from making any copies of
173
+ your copyrighted material outside their relationship with you.
174
+
175
+ Conveying under any other circumstances is permitted solely under
176
+ the conditions stated below. Sublicensing is not allowed; section 10
177
+ makes it unnecessary.
178
+
179
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180
+
181
+ No covered work shall be deemed part of an effective technological
182
+ measure under any applicable law fulfilling obligations under article
183
+ 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184
+ similar laws prohibiting or restricting circumvention of such
185
+ measures.
186
+
187
+ When you convey a covered work, you waive any legal power to forbid
188
+ circumvention of technological measures to the extent such circumvention
189
+ is effected by exercising rights under this License with respect to
190
+ the covered work, and you disclaim any intention to limit operation or
191
+ modification of the work as a means of enforcing, against the work's
192
+ users, your or third parties' legal rights to forbid circumvention of
193
+ technological measures.
194
+
195
+ 4. Conveying Verbatim Copies.
196
+
197
+ You may convey verbatim copies of the Program's source code as you
198
+ receive it, in any medium, provided that you conspicuously and
199
+ appropriately publish on each copy an appropriate copyright notice;
200
+ keep intact all notices stating that this License and any
201
+ non-permissive terms added in accord with section 7 apply to the code;
202
+ keep intact all notices of the absence of any warranty; and give all
203
+ recipients a copy of this License along with the Program.
204
+
205
+ You may charge any price or no price for each copy that you convey,
206
+ and you may offer support or warranty protection for a fee.
207
+
208
+ 5. Conveying Modified Source Versions.
209
+
210
+ You may convey a work based on the Program, or the modifications to
211
+ produce it from the Program, in the form of source code under the
212
+ terms of section 4, provided that you also meet all of these conditions:
213
+
214
+ a) The work must carry prominent notices stating that you modified
215
+ it, and giving a relevant date.
216
+
217
+ b) The work must carry prominent notices stating that it is
218
+ released under this License and any conditions added under section
219
+ 7. This requirement modifies the requirement in section 4 to
220
+ "keep intact all notices".
221
+
222
+ c) You must license the entire work, as a whole, under this
223
+ License to anyone who comes into possession of a copy. This
224
+ License will therefore apply, along with any applicable section 7
225
+ additional terms, to the whole of the work, and all its parts,
226
+ regardless of how they are packaged. This License gives no
227
+ permission to license the work in any other way, but it does not
228
+ invalidate such permission if you have separately received it.
229
+
230
+ d) If the work has interactive user interfaces, each must display
231
+ Appropriate Legal Notices; however, if the Program has interactive
232
+ interfaces that do not display Appropriate Legal Notices, your
233
+ work need not make them do so.
234
+
235
+ A compilation of a covered work with other separate and independent
236
+ works, which are not by their nature extensions of the covered work,
237
+ and which are not combined with it such as to form a larger program,
238
+ in or on a volume of a storage or distribution medium, is called an
239
+ "aggregate" if the compilation and its resulting copyright are not
240
+ used to limit the access or legal rights of the compilation's users
241
+ beyond what the individual works permit. Inclusion of a covered work
242
+ in an aggregate does not cause this License to apply to the other
243
+ parts of the aggregate.
244
+
245
+ 6. Conveying Non-Source Forms.
246
+
247
+ You may convey a covered work in object code form under the terms
248
+ of sections 4 and 5, provided that you also convey the
249
+ machine-readable Corresponding Source under the terms of this License,
250
+ in one of these ways:
251
+
252
+ a) Convey the object code in, or embodied in, a physical product
253
+ (including a physical distribution medium), accompanied by the
254
+ Corresponding Source fixed on a durable physical medium
255
+ customarily used for software interchange.
256
+
257
+ b) Convey the object code in, or embodied in, a physical product
258
+ (including a physical distribution medium), accompanied by a
259
+ written offer, valid for at least three years and valid for as
260
+ long as you offer spare parts or customer support for that product
261
+ model, to give anyone who possesses the object code either (1) a
262
+ copy of the Corresponding Source for all the software in the
263
+ product that is covered by this License, on a durable physical
264
+ medium customarily used for software interchange, for a price no
265
+ more than your reasonable cost of physically performing this
266
+ conveying of source, or (2) access to copy the
267
+ Corresponding Source from a network server at no charge.
268
+
269
+ c) Convey individual copies of the object code with a copy of the
270
+ written offer to provide the Corresponding Source. This
271
+ alternative is allowed only occasionally and noncommercially, and
272
+ only if you received the object code with such an offer, in accord
273
+ with subsection 6b.
274
+
275
+ d) Convey the object code by offering access from a designated
276
+ place (gratis or for a charge), and offer equivalent access to the
277
+ Corresponding Source in the same way through the same place at no
278
+ further charge. You need not require recipients to copy the
279
+ Corresponding Source along with the object code. If the place to
280
+ copy the object code is a network server, the Corresponding Source
281
+ may be on a different server (operated by you or a third party)
282
+ that supports equivalent copying facilities, provided you maintain
283
+ clear directions next to the object code saying where to find the
284
+ Corresponding Source. Regardless of what server hosts the
285
+ Corresponding Source, you remain obligated to ensure that it is
286
+ available for as long as needed to satisfy these requirements.
287
+
288
+ e) Convey the object code using peer-to-peer transmission, provided
289
+ you inform other peers where the object code and Corresponding
290
+ Source of the work are being offered to the general public at no
291
+ charge under subsection 6d.
292
+
293
+ A separable portion of the object code, whose source code is excluded
294
+ from the Corresponding Source as a System Library, need not be
295
+ included in conveying the object code work.
296
+
297
+ A "User Product" is either (1) a "consumer product", which means any
298
+ tangible personal property which is normally used for personal, family,
299
+ or household purposes, or (2) anything designed or sold for incorporation
300
+ into a dwelling. In determining whether a product is a consumer product,
301
+ doubtful cases shall be resolved in favor of coverage. For a particular
302
+ product received by a particular user, "normally used" refers to a
303
+ typical or common use of that class of product, regardless of the status
304
+ of the particular user or of the way in which the particular user
305
+ actually uses, or expects or is expected to use, the product. A product
306
+ is a consumer product regardless of whether the product has substantial
307
+ commercial, industrial or non-consumer uses, unless such uses represent
308
+ the only significant mode of use of the product.
309
+
310
+ "Installation Information" for a User Product means any methods,
311
+ procedures, authorization keys, or other information required to install
312
+ and execute modified versions of a covered work in that User Product from
313
+ a modified version of its Corresponding Source. The information must
314
+ suffice to ensure that the continued functioning of the modified object
315
+ code is in no case prevented or interfered with solely because
316
+ modification has been made.
317
+
318
+ If you convey an object code work under this section in, or with, or
319
+ specifically for use in, a User Product, and the conveying occurs as
320
+ part of a transaction in which the right of possession and use of the
321
+ User Product is transferred to the recipient in perpetuity or for a
322
+ fixed term (regardless of how the transaction is characterized), the
323
+ Corresponding Source conveyed under this section must be accompanied
324
+ by the Installation Information. But this requirement does not apply
325
+ if neither you nor any third party retains the ability to install
326
+ modified object code on the User Product (for example, the work has
327
+ been installed in ROM).
328
+
329
+ The requirement to provide Installation Information does not include a
330
+ requirement to continue to provide support service, warranty, or updates
331
+ for a work that has been modified or installed by the recipient, or for
332
+ the User Product in which it has been modified or installed. Access to a
333
+ network may be denied when the modification itself materially and
334
+ adversely affects the operation of the network or violates the rules and
335
+ protocols for communication across the network.
336
+
337
+ Corresponding Source conveyed, and Installation Information provided,
338
+ in accord with this section must be in a format that is publicly
339
+ documented (and with an implementation available to the public in
340
+ source code form), and must require no special password or key for
341
+ unpacking, reading or copying.
342
+
343
+ 7. Additional Terms.
344
+
345
+ "Additional permissions" are terms that supplement the terms of this
346
+ License by making exceptions from one or more of its conditions.
347
+ Additional permissions that are applicable to the entire Program shall
348
+ be treated as though they were included in this License, to the extent
349
+ that they are valid under applicable law. If additional permissions
350
+ apply only to part of the Program, that part may be used separately
351
+ under those permissions, but the entire Program remains governed by
352
+ this License without regard to the additional permissions.
353
+
354
+ When you convey a copy of a covered work, you may at your option
355
+ remove any additional permissions from that copy, or from any part of
356
+ it. (Additional permissions may be written to require their own
357
+ removal in certain cases when you modify the work.) You may place
358
+ additional permissions on material, added by you to a covered work,
359
+ for which you have or can give appropriate copyright permission.
360
+
361
+ Notwithstanding any other provision of this License, for material you
362
+ add to a covered work, you may (if authorized by the copyright holders of
363
+ that material) supplement the terms of this License with terms:
364
+
365
+ a) Disclaiming warranty or limiting liability differently from the
366
+ terms of sections 15 and 16 of this License; or
367
+
368
+ b) Requiring preservation of specified reasonable legal notices or
369
+ author attributions in that material or in the Appropriate Legal
370
+ Notices displayed by works containing it; or
371
+
372
+ c) Prohibiting misrepresentation of the origin of that material, or
373
+ requiring that modified versions of such material be marked in
374
+ reasonable ways as different from the original version; or
375
+
376
+ d) Limiting the use for publicity purposes of names of licensors or
377
+ authors of the material; or
378
+
379
+ e) Declining to grant rights under trademark law for use of some
380
+ trade names, trademarks, or service marks; or
381
+
382
+ f) Requiring indemnification of licensors and authors of that
383
+ material by anyone who conveys the material (or modified versions of
384
+ it) with contractual assumptions of liability to the recipient, for
385
+ any liability that these contractual assumptions directly impose on
386
+ those licensors and authors.
387
+
388
+ All other non-permissive additional terms are considered "further
389
+ restrictions" within the meaning of section 10. If the Program as you
390
+ received it, or any part of it, contains a notice stating that it is
391
+ governed by this License along with a term that is a further
392
+ restriction, you may remove that term. If a license document contains
393
+ a further restriction but permits relicensing or conveying under this
394
+ License, you may add to a covered work material governed by the terms
395
+ of that license document, provided that the further restriction does
396
+ not survive such relicensing or conveying.
397
+
398
+ If you add terms to a covered work in accord with this section, you
399
+ must place, in the relevant source files, a statement of the
400
+ additional terms that apply to those files, or a notice indicating
401
+ where to find the applicable terms.
402
+
403
+ Additional terms, permissive or non-permissive, may be stated in the
404
+ form of a separately written license, or stated as exceptions;
405
+ the above requirements apply either way.
406
+
407
+ 8. Termination.
408
+
409
+ You may not propagate or modify a covered work except as expressly
410
+ provided under this License. Any attempt otherwise to propagate or
411
+ modify it is void, and will automatically terminate your rights under
412
+ this License (including any patent licenses granted under the third
413
+ paragraph of section 11).
414
+
415
+ However, if you cease all violation of this License, then your
416
+ license from a particular copyright holder is reinstated (a)
417
+ provisionally, unless and until the copyright holder explicitly and
418
+ finally terminates your license, and (b) permanently, if the copyright
419
+ holder fails to notify you of the violation by some reasonable means
420
+ prior to 60 days after the cessation.
421
+
422
+ Moreover, your license from a particular copyright holder is
423
+ reinstated permanently if the copyright holder notifies you of the
424
+ violation by some reasonable means, this is the first time you have
425
+ received notice of violation of this License (for any work) from that
426
+ copyright holder, and you cure the violation prior to 30 days after
427
+ your receipt of the notice.
428
+
429
+ Termination of your rights under this section does not terminate the
430
+ licenses of parties who have received copies or rights from you under
431
+ this License. If your rights have been terminated and not permanently
432
+ reinstated, you do not qualify to receive new licenses for the same
433
+ material under section 10.
434
+
435
+ 9. Acceptance Not Required for Having Copies.
436
+
437
+ You are not required to accept this License in order to receive or
438
+ run a copy of the Program. Ancillary propagation of a covered work
439
+ occurring solely as a consequence of using peer-to-peer transmission
440
+ to receive a copy likewise does not require acceptance. However,
441
+ nothing other than this License grants you permission to propagate or
442
+ modify any covered work. These actions infringe copyright if you do
443
+ not accept this License. Therefore, by modifying or propagating a
444
+ covered work, you indicate your acceptance of this License to do so.
445
+
446
+ 10. Automatic Licensing of Downstream Recipients.
447
+
448
+ Each time you convey a covered work, the recipient automatically
449
+ receives a license from the original licensors, to run, modify and
450
+ propagate that work, subject to this License. You are not responsible
451
+ for enforcing compliance by third parties with this License.
452
+
453
+ An "entity transaction" is a transaction transferring control of an
454
+ organization, or substantially all assets of one, or subdividing an
455
+ organization, or merging organizations. If propagation of a covered
456
+ work results from an entity transaction, each party to that
457
+ transaction who receives a copy of the work also receives whatever
458
+ licenses to the work the party's predecessor in interest had or could
459
+ give under the previous paragraph, plus a right to possession of the
460
+ Corresponding Source of the work from the predecessor in interest, if
461
+ the predecessor has it or can get it with reasonable efforts.
462
+
463
+ You may not impose any further restrictions on the exercise of the
464
+ rights granted or affirmed under this License. For example, you may
465
+ not impose a license fee, royalty, or other charge for exercise of
466
+ rights granted under this License, and you may not initiate litigation
467
+ (including a cross-claim or counterclaim in a lawsuit) alleging that
468
+ any patent claim is infringed by making, using, selling, offering for
469
+ sale, or importing the Program or any portion of it.
470
+
471
+ 11. Patents.
472
+
473
+ A "contributor" is a copyright holder who authorizes use under this
474
+ License of the Program or a work on which the Program is based. The
475
+ work thus licensed is called the contributor's "contributor version".
476
+
477
+ A contributor's "essential patent claims" are all patent claims
478
+ owned or controlled by the contributor, whether already acquired or
479
+ hereafter acquired, that would be infringed by some manner, permitted
480
+ by this License, of making, using, or selling its contributor version,
481
+ but do not include claims that would be infringed only as a
482
+ consequence of further modification of the contributor version. For
483
+ purposes of this definition, "control" includes the right to grant
484
+ patent sublicenses in a manner consistent with the requirements of
485
+ this License.
486
+
487
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
488
+ patent license under the contributor's essential patent claims, to
489
+ make, use, sell, offer for sale, import and otherwise run, modify and
490
+ propagate the contents of its contributor version.
491
+
492
+ In the following three paragraphs, a "patent license" is any express
493
+ agreement or commitment, however denominated, not to enforce a patent
494
+ (such as an express permission to practice a patent or covenant not to
495
+ sue for patent infringement). To "grant" such a patent license to a
496
+ party means to make such an agreement or commitment not to enforce a
497
+ patent against the party.
498
+
499
+ If you convey a covered work, knowingly relying on a patent license,
500
+ and the Corresponding Source of the work is not available for anyone
501
+ to copy, free of charge and under the terms of this License, through a
502
+ publicly available network server or other readily accessible means,
503
+ then you must either (1) cause the Corresponding Source to be so
504
+ available, or (2) arrange to deprive yourself of the benefit of the
505
+ patent license for this particular work, or (3) arrange, in a manner
506
+ consistent with the requirements of this License, to extend the patent
507
+ license to downstream recipients. "Knowingly relying" means you have
508
+ actual knowledge that, but for the patent license, your conveying the
509
+ covered work in a country, or your recipient's use of the covered work
510
+ in a country, would infringe one or more identifiable patents in that
511
+ country that you have reason to believe are valid.
512
+
513
+ If, pursuant to or in connection with a single transaction or
514
+ arrangement, you convey, or propagate by procuring conveyance of, a
515
+ covered work, and grant a patent license to some of the parties
516
+ receiving the covered work authorizing them to use, propagate, modify
517
+ or convey a specific copy of the covered work, then the patent license
518
+ you grant is automatically extended to all recipients of the covered
519
+ work and works based on it.
520
+
521
+ A patent license is "discriminatory" if it does not include within
522
+ the scope of its coverage, prohibits the exercise of, or is
523
+ conditioned on the non-exercise of one or more of the rights that are
524
+ specifically granted under this License. You may not convey a covered
525
+ work if you are a party to an arrangement with a third party that is
526
+ in the business of distributing software, under which you make payment
527
+ to the third party based on the extent of your activity of conveying
528
+ the work, and under which the third party grants, to any of the
529
+ parties who would receive the covered work from you, a discriminatory
530
+ patent license (a) in connection with copies of the covered work
531
+ conveyed by you (or copies made from those copies), or (b) primarily
532
+ for and in connection with specific products or compilations that
533
+ contain the covered work, unless you entered into that arrangement,
534
+ or that patent license was granted, prior to 28 March 2007.
535
+
536
+ Nothing in this License shall be construed as excluding or limiting
537
+ any implied license or other defenses to infringement that may
538
+ otherwise be available to you under applicable patent law.
539
+
540
+ 12. No Surrender of Others' Freedom.
541
+
542
+ If conditions are imposed on you (whether by court order, agreement or
543
+ otherwise) that contradict the conditions of this License, they do not
544
+ excuse you from the conditions of this License. If you cannot convey a
545
+ covered work so as to satisfy simultaneously your obligations under this
546
+ License and any other pertinent obligations, then as a consequence you may
547
+ not convey it at all. For example, if you agree to terms that obligate you
548
+ to collect a royalty for further conveying from those to whom you convey
549
+ the Program, the only way you could satisfy both those terms and this
550
+ License would be to refrain entirely from conveying the Program.
551
+
552
+ 13. Use with the GNU Affero General Public License.
553
+
554
+ Notwithstanding any other provision of this License, you have
555
+ permission to link or combine any covered work with a work licensed
556
+ under version 3 of the GNU Affero General Public License into a single
557
+ combined work, and to convey the resulting work. The terms of this
558
+ License will continue to apply to the part which is the covered work,
559
+ but the special requirements of the GNU Affero General Public License,
560
+ section 13, concerning interaction through a network will apply to the
561
+ combination as such.
562
+
563
+ 14. Revised Versions of this License.
564
+
565
+ The Free Software Foundation may publish revised and/or new versions of
566
+ the GNU General Public License from time to time. Such new versions will
567
+ be similar in spirit to the present version, but may differ in detail to
568
+ address new problems or concerns.
569
+
570
+ Each version is given a distinguishing version number. If the
571
+ Program specifies that a certain numbered version of the GNU General
572
+ Public License "or any later version" applies to it, you have the
573
+ option of following the terms and conditions either of that numbered
574
+ version or of any later version published by the Free Software
575
+ Foundation. If the Program does not specify a version number of the
576
+ GNU General Public License, you may choose any version ever published
577
+ by the Free Software Foundation.
578
+
579
+ If the Program specifies that a proxy can decide which future
580
+ versions of the GNU General Public License can be used, that proxy's
581
+ public statement of acceptance of a version permanently authorizes you
582
+ to choose that version for the Program.
583
+
584
+ Later license versions may give you additional or different
585
+ permissions. However, no additional obligations are imposed on any
586
+ author or copyright holder as a result of your choosing to follow a
587
+ later version.
588
+
589
+ 15. Disclaimer of Warranty.
590
+
591
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592
+ APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593
+ HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594
+ OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595
+ THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596
+ PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597
+ IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598
+ ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599
+
600
+ 16. Limitation of Liability.
601
+
602
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603
+ WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604
+ THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605
+ GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606
+ USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607
+ DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608
+ PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609
+ EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610
+ SUCH DAMAGES.
611
+
612
+ 17. Interpretation of Sections 15 and 16.
613
+
614
+ If the disclaimer of warranty and limitation of liability provided
615
+ above cannot be given local legal effect according to their terms,
616
+ reviewing courts shall apply local law that most closely approximates
617
+ an absolute waiver of all civil liability in connection with the
618
+ Program, unless a warranty or assumption of liability accompanies a
619
+ copy of the Program in return for a fee.
620
+
621
+ END OF TERMS AND CONDITIONS
622
+
623
+ How to Apply These Terms to Your New Programs
624
+
625
+ If you develop a new program, and you want it to be of the greatest
626
+ possible use to the public, the best way to achieve this is to make it
627
+ free software which everyone can redistribute and change under these terms.
628
+
629
+ To do so, attach the following notices to the program. It is safest
630
+ to attach them to the start of each source file to most effectively
631
+ state the exclusion of warranty; and each file should have at least
632
+ the "copyright" line and a pointer to where the full notice is found.
633
+
634
+ <one line to give the program's name and a brief idea of what it does.>
635
+ Copyright (C) <year> <name of author>
636
+
637
+ This program is free software: you can redistribute it and/or modify
638
+ it under the terms of the GNU General Public License as published by
639
+ the Free Software Foundation, either version 3 of the License, or
640
+ (at your option) any later version.
641
+
642
+ This program is distributed in the hope that it will be useful,
643
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
644
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645
+ GNU General Public License for more details.
646
+
647
+ You should have received a copy of the GNU General Public License
648
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
649
+
650
+ Also add information on how to contact you by electronic and paper mail.
651
+
652
+ If the program does terminal interaction, make it output a short
653
+ notice like this when it starts in an interactive mode:
654
+
655
+ <program> Copyright (C) <year> <name of author>
656
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657
+ This is free software, and you are welcome to redistribute it
658
+ under certain conditions; type `show c' for details.
659
+
660
+ The hypothetical commands `show w' and `show c' should show the appropriate
661
+ parts of the General Public License. Of course, your program's commands
662
+ might be different; for a GUI interface, you would use an "about box".
663
+
664
+ You should also get your employer (if you work as a programmer) or school,
665
+ if any, to sign a "copyright disclaimer" for the program, if necessary.
666
+ For more information on this, and how to apply and follow the GNU GPL, see
667
+ <https://www.gnu.org/licenses/>.
668
+
669
+ The GNU General Public License does not permit incorporating your program
670
+ into proprietary programs. If your program is a subroutine library, you
671
+ may consider it more useful to permit linking proprietary applications with
672
+ the library. If this is what you want to do, use the GNU Lesser General
673
+ Public License instead of this License. But first, please read
674
+ <https://www.gnu.org/licenses/why-not-lgpl.html>.
api.php ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ // api.php
3
+ header('Content-Type: application/json');
4
+
5
+ require_once 'settings.php';
6
+
7
+ class PasteAPI {
8
+ private $db;
9
+ private $settings;
10
+
11
+ public function __construct() {
12
+ $this->settings = new Settings();
13
+ $this->db = new SQLite3($this->settings->getSetting('db_path'));
14
+ }
15
+
16
+ public function createPaste($data) {
17
+ // 验证输入
18
+ if (empty($data['content'])) {
19
+ return ['status' => 'error', 'message' => 'Content is required'];
20
+ }
21
+
22
+ // 验证和规范化参数
23
+ $expire_time = isset($data['expire_time']) ? (int)$data['expire_time'] : 2592000; // 默认1个月
24
+ $max_expire_time = (int)$this->settings->getSetting('max_expire_time', 31536000);
25
+ if ($expire_time > $max_expire_time) {
26
+ $expire_time = $max_expire_time;
27
+ }
28
+
29
+ $max_views = isset($data['max_views']) ? (int)$data['max_views'] : 0;
30
+ if ($max_views > 25565) {
31
+ $max_views = 25565;
32
+ }
33
+
34
+ $is_markdown = isset($data['is_markdown']) && $data['is_markdown'] ? 1 : 0;
35
+ $is_encrypted = isset($data['is_encrypted']) && $data['is_encrypted'] ? 1 : 0;
36
+
37
+ // 生成UUID
38
+ $uuid = $this->generateUUID();
39
+
40
+ // 准备数据
41
+ $now = time();
42
+ $expires_at = $now + $expire_time;
43
+
44
+ // 插入数据
45
+ $stmt = $this->db->prepare('INSERT INTO notes (
46
+ uuid, content, created_at, expires_at, max_views,
47
+ current_views, is_encrypted, is_markdown
48
+ ) VALUES (
49
+ :uuid, :content, :created_at, :expires_at, :max_views,
50
+ 0, :is_encrypted, :is_markdown
51
+ )');
52
+
53
+ $stmt->bindValue(':uuid', $uuid, SQLITE3_TEXT);
54
+ $stmt->bindValue(':content', $data['content'], SQLITE3_TEXT);
55
+ $stmt->bindValue(':created_at', $now, SQLITE3_INTEGER);
56
+ $stmt->bindValue(':expires_at', $expires_at, SQLITE3_INTEGER);
57
+ $stmt->bindValue(':max_views', $max_views, SQLITE3_INTEGER);
58
+ $stmt->bindValue(':is_encrypted', $is_encrypted, SQLITE3_INTEGER);
59
+ $stmt->bindValue(':is_markdown', $is_markdown, SQLITE3_INTEGER);
60
+
61
+ if ($stmt->execute()) {
62
+ $response = ['status' => 'success', 'uuid' => $uuid];
63
+ if ($is_encrypted && isset($data['encryption_key'])) {
64
+ $response['encryption_key'] = $data['encryption_key'];
65
+ }
66
+ return $response;
67
+ }
68
+
69
+ return ['status' => 'error', 'message' => 'Failed to create paste'];
70
+ }
71
+
72
+ private function generateUUID() {
73
+ return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
74
+ mt_rand(0, 0xffff), mt_rand(0, 0xffff),
75
+ mt_rand(0, 0xffff),
76
+ mt_rand(0, 0x0fff) | 0x4000,
77
+ mt_rand(0, 0x3fff) | 0x8000,
78
+ mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
79
+ );
80
+ }
81
+ }
82
+
83
+ // 处理请求
84
+ $method = $_SERVER['REQUEST_METHOD'];
85
+ $api = new PasteAPI();
86
+
87
+ if ($method === 'POST') {
88
+ $data = json_decode(file_get_contents('php://input'), true);
89
+ echo json_encode($api->createPaste($data));
90
+ } else {
91
+ http_response_code(405);
92
+ echo json_encode(['status' => 'error', 'message' => 'Method not allowed']);
93
+ }
css/marked.css ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Markdown styling */
2
+ #markdown-content {
3
+ line-height: 1.8;
4
+ color: var(--text-color);
5
+ }
6
+
7
+ #markdown-content h1,
8
+ #markdown-content h2,
9
+ #markdown-content h3,
10
+ #markdown-content h4,
11
+ #markdown-content h5,
12
+ #markdown-content h6 {
13
+ margin: 1.5rem 0 1rem;
14
+ line-height: 1.25;
15
+ font-weight: 600;
16
+ }
17
+
18
+ #markdown-content h1 { font-size: 2em; }
19
+ #markdown-content h2 { font-size: 1.5em; }
20
+ #markdown-content h3 { font-size: 1.25em; }
21
+ #markdown-content h4 { font-size: 1em; }
22
+ #markdown-content h5 { font-size: 0.875em; }
23
+ #markdown-content h6 { font-size: 0.85em; }
24
+
25
+ #markdown-content p {
26
+ margin-bottom: 1rem;
27
+ }
28
+
29
+ #markdown-content strong {
30
+ font-weight: 600;
31
+ }
32
+
33
+ #markdown-content em {
34
+ font-style: italic;
35
+ }
36
+
37
+ #markdown-content blockquote {
38
+ padding: 0.5rem 1rem;
39
+ margin: 1rem 0;
40
+ border-left: 4px solid var(--primary-color);
41
+ background: rgba(0, 0, 0, 0.05);
42
+ }
43
+
44
+ #markdown-content code {
45
+ background: rgba(0, 0, 0, 0.05);
46
+ padding: 0.2rem 0.4rem;
47
+ border-radius: 4px;
48
+ font-family: 'Monaco', 'Consolas', monospace;
49
+ font-size: 0.9em;
50
+ }
51
+
52
+ #markdown-content pre {
53
+ background: rgba(0, 0, 0, 0.05);
54
+ padding: 1rem;
55
+ border-radius: 8px;
56
+ overflow-x: auto;
57
+ margin: 1rem 0;
58
+ }
59
+
60
+ #markdown-content pre code {
61
+ background: none;
62
+ padding: 0;
63
+ border-radius: 0;
64
+ }
65
+
66
+ #markdown-content ul,
67
+ #markdown-content ol {
68
+ margin: 1rem 0;
69
+ padding-left: 2rem;
70
+ }
71
+
72
+ #markdown-content li {
73
+ margin: 0.5rem 0;
74
+ }
75
+
76
+ #markdown-content table {
77
+ border-collapse: collapse;
78
+ width: 100%;
79
+ margin: 1rem 0;
80
+ }
81
+
82
+ #markdown-content table th,
83
+ #markdown-content table td {
84
+ border: 1px solid var(--border-color);
85
+ padding: 0.5rem;
86
+ }
87
+
88
+ #markdown-content table th {
89
+ background: rgba(0, 0, 0, 0.05);
90
+ font-weight: 600;
91
+ }
92
+
93
+ #markdown-content hr {
94
+ border: none;
95
+ border-top: 2px solid var(--border-color);
96
+ margin: 2rem 0;
97
+ }
98
+
99
+ #markdown-content img {
100
+ max-width: 100%;
101
+ height: auto;
102
+ border-radius: 8px;
103
+ margin: 1rem 0;
104
+ }
105
+
106
+ #markdown-content a {
107
+ color: var(--primary-color);
108
+ text-decoration: none;
109
+ }
110
+
111
+ #markdown-content a:hover {
112
+ text-decoration: underline;
113
+ }
114
+
115
+ /* Dark mode adjustments for markdown */
116
+ @media (prefers-color-scheme: dark) {
117
+ #markdown-content blockquote {
118
+ background: rgba(255, 255, 255, 0.05);
119
+ }
120
+
121
+ #markdown-content code {
122
+ background: rgba(255, 255, 255, 0.1);
123
+ }
124
+
125
+ #markdown-content pre {
126
+ background: rgba(255, 255, 255, 0.05);
127
+ }
128
+
129
+ #markdown-content table th {
130
+ background: rgba(255, 255, 255, 0.05);
131
+ }
132
+ }
css/style.css ADDED
@@ -0,0 +1,892 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Reset and base styles */
2
+ :root {
3
+ --primary-color: #3498db;
4
+ --primary-hover: #2980b9;
5
+ --text-color: #2c3e50; /* 更深的默认文本颜色 */
6
+ --text-content: #1a1a1a; /* 新增:笔记内容的颜色 */
7
+ --bg-color: #f9f9f9;
8
+ --container-bg: #ffffff;
9
+ --border-color: #ddd; /* 修改为更明显的边框颜色 */
10
+ --success-color: #2ecc71;
11
+ --shadow: 0 2px 15px rgba(0, 0, 0, 0.1);
12
+ }
13
+
14
+ * {
15
+ margin: 0;
16
+ padding: 0;
17
+ box-sizing: border-box;
18
+ }
19
+
20
+ body {
21
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
22
+ line-height: 1.6;
23
+ color: var(--text-color);
24
+ background: var(--bg-color) url('https://image.ulaara.xyz/file/1729940342977_pFyErOU.png') no-repeat center center fixed;
25
+ background-size: cover;
26
+ min-height: 100vh;
27
+ padding: 20px;
28
+ }
29
+
30
+ /* Container styling */
31
+ .container {
32
+ max-width: 1200px;
33
+ margin: 0 auto;
34
+ background: var(--container-bg);
35
+ padding: 2rem;
36
+ border-radius: 12px;
37
+ box-shadow: var(--shadow);
38
+ backdrop-filter: blur(10px);
39
+ background-color: rgba(255, 255, 255, 0.9); /* 添加半透明背景 */
40
+ }
41
+
42
+ /* 深色模式下的调整 */
43
+ @media (prefers-color-scheme: dark) {
44
+ .container {
45
+ background-color: rgba(45, 45, 45, 0.9); /* 深色模式下的半透明背景 */
46
+ }
47
+ }
48
+
49
+ /* Header styling */
50
+ h1 {
51
+ color: var(--text-color);
52
+ font-size: 2.5rem;
53
+ font-weight: 700;
54
+ margin-bottom: 0.5rem;
55
+ }
56
+
57
+ p {
58
+ color: #666;
59
+ margin-bottom: 2rem;
60
+ }
61
+
62
+ /* Form elements */
63
+ #pasteForm {
64
+ display: flex;
65
+ flex-direction: column;
66
+ gap: 1.5rem;
67
+ width: 100%;
68
+ }
69
+
70
+ textarea {
71
+ width: 100%;
72
+ min-height: 400px;
73
+ padding: 1rem;
74
+ border: 2px solid var(--border-color);
75
+ border-radius: 8px;
76
+ font-family: 'Monaco', 'Consolas', monospace;
77
+ font-size: 1rem;
78
+ line-height: 1.5;
79
+ color: var(--text-content);
80
+ font-weight: 500; /* 略微加粗 */
81
+ resize: vertical;
82
+ transition: border-color 0.3s ease;
83
+ }
84
+
85
+ textarea:focus {
86
+ outline: none;
87
+ border-color: var(--primary-color);
88
+ }
89
+
90
+ /* Options grid */
91
+ .options {
92
+ display: grid;
93
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
94
+ gap: 1.5rem;
95
+ width: 100%;
96
+ margin: 1rem 0;
97
+ padding: 1rem;
98
+ border: 2px solid var(--border-color);
99
+ border-radius: 8px;
100
+ background: var(--container-bg);
101
+ }
102
+
103
+ .option {
104
+ display: flex;
105
+ flex-direction: column;
106
+ gap: 0.5rem;
107
+ }
108
+
109
+ /* Form controls */
110
+ label {
111
+ font-weight: 500;
112
+ color: var(--text-color);
113
+ display: flex;
114
+ align-items: center;
115
+ gap: 0.5rem;
116
+ }
117
+
118
+ select,
119
+ input[type="number"] {
120
+ padding: 0.75rem;
121
+ border: 2px solid var(--border-color);
122
+ border-radius: 6px;
123
+ font-size: 1rem;
124
+ width: 100%;
125
+ background: white;
126
+ transition: border-color 0.3s ease;
127
+ }
128
+
129
+ select:focus,
130
+ input[type="number"]:focus {
131
+ outline: none;
132
+ border-color: var(--primary-color);
133
+ }
134
+
135
+ input[type="checkbox"] {
136
+ width: 1.2rem;
137
+ height: 1.2rem;
138
+ border-radius: 4px;
139
+ accent-color: var(--primary-color);
140
+ }
141
+
142
+ /* Button container */
143
+ .form-actions {
144
+ display: flex;
145
+ justify-content: flex-end;
146
+ width: 100%;
147
+ margin-top: 1rem;
148
+ }
149
+
150
+ /* Button styling */
151
+ .form-actions button {
152
+ background: var(--primary-color);
153
+ color: white;
154
+ border: none;
155
+ padding: 1rem 2rem;
156
+ border-radius: 8px;
157
+ font-size: 1rem;
158
+ font-weight: 600;
159
+ cursor: pointer;
160
+ transition: all 0.3s ease;
161
+ width: auto;
162
+ min-width: 150px;
163
+ max-width: 200px;
164
+ }
165
+
166
+ .form-actions button:hover {
167
+ background: var(--primary-hover);
168
+ transform: translateY(-1px);
169
+ }
170
+
171
+ /* Dark mode adjustments */
172
+ @media (prefers-color-scheme: dark) {
173
+ select,
174
+ input[type="number"] {
175
+ background: #333;
176
+ color: var(--text-color);
177
+ border-color: var(--border-color);
178
+ }
179
+
180
+ .options {
181
+ background: var(--container-bg);
182
+ border-color: var(--border-color);
183
+ }
184
+ }
185
+
186
+ /* Mobile adjustments */
187
+ @media screen and (max-width: 768px) {
188
+ .options {
189
+ grid-template-columns: 1fr;
190
+ padding: 0.75rem;
191
+ }
192
+
193
+ .form-actions button {
194
+ width: 100%;
195
+ max-width: none;
196
+ }
197
+ }
198
+
199
+ /* Content display */
200
+ #content {
201
+ background: white;
202
+ padding: 2rem;
203
+ border-radius: 8px;
204
+ border: 2px solid var(--border-color);
205
+ margin-top: 1rem;
206
+ overflow-x: auto;
207
+ color: var(--text-content);
208
+ font-weight: 500;
209
+ }
210
+
211
+ pre {
212
+ font-family: 'Monaco', 'Consolas', monospace;
213
+ white-space: pre-wrap;
214
+ word-wrap: break-word;
215
+ color: var(--text-content);
216
+ font-weight: 500;
217
+ }
218
+
219
+ /* Info section */
220
+ .info {
221
+ background: #f8f9fa;
222
+ padding: 1rem;
223
+ border-radius: 6px;
224
+ color: #666;
225
+ font-size: 0.9rem;
226
+ margin-bottom: 1rem;
227
+ display: flex;
228
+ gap: 1rem;
229
+ flex-wrap: wrap;
230
+ }
231
+
232
+ /* Decrypt section */
233
+ #decrypt-section {
234
+ background: #fff3cd;
235
+ color: #856404;
236
+ padding: 1rem;
237
+ border-radius: 6px;
238
+ margin: 1rem 0;
239
+ }
240
+
241
+ #decryption-error {
242
+ background: #f8d7da;
243
+ color: #721c24;
244
+ padding: 1rem;
245
+ border-radius: 6px;
246
+ margin-top: 1rem;
247
+ }
248
+
249
+ /* Mobile Responsive Design */
250
+ @media screen and (max-width: 768px) {
251
+ body {
252
+ padding: 10px;
253
+ }
254
+
255
+ .container {
256
+ padding: 1rem;
257
+ }
258
+
259
+ h1 {
260
+ font-size: 2rem;
261
+ }
262
+
263
+ textarea {
264
+ min-height: 300px;
265
+ }
266
+
267
+ .options {
268
+ grid-template-columns: 1fr;
269
+ padding: 0.75rem;
270
+ }
271
+
272
+ button[type="submit"] {
273
+ width: 100%;
274
+ max-width: none;
275
+ }
276
+
277
+ #content {
278
+ padding: 1rem;
279
+ }
280
+ }
281
+
282
+ /* Dark mode support */
283
+ @media (prefers-color-scheme: dark) {
284
+ :root {
285
+ --bg-color: #1a1a1a;
286
+ --container-bg: #2d2d2d;
287
+ --text-color: #e0e0e0;
288
+ --text-content: #ffffff; /* 深色模式下的内容颜色 */
289
+ --border-color: #555; /* 深色模式下更明显的边框颜色 */
290
+ --primary-color: #4dabf7;
291
+ --primary-hover: #339af0;
292
+ }
293
+
294
+ select, input[type="number"], textarea {
295
+ background: #333;
296
+ color: var(--text-color);
297
+ border-color: var(--border-color);
298
+ }
299
+
300
+ #content {
301
+ background: #333;
302
+ }
303
+
304
+ #decrypt-section {
305
+ background: #2c2c1d;
306
+ color: #ffd700;
307
+ }
308
+
309
+ #decryption-error {
310
+ background: #2c1d1d;
311
+ color: #ff7070;
312
+ }
313
+
314
+ .info {
315
+ background: #333;
316
+ color: #999;
317
+ }
318
+
319
+ textarea, #content, pre {
320
+ color: var(--text-content);
321
+ }
322
+
323
+ #markdown-content {
324
+ color: var(--text-content);
325
+ }
326
+
327
+ .options {
328
+ background: var(--container-bg);
329
+ border-color: var(--border-color);
330
+ }
331
+ }
332
+
333
+ #markdown-content {
334
+ color: var(--text-content);
335
+ font-weight: 500;
336
+ }
337
+
338
+ /* 编辑器容器 */
339
+ .editor-container {
340
+ margin-bottom: 1.5rem;
341
+ width: 100%;
342
+ }
343
+
344
+ /* Markdown split view */
345
+ .split-view {
346
+ display: grid;
347
+ grid-template-columns: 1fr 1fr;
348
+ gap: 1rem;
349
+ width: 100%;
350
+ position: relative;
351
+ margin-bottom: 1.5rem;
352
+ }
353
+
354
+ .split-view textarea {
355
+ min-height: 500px;
356
+ height: 500px;
357
+ resize: none;
358
+ width: 100%;
359
+ overflow-y: auto;
360
+ }
361
+
362
+ /* 修改预览窗口的选择器 */
363
+ .split-view .markdown-preview {
364
+ border: 2px solid var(--border-color);
365
+ border-radius: 8px;
366
+ padding: 1rem;
367
+ overflow-y: auto;
368
+ background: var(--container-bg); /* 使用容器背景色 */
369
+ height: 500px;
370
+ width: 100%;
371
+ line-height: 1.5;
372
+ font-size: 1rem;
373
+ color: var(--text-color); /* 确保文字颜色正确 */
374
+ }
375
+
376
+ /* 深色模式下的预览窗 */
377
+ @media (prefers-color-scheme: dark) {
378
+ .split-view .markdown-preview {
379
+ background: #333;
380
+ color: var(--text-color);
381
+ }
382
+ }
383
+
384
+ /* 确保滚动条在深色模式下可见 */
385
+ .split-view textarea::-webkit-scrollbar,
386
+ .split-view .markdown-preview::-webkit-scrollbar {
387
+ width: 8px;
388
+ background: transparent;
389
+ }
390
+
391
+ .split-view textarea::-webkit-scrollbar-track,
392
+ .split-view .markdown-preview::-webkit-scrollbar-track {
393
+ background: var(--bg-color);
394
+ border-radius: 4px;
395
+ }
396
+
397
+ .split-view textarea::-webkit-scrollbar-thumb,
398
+ .split-view .markdown-preview::-webkit-scrollbar-thumb {
399
+ background: #888;
400
+ border-radius: 4px;
401
+ }
402
+
403
+ .split-view textarea::-webkit-scrollbar-thumb:hover,
404
+ .split-view .markdown-preview::-webkit-scrollbar-thumb:hover {
405
+ background: #666;
406
+ }
407
+
408
+ /* 修改预览窗口中图片的样 */
409
+ .markdown-preview img {
410
+ max-width: 80% !important;
411
+ height: auto !important;
412
+ display: block;
413
+ margin: 0.5rem auto; /* 减小图片上下间距 */
414
+ max-height: 300px !important; /* 稍微降低最大高度 */
415
+ object-fit: contain;
416
+ border: 1px solid var(--border-color);
417
+ border-radius: 4px;
418
+ transition: all 0.3s ease;
419
+ }
420
+
421
+ /* 图片悬停效果 */
422
+ .markdown-preview img:hover {
423
+ cursor: zoom-in;
424
+ opacity: 0.95;
425
+ box-shadow: 0 0 12px rgba(0, 0, 0, 0.15);
426
+ }
427
+
428
+ /* 移动端的图片样式调整 */
429
+ @media screen and (max-width: 768px) {
430
+ .markdown-preview img {
431
+ max-width: 90% !important; /* 增加到90% */
432
+ max-height: 250px !important;
433
+ }
434
+ }
435
+
436
+ /* 表单选项区域 */
437
+ #pasteForm {
438
+ display: flex;
439
+ flex-direction: column;
440
+ gap: 1.5rem;
441
+ width: 100%;
442
+ margin-bottom: 2rem;
443
+ }
444
+
445
+ .options {
446
+ display: grid;
447
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
448
+ gap: 1.5rem;
449
+ width: 100%;
450
+ margin: 1rem 0;
451
+ padding: 1rem;
452
+ border: 2px solid var(--border-color);
453
+ border-radius: 8px;
454
+ background: var(--container-bg);
455
+ }
456
+
457
+ /* 确按钮区域正确定位 */
458
+ .form-actions {
459
+ display: flex;
460
+ justify-content: flex-end;
461
+ width: 100%;
462
+ margin-top: 1rem;
463
+ }
464
+
465
+ /* 移动端的图片尺寸 */
466
+ @media screen and (max-width: 768px) {
467
+ .split-view .preview img {
468
+ max-width: 70% !important;
469
+ max-height: 150px !important;
470
+ }
471
+ }
472
+
473
+ /* 调整图片悬停效果 */
474
+ .split-view .preview img:hover {
475
+ cursor: pointer;
476
+ opacity: 0.9;
477
+ }
478
+
479
+ /* 调整预览窗口中的段落间距 */
480
+ .markdown-preview p {
481
+ margin: 0 0 1rem 0; /* 减小段落间距 */
482
+ line-height: 1.5; /* 确保与textarea一致 */
483
+ }
484
+
485
+ /* 调整预览窗口中的图片布局 */
486
+ .markdown-preview img {
487
+ max-width: 80% !important;
488
+ height: auto !important;
489
+ display: block;
490
+ margin: 0.5rem auto; /* 减小图片上下间距 */
491
+ max-height: 300px !important; /* 稍微降低最大高度 */
492
+ object-fit: contain;
493
+ border: 1px solid var(--border-color);
494
+ border-radius: 4px;
495
+ }
496
+
497
+ /* 调整预览窗口中的标题间距 */
498
+ .markdown-preview h1,
499
+ .markdown-preview h2,
500
+ .markdown-preview h3,
501
+ .markdown-preview h4,
502
+ .markdown-preview h5,
503
+ .markdown-preview h6 {
504
+ margin: 0.5rem 0; /* 减小标题的上下间距 */
505
+ line-height: 1.5;
506
+ }
507
+
508
+ /* 调整预览窗口中的列表间距 */
509
+ .markdown-preview ul,
510
+ .markdown-preview ol {
511
+ margin: 0.5rem 0;
512
+ padding-left: 1.5rem;
513
+ }
514
+
515
+ .markdown-preview li {
516
+ margin: 0;
517
+ line-height: 1.5;
518
+ }
519
+
520
+ /* 调整预览窗口中的代码块间距 */
521
+ .markdown-preview pre,
522
+ .markdown-preview code {
523
+ margin: 0.5rem 0;
524
+ line-height: 1.5;
525
+ }
526
+
527
+ /* 调整预览窗口中的引用块间距 */
528
+ .markdown-preview blockquote {
529
+ margin: 0.5rem 0;
530
+ padding: 0.5rem 1rem;
531
+ line-height: 1.5;
532
+ }
533
+
534
+ /* 确保表单控件有明显的边框 */
535
+ textarea,
536
+ select,
537
+ input[type="number"] {
538
+ border: 2px solid var(--border-color);
539
+ background: #ffffff;
540
+ }
541
+
542
+ /* 深色模式下的边框和背景调整 */
543
+ @media (prefers-color-scheme: dark) {
544
+ :root {
545
+ --bg-color: #1a1a1a;
546
+ --container-bg: #2d2d2d;
547
+ --text-color: #e0e0e0;
548
+ --text-content: #ffffff;
549
+ --border-color: #555; /* 深色模式下更明显的边框颜色 */
550
+ --primary-color: #4dabf7;
551
+ --primary-hover: #339af0;
552
+ }
553
+
554
+ textarea,
555
+ select,
556
+ input[type="number"] {
557
+ background: #333;
558
+ border-color: var(--border-color);
559
+ color: var(--text-color);
560
+ }
561
+ }
562
+
563
+ /* 删除滚动提示样式 */
564
+ .editor-container::after {
565
+ display: none; /* 或直接删除这个样式块 */
566
+ }
567
+
568
+ /* 调整编辑器容器的边距 */
569
+ .editor-container {
570
+ position: relative;
571
+ margin-bottom: 1.5rem; /* 恢复原来的边距 */
572
+ }
573
+
574
+ /* 修改 split-view 相关样式 */
575
+ .split-view {
576
+ display: grid;
577
+ grid-template-columns: 1fr 1fr;
578
+ gap: 1rem;
579
+ width: 100%;
580
+ position: relative;
581
+ margin-bottom: 1.5rem;
582
+ }
583
+
584
+ /* 修改 textarea 和预览窗口的样式 */
585
+ .split-view textarea,
586
+ .split-view .markdown-preview {
587
+ height: 100%; /* 改为相对高度 */
588
+ min-height: 500px;
589
+ resize: vertical; /* 允许垂直调整大小 */
590
+ width: 100%;
591
+ overflow-y: auto;
592
+ }
593
+
594
+ /* 添加新的容器样式来同步高度 */
595
+ .editor-container {
596
+ display: flex;
597
+ flex-direction: column;
598
+ }
599
+
600
+ .editor-container.split-view {
601
+ display: grid;
602
+ grid-template-columns: 1fr 1fr;
603
+ gap: 1rem;
604
+ }
605
+
606
+ /* 确保预览窗口跟随 textarea 的高度 */
607
+ .markdown-preview {
608
+ height: 100%;
609
+ border: 2px solid var(--border-color);
610
+ border-radius: 8px;
611
+ padding: 1rem;
612
+ overflow-y: auto;
613
+ background: var(--container-bg);
614
+ line-height: 1.5;
615
+ font-size: 1rem;
616
+ color: var(--text-color);
617
+ }
618
+
619
+ /* 添加加密内容的样式规则 */
620
+ #content #markdown-content img {
621
+ max-width: 80% !important;
622
+ height: auto !important;
623
+ display: block;
624
+ margin: 0.5rem auto;
625
+ max-height: 300px !important;
626
+ object-fit: contain;
627
+ border: 1px solid var(--border-color);
628
+ border-radius: 4px;
629
+ transition: all 0.3s ease;
630
+ }
631
+
632
+ #content #markdown-content {
633
+ color: var(--text-content);
634
+ font-weight: 500;
635
+ line-height: 1.5;
636
+ }
637
+
638
+ /* 确保加密内容的容器样式正确 */
639
+ #content {
640
+ background: var(--container-bg);
641
+ padding: 2rem;
642
+ border-radius: 8px;
643
+ border: 2px solid var(--border-color);
644
+ margin-top: 1rem;
645
+ overflow-x: auto;
646
+ color: var(--text-content);
647
+ font-weight: 500;
648
+ width: 100%;
649
+ }
650
+
651
+ /* 修改预览窗口中图片的样式 */
652
+ .markdown-preview img,
653
+ #content #markdown-content img {
654
+ max-width: 100% !important; /* 改为100% */
655
+ height: auto !important;
656
+ display: block;
657
+ margin: 0.5rem auto;
658
+ max-height: 600px !important; /* 增加最大高度 */
659
+ object-fit: contain;
660
+ border: 1px solid var(--border-color);
661
+ border-radius: 4px;
662
+ transition: all 0.3s ease;
663
+ }
664
+
665
+ /* 移动端的图片样式调整 */
666
+ @media screen and (max-width: 768px) {
667
+ .markdown-preview img,
668
+ #content #markdown-content img {
669
+ max-width: 100% !important;
670
+ max-height: 400px !important; /* 移动端也适当增加高度 */
671
+ }
672
+ }
673
+
674
+ /* 文件分享相关样式 */
675
+ .tab-buttons {
676
+ display: flex;
677
+ gap: 1rem;
678
+ margin-bottom: 1rem;
679
+ }
680
+
681
+ .tab-button {
682
+ padding: 0.5rem 1rem;
683
+ border: 2px solid var(--border-color);
684
+ border-radius: 4px;
685
+ background: none;
686
+ cursor: pointer;
687
+ transition: all 0.3s ease;
688
+ }
689
+
690
+ .tab-button.active {
691
+ background: var(--primary-color);
692
+ color: white;
693
+ border-color: var(--primary-color);
694
+ }
695
+
696
+ .file-upload-container {
697
+ border: 2px dashed var(--border-color);
698
+ padding: 2rem;
699
+ text-align: center;
700
+ margin-bottom: 1rem;
701
+ border-radius: 8px;
702
+ transition: border-color 0.3s ease;
703
+ }
704
+
705
+ .file-upload-container:hover {
706
+ border-color: var(--primary-color);
707
+ }
708
+
709
+ .file-upload-container input[type="file"] {
710
+ margin-bottom: 1rem;
711
+ }
712
+
713
+ .file-info {
714
+ margin-top: 1rem;
715
+ font-size: 0.9rem;
716
+ color: #666;
717
+ }
718
+
719
+ .code-input-container {
720
+ display: flex;
721
+ gap: 1rem;
722
+ margin-top: 2rem;
723
+ padding-top: 2rem;
724
+ border-top: 1px solid var(--border-color);
725
+ }
726
+
727
+ .code-input-container input {
728
+ flex: 1;
729
+ padding: 0.75rem;
730
+ border: 2px solid var(--border-color);
731
+ border-radius: 6px;
732
+ font-size: 1rem;
733
+ }
734
+
735
+ .code-input-container button {
736
+ padding: 0.75rem 1.5rem;
737
+ background: var(--primary-color);
738
+ color: white;
739
+ border: none;
740
+ border-radius: 6px;
741
+ cursor: pointer;
742
+ transition: all 0.3s ease;
743
+ }
744
+
745
+ .code-input-container button:hover {
746
+ background: var(--primary-hover);
747
+ }
748
+
749
+ /* 上传进度条样式 */
750
+ .upload-progress {
751
+ margin-top: 1rem;
752
+ width: 100%;
753
+ }
754
+
755
+ .progress-bar {
756
+ width: 100%;
757
+ height: 20px;
758
+ background-color: var(--border-color);
759
+ border-radius: 10px;
760
+ overflow: hidden;
761
+ }
762
+
763
+ .progress-bar-fill {
764
+ height: 100%;
765
+ background-color: var(--primary-color);
766
+ width: 0;
767
+ transition: width 0.3s ease;
768
+ }
769
+
770
+ .progress-text {
771
+ text-align: center;
772
+ margin-top: 0.5rem;
773
+ font-size: 0.9rem;
774
+ color: var(--text-color);
775
+ }
776
+
777
+ /* 文件上传容器样式优化 */
778
+ .file-upload-container {
779
+ border: 2px dashed var(--border-color);
780
+ padding: 2rem;
781
+ text-align: center;
782
+ margin-bottom: 1rem;
783
+ border-radius: 8px;
784
+ transition: border-color 0.3s ease;
785
+ }
786
+
787
+ .file-upload-container:hover {
788
+ border-color: var(--primary-color);
789
+ }
790
+
791
+ .file-upload-container input[type="file"] {
792
+ margin-bottom: 1rem;
793
+ }
794
+
795
+ /* 添加加载动画样式 */
796
+ .loading-spinner {
797
+ width: 40px;
798
+ height: 40px;
799
+ margin: 20px auto;
800
+ border: 4px solid var(--border-color);
801
+ border-top: 4px solid var(--primary-color);
802
+ border-radius: 50%;
803
+ animation: spin 1s linear infinite;
804
+ }
805
+
806
+ @keyframes spin {
807
+ 0% { transform: rotate(0deg); }
808
+ 100% { transform: rotate(360deg); }
809
+ }
810
+
811
+ /* 添加提取码和分享链接的样式 */
812
+ .share-info {
813
+ margin: 1.5rem 0;
814
+ }
815
+
816
+ .copy-group {
817
+ display: flex;
818
+ align-items: center;
819
+ gap: 0.5rem;
820
+ margin: 0.5rem 0;
821
+ padding: 0.5rem;
822
+ background: var(--bg-color);
823
+ border-radius: 4px;
824
+ }
825
+
826
+ .copy-group label {
827
+ font-weight: 500;
828
+ color: var(--text-color);
829
+ white-space: nowrap;
830
+ }
831
+
832
+ .copy-text {
833
+ flex: 1;
834
+ padding: 0.5rem;
835
+ background: var(--container-bg);
836
+ border: 1px solid var(--border-color);
837
+ border-radius: 4px;
838
+ font-family: monospace;
839
+ overflow: hidden;
840
+ text-overflow: ellipsis;
841
+ white-space: nowrap;
842
+ }
843
+
844
+ .copy-btn {
845
+ padding: 0.5rem 1rem;
846
+ background: var(--primary-color);
847
+ color: white;
848
+ border: none;
849
+ border-radius: 4px;
850
+ cursor: pointer;
851
+ transition: background 0.3s ease;
852
+ }
853
+
854
+ .copy-btn:hover {
855
+ background: var(--primary-hover);
856
+ }
857
+
858
+ .close-btn {
859
+ margin-top: 1rem;
860
+ padding: 0.5rem 1.5rem;
861
+ background: var(--border-color);
862
+ color: var(--text-color);
863
+ border: none;
864
+ border-radius: 4px;
865
+ cursor: pointer;
866
+ transition: background 0.3s ease;
867
+ }
868
+
869
+ .close-btn:hover {
870
+ background: #ccc;
871
+ }
872
+
873
+ /* 深色模式适配 */
874
+ @media (prefers-color-scheme: dark) {
875
+ .copy-group {
876
+ background: rgba(255, 255, 255, 0.1);
877
+ }
878
+
879
+ .copy-text {
880
+ background: var(--container-bg);
881
+ color: var(--text-color);
882
+ }
883
+
884
+ .close-btn {
885
+ background: #444;
886
+ color: #fff;
887
+ }
888
+
889
+ .close-btn:hover {
890
+ background: #555;
891
+ }
892
+ }
docker-compose.yml ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: '3'
2
+ services:
3
+ web:
4
+ build: .
5
+ ports:
6
+ - "7860:7860"
7
+ volumes:
8
+ - ./data:/var/www/html/data
9
+ - ./uploads:/var/www/html/uploads
10
+ environment:
11
+ - APACHE_DOCUMENT_ROOT=/var/www/html
favicon.png ADDED
file_api.php ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ header('Content-Type: application/json');
3
+
4
+ require_once 'settings.php';
5
+ require_once 'FileShare.php';
6
+
7
+ $fileShare = new FileShare();
8
+
9
+ try {
10
+ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
11
+ // 处理上传
12
+ $data = [
13
+ 'expire_time' => $_POST['expire_time'] ?? 86400,
14
+ 'max_downloads' => $_POST['max_downloads'] ?? 0
15
+ ];
16
+
17
+ if (isset($_FILES['file']) || isset($_POST['content'])) {
18
+ if (isset($_POST['content'])) {
19
+ $data['content'] = $_POST['content'];
20
+ }
21
+
22
+ $code = $fileShare->save($data);
23
+ echo json_encode([
24
+ 'status' => 'success',
25
+ 'code' => $code
26
+ ]);
27
+ } else {
28
+ throw new Exception('未收到文件或文本内容');
29
+ }
30
+ } else if ($_SERVER['REQUEST_METHOD'] === 'GET') {
31
+ // 处理下载/获取
32
+ if (isset($_GET['download'])) {
33
+ $code = $_GET['download'];
34
+ $file = $fileShare->get($code);
35
+
36
+ if (!$file || $file['type'] !== 'file') {
37
+ throw new Exception('文件不存在或已过期');
38
+ }
39
+
40
+ // 获取文件 MIME 类型
41
+ $finfo = finfo_open(FILEINFO_MIME_TYPE);
42
+ $mime_type = finfo_file($finfo, $file['filepath']);
43
+ finfo_close($finfo);
44
+
45
+ // 清除之前的输出缓冲和头信息
46
+ ob_clean();
47
+ header('Content-Type: ' . $mime_type);
48
+ header('Content-Transfer-Encoding: binary');
49
+ header('Content-Disposition: attachment; filename="' . rawurlencode($file['filename']) . '"');
50
+ header('Content-Length: ' . filesize($file['filepath']));
51
+ header('Accept-Ranges: bytes');
52
+
53
+ // 处理断点续传
54
+ if (isset($_SERVER['HTTP_RANGE'])) {
55
+ list($start, $end) = sscanf($_SERVER['HTTP_RANGE'], 'bytes=%d-%d');
56
+ $filesize = filesize($file['filepath']);
57
+
58
+ if (!isset($end)) {
59
+ $end = $filesize - 1;
60
+ }
61
+
62
+ $length = $end - $start + 1;
63
+
64
+ header('HTTP/1.1 206 Partial Content');
65
+ header("Content-Range: bytes $start-$end/$filesize");
66
+ header('Content-Length: ' . $length);
67
+
68
+ $fp = fopen($file['filepath'], 'rb');
69
+ fseek($fp, $start);
70
+ $buffer = 1024 * 8;
71
+ while ($length > 0) {
72
+ $read = min($buffer, $length);
73
+ echo fread($fp, $read);
74
+ flush();
75
+ $length -= $read;
76
+ }
77
+ fclose($fp);
78
+ } else {
79
+ readfile($file['filepath']);
80
+ }
81
+ exit;
82
+ } else {
83
+ $code = $_GET['code'] ?? null;
84
+ if (!$code) {
85
+ throw new Exception('未提供提取码');
86
+ }
87
+
88
+ $file = $fileShare->get($code);
89
+ if (!$file) {
90
+ throw new Exception('文件不存在或已过期');
91
+ }
92
+
93
+ // 返回文件信息或文本内容
94
+ echo json_encode([
95
+ 'status' => 'success',
96
+ 'type' => $file['type'],
97
+ 'content' => $file['content'],
98
+ 'filename' => $file['filename'],
99
+ 'current_downloads' => $file['current_downloads'],
100
+ 'max_downloads' => $file['max_downloads'],
101
+ 'expires_at' => $file['expires_at'],
102
+ 'expires_at_formatted' => date('Y-m-d H:i:s', $file['expires_at']) // 添加格式化的时间
103
+ ]);
104
+ }
105
+ }
106
+ } catch (Exception $e) {
107
+ http_response_code(400);
108
+ echo json_encode([
109
+ 'status' => 'error',
110
+ 'message' => $e->getMessage()
111
+ ]);
112
+ }
index.php ADDED
@@ -0,0 +1,814 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ // index.php
3
+ require_once 'settings.php';
4
+
5
+ class PasteBoard {
6
+ private $db;
7
+ private $settings;
8
+
9
+ public function __construct() {
10
+ $this->settings = new Settings();
11
+ $this->db = new SQLite3($this->settings->getSetting('db_path'));
12
+ }
13
+
14
+ private function getChineseTime($timestamp) {
15
+ date_default_timezone_set('Asia/Shanghai');
16
+ return $timestamp;
17
+ }
18
+
19
+ public function getPaste($uuid) {
20
+ $stmt = $this->db->prepare('SELECT * FROM notes WHERE uuid = :uuid');
21
+ $stmt->bindValue(':uuid', $uuid, SQLITE3_TEXT);
22
+ $result = $stmt->execute();
23
+
24
+ if ($row = $result->fetchArray(SQLITE3_ASSOC)) {
25
+ // 检查是否过期(使用中国时间)
26
+ date_default_timezone_set('Asia/Shanghai');
27
+ if (time() > $row['expires_at']) {
28
+ $this->deletePaste($uuid);
29
+ return null;
30
+ }
31
+
32
+ // 检查访问次数
33
+ if ($row['max_views'] > 0 && $row['current_views'] >= $row['max_views']) {
34
+ $this->deletePaste($uuid);
35
+ return null;
36
+ }
37
+
38
+ // 更新访问次数
39
+ $stmt = $this->db->prepare('UPDATE notes SET current_views = current_views + 1 WHERE uuid = :uuid');
40
+ $stmt->bindValue(':uuid', $uuid, SQLITE3_TEXT);
41
+ $stmt->execute();
42
+
43
+ return $row;
44
+ }
45
+ return null;
46
+ }
47
+
48
+ private function deletePaste($uuid) {
49
+ $stmt = $this->db->prepare('DELETE FROM notes WHERE uuid = :uuid');
50
+ $stmt->bindValue(':uuid', $uuid, SQLITE3_TEXT);
51
+ $stmt->execute();
52
+ }
53
+ }
54
+
55
+ $pasteboard = new PasteBoard();
56
+ $settings = new Settings();
57
+ $paste = null;
58
+ $uuid = isset($_GET['id']) ? $_GET['id'] : null;
59
+
60
+ if ($uuid) {
61
+ $paste = $pasteboard->getPaste($uuid);
62
+ if (!$paste) {
63
+ header("HTTP/1.0 404 Not Found");
64
+ echo "Paste not found or expired";
65
+ exit;
66
+ }
67
+ }
68
+ ?>
69
+
70
+ <!DOCTYPE html>
71
+ <html lang="zh-CN">
72
+ <head>
73
+ <meta charset="UTF-8">
74
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
75
+ <title><?php echo htmlspecialchars($settings->getSetting('site_name')); ?></title>
76
+ <link rel="icon" href="<?php echo htmlspecialchars($settings->getSetting('favicon_path')); ?>">
77
+ <link rel="stylesheet" href="/css/marked.css">
78
+ <link rel="stylesheet" href="/css/style.css">
79
+ </head>
80
+ <body>
81
+ <div class="container">
82
+ <h1><?php echo htmlspecialchars($settings->getSetting('site_name')); ?></h1>
83
+ <p><?php echo htmlspecialchars($settings->getSetting('site_description')); ?></p>
84
+
85
+ <?php if ($paste): ?>
86
+ <div class="info">
87
+ 浏览次数: <?php echo $paste['current_views']; ?>/<?php echo $paste['max_views'] ? $paste['max_views'] : '∞'; ?>
88
+ | 过期时间: <?php
89
+ date_default_timezone_set('Asia/Shanghai');
90
+ echo date('Y-m-d H:i:s', $paste['expires_at']);
91
+ ?>
92
+ </div>
93
+ <div id="content" <?php echo $paste['is_encrypted'] ? 'style="display:none;"' : ''; ?>>
94
+ <?php if ($paste['is_markdown']): ?>
95
+ <div id="markdown-content"></div>
96
+ <?php else: ?>
97
+ <pre><?php echo htmlspecialchars($paste['content']); ?></pre>
98
+ <?php endif; ?>
99
+ </div>
100
+ <?php if ($paste['is_encrypted']): ?>
101
+ <div id="decrypt-section">
102
+ <p>此内容已加密。解密密钥应该在URL的#后面。</p>
103
+ <div id="decryption-error" style="color: red; display: none;">
104
+ 解密失败。请检查解密密钥。
105
+ </div>
106
+ </div>
107
+ <?php endif; ?>
108
+ <?php else: ?>
109
+ <div class="tab-buttons">
110
+ <button class="tab-button active" data-tab="paste">URL分享</button>
111
+ <button class="tab-button" data-tab="code">提取码分享</button>
112
+ <button class="tab-button" data-tab="file">文件分享</button>
113
+ </div>
114
+
115
+ <!-- 原有的文本分享表单 -->
116
+ <form id="pasteForm" style="display: block;">
117
+ <div class="editor-container">
118
+ <textarea name="content" placeholder="在此粘贴您要分享的内容"></textarea>
119
+ <div class="markdown-preview" style="display: none;"></div>
120
+ </div>
121
+
122
+ <div class="options">
123
+ <div class="option">
124
+ <label>过期时间:</label>
125
+ <select name="expire_time">
126
+ <option value="3600">1小时</option>
127
+ <option value="86400">1天</option>
128
+ <option value="604800">1周</option>
129
+ <option value="2592000" selected>1个月</option>
130
+ <option value="31536000">1年</option>
131
+ </select>
132
+ </div>
133
+ <div class="option">
134
+ <label>最大浏览次数 (0表示无限制):</label>
135
+ <input type="number" name="max_views" value="0" min="0" max="25565">
136
+ </div>
137
+ <div class="option">
138
+ <label>
139
+ <input type="checkbox" name="is_markdown"> 启用Markdown
140
+ </label>
141
+ </div>
142
+ <div class="option">
143
+ <label>
144
+ <input type="checkbox" name="is_encrypted"> 启用加密
145
+ </label>
146
+ </div>
147
+ </div>
148
+
149
+ <div class="form-actions">
150
+ <button type="submit">创建笔记</button>
151
+ </div>
152
+ </form>
153
+
154
+ <!-- 提取码分享表单 -->
155
+ <form id="textShareForm" style="display: none;">
156
+ <div class="editor-container">
157
+ <textarea name="content" placeholder="在此输入您要分享的文本内容..."></textarea>
158
+ </div>
159
+
160
+ <div class="options">
161
+ <div class="option">
162
+ <label>过期时间:</label>
163
+ <select name="expire_time">
164
+ <option value="3600">1小时</option>
165
+ <option value="86400">1天</option>
166
+ <option value="604800">1周</option>
167
+ <option value="2592000" selected>1个月</option>
168
+ <option value="31536000">1年</option>
169
+ </select>
170
+ </div>
171
+ <div class="option">
172
+ <label>最大查看次数 (0表示无限制):</label>
173
+ <input type="number" name="max_downloads" value="0" min="0">
174
+ </div>
175
+ </div>
176
+
177
+ <div class="form-actions">
178
+ <button type="submit">生成提取码</button>
179
+ </div>
180
+ </form>
181
+
182
+ <!-- 文件上传表单 -->
183
+ <form id="fileForm" style="display: none;">
184
+ <div class="file-upload-container">
185
+ <input type="file" name="file" id="fileInput">
186
+ <div class="file-info"></div>
187
+ <div class="upload-progress" style="display: none;">
188
+ <div class="progress-bar">
189
+ <div class="progress-bar-fill"></div>
190
+ </div>
191
+ <div class="progress-text">0%</div>
192
+ </div>
193
+ </div>
194
+
195
+ <div class="options">
196
+ <div class="option">
197
+ <label>过期时间:</label>
198
+ <select name="expire_time">
199
+ <option value="3600">1小时</option>
200
+ <option value="86400">1天</option>
201
+ <option value="604800">1周</option>
202
+ <option value="2592000" selected>1个月</option>
203
+ <option value="31536000">1年</option>
204
+ </select>
205
+ </div>
206
+ <div class="option">
207
+ <label>最大下载次数 (0表示无限制):</label>
208
+ <input type="number" name="max_downloads" value="0" min="0">
209
+ </div>
210
+ </div>
211
+
212
+ <div class="form-actions">
213
+ <button type="submit">上传文件</button>
214
+ </div>
215
+ </form>
216
+
217
+ <!-- 提取码输入区域 -->
218
+ <div class="code-input-container">
219
+ <input type="text" id="codeInput" placeholder="输入提取码">
220
+ <button onclick="getFile()">获取内容</button>
221
+ </div>
222
+
223
+ <!-- 文件信息显示区域 -->
224
+ <div id="fileInfo" style="display: none;">
225
+ <div class="info">
226
+ <span class="downloads"></span>
227
+ <span class="expires"></span>
228
+ </div>
229
+ <div class="content"></div>
230
+ </div>
231
+ <?php endif; ?>
232
+ </div>
233
+
234
+ <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
235
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
236
+ <script>
237
+ // 修改获取网站域名的方法
238
+ const siteUrl = (() => {
239
+ // 优使用 X-Forwarded-Host 和 X-Forwarded-Proto
240
+ const forwardedHost = '<?php echo isset($_SERVER["HTTP_X_FORWARDED_HOST"]) ? $_SERVER["HTTP_X_FORWARDED_HOST"] : ""; ?>';
241
+ const forwardedProto = '<?php echo isset($_SERVER["HTTP_X_FORWARDED_PROTO"]) ? $_SERVER["HTTP_X_FORWARDED_PROTO"] : ""; ?>';
242
+
243
+ if (forwardedHost && forwardedProto) {
244
+ return `${forwardedProto}://${forwardedHost}`;
245
+ }
246
+
247
+ // 如果没有转发的头部信息,则使用当前请求的 Host
248
+ const protocol = window.location.protocol;
249
+ const host = window.location.host;
250
+ return `${protocol}//${host}`;
251
+ })();
252
+
253
+ <?php if ($paste && $paste['is_markdown']): ?>
254
+ document.getElementById('markdown-content').innerHTML = marked.parse(<?php echo json_encode($paste['content']); ?>);
255
+ <?php endif; ?>
256
+
257
+ <?php if ($paste && $paste['is_encrypted']): ?>
258
+ const encryptedContent = <?php echo json_encode($paste['content']); ?>;
259
+ const decryptionKey = window.location.hash.substring(1);
260
+
261
+ if (decryptionKey) {
262
+ try {
263
+ const decryptedBytes = CryptoJS.AES.decrypt(encryptedContent, decryptionKey);
264
+ const decryptedContent = decryptedBytes.toString(CryptoJS.enc.Utf8);
265
+
266
+ if (decryptedContent) {
267
+ const content = document.getElementById('content');
268
+ content.style.display = 'block';
269
+ if (<?php echo $paste['is_markdown'] ? 'true' : 'false'; ?>) {
270
+ // 修改这里:使用 markdown-content div
271
+ const markdownContent = document.getElementById('markdown-content');
272
+ markdownContent.innerHTML = marked.parse(decryptedContent);
273
+
274
+ // 确保图片样式一致
275
+ markdownContent.querySelectorAll('img').forEach(img => {
276
+ img.style.maxWidth = '100%'; // 改为100%
277
+ img.style.height = 'auto';
278
+ img.style.maxHeight = '600px'; // 增加最大高度
279
+ img.style.margin = '0.5rem auto';
280
+ img.style.display = 'block';
281
+
282
+ // 添加点击放大功能
283
+ img.addEventListener('click', function(e) {
284
+ e.preventDefault();
285
+ const modal = document.createElement('div');
286
+ modal.style.cssText = `
287
+ position: fixed;
288
+ top: 0;
289
+ left: 0;
290
+ width: 100%;
291
+ height: 100%;
292
+ background: rgba(0, 0, 0, 0.9);
293
+ display: flex;
294
+ justify-content: center;
295
+ align-items: center;
296
+ z-index: 1000;
297
+ cursor: zoom-out;
298
+ `;
299
+
300
+ const modalImg = document.createElement('img');
301
+ modalImg.src = this.src;
302
+ modalImg.style.cssText = `
303
+ max-width: 90%;
304
+ max-height: 90vh;
305
+ object-fit: contain;
306
+ border-radius: 4px;
307
+ `;
308
+
309
+ modal.appendChild(modalImg);
310
+ document.body.appendChild(modal);
311
+
312
+ modal.addEventListener('click', function() {
313
+ document.body.removeChild(modal);
314
+ });
315
+ });
316
+ });
317
+ } else {
318
+ content.innerHTML = `<pre>${decryptedContent}</pre>`;
319
+ }
320
+ document.getElementById('decrypt-section').style.display = 'none';
321
+ } else {
322
+ document.getElementById('decryption-error').style.display = 'block';
323
+ }
324
+ } catch (e) {
325
+ document.getElementById('decryption-error').style.display = 'block';
326
+ }
327
+ }
328
+ <?php endif; ?>
329
+
330
+ if (document.getElementById('pasteForm')) {
331
+ document.getElementById('pasteForm').onsubmit = async (e) => {
332
+ e.preventDefault();
333
+ const formData = new FormData(e.target);
334
+ const data = Object.fromEntries(formData.entries());
335
+
336
+ if (data.is_encrypted) {
337
+ const key = CryptoJS.lib.WordArray.random(32).toString();
338
+ data.content = CryptoJS.AES.encrypt(data.content, key).toString();
339
+ data.encryption_key = key;
340
+ }
341
+
342
+ try {
343
+ const response = await fetch('api.php', {
344
+ method: 'POST',
345
+ headers: {
346
+ 'Content-Type': 'application/json',
347
+ },
348
+ body: JSON.stringify(data)
349
+ });
350
+
351
+ const result = await response.json();
352
+ if (result.status === 'success') {
353
+ let url = `${window.location.origin}/${result.uuid}`; // let url = `${window.location.origin}${window.location.pathname}?id=${result.uuid}`;
354
+ if (data.is_encrypted) {
355
+ url += '#' + result.encryption_key;
356
+ }
357
+ window.location.href = url;
358
+ } else {
359
+ alert('Error creating paste: ' + result.message);
360
+ }
361
+ } catch (error) {
362
+ alert('Error creating paste: ' + error.message);
363
+ }
364
+ };
365
+ }
366
+ document.addEventListener('DOMContentLoaded', function() {
367
+ const markdownCheckbox = document.querySelector('input[name="is_markdown"]');
368
+ const editorContainer = document.querySelector('.editor-container');
369
+ const textarea = document.querySelector('textarea[name="content"]');
370
+ const markdownPreview = document.querySelector('.markdown-preview');
371
+
372
+ let isPreviewScrolling = false;
373
+ let isEditorScrolling = false;
374
+ let lastEditorScrollTop = 0;
375
+ let lastPreviewScrollTop = 0;
376
+
377
+ // 只保留输入窗口的滚动同步
378
+ textarea.addEventListener('scroll', function() {
379
+ if (!isPreviewScrolling && markdownPreview.style.display !== 'none') {
380
+ isEditorScrolling = true;
381
+
382
+ // 计算输入窗口的滚动百分比变化
383
+ const editorScrollPercentage = this.scrollTop / (this.scrollHeight - this.clientHeight);
384
+ const previewMaxScroll = markdownPreview.scrollHeight - markdownPreview.clientHeight;
385
+
386
+ // 根据输入窗口的滚动百比设置预览窗口的位置
387
+ markdownPreview.scrollTop = previewMaxScroll * editorScrollPercentage;
388
+
389
+ setTimeout(() => isEditorScrolling = false, 50);
390
+ }
391
+ });
392
+
393
+ // 删除 markdownPreview 的滚动监听器
394
+ // 移除这代码:
395
+ // markdownPreview.addEventListener('scroll', function() { ... });
396
+
397
+ markdownCheckbox.addEventListener('change', function() {
398
+ if (this.checked) {
399
+ editorContainer.classList.add('split-view');
400
+ markdownPreview.style.display = 'block';
401
+ // 立即同步高度
402
+ markdownPreview.style.height = `${textarea.offsetHeight}px`;
403
+ updatePreview(markdownPreview, textarea);
404
+ } else {
405
+ editorContainer.classList.remove('split-view');
406
+ markdownPreview.style.display = 'none';
407
+ }
408
+ });
409
+
410
+ // 在每次入内容更新时更新预览
411
+ textarea.addEventListener('input', function() {
412
+ updatePreview(markdownPreview, textarea);
413
+ });
414
+
415
+ // 添加粘贴事件处理
416
+ textarea.addEventListener('paste', async function(e) {
417
+ const items = (e.clipboardData || e.originalEvent.clipboardData).items;
418
+
419
+ for (let item of items) {
420
+ if (item.type.indexOf('image') === 0) {
421
+ e.preventDefault();
422
+
423
+ const blob = item.getAsFile();
424
+ const formData = new FormData();
425
+ formData.append('image', blob);
426
+
427
+ try {
428
+ // 显示上传进度提示
429
+ const uploadingText = `![Uploading...]()\n`;
430
+ const startPos = this.selectionStart;
431
+ this.value = this.value.substring(0, startPos) +
432
+ uploadingText +
433
+ this.value.substring(this.selectionEnd);
434
+
435
+ // 发送图片到服务器
436
+ const response = await fetch('upload.php', {
437
+ method: 'POST',
438
+ body: formData
439
+ });
440
+
441
+ const result = await response.json();
442
+
443
+ if (result.success) {
444
+ // 使用完整的URL路径
445
+ const imageMarkdown = `![](${siteUrl}${result.url})\n`;
446
+ const currentContent = this.value;
447
+ const uploadingIndex = currentContent.indexOf(uploadingText);
448
+
449
+ if (uploadingIndex !== -1) {
450
+ this.value = currentContent.substring(0, uploadingIndex) +
451
+ imageMarkdown +
452
+ currentContent.substring(uploadingIndex + uploadingText.length);
453
+
454
+ // 触发预览更新
455
+ updatePreview(markdownPreview, textarea);
456
+ }
457
+ } else {
458
+ // 处理上传失败
459
+ alert('图片上传失败: ' + result.message);
460
+ }
461
+ } catch (error) {
462
+ alert('图片上传出错: ' + error.message);
463
+ }
464
+ }
465
+ }
466
+ });
467
+ });
468
+
469
+ function updatePreview(markdownPreview, textarea) {
470
+ if (markdownPreview && markdownPreview.style.display !== 'none') {
471
+ try {
472
+ const content = textarea.value || '';
473
+ const parsedContent = marked.parse(content);
474
+ markdownPreview.innerHTML = parsedContent;
475
+
476
+ // 为预览窗口中的图片添加点击事件
477
+ markdownPreview.querySelectorAll('img').forEach(img => {
478
+ img.addEventListener('click', function(e) {
479
+ e.preventDefault();
480
+ // 创建模态框显示大图
481
+ const modal = document.createElement('div');
482
+ modal.style.cssText = `
483
+ position: fixed;
484
+ top: 0;
485
+ left: 0;
486
+ width: 100%;
487
+ height: 100%;
488
+ background: rgba(0, 0, 0, 0.9);
489
+ display: flex;
490
+ justify-content: center;
491
+ align-items: center;
492
+ z-index: 1000;
493
+ cursor: zoom-out;
494
+ `;
495
+
496
+ const modalImg = document.createElement('img');
497
+ modalImg.src = this.src;
498
+ modalImg.style.cssText = `
499
+ max-width: 90%;
500
+ max-height: 90vh;
501
+ object-fit: contain;
502
+ border-radius: 4px;
503
+ `;
504
+
505
+ modal.appendChild(modalImg);
506
+ document.body.appendChild(modal);
507
+
508
+ // 点击模态框关闭
509
+ modal.addEventListener('click', function() {
510
+ document.body.removeChild(modal);
511
+ });
512
+ });
513
+ });
514
+ } catch (error) {
515
+ console.error('Markdown parsing error:', error);
516
+ markdownPreview.innerHTML = '<p style="color: red;">Error parsing markdown content</p>';
517
+ }
518
+ }
519
+ }
520
+
521
+ // 在原有的 script 标中添加/修改
522
+ document.addEventListener('DOMContentLoaded', function() {
523
+ // 标签切换
524
+ const tabButtons = document.querySelectorAll('.tab-button');
525
+ const pasteForm = document.getElementById('pasteForm');
526
+ const textShareForm = document.getElementById('textShareForm');
527
+ const fileForm = document.getElementById('fileForm');
528
+
529
+ tabButtons.forEach(button => {
530
+ button.addEventListener('click', function() {
531
+ const tab = this.dataset.tab;
532
+
533
+ // 更新按钮状态
534
+ tabButtons.forEach(btn => btn.classList.remove('active'));
535
+ this.classList.add('active');
536
+
537
+ // 显示/隐藏表单
538
+ pasteForm.style.display = tab === 'paste' ? 'block' : 'none';
539
+ textShareForm.style.display = tab === 'code' ? 'block' : 'none';
540
+ fileForm.style.display = tab === 'file' ? 'block' : 'none';
541
+ });
542
+ });
543
+
544
+ // 文本分享表单处理
545
+ if (textShareForm) {
546
+ textShareForm.onsubmit = async (e) => {
547
+ e.preventDefault();
548
+ const formData = new FormData(e.target);
549
+
550
+ try {
551
+ const response = await fetch('file_api.php', {
552
+ method: 'POST',
553
+ body: formData
554
+ });
555
+
556
+ const result = await response.json();
557
+ if (result.status === 'success') {
558
+ // 显示提取码和分享链接
559
+ const shareUrl = `${window.location.origin}?code=${result.code}`;
560
+ const modal = document.createElement('div');
561
+ modal.className = 'modal';
562
+ modal.innerHTML = `
563
+ <div class="modal-content">
564
+ <h3>分享成功!</h3>
565
+ <div class="share-info">
566
+ <div class="copy-group">
567
+ <label>提取码:</label>
568
+ <span class="copy-text" data-clipboard="${result.code}">${result.code}</span>
569
+ <button class="copy-btn" onclick="copyText(this.previousElementSibling)">复制</button>
570
+ </div>
571
+ <div class="copy-group">
572
+ <label>分享链接:</label>
573
+ <span class="copy-text" data-clipboard="${shareUrl}">${shareUrl}</span>
574
+ <button class="copy-btn" onclick="copyText(this.previousElementSibling)">复制</button>
575
+ </div>
576
+ </div>
577
+ <button class="close-btn" onclick="this.parentElement.parentElement.remove()">关闭</button>
578
+ </div>
579
+ `;
580
+ document.body.appendChild(modal);
581
+ } else {
582
+ alert('分享失败:' + result.message);
583
+ }
584
+ } catch (error) {
585
+ alert('分享出错:' + error.message);
586
+ }
587
+ };
588
+ }
589
+
590
+ // 文件上传表单处理
591
+ if (fileForm) {
592
+ const fileInput = document.getElementById('fileInput');
593
+ const fileInfo = document.querySelector('.file-info');
594
+ const progressBar = document.querySelector('.progress-bar-fill');
595
+ const progressText = document.querySelector('.progress-text');
596
+ const uploadProgress = document.querySelector('.upload-progress');
597
+
598
+ fileInput.addEventListener('change', function() {
599
+ if (this.files[0]) {
600
+ const file = this.files[0];
601
+ const size = (file.size / 1024 / 1024).toFixed(2);
602
+ fileInfo.textContent = `文件名: ${file.name}, 大小: ${size}MB`;
603
+ }
604
+ });
605
+
606
+ fileForm.onsubmit = async (e) => {
607
+ e.preventDefault();
608
+ const formData = new FormData(e.target);
609
+
610
+ try {
611
+ uploadProgress.style.display = 'block';
612
+
613
+ const xhr = new XMLHttpRequest();
614
+ xhr.upload.onprogress = (e) => {
615
+ if (e.lengthComputable) {
616
+ const percentComplete = Math.round((e.loaded / e.total) * 100);
617
+ progressBar.style.width = percentComplete + '%';
618
+ progressText.textContent = percentComplete + '%';
619
+ }
620
+ };
621
+
622
+ // 使用 Promise 包装 XHR 请求
623
+ const response = await new Promise((resolve, reject) => {
624
+ xhr.onload = () => {
625
+ if (xhr.status === 200) {
626
+ try {
627
+ resolve(JSON.parse(xhr.responseText));
628
+ } catch (e) {
629
+ reject(new Error('解析响应失败'));
630
+ }
631
+ } else {
632
+ reject(new Error('上传失败'));
633
+ }
634
+ };
635
+ xhr.onerror = () => reject(new Error('网络错误'));
636
+
637
+ xhr.open('POST', 'file_api.php', true);
638
+ xhr.send(formData);
639
+ });
640
+
641
+ if (response.status === 'success') {
642
+ const shareUrl = `${window.location.origin}?code=${response.code}`;
643
+ const modal = document.createElement('div');
644
+ modal.className = 'modal';
645
+ modal.innerHTML = `
646
+ <div class="modal-content">
647
+ <h3>上传成功!</h3>
648
+ <div class="share-info">
649
+ <div class="copy-group">
650
+ <label>提取码:</label>
651
+ <span class="copy-text" data-clipboard="${response.code}">${response.code}</span>
652
+ <button class="copy-btn" onclick="copyText(this.previousElementSibling)">复制</button>
653
+ </div>
654
+ <div class="copy-group">
655
+ <label>分享链接:</label>
656
+ <span class="copy-text" data-clipboard="${shareUrl}">${shareUrl}</span>
657
+ <button class="copy-btn" onclick="copyText(this.previousElementSibling)">复制</button>
658
+ </div>
659
+ </div>
660
+ <button class="close-btn" onclick="this.parentElement.parentElement.remove()">关闭</button>
661
+ </div>
662
+ `;
663
+ document.body.appendChild(modal);
664
+
665
+ // 重置进度条
666
+ setTimeout(() => {
667
+ uploadProgress.style.display = 'none';
668
+ progressBar.style.width = '0%';
669
+ progressText.textContent = '0%';
670
+ }, 1000);
671
+ } else {
672
+ alert('上传失败:' + response.message);
673
+ }
674
+ } catch (error) {
675
+ alert('上传出错:' + error.message);
676
+ }
677
+ };
678
+ }
679
+ });
680
+
681
+ // 修改获取文件函数
682
+ async function getFile() {
683
+ const code = document.getElementById('codeInput').value.trim();
684
+ if (!code) {
685
+ alert('请输入提取码');
686
+ return;
687
+ }
688
+
689
+ // 显示加载动画
690
+ const loadingModal = document.createElement('div');
691
+ loadingModal.className = 'modal';
692
+ loadingModal.innerHTML = `
693
+ <div class="modal-content">
694
+ <h3>正在获取内容...</h3>
695
+ <div class="loading-spinner"></div>
696
+ </div>
697
+ `;
698
+ document.body.appendChild(loadingModal);
699
+
700
+ try {
701
+ const response = await fetch(`file_api.php?code=${code}`);
702
+ const result = await response.json();
703
+
704
+ if (result.status === 'success') {
705
+ if (result.content) {
706
+ // 文本内容
707
+ const fileInfo = document.getElementById('fileInfo');
708
+ fileInfo.style.display = 'block';
709
+ fileInfo.querySelector('.content').innerHTML = `<pre>${result.content}</pre>`;
710
+
711
+ // 添加过期时间显示
712
+ const info = fileInfo.querySelector('.info');
713
+ if (info) {
714
+ info.innerHTML = `
715
+ 下载次数: ${result.current_downloads}/${result.max_downloads ? result.max_downloads : '∞'}
716
+ | 过期时间: ${result.expires_at_formatted}
717
+ `;
718
+ }
719
+ } else {
720
+ // 文件下载 - 创建下载链接
721
+ const downloadUrl = `${siteUrl}/file_api.php?download=${code}&filename=${encodeURIComponent(result.filename)}`;
722
+ window.location.href = downloadUrl;
723
+ }
724
+ } else {
725
+ alert(result.message || '获取内容失败');
726
+ }
727
+ } catch (error) {
728
+ alert('获取文件失败:' + error.message);
729
+ } finally {
730
+ // 移除加载动画
731
+ loadingModal.remove();
732
+ }
733
+ }
734
+
735
+ // 添加复制功能
736
+ function copyText(element) {
737
+ const text = element.getAttribute('data-clipboard');
738
+ navigator.clipboard.writeText(text).then(() => {
739
+ // 显示复制成功提示
740
+ const originalText = element.nextElementSibling.textContent;
741
+ element.nextElementSibling.textContent = '已复制!';
742
+ setTimeout(() => {
743
+ element.nextElementSibling.textContent = originalText;
744
+ }, 1000);
745
+ }).catch(err => {
746
+ console.error('复制失败:', err);
747
+ });
748
+ }
749
+
750
+ // 在 DOMContentLoaded 事件中添加自动填充提取码的功能
751
+ document.addEventListener('DOMContentLoaded', function() {
752
+ // 检查 URL 中是否有提取码
753
+ const urlParams = new URLSearchParams(window.location.search);
754
+ const code = urlParams.get('code');
755
+ if (code) {
756
+ // 自动填充提取码并触发获取
757
+ const codeInput = document.getElementById('codeInput');
758
+ if (codeInput) {
759
+ codeInput.value = code;
760
+ getFile(); // 自动获取文件
761
+ }
762
+ }
763
+ // ... 其他现有的 DOMContentLoaded 代码 ...
764
+ });
765
+ </script>
766
+
767
+ <!-- 添加模态框样式 -->
768
+ <style>
769
+ .modal {
770
+ position: fixed;
771
+ top: 0;
772
+ left: 0;
773
+ width: 100%;
774
+ height: 100%;
775
+ background: rgba(0, 0, 0, 0.5);
776
+ display: flex;
777
+ justify-content: center;
778
+ align-items: center;
779
+ z-index: 1000;
780
+ }
781
+
782
+ .modal-content {
783
+ background: var(--container-bg);
784
+ padding: 2rem;
785
+ border-radius: 8px;
786
+ box-shadow: var(--shadow);
787
+ text-align: center;
788
+ }
789
+
790
+ .modal-content h3 {
791
+ margin-bottom: 1rem;
792
+ }
793
+
794
+ .modal-content strong {
795
+ font-size: 1.2rem;
796
+ color: var(--primary-color);
797
+ }
798
+
799
+ .modal-content button {
800
+ margin-top: 1rem;
801
+ padding: 0.5rem 1rem;
802
+ background: var(--primary-color);
803
+ color: white;
804
+ border: none;
805
+ border-radius: 4px;
806
+ cursor: pointer;
807
+ }
808
+
809
+ .modal-content button:hover {
810
+ background: var(--primary-hover);
811
+ }
812
+ </style>
813
+ </body>
814
+ </html>
init.php ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ // init.php
3
+
4
+ // 定义基础配置
5
+ $CONFIG = [
6
+ 'data_dir' => './data-ekdvbq/',
7
+ 'db_path' => './data-ekdvbq/notes.db',
8
+ ];
9
+
10
+ // 创建数据目录
11
+ if (!file_exists($CONFIG['data_dir'])) {
12
+ if (!mkdir($CONFIG['data_dir'], 0755, true)) {
13
+ die("无法创建数据目录:" . $CONFIG['data_dir'] . "\n");
14
+ }
15
+ echo "已创建数据目录:" . $CONFIG['data_dir'] . "\n";
16
+ } else {
17
+ echo "数据目录已存在:" . $CONFIG['data_dir'] . "\n";
18
+ }
19
+
20
+ // 检查目录权限
21
+ if (!is_writable($CONFIG['data_dir'])) {
22
+ die("数据目录不可写:" . $CONFIG['data_dir'] . "\n");
23
+ }
24
+
25
+ // 初始化数据库
26
+ if (file_exists($CONFIG['db_path'])) {
27
+ echo "数据库文件已存在。是否重新初始化?(y/N): ";
28
+ $handle = fopen("php://stdin", "r");
29
+ $line = trim(fgets($handle));
30
+ fclose($handle);
31
+ if (strtolower($line) !== 'y') {
32
+ die("操作取消\n");
33
+ }
34
+ unlink($CONFIG['db_path']);
35
+ }
36
+
37
+ try {
38
+ $db = new SQLite3($CONFIG['db_path']);
39
+
40
+ // 创建笔记表
41
+ $db->exec('CREATE TABLE IF NOT EXISTS notes (
42
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
43
+ uuid TEXT UNIQUE NOT NULL,
44
+ content TEXT NOT NULL,
45
+ created_at INTEGER NOT NULL,
46
+ expires_at INTEGER NOT NULL,
47
+ max_views INTEGER NOT NULL DEFAULT 0,
48
+ current_views INTEGER NOT NULL DEFAULT 0,
49
+ is_encrypted INTEGER NOT NULL DEFAULT 0,
50
+ is_markdown INTEGER NOT NULL DEFAULT 0
51
+ )');
52
+
53
+ // 创建设置表
54
+ $db->exec('CREATE TABLE IF NOT EXISTS settings (
55
+ key TEXT PRIMARY KEY,
56
+ value TEXT NOT NULL
57
+ )');
58
+
59
+ // 创建索引
60
+ $db->exec('CREATE INDEX IF NOT EXISTS idx_uuid ON notes (uuid)');
61
+ $db->exec('CREATE INDEX IF NOT EXISTS idx_expires_at ON notes (expires_at)');
62
+
63
+ // 设置文件权限
64
+ chmod($CONFIG['db_path'], 0644);
65
+
66
+ echo "数据库初始化完成\n";
67
+
68
+ } catch (Exception $e) {
69
+ die("数据库初始化失败:" . $e->getMessage() . "\n");
70
+ }
router.php ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ // 设置时区
3
+ date_default_timezone_set('Asia/Shanghai');
4
+
5
+ // 处理错误输出
6
+ ini_set('display_errors', 'Off');
7
+ error_reporting(E_ALL);
8
+ ini_set('log_errors', 'On');
9
+ ini_set('error_log', '/dev/stderr');
10
+
11
+ // 确保输出正确的 Content-Type
12
+ if (strpos($_SERVER['REQUEST_URI'], '/api.php') === 0 ||
13
+ strpos($_SERVER['REQUEST_URI'], '/file_api.php') === 0 ||
14
+ strpos($_SERVER['REQUEST_URI'], '/upload.php') === 0) {
15
+ header('Content-Type: application/json');
16
+ }
17
+
18
+ // 处理 UUID 格式的 URL
19
+ if (preg_match('/^\/([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})$/', $_SERVER['REQUEST_URI'], $matches)) {
20
+ $_GET['id'] = $matches[1];
21
+ require __DIR__ . '/index.php';
22
+ return true;
23
+ }
24
+
25
+ // 处理静态文件
26
+ if (preg_match('/\.(css|js|png|jpg|jpeg|gif|ico|webp)$/', $_SERVER['REQUEST_URI'])) {
27
+ return false; // 让 PHP 内置服务器处理静态文件
28
+ }
29
+
30
+ // 处理 API 请求
31
+ if (strpos($_SERVER['REQUEST_URI'], '/api.php') === 0) {
32
+ ob_start(); // 开始输出缓冲
33
+ require __DIR__ . '/api.php';
34
+ $output = ob_get_clean(); // 获取并清除缓冲
35
+ if (json_decode($output) === null) {
36
+ // 如果不是有效的 JSON,返回错误
37
+ echo json_encode(['status' => 'error', 'message' => 'Invalid response']);
38
+ } else {
39
+ echo $output;
40
+ }
41
+ return true;
42
+ }
43
+
44
+ if (strpos($_SERVER['REQUEST_URI'], '/file_api.php') === 0) {
45
+ ob_start(); // 开始输出缓冲
46
+ require __DIR__ . '/file_api.php';
47
+ $output = ob_get_clean(); // 获取并清除缓冲
48
+ if (json_decode($output) === null && !headers_sent()) {
49
+ // 如果不是有效的 JSON 且头部未发送,返回错误
50
+ echo json_encode(['status' => 'error', 'message' => 'Invalid response']);
51
+ } else {
52
+ echo $output;
53
+ }
54
+ return true;
55
+ }
56
+
57
+ if (strpos($_SERVER['REQUEST_URI'], '/upload.php') === 0) {
58
+ ob_start(); // 开始输出缓冲
59
+ require __DIR__ . '/upload.php';
60
+ $output = ob_get_clean(); // 获取并清除缓冲
61
+ if (json_decode($output) === null) {
62
+ // 如果不是有效的 JSON,返回错误
63
+ echo json_encode(['status' => 'error', 'message' => 'Invalid response']);
64
+ } else {
65
+ echo $output;
66
+ }
67
+ return true;
68
+ }
69
+
70
+ // 默认路由到 index.php
71
+ require __DIR__ . '/index.php';
72
+ return true;
settings.php ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ // settings.php
3
+
4
+ class Settings {
5
+ private $db = null;
6
+ private $defaultSettings = [
7
+ 'site_name' => 'MoeNote',
8
+ 'site_description' => 'A simple online note that focuses on safe sharing.',
9
+ 'data_directory' => '/var/www/html/data/',
10
+ 'db_path' => '/var/www/html/data/notes.db',
11
+ 'favicon_path' => '/favicon.png',
12
+ 'max_expire_time' => 31536000, // 1yr
13
+ ];
14
+
15
+ public function __construct() {
16
+ $this->initializeDatabase();
17
+ }
18
+
19
+ private function initializeDatabase() {
20
+ // 确保数据目录存在
21
+ $dataDir = dirname($this->defaultSettings['db_path']);
22
+ if (!file_exists($dataDir)) {
23
+ if (!mkdir($dataDir, 0777, true)) {
24
+ throw new Exception('无法创建数据目录,请检查权限');
25
+ }
26
+
27
+ // 创建 .htaccess 文件来保护数据目录
28
+ $htaccess = $dataDir . '/.htaccess';
29
+ if (!file_exists($htaccess)) {
30
+ $htaccess_content = "Require all denied\n";
31
+ $htaccess_content .= "Options -Indexes\n";
32
+ file_put_contents($htaccess, $htaccess_content);
33
+ }
34
+
35
+ // 创建 index.html
36
+ $index_html = $dataDir . '/index.html';
37
+ if (!file_exists($index_html)) {
38
+ file_put_contents($index_html, '<!DOCTYPE html><html><head><title>403 Forbidden</title></head><body><h1>403 Forbidden</h1></body></html>');
39
+ }
40
+ }
41
+
42
+ // 连接或创建数据库
43
+ $this->db = new SQLite3($this->defaultSettings['db_path']);
44
+
45
+ // 创建必要的表
46
+ $this->db->exec('
47
+ CREATE TABLE IF NOT EXISTS settings (
48
+ key TEXT PRIMARY KEY,
49
+ value TEXT
50
+ )
51
+ ');
52
+
53
+ $this->db->exec('
54
+ CREATE TABLE IF NOT EXISTS notes (
55
+ uuid TEXT PRIMARY KEY,
56
+ content TEXT,
57
+ created_at INTEGER,
58
+ expires_at INTEGER,
59
+ max_views INTEGER DEFAULT 0,
60
+ current_views INTEGER DEFAULT 0,
61
+ is_markdown INTEGER DEFAULT 0,
62
+ is_encrypted INTEGER DEFAULT 0
63
+ )
64
+ ');
65
+
66
+ // 在 initializeDatabase 方法中添加新表
67
+ $this->db->exec('
68
+ CREATE TABLE IF NOT EXISTS files (
69
+ code TEXT PRIMARY KEY, -- 提取码
70
+ filename TEXT, -- 原始文件名
71
+ filepath TEXT, -- 存储路径
72
+ content TEXT, -- 文本内容(如果是文本类型)
73
+ type TEXT, -- 类型:file 或 text
74
+ created_at INTEGER, -- 创建时间
75
+ expires_at INTEGER, -- 过期时间
76
+ max_downloads INTEGER DEFAULT 0, -- 最大下载/查看次数
77
+ current_downloads INTEGER DEFAULT 0 -- 当前下载/查看次数
78
+ )
79
+ ');
80
+
81
+ // 初始化默认设置
82
+ foreach ($this->defaultSettings as $key => $value) {
83
+ $stmt = $this->db->prepare('INSERT OR IGNORE INTO settings (key, value) VALUES (:key, :value)');
84
+ $stmt->bindValue(':key', $key, SQLITE3_TEXT);
85
+ $stmt->bindValue(':value', $value, SQLITE3_TEXT);
86
+ $stmt->execute();
87
+ }
88
+ }
89
+
90
+ public function getSetting($key, $default = null) {
91
+ try {
92
+ $stmt = $this->db->prepare('SELECT value FROM settings WHERE key = :key');
93
+ $stmt->bindValue(':key', $key, SQLITE3_TEXT);
94
+ $result = $stmt->execute();
95
+
96
+ if ($row = $result->fetchArray(SQLITE3_ASSOC)) {
97
+ return $row['value'];
98
+ }
99
+ } catch (Exception $e) {
100
+ // 如果发生错误,返回默认值
101
+ }
102
+
103
+ return isset($this->defaultSettings[$key]) ? $this->defaultSettings[$key] : $default;
104
+ }
105
+
106
+ public function setSetting($key, $value) {
107
+ $stmt = $this->db->prepare('INSERT OR REPLACE INTO settings (key, value) VALUES (:key, :value)');
108
+ $stmt->bindValue(':key', $key, SQLITE3_TEXT);
109
+ $stmt->bindValue(':value', $value, SQLITE3_TEXT);
110
+ return $stmt->execute();
111
+ }
112
+
113
+ public function getDefaultSettings() {
114
+ return $this->defaultSettings;
115
+ }
116
+ }
upload.php ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ header('Content-Type: application/json');
3
+
4
+ // 设置允许的图片类型
5
+ $allowed_types = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
6
+ $max_size = 20 * 1024 * 1024; // 20MB
7
+
8
+ try {
9
+ // 确保上传目录存在
10
+ $upload_dir = __DIR__ . '/uploads/images/';
11
+ if (!is_dir($upload_dir)) {
12
+ // 尝试创建目录
13
+ if (!mkdir($upload_dir, 0755, true)) {
14
+ throw new Exception('无法创建上传目录,请检查权限');
15
+ }
16
+
17
+ // 创建 .htaccess 文件来保护上传目录
18
+ $htaccess = $upload_dir . '.htaccess';
19
+ if (!file_exists($htaccess)) {
20
+ $htaccess_content = "Options -Indexes\n";
21
+ $htaccess_content .= "DirectoryIndex 403.html\n"; // 使用 403.html 作为目录默认页
22
+ $htaccess_content .= "AddType text/plain .php\n";
23
+ $htaccess_content .= "AddType text/plain .html\n";
24
+ $htaccess_content .= "AddType text/plain .htm\n";
25
+ $htaccess_content .= "AddType text/plain .htaccess\n";
26
+ file_put_contents($htaccess, $htaccess_content);
27
+ }
28
+
29
+ // 创建 index.html 文件来防止目录列表
30
+ $index_html = $upload_dir . 'index.html';
31
+ if (!file_exists($index_html)) {
32
+ file_put_contents($index_html, '<!DOCTYPE html><html><head><title>403 Forbidden</title></head><body><h1>403 Forbidden</h1></body></html>');
33
+ }
34
+
35
+ // 创建 403.html 文件来防止目录列表
36
+ $forbidden_page = $upload_dir . '403.html';
37
+ if (!file_exists($forbidden_page)) {
38
+ file_put_contents($forbidden_page, '<!DOCTYPE html><html><head><title>403 Forbidden</title></head><body><h1>403 Forbidden</h1><p>Access to this directory is forbidden.</p></body></html>');
39
+ }
40
+ }
41
+
42
+ if (!isset($_FILES['image'])) {
43
+ throw new Exception('没有收到图片文件');
44
+ }
45
+
46
+ $file = $_FILES['image'];
47
+
48
+ // 检查错误
49
+ if ($file['error'] !== UPLOAD_ERR_OK) {
50
+ throw new Exception('文件上传错误: ' . $file['error']);
51
+ }
52
+
53
+ // 检查文件类型
54
+ if (!in_array($file['type'], $allowed_types)) {
55
+ throw new Exception('不支持的文件类型');
56
+ }
57
+
58
+ // 检查文件大小
59
+ if ($file['size'] > $max_size) {
60
+ throw new Exception('文件大小超过限制');
61
+ }
62
+
63
+ // 生成唯一文件名
64
+ $extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
65
+ $filename = uniqid() . '_' . bin2hex(random_bytes(8)) . '.' . $extension;
66
+
67
+ // 移动文件到目标位置
68
+ $filepath = $upload_dir . $filename;
69
+ if (!move_uploaded_file($file['tmp_name'], $filepath)) {
70
+ throw new Exception('文件保存失败');
71
+ }
72
+
73
+ // 设置文件权限
74
+ chmod($filepath, 0644);
75
+
76
+ // 在成功上传后添加 .htaccess 文件来设置图片缓存
77
+ $images_htaccess = $upload_dir . '.htaccess';
78
+ if (!file_exists($images_htaccess)) {
79
+ $cache_rules = "
80
+ <IfModule mod_expires.c>
81
+ ExpiresActive On
82
+ ExpiresByType image/jpg \"access plus 1 year\"
83
+ ExpiresByType image/jpeg \"access plus 1 year\"
84
+ ExpiresByType image/gif \"access plus 1 year\"
85
+ ExpiresByType image/png \"access plus 1 year\"
86
+ ExpiresByType image/webp \"access plus 1 year\"
87
+ </IfModule>
88
+
89
+ <IfModule mod_headers.c>
90
+ <FilesMatch \"\.(jpg|jpeg|png|gif|webp)$\">
91
+ Header set Cache-Control \"public, max-age=31536000\"
92
+ </FilesMatch>
93
+ </IfModule>
94
+
95
+ Options -Indexes
96
+ DirectoryIndex 403.html
97
+ AddType text/plain .php
98
+ AddType text/plain .html
99
+ AddType text/plain .htm
100
+ AddType text/plain .htaccess
101
+ ";
102
+ file_put_contents($images_htaccess, $cache_rules);
103
+ }
104
+
105
+ // 返回成功响应
106
+ echo json_encode([
107
+ 'success' => true,
108
+ 'url' => '/uploads/images/' . $filename
109
+ ]);
110
+
111
+ } catch (Exception $e) {
112
+ error_log('Image upload error: ' . $e->getMessage());
113
+ http_response_code(400);
114
+ echo json_encode([
115
+ 'success' => false,
116
+ 'message' => $e->getMessage()
117
+ ]);
118
+ }