mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-14 12:02:27 +08:00
Merge pull request #7426 from xlplbo/fix/openai-codex-turn-metadata-ascii
fix(openai): 改写 Codex turn metadata 时保留非 ASCII 转义
This commit is contained in:
@@ -154,7 +154,7 @@ func applyCodexAccountIdentityEmbeddedMetadata(values map[string]any, account *A
|
||||
if !applyCodexAccountIdentityFields(metadata, account, apiKeyID) {
|
||||
return false
|
||||
}
|
||||
rebuilt, err := json.Marshal(metadata)
|
||||
rebuilt, err := marshalCodexTurnMetadata(metadata)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
@@ -267,7 +267,7 @@ func applyCodexAccountIdentityHeaders(headers http.Header, account *Account, api
|
||||
if raw := strings.TrimSpace(headers.Get(openAIWSTurnMetadataHeader)); raw != "" {
|
||||
metadata := map[string]any{}
|
||||
if err := json.Unmarshal([]byte(raw), &metadata); err == nil && metadata != nil && applyCodexAccountIdentityFields(metadata, account, apiKeyID) {
|
||||
if rebuilt, err := json.Marshal(metadata); err == nil {
|
||||
if rebuilt, err := marshalCodexTurnMetadata(metadata); err == nil {
|
||||
headers.Set(openAIWSTurnMetadataHeader, string(rebuilt))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -402,7 +402,7 @@ func rewriteCodexTurnMetadataFields(h http.Header, fields map[string]any) {
|
||||
for k, v := range fields {
|
||||
metadata[k] = v
|
||||
}
|
||||
rebuilt, err := json.Marshal(metadata)
|
||||
rebuilt, err := marshalCodexTurnMetadata(metadata)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -595,7 +595,7 @@ func rewriteClientMetadataEmbeddedTurnMetadata(clientMetadata map[string]any, fi
|
||||
for k, v := range fields {
|
||||
metadata[k] = v
|
||||
}
|
||||
if rebuilt, err := json.Marshal(metadata); err == nil {
|
||||
if rebuilt, err := marshalCodexTurnMetadata(metadata); err == nil {
|
||||
clientMetadata["x-codex-turn-metadata"] = string(rebuilt)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"unicode/utf16"
|
||||
)
|
||||
|
||||
// Turn metadata is also used as an HTTP header, including when carried in WS JSON.
|
||||
func marshalCodexTurnMetadata(metadata map[string]any) ([]byte, error) {
|
||||
raw, err := json.Marshal(metadata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i, b := range raw {
|
||||
if b < 0x7f {
|
||||
continue
|
||||
}
|
||||
out := make([]byte, 0, len(raw)+16)
|
||||
out = append(out, raw[:i]...)
|
||||
appendEscape := func(r rune) {
|
||||
const hex = "0123456789abcdef"
|
||||
out = append(out, '\\', 'u', hex[r>>12&15], hex[r>>8&15], hex[r>>4&15], hex[r&15])
|
||||
}
|
||||
for _, r := range string(raw[i:]) {
|
||||
switch {
|
||||
case r < 0x7f:
|
||||
out = append(out, byte(r))
|
||||
case r <= 0xffff:
|
||||
appendEscape(r)
|
||||
default:
|
||||
high, low := utf16.EncodeRune(r)
|
||||
appendEscape(high)
|
||||
appendEscape(low)
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
return raw, nil
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
func TestCodexTurnMetadataRewritePreservesHeaderSafeJSON(t *testing.T) {
|
||||
account := newTestOAuthAccount(1, map[string]any{codexFingerprintModeExtraKey: "session"})
|
||||
account.Credentials = map[string]any{"chatgpt_account_id": "test-account"}
|
||||
ids := resolveCodexFingerprintIDsFromRequest(account, http.Header{})
|
||||
require.NotNil(t, ids)
|
||||
|
||||
rewriters := map[string]func(*testing.T, string) string{
|
||||
"account_header": func(t *testing.T, raw string) string {
|
||||
h := make(http.Header)
|
||||
h.Set(openAIWSTurnMetadataHeader, raw)
|
||||
applyCodexAccountIdentityHeaders(h, account, 77)
|
||||
return h.Get(openAIWSTurnMetadataHeader)
|
||||
},
|
||||
"account_body_map": func(t *testing.T, raw string) string {
|
||||
cm := map[string]any{openAIWSTurnMetadataHeader: raw}
|
||||
require.True(t, applyCodexAccountIdentityClientMetadataMap(map[string]any{"client_metadata": cm}, account, 77))
|
||||
value, ok := cm[openAIWSTurnMetadataHeader].(string)
|
||||
require.True(t, ok)
|
||||
return value
|
||||
},
|
||||
"account_body_raw": func(t *testing.T, raw string) string {
|
||||
body, err := json.Marshal(map[string]any{"client_metadata": map[string]any{openAIWSTurnMetadataHeader: raw}})
|
||||
require.NoError(t, err)
|
||||
updated, changed, err := applyCodexAccountIdentityClientMetadataRaw(body, account, 77)
|
||||
require.NoError(t, err)
|
||||
require.True(t, changed)
|
||||
return gjson.GetBytes(updated, "client_metadata."+openAIWSTurnMetadataHeader).String()
|
||||
},
|
||||
"fingerprint_header": func(t *testing.T, raw string) string {
|
||||
h := make(http.Header)
|
||||
h.Set(openAIWSTurnMetadataHeader, raw)
|
||||
applyCodexFingerprintHeaders(h, ids)
|
||||
return h.Get(openAIWSTurnMetadataHeader)
|
||||
},
|
||||
"fingerprint_body_map": func(t *testing.T, raw string) string {
|
||||
cm := map[string]any{openAIWSTurnMetadataHeader: raw}
|
||||
require.True(t, applyCodexFingerprintClientMetadata(map[string]any{"client_metadata": cm}, ids))
|
||||
value, ok := cm[openAIWSTurnMetadataHeader].(string)
|
||||
require.True(t, ok)
|
||||
return value
|
||||
},
|
||||
"fingerprint_body_raw": func(t *testing.T, raw string) string {
|
||||
body, err := json.Marshal(map[string]any{"client_metadata": map[string]any{openAIWSTurnMetadataHeader: raw}})
|
||||
require.NoError(t, err)
|
||||
updated, changed, err := applyCodexFingerprintClientMetadataRaw(body, ids)
|
||||
require.NoError(t, err)
|
||||
require.True(t, changed)
|
||||
return gjson.GetBytes(updated, "client_metadata."+openAIWSTurnMetadataHeader).String()
|
||||
},
|
||||
}
|
||||
|
||||
for _, raw := range []string{
|
||||
`{"installation_id":"client-install","workspaces":{"C:\\work\\中文🚀":{}}}`,
|
||||
`{"installation_id":"client-install","workspaces":{"C:\\work\\中文🚀":{"label":"café"}},"literal":"\\u4e2d","quote":"\"\\","controls":"\b\f\n\r\t\u0000\u007f","timestamp":1789302780858,"enabled":true}`,
|
||||
`{"installation_id":"client-install","workspaces":{"C:\\work\\\u4e2d\u6587\ud83d\ude80":{"label":"caf\u00e9"}},"literal":"\\u4e2d","quote":"\"\\","controls":"\b\f\n\r\t\u0000\u007f","timestamp":1789302780858,"enabled":true}`,
|
||||
`{"installation_id":"client-install","workspaces":{"C:\\work\\ascii":{}},"literal":"\\u4e2d"}`,
|
||||
} {
|
||||
var original map[string]any
|
||||
require.NoError(t, json.Unmarshal([]byte(raw), &original))
|
||||
for name, rewrite := range rewriters {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
updated := rewrite(t, raw)
|
||||
var decoded map[string]any
|
||||
require.NoError(t, json.Unmarshal([]byte(updated), &decoded))
|
||||
for key, value := range original {
|
||||
if key != "installation_id" {
|
||||
require.Equal(t, value, decoded[key], key)
|
||||
}
|
||||
}
|
||||
require.NotEqual(t, original["installation_id"], decoded["installation_id"])
|
||||
for _, b := range []byte(updated) {
|
||||
require.True(t, b >= 0x20 && b < 0x7f, "non-printable ASCII header byte: 0x%02x", b)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user