diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 6538ca9..6d78745 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.8.0" + ".": "0.9.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index d5b8f44..582a92c 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 21 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cas-parser/cas-parser-2fd773786951b723a5d7d7342bf1c6ab46f08bd2851e916d188faae379d5aa4c.yml -openapi_spec_hash: 7515d1e5fe3130b9f5411f7aacbc8a64 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cas-parser/cas-parser-e572d88c2af6e4d7bc4f7e119357fd3f68b1e67d612fd1d3a657d916cde0087c.yml +openapi_spec_hash: a9fc7d947111bffa9184f8ca8be4a579 config_hash: 5509bb7a961ae2e79114b24c381606d4 diff --git a/CHANGELOG.md b/CHANGELOG.md index fd5c02f..b749fd5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ # Changelog +## 0.9.0 (2026-05-08) + +Full Changelog: [v0.8.0...v0.9.0](https://github.com/CASParser/cas-parser-go/compare/v0.8.0...v0.9.0) + +### Features + +* **api:** api update ([1383709](https://github.com/CASParser/cas-parser-go/commit/1383709c5b1b49ffd5fcb142c8abd73f2f6728be)) +* **api:** api update ([6e974d9](https://github.com/CASParser/cas-parser-go/commit/6e974d99b79519daa77c5266f54077b5d41cc580)) + + +### Bug Fixes + +* **go:** avoid panic when http.DefaultTransport is wrapped ([a0df656](https://github.com/CASParser/cas-parser-go/commit/a0df65678c6806e6c95ad992087c857275c0cdac)) + + +### Chores + +* redact api-key headers in debug logs ([8cf162d](https://github.com/CASParser/cas-parser-go/commit/8cf162dc65df24e5f4ab99b8423d42c72c5ac70d)) + ## 0.8.0 (2026-05-01) Full Changelog: [v0.7.0...v0.8.0](https://github.com/CASParser/cas-parser-go/compare/v0.7.0...v0.8.0) diff --git a/README.md b/README.md index 69b85cd..b526cc3 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ Or to pin the version: ```sh -go get -u 'github.com/CASParser/cas-parser-go@v0.8.0' +go get -u 'github.com/CASParser/cas-parser-go@v0.9.0' ``` diff --git a/default_http_client.go b/default_http_client.go index d49a4e4..08fdb3c 100644 --- a/default_http_client.go +++ b/default_http_client.go @@ -14,11 +14,17 @@ import ( const defaultResponseHeaderTimeout = 10 * time.Minute // defaultHTTPClient returns an [*http.Client] used when the caller does not -// supply one via [option.WithHTTPClient]. It clones [http.DefaultTransport] -// and adds a [http.Transport.ResponseHeaderTimeout] so stuck connections -// fail fast instead of compounding across retries. +// supply one via [option.WithHTTPClient]. When [http.DefaultTransport] is the +// stdlib [*http.Transport], it is cloned and a [http.Transport.ResponseHeaderTimeout] +// is set so stuck connections fail fast instead of compounding across retries. +// If [http.DefaultTransport] has been wrapped (for example by otelhttp for +// distributed tracing), the wrapping is preserved and the header timeout is +// skipped. func defaultHTTPClient() *http.Client { - transport := http.DefaultTransport.(*http.Transport).Clone() - transport.ResponseHeaderTimeout = defaultResponseHeaderTimeout - return &http.Client{Transport: transport} + if t, ok := http.DefaultTransport.(*http.Transport); ok { + t = t.Clone() + t.ResponseHeaderTimeout = defaultResponseHeaderTimeout + return &http.Client{Transport: t} + } + return &http.Client{Transport: http.DefaultTransport} } diff --git a/inboundemail.go b/inboundemail.go index 47131a3..d7d0a69 100644 --- a/inboundemail.go +++ b/inboundemail.go @@ -82,7 +82,7 @@ func (r *InboundEmailService) New(ctx context.Context, body InboundEmailNewParam return res, err } -// Retrieve details of a specific mailbox including statistics. +// Retrieve details of a specific inbound email including statistics. func (r *InboundEmailService) Get(ctx context.Context, inboundEmailID string, opts ...option.RequestOption) (res *InboundEmailGetResponse, err error) { opts = slices.Concat(r.Options, opts) if inboundEmailID == "" { @@ -94,8 +94,8 @@ func (r *InboundEmailService) Get(ctx context.Context, inboundEmailID string, op return res, err } -// List all mailboxes associated with your API key. Returns active and inactive -// mailboxes (deleted mailboxes are excluded). +// List all inbound emails associated with your API key. Returns active and paused +// inbound emails (deleted ones are excluded). func (r *InboundEmailService) List(ctx context.Context, query InboundEmailListParams, opts ...option.RequestOption) (res *InboundEmailListResponse, err error) { opts = slices.Concat(r.Options, opts) path := "v4/inbound-email" @@ -124,10 +124,10 @@ type InboundEmailNewResponse struct { // // Any of "cdsl", "nsdl", "cams", "kfintech". AllowedSources []string `json:"allowed_sources"` - // Webhook URL for email notifications. `null` means files are only retrievable via - // `GET /v4/inbound-email/{id}/files` (pull delivery). - CallbackURL string `json:"callback_url" api:"nullable" format:"uri"` - // When the mailbox was created + // Webhook URL for email notifications. If set, we POST each parsed email here. If + // omitted, files are only retrievable via `GET /v4/inbound-email/{id}/files`. + CallbackURL string `json:"callback_url" format:"uri"` + // When the inbound email was created CreatedAt time.Time `json:"created_at" format:"date-time"` // The inbound email address to forward CAS statements to Email string `json:"email" format:"email"` @@ -137,11 +137,11 @@ type InboundEmailNewResponse struct { Metadata map[string]string `json:"metadata"` // Your internal reference identifier Reference string `json:"reference" api:"nullable"` - // Current mailbox status + // Current inbound email lifecycle status // // Any of "active", "paused". Status InboundEmailNewResponseStatus `json:"status"` - // When the mailbox was last updated + // When the inbound email was last updated UpdatedAt time.Time `json:"updated_at" format:"date-time"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. JSON struct { @@ -165,7 +165,7 @@ func (r *InboundEmailNewResponse) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } -// Current mailbox status +// Current inbound email lifecycle status type InboundEmailNewResponseStatus string const ( @@ -179,10 +179,10 @@ type InboundEmailGetResponse struct { // // Any of "cdsl", "nsdl", "cams", "kfintech". AllowedSources []string `json:"allowed_sources"` - // Webhook URL for email notifications. `null` means files are only retrievable via - // `GET /v4/inbound-email/{id}/files` (pull delivery). - CallbackURL string `json:"callback_url" api:"nullable" format:"uri"` - // When the mailbox was created + // Webhook URL for email notifications. If set, we POST each parsed email here. If + // omitted, files are only retrievable via `GET /v4/inbound-email/{id}/files`. + CallbackURL string `json:"callback_url" format:"uri"` + // When the inbound email was created CreatedAt time.Time `json:"created_at" format:"date-time"` // The inbound email address to forward CAS statements to Email string `json:"email" format:"email"` @@ -192,11 +192,11 @@ type InboundEmailGetResponse struct { Metadata map[string]string `json:"metadata"` // Your internal reference identifier Reference string `json:"reference" api:"nullable"` - // Current mailbox status + // Current inbound email lifecycle status // // Any of "active", "paused". Status InboundEmailGetResponseStatus `json:"status"` - // When the mailbox was last updated + // When the inbound email was last updated UpdatedAt time.Time `json:"updated_at" format:"date-time"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. JSON struct { @@ -220,7 +220,7 @@ func (r *InboundEmailGetResponse) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } -// Current mailbox status +// Current inbound email lifecycle status type InboundEmailGetResponseStatus string const ( @@ -259,10 +259,10 @@ type InboundEmailListResponseInboundEmail struct { // // Any of "cdsl", "nsdl", "cams", "kfintech". AllowedSources []string `json:"allowed_sources"` - // Webhook URL for email notifications. `null` means files are only retrievable via - // `GET /v4/inbound-email/{id}/files` (pull delivery). - CallbackURL string `json:"callback_url" api:"nullable" format:"uri"` - // When the mailbox was created + // Webhook URL for email notifications. If set, we POST each parsed email here. If + // omitted, files are only retrievable via `GET /v4/inbound-email/{id}/files`. + CallbackURL string `json:"callback_url" format:"uri"` + // When the inbound email was created CreatedAt time.Time `json:"created_at" format:"date-time"` // The inbound email address to forward CAS statements to Email string `json:"email" format:"email"` @@ -272,11 +272,11 @@ type InboundEmailListResponseInboundEmail struct { Metadata map[string]string `json:"metadata"` // Your internal reference identifier Reference string `json:"reference" api:"nullable"` - // Current mailbox status + // Current inbound email lifecycle status // // Any of "active", "paused". Status string `json:"status"` - // When the mailbox was last updated + // When the inbound email was last updated UpdatedAt time.Time `json:"updated_at" format:"date-time"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. JSON struct { diff --git a/inbox.go b/inbox.go index ae22ef0..a1315ef 100644 --- a/inbox.go +++ b/inbox.go @@ -220,7 +220,11 @@ type InboxListCasFilesResponseFile struct { // // Any of "cdsl", "nsdl", "cams", "kfintech". CasType string `json:"cas_type"` - // URL expiration time in seconds (default 86400 = 24 hours) + // URL expiration time in seconds. Defaults vary by source: + // + // - Gmail Inbox Import: 86400 (24h) + // - Inbound Email with `callback_url` set: 172800 (48h) + // - Inbound Email without `callback_url`: aligned with the session TTL (~30 min) ExpiresIn int64 `json:"expires_in"` // Standardized filename (provider_YYYYMMDD_uniqueid.pdf) Filename string `json:"filename"` diff --git a/internal/version.go b/internal/version.go index 3c8392e..0e818c5 100644 --- a/internal/version.go +++ b/internal/version.go @@ -2,4 +2,4 @@ package internal -const PackageVersion = "0.8.0" // x-release-please-version +const PackageVersion = "0.9.0" // x-release-please-version diff --git a/option/middleware.go b/option/middleware.go index 8ec9dd6..4be0987 100644 --- a/option/middleware.go +++ b/option/middleware.go @@ -8,6 +8,10 @@ import ( "net/http/httputil" ) +// sensitiveLogHeaders are redacted before request and response content is +// written to the debug logger. +var sensitiveLogHeaders = []string{"authorization", "api-key", "x-api-key", "cookie", "set-cookie"} + // WithDebugLog logs the HTTP request and response content. // If the logger parameter is nil, it uses the default logger. // @@ -20,7 +24,7 @@ func WithDebugLog(logger *log.Logger) RequestOption { logger = log.Default() } - if reqBytes, err := httputil.DumpRequest(req, true); err == nil { + if reqBytes, err := dumpRedactedRequest(req); err == nil { logger.Printf("Request Content:\n%s\n", reqBytes) } @@ -29,10 +33,48 @@ func WithDebugLog(logger *log.Logger) RequestOption { return resp, err } - if respBytes, err := httputil.DumpResponse(resp, true); err == nil { + if respBytes, err := dumpRedactedResponse(resp); err == nil { logger.Printf("Response Content:\n%s\n", respBytes) } return resp, err }) } + +// dumpRedactedRequest dumps req with sensitive headers replaced. The +// original headers are restored via defer so a panic in DumpRequest cannot +// leak the placeholder map into the live request sent downstream. +func dumpRedactedRequest(req *http.Request) ([]byte, error) { + origHeaders := req.Header + req.Header = redactDebugHeaders(origHeaders) + defer func() { req.Header = origHeaders }() + return httputil.DumpRequest(req, true) +} + +func dumpRedactedResponse(resp *http.Response) ([]byte, error) { + origHeaders := resp.Header + resp.Header = redactDebugHeaders(origHeaders) + defer func() { resp.Header = origHeaders }() + return httputil.DumpResponse(resp, true) +} + +func redactDebugHeaders(headers http.Header) http.Header { + var redacted http.Header + for _, name := range sensitiveLogHeaders { + values := headers.Values(name) + if len(values) == 0 { + continue + } + if redacted == nil { + redacted = headers.Clone() + } + redacted.Del(name) + for range values { + redacted.Add(name, "***") + } + } + if redacted == nil { + return headers + } + return redacted +}