Compare commits
No commits in common. "main" and "v1.5.0" have entirely different histories.
@ -1,12 +1,5 @@
|
|||||||
# CHANGELOG
|
# CHANGELOG
|
||||||
|
|
||||||
## v1.5.1 (2026-06-08)
|
|
||||||
- **JS 对齐 & 安全加固**:
|
|
||||||
- HTTP 方法名统一更正为全大写(`GET`, `POST`, `PUT`, `DELETE`),强化协议语义。
|
|
||||||
- **Headers 优化**: JS 侧 Headers 参数从变长字符串改为更符合 JS 习惯的 `map[string]string` 对象。
|
|
||||||
- **深度安全扫描**: 在 `POST/PUT` 请求中对 `Multipart` 涉及的文件路径进行前置扫描,强制通过 `file.VerifyPathForSafeMode` 校验,严防低代码环境下的敏感文件泄露。
|
|
||||||
- **结果集对齐**: `Result.Save` 方法现在同样受安全沙箱控制。
|
|
||||||
|
|
||||||
## v1.3.4 (2026-05-30)
|
## v1.3.4 (2026-05-30)
|
||||||
- **API 变更**: 将 `timeout(ms)` 拆分为 `new(ms)` 和 `newH2C(ms)` 以支持 HTTP/2 Cleartext。
|
- **API 变更**: 将 `timeout(ms)` 拆分为 `new(ms)` 和 `newH2C(ms)` 以支持 HTTP/2 Cleartext。
|
||||||
- **安全性**: 移除 `setGlobalHeader` / `getGlobalHeader` 以增强脚本间隔离。
|
- **安全性**: 移除 `setGlobalHeader` / `getGlobalHeader` 以增强脚本间隔离。
|
||||||
|
|||||||
@ -188,7 +188,7 @@ func (client *Client) doByRequest(manualDo bool, request *http.Request, method,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !foundID {
|
if !foundID {
|
||||||
headers = append(headers, HeaderRequestID, encoding.Hex(rand.Bytes(16)))
|
headers = append(headers, HeaderRequestID, string(encoding.Hex(rand.Bytes(16))))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 续传 X-Forwarded-For
|
// 续传 X-Forwarded-For
|
||||||
|
|||||||
4
go.mod
4
go.mod
@ -17,8 +17,8 @@ require (
|
|||||||
apigo.cc/go/id v1.5.0 // indirect
|
apigo.cc/go/id v1.5.0 // indirect
|
||||||
apigo.cc/go/safe v1.5.0 // indirect
|
apigo.cc/go/safe v1.5.0 // indirect
|
||||||
apigo.cc/go/shell v1.5.0 // indirect
|
apigo.cc/go/shell v1.5.0 // indirect
|
||||||
golang.org/x/crypto v0.52.0 // indirect
|
golang.org/x/crypto v0.51.0 // indirect
|
||||||
golang.org/x/sys v0.45.0 // indirect
|
golang.org/x/sys v0.44.0 // indirect
|
||||||
golang.org/x/text v0.37.0 // indirect
|
golang.org/x/text v0.37.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
6
go.sum
6
go.sum
@ -24,10 +24,12 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||||
golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
|
golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
|
||||||
|
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
|
||||||
golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w=
|
golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w=
|
||||||
golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ=
|
golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ=
|
||||||
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
|
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
|
||||||
|
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||||
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
|||||||
91
js_export.go
91
js_export.go
@ -1,112 +1,73 @@
|
|||||||
package http
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"apigo.cc/go/file"
|
|
||||||
"apigo.cc/go/jsmod"
|
"apigo.cc/go/jsmod"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
jsmod.Register("http", map[string]any{
|
jsmod.Register("http", map[string]any{
|
||||||
// Static requests with default timeout (30s)
|
// Static requests with default timeout (30s)
|
||||||
"GET": func(ctx context.Context, url string, headers map[string]string) *jsResult {
|
"get": func(url string, headers ...string) *jsResult {
|
||||||
return wrapResult(ctx, Get(url, flattenHeaders(headers)...))
|
return wrapResult(Get(url, headers...))
|
||||||
},
|
},
|
||||||
"POST": func(ctx context.Context, url string, data any, headers map[string]string) (*jsResult, error) {
|
"post": func(url string, data any, headers ...string) *jsResult {
|
||||||
if err := verifyMultipart(ctx, data); err != nil {
|
return wrapResult(Post(url, data, headers...))
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return wrapResult(ctx, Post(url, data, flattenHeaders(headers)...)), nil
|
|
||||||
},
|
},
|
||||||
"PUT": func(ctx context.Context, url string, data any, headers map[string]string) (*jsResult, error) {
|
"put": func(url string, data any, headers ...string) *jsResult {
|
||||||
if err := verifyMultipart(ctx, data); err != nil {
|
return wrapResult(Put(url, data, headers...))
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return wrapResult(ctx, Put(url, data, flattenHeaders(headers)...)), nil
|
|
||||||
},
|
},
|
||||||
"DELETE": func(ctx context.Context, url string, data any, headers map[string]string) *jsResult {
|
"delete": func(url string, data any, headers ...string) *jsResult {
|
||||||
return wrapResult(ctx, Delete(url, data, flattenHeaders(headers)...))
|
return wrapResult(Delete(url, data, headers...))
|
||||||
},
|
},
|
||||||
|
|
||||||
// Client creation
|
// Client creation
|
||||||
"New": func(ms int) *jsClient {
|
"new": func(ms int) *jsClient {
|
||||||
return &jsClient{c: NewClient(time.Duration(ms) * time.Millisecond)}
|
return &jsClient{c: NewClient(time.Duration(ms) * time.Millisecond)}
|
||||||
},
|
},
|
||||||
"NewH2C": func(ms int) *jsClient {
|
"newH2C": func(ms int) *jsClient {
|
||||||
return &jsClient{c: NewClientH2C(time.Duration(ms) * time.Millisecond)}
|
return &jsClient{c: NewClientH2C(time.Duration(ms) * time.Millisecond)}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Data markers
|
// Data markers
|
||||||
"Form": func(data map[string]string) Form {
|
"form": func(data map[string]string) Form {
|
||||||
return Form(data)
|
return Form(data)
|
||||||
},
|
},
|
||||||
"Multipart": func(data map[string]any) Multipart {
|
"multipart": func(data map[string]any) Multipart {
|
||||||
return Multipart(data)
|
return Multipart(data)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func flattenHeaders(m map[string]string) []string {
|
|
||||||
if len(m) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
res := make([]string, 0, len(m)*2)
|
|
||||||
for k, v := range m {
|
|
||||||
res = append(res, k, v)
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
func verifyMultipart(ctx context.Context, data any) error {
|
|
||||||
if m, ok := data.(Multipart); ok {
|
|
||||||
for _, v := range m {
|
|
||||||
if s, ok := v.(string); ok && file.Exists(s) {
|
|
||||||
if _, err := file.VerifyPathForSafeMode(ctx, s); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// jsClient wraps the internal Client to provide a JS-friendly interface
|
// jsClient wraps the internal Client to provide a JS-friendly interface
|
||||||
type jsClient struct {
|
type jsClient struct {
|
||||||
c *Client
|
c *Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func (jc *jsClient) GET(ctx context.Context, url string, headers map[string]string) *jsResult {
|
func (jc *jsClient) Get(url string, headers ...string) *jsResult {
|
||||||
return wrapResult(ctx, jc.c.Get(url, flattenHeaders(headers)...))
|
return wrapResult(jc.c.Get(url, headers...))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (jc *jsClient) POST(ctx context.Context, url string, data any, headers map[string]string) (*jsResult, error) {
|
func (jc *jsClient) Post(url string, data any, headers ...string) *jsResult {
|
||||||
if err := verifyMultipart(ctx, data); err != nil {
|
return wrapResult(jc.c.Post(url, data, headers...))
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return wrapResult(ctx, jc.c.Post(url, data, flattenHeaders(headers)...)), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (jc *jsClient) PUT(ctx context.Context, url string, data any, headers map[string]string) (*jsResult, error) {
|
func (jc *jsClient) Put(url string, data any, headers ...string) *jsResult {
|
||||||
if err := verifyMultipart(ctx, data); err != nil {
|
return wrapResult(jc.c.Put(url, data, headers...))
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return wrapResult(ctx, jc.c.Put(url, data, flattenHeaders(headers)...)), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (jc *jsClient) DELETE(ctx context.Context, url string, data any, headers map[string]string) *jsResult {
|
func (jc *jsClient) Delete(url string, data any, headers ...string) *jsResult {
|
||||||
return wrapResult(ctx, jc.c.Delete(url, data, flattenHeaders(headers)...))
|
return wrapResult(jc.c.Delete(url, data, headers...))
|
||||||
}
|
}
|
||||||
|
|
||||||
// jsResult wraps *Result to hide sensitive methods like Save()
|
// jsResult wraps *Result to hide sensitive methods like Save()
|
||||||
type jsResult struct {
|
type jsResult struct {
|
||||||
ctx context.Context
|
|
||||||
r *Result
|
r *Result
|
||||||
}
|
}
|
||||||
|
|
||||||
func wrapResult(ctx context.Context, r *Result) *jsResult {
|
func wrapResult(r *Result) *jsResult {
|
||||||
return &jsResult{ctx: ctx, r: r}
|
return &jsResult{r: r}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (jr *jsResult) String() string {
|
func (jr *jsResult) String() string {
|
||||||
@ -138,11 +99,3 @@ func (jr *jsResult) Error() string {
|
|||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (jr *jsResult) Save(filename string) error {
|
|
||||||
p, err := file.VerifyPathForSafeMode(jr.ctx, filename)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return jr.r.Save(p)
|
|
||||||
}
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user