PicoShare 1.5.4 API 使用详解

PicoShare 1.5.4 全部 REST 端点实测手册:Cookie 会话认证、文件上传/下载/编辑/删除、Guest Links 访客通道与运行期设置逐域 curl 实测回显,附 expiration 必填约束、fileLifetime 时长语法与错误码速查。

PicoSharePicoShareGolang文件传输文件分享自托管2026/10/31

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

二、前置条件

开始调用前需要确认三件事:实例可达、知道认证方式、选好客户端工具。

  1. 一个可访问的 PicoShare 1.5.4 实例。健康自检直接请求首页,返回 200 即可:
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8130/
200
  1. 理解认证载体:PicoShare 用签名 Cookie(sharedSecret)承载会话,登录一次换取 30 天有效期的 Cookie,后续请求以 Cookie: sharedSecret=... 头携带(详见 3.2 节)。

  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/authCookie登出(清除客户端 Cookie)
条目POST/api/entryCookie上传文件,返回条目 ID
条目PUT/api/entry/{id}Cookie修改文件名/备注/过期时间
条目DELETE/api/entry/{id}Cookie删除文件
条目GET/-{id}、/-{id}/{filename}、/!{id}无下载(直链,访客可访问)
条目GET/files/{id}/downloadsCookie下载记录页(HTML 视图)
访客POST/api/guest-linksCookie创建访客上传通道
访客DELETE/api/guest-links/{id}Cookie删除访客通道
访客PUT/api/guest-links/{id}/enable、/{id}/disableCookie启用/禁用通道
访客POST/api/guest/{guestLinkID}无(通道自身约束)匿名经通道上传
设置PUT/api/settingsCookie修改默认文件存活期
视图GET/settings、/information、/files 等CookieWeb 管理页面(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 层无分页参数,脚本化操作围绕单条资源端点展开。

四、认证与首次调用

向 /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>

访客通道让外部人员在无账号、无登录的情况下向服务器投递文件。流程:管理员创建通道 → 把 /g/<通道ID> 页面链接发给对方 → 对方经页面上传(或直接调 /api/guest/<通道ID> 接口)。

6.1 创建通道 POST /api/guest-links

请求体五个字段:

字段类型约束说明
labelstring非空通道名称,管理列表中显示
urlExpirationTimestringRFC3339,至少晚于当前 1 小时通道链接本身的有效期
fileLifetimestringGo duration 格式,1 天~10 年经此通道上传文件的存活期上限
maxFileBytesnumber/nullnull 或 ≥1048576(1MiB)单文件大小上限
maxFileUploadsnumber/nullnull 或 ≥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)
400expiration 缺失/格式错/过早;filename 空;fileLifetime 越界;maxFileBytes < 1MiB;settings 天数为 0Invalid 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 复活(次数限额是硬约束)。需要继续收件时创建新通道即可。

十、参考链接