PicoShare 1.5.4 API 使用详解
PicoShare 1.5.4 全部 REST 端点实测手册:Cookie 会话认证、文件上传/下载/编辑/删除、Guest Links 访客通道与运行期设置逐域 curl 实测回显,附 expiration 必填约束、fileLifetime 时长语法与错误码速查。
PicoShare 1.5.4 API 使用详解
目标:把 PicoShare 1.5.4 的 REST API 逐域讲透——认证会话、文件上传/下载/编辑/删除、访客上传通道(Guest Links)与运行期设置,每个端点给出真实 curl 请求与实测响应回显,读者凭本篇即可用脚本完整驱动一个 PicoShare 实例。全部请求/响应在 Ubuntu 22.04 服务器的 v1.5.4 实例上逐条实测通过。
一、环境信息
本文全部请求均在以下环境实测取得:
- 操作系统:Ubuntu 22.04(x86_64)
- PicoShare 版本:v1.5.4(GitHub Releases 当前最新稳定版,发布于 2026-05-26,查询日期 2026-10-31)
- 部署形态:单实例部署(部署过程见 《PicoShare 1.5.4 Docker Compose 部署指南》)
- API 基地址:
http://localhost:8130(服务器端口 8130 映射服务监听的 4001) - 认证口令:
q1w2e3r4(环境变量PS_SHARED_SECRET注入的演示口令) - 客户端工具:curl(系统自带即可,全部示例可直接复制执行)
- 实测日期:2026-10-31
二、前置条件
开始调用前需要确认三件事:实例可达、知道认证方式、选好客户端工具。
- 一个可访问的 PicoShare 1.5.4 实例。健康自检直接请求首页,返回 200 即可:
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8130/
200
-
理解认证载体:PicoShare 用签名 Cookie(
sharedSecret)承载会话,登录一次换取 30 天有效期的 Cookie,后续请求以Cookie: sharedSecret=...头携带(详见 3.2 节)。 -
客户端工具无特殊要求:curl、httpie、Postman 均可。本文统一用 curl——上传用
-F(multipart 表单),JSON 请求用-H 'Content-Type: application/json' -d '{...}',调试响应头用-i。
⚠️ 注意:v1.5.4 的 API 路径以
/-<ID>为下载链接、/api/...为操作接口。旧教程中/!<ID>是兼容旧客户端保留的等价格式,新脚本建议统一用/-<ID>。
三、API 总览
3.1 协议与端点清单
REST 风格:JSON 报文为主(上传为 multipart 表单,下载为原始文件流,错误响应为 text/plain)。认证端点公开,其余写操作端点均要求会话 Cookie。全部端点如下(经 v1.5.4 实例逐一验证可达):
| 分域 | 方法 | 端点 | 认证 | 作用 |
|---|---|---|---|---|
| 认证 | POST | /api/auth | 无 | 口令登录,签发会话 Cookie |
| 认证 | DELETE | /api/auth | Cookie | 登出(清除客户端 Cookie) |
| 条目 | POST | /api/entry | Cookie | 上传文件,返回条目 ID |
| 条目 | PUT | /api/entry/{id} | Cookie | 修改文件名/备注/过期时间 |
| 条目 | DELETE | /api/entry/{id} | Cookie | 删除文件 |
| 条目 | GET | /-{id}、/-{id}/{filename}、/!{id} | 无 | 下载(直链,访客可访问) |
| 条目 | GET | /files/{id}/downloads | Cookie | 下载记录页(HTML 视图) |
| 访客 | POST | /api/guest-links | Cookie | 创建访客上传通道 |
| 访客 | DELETE | /api/guest-links/{id} | Cookie | 删除访客通道 |
| 访客 | PUT | /api/guest-links/{id}/enable、/{id}/disable | Cookie | 启用/禁用通道 |
| 访客 | POST | /api/guest/{guestLinkID} | 无(通道自身约束) | 匿名经通道上传 |
| 设置 | PUT | /api/settings | Cookie | 修改默认文件存活期 |
| 视图 | GET | /settings、/information、/files 等 | Cookie | Web 管理页面(HTML) |
💡 技术拓展:Web 服务的 API 认证载体常见三种——
方式 原理 适用场景 会话 Cookie 登录后服务端签发签名 Cookie,浏览器自动携带 浏览器为主的应用(PicoShare 采用) Bearer JWT 无状态令牌放 Authorization头,适合跨域/移动端前后端分离、开放 API API Key 长期静态密钥,常配独立权限 机器对机器集成 PicoShare 选择 Cookie 会话:单管理员产品,登录即用浏览器,Cookie 配合
HttpOnly防 XSS 窃取;代价是 curl 脚本必须先登录再手动带 Cookie 头(下文 4.1 节)。
3.2 认证模型与会话约定
- 登录凭据:环境变量
PS_SHARED_SECRET的值就是唯一口令,没有独立用户体系; - 会话 Cookie:登录成功后服务端返回
Set-Cookie: sharedSecret=...,有效期 30 天(Max-Age=2592000),属性HttpOnly+SameSite=Strict;Cookie 值不是口令本体,而是服务端用口令做 KDF 派生的密钥(同一口令派生值固定,因此 Cookie 可跨请求复现); - 登出语义:
DELETE /api/auth只返回 200 让客户端清 Cookie,服务端不吊销派生密钥——登出后旧 Cookie 仍然可用(实测见 4.3 节)。轮换口令并重启服务才会使旧会话失效; - 无分页/过滤约定:条目与访客通道的列表仅以 HTML 视图呈现,API 层无分页参数,脚本化操作围绕单条资源端点展开。
四、认证与首次调用
4.1 登录换取会话 Cookie
向 /api/auth 提交 JSON,字段名为 sharedSecretKey:
curl -si -X POST http://localhost:8130/api/auth -H 'Content-Type: application/json' \
-d '{"sharedSecretKey":"q1w2e3r4"}'
HTTP/1.1 200 OK
Set-Cookie: sharedSecret=a7XY9QIgm2mimIUtd0ZWY8sETVBgF/ERlYeATvYexqw=; Path=/; Max-Age=2592000; HttpOnly; SameSite=Strict
Date: Sat, 31 Oct 2026 00:45:45 GMT
Content-Length: 0
(响应头节选:省略与调用无关的头。)后续所有请求带上这个 Cookie:
COOKIE="sharedSecret=a7XY9QIgm2mimIUtd0ZWY8sETVBgF/ERlYeATvYexqw="
💡 说明:同一口令每次登录返回的 Cookie 值相同(KDF 派生确定),脚本可以固定复用该值,不必每次登录。但轮换口令后必须重新登录取新值。
4.2 认证失败与未认证访问
错误口令返回 401,错误体为纯文本:
curl -si -X POST http://localhost:8130/api/auth -H 'Content-Type: application/json' \
-d '{"sharedSecretKey":"wrongpass"}'
HTTP/1.1 401 Unauthorized
Content-Type: text/plain; charset=utf-8
X-Content-Type-Options: nosniff
Date: Sat, 31 Oct 2026 00:45:44 GMT
Content-Length: 24
incorrect shared secret
不带 Cookie 直接调用受保护端点(如上传)同样 401;伪造 Cookie 值也是 401:
curl -s -o /dev/null -w 'HTTP %{http_code}\n' -F "file=@/tmp/anon.txt" http://localhost:8130/api/entry
HTTP 401
curl -si -X POST http://localhost:8130/api/entry -H 'Cookie: sharedSecret=forgedvalue' -F "file=@/tmp/anon.txt" | head -1
HTTP/1.1 401 Unauthorized
4.3 登出与会话有效性
curl -si -b "$COOKIE" -X DELETE http://localhost:8130/api/auth | head -1
HTTP/1.1 200 OK
关键验证——登出后用同一枚旧 Cookie继续调 API(对合法条目执行操作)仍被接受:
curl -s -b "$COOKIE" -F "file=@/tmp/picoapi3.txt" "http://localhost:8130/api/entry?expiration=2026-11-30T00:00:00Z"
{"id":"YgAbHBrrPA"}
⚠️ 注意:服务端不维护可吊销的会话表,登出仅是客户端行为。把浏览器借给他人用过的环境,正确做法是轮换
PS_SHARED_SECRET并重启服务,让派生密钥重建、旧 Cookie 全部失效。
五、文件条目域(/api/entry 与下载链接)
5.1 上传文件 POST /api/entry
上传用 multipart 表单,字段名固定为 file;可选表单字段 note 存备注。查询参数 expiration 为必填——RFC3339 格式、至少晚于当前 1 小时:
echo "note-test" > /tmp/picoapi2.txt
curl -s -b "$COOKIE" -F "file=@/tmp/picoapi2.txt" -F "note=API 实测样例文件" \
"http://localhost:8130/api/entry?expiration=2026-12-01T00:00:00Z"
{"id":"XMJknT7AFa"}
成功响应只有一个 id 字段(10 字符条目 ID),下载链接即 http://localhost:8130/-XMJknT7AFa。
三类过期参数错误(实测回显,均为 400):
curl -s -b "$COOKIE" -F "file=@/tmp/picoapi2.txt" "http://localhost:8130/api/entry?expiration=2026/12/01"
Invalid expiration URL parameter: unrecognized format for expiration time, must be in 2006-01-02T15:04:05Z07:00 format
curl -s -b "$COOKIE" -F "file=@/tmp/picoapi2.txt" "http://localhost:8130/api/entry?expiration=2026-10-31T00:50:00Z"
Invalid expiration URL parameter: expire time must be at least one hour in the future
curl -si -b "$COOKIE" -F "file=@/tmp/picoapi1.txt" http://localhost:8130/api/entry | grep -vE '^(Date|X-Content)'
HTTP/1.1 400 Bad Request
Content-Type: text/plain; charset=utf-8
Content-Length: 119
Invalid expiration URL parameter: unrecognized format for expiration time, must be in 2006-01-02T15:04:05Z07:00 format
⚠️ 注意:v1.5.4 的上传 API 没有「永不过期」表达——
expiration省略或传Never都会被 400 拒绝(Never字面量实测同样报 unrecognized format)。脚本要近似「不过期」时传一个远期时间即可(上限受 RFC3339 表达约束,如2126-01-01T00:00:00Z)。Web 界面的「Never」选项由前端设置体系管理,与 API 层无对应参数。
5.2 下载链接与响应头 GET /-<id>
下载端点无需认证,三种 URL 形态等价:/-<id>、/-<id>/<filename>、旧版兼容 /!<id>(均实测 200):
curl -si http://localhost:8130/-XMJknT7AFa | head -9
HTTP/1.1 200 OK
Accept-Ranges: bytes
Content-Disposition: filename="picoapi2.txt"
Content-Length: 10
Content-Security-Policy: sandbox
Content-Type: text/plain
Last-Modified: Sat, 31 Oct 2026 00:45:55 GMT
Date: Sat, 31 Oct 2026 00:47:31 GMT
(响应头节选。)要点逐项解读:
Content-Disposition: filename="picoapi2.txt":浏览器访问时按原文件名落盘;改名后此处同步变化(见 5.3 节);Content-Security-Policy: sandbox:PicoShare 的安全设计——即使分享的是恶意 HTML/JS 文件,也不会在主域上下文执行;Accept-Ranges: bytes:支持断点续传与分段下载。
非法 ID 的两类报错(400,实测)——长度不为 10:
curl -s http://localhost:8130/-ZZZZ
bad entry ID: entry ID (ZZZZ) has invalid length: got 4, want 10
字符集非法(ID 字符集剔除 I、l、1、0、O 等易混淆字符,报错给出字符的 ASCII 码):
bad entry ID: entry ID (jPZIdm0GoE) contains invalid character: 73
(73 即字符 I 的 ASCII 码。)ID 合法但文件不存在(已删除或未上传过)返回 404:
curl -si http://localhost:8130/-AAAAAAAAAA | grep -vE '^(Date|Content-Length|X-Content)'
HTTP/1.1 404 Not Found
Content-Security-Policy: sandbox
Content-Type: text/plain; charset=utf-8
entry not found
5.3 编辑条目 PUT /api/entry/{id}
JSON 请求体三个字段:filename(必填,传空串报错)、note、expiration(可选项均可单独更新,但 filename 必须非空):
curl -si -b "$COOKIE" -X PUT http://localhost:8130/api/entry/XMJknT7AFa \
-H 'Content-Type: application/json' \
-d '{"filename":"renamed-api.txt","note":"改名备注","expiration":"2026-12-15T00:00:00Z"}' | head -3
HTTP/1.1 200 OK
Date: Sat, 31 Oct 2026 00:47:31 GMT
Content-Length: 0
改名后下载响应头立即同步(直链 ID 不变):
curl -si http://localhost:8130/-XMJknT7AFa | grep -iE 'content-disposition|HTTP'
HTTP/1.1 200 OK
Content-Disposition: filename="renamed-api.txt"
只更新文件名(不传 expiration/note)也返回 200:
curl -si -b "$COOKIE" -X PUT http://localhost:8130/api/entry/34bwiT8TQi \
-H 'Content-Type: application/json' -d '{"filename":"only-name.txt"}' | head -1
HTTP/1.1 200 OK
空 filename 被 400 拒绝(实测错误体):
Bad request: filename must be non-empty
5.4 删除条目 DELETE /api/entry/{id}
curl -si -b "$COOKIE" -X DELETE http://localhost:8130/api/entry/XMJknT7AFa | head -1
HTTP/1.1 200 OK
删除后立即验证下载链接失效:
curl -s -o /dev/null -w '删除后下载 -> HTTP %{http_code}\n' http://localhost:8130/-XMJknT7AFa
删除后下载 -> HTTP 404
5.5 下载记录页 GET /files/{id}/downloads
下载历史以 HTML 视图提供(非 JSON API),登录态访问:
curl -s -b "$COOKIE" http://localhost:8130/files/XMJknT7AFa/downloads | grep -oE '<title>[^<]*</title>'
<title>PicoShare - Downloads</title>
六、访客上传域(/api/guest-links 与 /api/guest)
访客通道让外部人员在无账号、无登录的情况下向服务器投递文件。流程:管理员创建通道 → 把 /g/<通道ID> 页面链接发给对方 → 对方经页面上传(或直接调 /api/guest/<通道ID> 接口)。
6.1 创建通道 POST /api/guest-links
请求体五个字段:
| 字段 | 类型 | 约束 | 说明 |
|---|---|---|---|
label | string | 非空 | 通道名称,管理列表中显示 |
urlExpirationTime | string | RFC3339,至少晚于当前 1 小时 | 通道链接本身的有效期 |
fileLifetime | string | Go duration 格式,1 天~10 年 | 经此通道上传文件的存活期上限 |
maxFileBytes | number/null | null 或 ≥1048576(1MiB) | 单文件大小上限 |
maxFileUploads | number/null | null 或 ≥1 | 上传次数上限,1 即一次性链接 |
创建一个全参数通道:
curl -s -b "$COOKIE" -X POST http://localhost:8130/api/guest-links -H 'Content-Type: application/json' \
-d '{"label":"API 实测通道","urlExpirationTime":"2026-12-31T00:00:00Z","fileLifetime":"168h","maxFileBytes":10485760,"maxFileUploads":5}'
{"id":"uEutDtg85VGWmtbP"}
返回 16 字符通道 ID,访客上传页为 http://localhost:8130/g/uEutDtg85VGWmtbP。
fileLifetime 的时长语法与「永不过期」:接受 Go duration 格式(24h、168h、720h 等),下限 1 天、上限 10 年;特殊值 876000h(=100 年)被服务端识别为「无限」,即访客上传的文件永不过期:
curl -s -b "$COOKIE" -X POST http://localhost:8130/api/guest-links -H 'Content-Type: application/json' \
-d '{"label":"最小参数通道","urlExpirationTime":"2026-12-31T00:00:00Z","fileLifetime":"876000h","maxFileBytes":null,"maxFileUploads":null}'
{"id":"XSfdr5Dv7qwVNKYp"}
三类参数错误(400,实测错误体)——存活期低于 1 天:
curl -s -b "$COOKIE" -X POST http://localhost:8130/api/guest-links -H 'Content-Type: application/json' \
-d '{"label":"short","urlExpirationTime":"2026-12-31T00:00:00Z","fileLifetime":"1h","maxFileBytes":null,"maxFileUploads":null}'
Invalid request: file lifetime must be at least 1 day
大小限额低于 1MiB:
Invalid request: guest upload size limit must be at at least 1048576 bytes
(注意错误原文中 "at at least" 为服务端原样输出。)链接有效期过早:
Invalid request: expire time must be at least one hour in the future
时长格式非法(如 "xyz")同样 400。
6.2 匿名上传 POST /api/guest/{guestLinkID}
访客端上传接口不需要 Cookie,但 expiration 仍为必填,且不得超过通道的 fileLifetime 上限。通道 uEutDtg85VGWmtbP 的存活期上限为 168h(7 天),传 30 天后被拒绝:
curl -s -F "file=@/tmp/guest2.txt" "http://localhost:8130/api/guest/uEutDtg85VGWmtbP?expiration=2026-12-01T00:00:00Z"
Invalid expiration: requested expiration time of 2026-12-01 00:00:00 +0000 UTC is beyond guest link's limit: 2026-11-07 00:48:10.78036833 +0000 UTC m=+659615.045826678
(错误体给出了服务端允许的上限时刻 = 通道创建时刻 + fileLifetime。)改传合规值后成功:
curl -si -F "file=@/tmp/guest2.txt" "http://localhost:8130/api/guest/uEutDtg85VGWmtbP?expiration=2026-11-05T00:00:00Z" | grep -vE '^(Date|Content-Length)'
HTTP/1.1 200 OK
Content-Type: text/plain
http://localhost:8130/-qguc4DiCvt
💡 说明:与登录态上传返回 JSON
{"id":...}不同,访客上传成功直接返回纯文本的下载直链(text/plain,内容即 URL),方便页面向访客展示「上传完成,链接如下」。
一次性通道(maxFileUploads: 1):第一次上传成功,第二次上传立即被拒:
curl -s -F "file=@/tmp/guest2.txt" "http://localhost:8130/api/guest/35eknsLz66P88KKR?expiration=2026-11-05T00:00:00Z"
http://localhost:8130/-Lha4XeFg3K
curl -si -F "file=@/tmp/guest2.txt" "http://localhost:8130/api/guest/35eknsLz66P88KKR?expiration=2026-11-05T00:00:00Z" | grep -vE '^(Date|Content-Length)'
HTTP/1.1 401 Unauthorized
Content-Type: text/plain; charset=utf-8
X-Content-Type-Options: nosniff
Guest link is no longer active
6.3 禁用与启用通道 PUT /api/guest-links/{id}/disable|enable
两个端点共用同一处理逻辑,成功均返回 204 No Content:
curl -si -b "$COOKIE" -X PUT http://localhost:8130/api/guest-links/uEutDtg85VGWmtbP/disable | head -1
HTTP/1.1 204 No Content
禁用后匿名上传立即 401(错误体同为 Guest link is no longer active);重新启用后恢复可用:
curl -si -b "$COOKIE" -X PUT http://localhost:8130/api/guest-links/uEutDtg85VGWmtbP/enable | head -1
HTTP/1.1 204 No Content
⚠️ 注意:
enable/disable是幂等开关,不需要请求体;重复禁用同一通道仍返回 204。
6.4 删除通道 DELETE /api/guest-links/{id}
curl -si -b "$COOKIE" -X DELETE http://localhost:8130/api/guest-links/uEutDtg85VGWmtbP | head -1
HTTP/1.1 200 OK
通道删除后其 ID 立即失效,经该通道的上传与页面访问均不可用;已通过通道上传的文件不受影响,仍按各自的过期时间正常 downloadable。
七、运行期设置域(PUT /api/settings)
该端点管理「新上传文件的默认存活期」,两个请求体字段:defaultExpirationDays(天数,合法区间 1~3650)与 defaultNeverExpire(布尔,true 时忽略天数、新文件默认永不过期)。设置为 30 天:
curl -si -b "$COOKIE" -X PUT http://localhost:8130/api/settings -H 'Content-Type: application/json' \
-d '{"defaultExpirationDays":30,"defaultNeverExpire":false}' | head -1
HTTP/1.1 200 OK
开启「默认永不过期」:
curl -si -b "$COOKIE" -X PUT http://localhost:8130/api/settings -H 'Content-Type: application/json' \
-d '{"defaultExpirationDays":30,"defaultNeverExpire":true}' | head -1
HTTP/1.1 200 OK
传 0 天被 400 拒绝(实测错误体):
Invalid request: file lifetime must be at least 1 days
该设置即时生效、存入 SQLite 随数据卷持久化,只影响之后的上传默认值,不追溯已有文件。当前值可在 Web 界面 Settings 页查看(页面标题实测为 PicoShare - Settings)。
八、通用约定与错误码速查
8.1 报文与时间格式约定
- 成功响应:创建类端点返回 JSON(
{"id":"..."}),更新/删除类端点返回空体(Content-Length: 0)加对应状态码,访客上传返回text/plain直链; - 错误响应:一律
text/plain纯文本错误体,无 JSON 包装、无错误码字段——脚本判定失败靠 HTTP 状态码 + 文本匹配; - 时间参数:
expiration、urlExpirationTime统一 RFC3339(如2026-12-01T00:00:00Z),必须至少晚于当前 1 小时;fileLifetime用 Go duration(24h/720h/876000h),下限 1 天(24h)、上限 10 年(876000h,等值「永不过期」); - ID 格式:条目 ID 10 字符、通道 ID 16 字符,字符集均剔除
I/l/1/0/O;ID 只出现在 URL 路径中,不区分大小写敏感——按返回值原样使用即可。
8.2 状态码速查表(全部经实测核对)
| 状态码 | 触发场景(实测) | 错误体(节选) |
|---|---|---|
| 200 | 登录/上传/编辑/删除成功;访客上传成功 | 成功体见对应小节 |
| 204 | 访客通道 enable/disable 成功 | 无(No Content) |
| 400 | expiration 缺失/格式错/过早;filename 空;fileLifetime 越界;maxFileBytes < 1MiB;settings 天数为 0 | Invalid expiration URL parameter: ... 等 |
| 401 | 口令错误;未带 Cookie 访问受保护端点;伪造 Cookie;通道禁用/次数用尽 | incorrect shared secret、Guest link is no longer active |
| 404 | 下载链接对应条目不存在(已删除) | entry not found |
8.3 幂等性与并发注意
- 同名文件可重复上传,每次得到新 ID(PicoShare 不做去重),脚本批量分发时注意按 ID 管理而不是文件名;
PUT /api/entry/{id}是全字段语义更新——只想改文件名时也必须带非空filename,不传的字段保持原值;- 删除类操作不可恢复(无回收站),脚本里建议先
GET /-<id>探测存在再删。
九、常见问题
Q1:上传一直 400,提示 Invalid expiration URL parameter?
expiration 是 v1.5.4 上传的必填查询参数,且必须是 RFC3339 格式、至少晚于当前 1 小时。检查三点:是否漏传;是否用了 2026/12/01 这类非 RFC3339 斜杠格式;时间是否已过或不足 1 小时。
Q2:脚本能传「永不过期」的文件吗?
上传 API 没有 Never 表达(传 Never 字面量实测 400)。两个替代:传远期时间(如 2126-01-01T00:00:00Z);或先用 PUT /api/settings 把 defaultNeverExpire 设为 true,之后经 Web 界面上传默认永久保存(API 直传仍需 expiration)。访客通道的 fileLifetime 则可用 876000h 精确表达无限。
Q3:登出后旧 Cookie 还能用吗?
能。DELETE /api/auth 只影响客户端,服务端派生密钥不变(4.3 节实测)。要真正失效所有会话,轮换 PS_SHARED_SECRET 并重启服务。
Q4:访客上传报 beyond guest link's limit?
访客传的 expiration 超过了通道 fileLifetime 允许的上限。错误体里直接给出了允许的最后时刻(通道创建时间 + fileLifetime),取一个更早的时间重试,或重建 fileLifetime 更长的通道。
Q5:改文件名后旧下载链接会失效吗?
不会。文件名只是元数据,下载 ID 不变;改完后 Content-Disposition 头同步为新文件名(5.3 节实测)。
Q6:一次性链接用完后如何恢复?
maxFileUploads 用尽的通道返回 401 且无法通过 enable 复活(次数限额是硬约束)。需要继续收件时创建新通道即可。
十、参考链接
- 官方 README(部署与环境变量说明):https://github.com/mtlynch/picoshare/blob/master/README.md
- 源码仓库(路由定义见
handlers/routes.go):https://github.com/mtlynch/picoshare - 官方 Releases 页(版本依据):https://github.com/mtlynch/picoshare/releases
- 本站配套文档:《PicoShare 介绍与选型指南》、《PicoShare 1.5.4 Docker Compose 部署指南》、《PicoShare 1.5.4 配置参数详解》