mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
fix(sap_s4hana): preserve raw Set-Cookie array for CSRF cookie join
SecureFetchHeaders previously collapsed multi-value Set-Cookie headers with ", ", forcing consumers to re-split via a fragile regex. Cookie values containing "=" or "," (e.g., Base64 session tokens) could be misparsed and produce malformed Cookie strings on CSRF-protected mutations. Add SecureFetchHeaders.getSetCookie() that returns the raw array, and update the S/4HANA OData proxy's joinSetCookies to consume it directly. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
d7c9f9cc53
commit
d767d76dd2
@@ -113,8 +113,8 @@ interface CsrfBundle {
|
||||
}
|
||||
|
||||
function joinSetCookies(response: SecureFetchResponse): string {
|
||||
const cookies = (response.headers.get('set-cookie') ?? '').split(/,\s*(?=[^=,;\s]+=)/)
|
||||
return cookies
|
||||
return response.headers
|
||||
.getSetCookie()
|
||||
.map((c) => c.split(';')[0]?.trim())
|
||||
.filter(Boolean)
|
||||
.join('; ')
|
||||
|
||||
@@ -217,15 +217,22 @@ export interface SecureFetchOptions {
|
||||
|
||||
export class SecureFetchHeaders {
|
||||
private headers: Map<string, string>
|
||||
private setCookies: string[]
|
||||
|
||||
constructor(headers: Record<string, string>) {
|
||||
constructor(headers: Record<string, string>, setCookies: string[] = []) {
|
||||
this.headers = new Map(Object.entries(headers).map(([k, v]) => [k.toLowerCase(), v]))
|
||||
this.setCookies = setCookies
|
||||
}
|
||||
|
||||
get(name: string): string | null {
|
||||
return this.headers.get(name.toLowerCase()) ?? null
|
||||
}
|
||||
|
||||
/** Returns the raw `Set-Cookie` header values as an array. Each entry is one cookie. */
|
||||
getSetCookie(): string[] {
|
||||
return [...this.setCookies]
|
||||
}
|
||||
|
||||
toRecord(): Record<string, string> {
|
||||
const record: Record<string, string> = {}
|
||||
for (const [key, value] of this.headers) {
|
||||
@@ -384,11 +391,21 @@ export async function secureFetchWithPinnedIP(
|
||||
const bodyBuffer = Buffer.concat(chunks)
|
||||
const body = bodyBuffer.toString('utf-8')
|
||||
const headersRecord: Record<string, string> = {}
|
||||
let setCookieArray: string[] = []
|
||||
for (const [key, value] of Object.entries(res.headers)) {
|
||||
if (typeof value === 'string') {
|
||||
headersRecord[key.toLowerCase()] = value
|
||||
const lowerKey = key.toLowerCase()
|
||||
if (lowerKey === 'set-cookie') {
|
||||
if (Array.isArray(value)) {
|
||||
setCookieArray = value
|
||||
headersRecord[lowerKey] = value.join(', ')
|
||||
} else if (typeof value === 'string') {
|
||||
setCookieArray = [value]
|
||||
headersRecord[lowerKey] = value
|
||||
}
|
||||
} else if (typeof value === 'string') {
|
||||
headersRecord[lowerKey] = value
|
||||
} else if (Array.isArray(value)) {
|
||||
headersRecord[key.toLowerCase()] = value.join(', ')
|
||||
headersRecord[lowerKey] = value.join(', ')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -396,7 +413,7 @@ export async function secureFetchWithPinnedIP(
|
||||
ok: statusCode >= 200 && statusCode < 300,
|
||||
status: statusCode,
|
||||
statusText: res.statusMessage || '',
|
||||
headers: new SecureFetchHeaders(headersRecord),
|
||||
headers: new SecureFetchHeaders(headersRecord, setCookieArray),
|
||||
text: async () => body,
|
||||
json: async () => JSON.parse(body),
|
||||
arrayBuffer: async () =>
|
||||
|
||||
Reference in New Issue
Block a user