mirror of
https://github.com/hcengineering/platform.git
synced 2025-05-10 17:30:51 +00:00
fix: handle double quotes in etag (#8362)
Signed-off-by: Alexander Onnikov <Alexander.Onnikov@xored.com>
This commit is contained in:
parent
3adc00dced
commit
57cd51ee1d
40
server/datalake/src/__tests__/utils.test.ts
Normal file
40
server/datalake/src/__tests__/utils.test.ts
Normal file
@ -0,0 +1,40 @@
|
||||
//
|
||||
// Copyright © 2025 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { wrapETag, unwrapETag } from '../utils'
|
||||
|
||||
describe('unwrapETag', () => {
|
||||
it('should unwrap weak validator prefix', () => {
|
||||
expect(unwrapETag('W/"abc"')).toBe('abc')
|
||||
})
|
||||
|
||||
it('should unwrap strong validator prefix', () => {
|
||||
expect(unwrapETag('"abc"')).toBe('abc')
|
||||
})
|
||||
|
||||
it('should unwrap no validator prefix', () => {
|
||||
expect(unwrapETag('abc')).toBe('abc')
|
||||
})
|
||||
})
|
||||
|
||||
describe('wrapETag', () => {
|
||||
it('should wrap strong validator prefix', () => {
|
||||
expect(wrapETag('abc')).toBe('"abc"')
|
||||
})
|
||||
|
||||
it('should wrap weak validator prefix', () => {
|
||||
expect(wrapETag('abc', true)).toBe('W/"abc"')
|
||||
})
|
||||
})
|
@ -19,6 +19,7 @@ import fetch, { type RequestInfo, type RequestInit, type Response } from 'node-f
|
||||
import { Readable } from 'stream'
|
||||
|
||||
import { DatalakeError, NetworkError, NotFoundError } from './error'
|
||||
import { unwrapETag } from './utils'
|
||||
|
||||
/** @public */
|
||||
export interface ObjectMetadata {
|
||||
@ -191,7 +192,7 @@ export class DatalakeClient {
|
||||
lastModified: isNaN(lastModified) ? 0 : lastModified,
|
||||
size: isNaN(size) ? 0 : size,
|
||||
type: headers.get('Content-Type') ?? '',
|
||||
etag: headers.get('ETag') ?? ''
|
||||
etag: unwrapETag(headers.get('ETag') ?? '')
|
||||
}
|
||||
}
|
||||
|
||||
|
32
server/datalake/src/utils.ts
Normal file
32
server/datalake/src/utils.ts
Normal file
@ -0,0 +1,32 @@
|
||||
//
|
||||
// Copyright © 2025 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
export function unwrapETag (etag: string): string {
|
||||
if (etag.startsWith('W/')) {
|
||||
etag = etag.substring(2)
|
||||
}
|
||||
|
||||
if (etag.startsWith('"') && etag.endsWith('"')) {
|
||||
etag = etag.slice(1, -1)
|
||||
}
|
||||
|
||||
return etag
|
||||
}
|
||||
|
||||
export function wrapETag (etag: string, weak: boolean = false): string {
|
||||
etag = unwrapETag(etag)
|
||||
const quoted = etag.startsWith('"') ? etag : `"${etag}"`
|
||||
return weak ? `W/${quoted}` : quoted
|
||||
}
|
@ -15,3 +15,4 @@
|
||||
|
||||
export * from './datalake'
|
||||
export * from './types'
|
||||
export * from './utils'
|
||||
|
39
services/datalake/pod-datalake/src/datalake/utils.ts
Normal file
39
services/datalake/pod-datalake/src/datalake/utils.ts
Normal file
@ -0,0 +1,39 @@
|
||||
//
|
||||
// Copyright © 2025 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
export function unwrapETag (etag: string): string {
|
||||
// Remove weak validator prefix 'W/'
|
||||
if (etag.startsWith('W/')) {
|
||||
etag = etag.substring(2)
|
||||
}
|
||||
|
||||
// Remove surrounding quotes
|
||||
if (etag.startsWith('"') && etag.endsWith('"')) {
|
||||
etag = etag.slice(1, -1)
|
||||
}
|
||||
|
||||
return etag
|
||||
}
|
||||
|
||||
export function wrapETag (etag: string, weak: boolean = false): string {
|
||||
// Remove any existing wrapping first to ensure clean wrap
|
||||
etag = unwrapETag(etag)
|
||||
|
||||
// Add quotes if not present
|
||||
const quoted = etag.startsWith('"') ? etag : `"${etag}"`
|
||||
|
||||
// Add weak prefix if requested
|
||||
return weak ? `W/${quoted}` : quoted
|
||||
}
|
@ -19,7 +19,7 @@ import { UploadedFile } from 'express-fileupload'
|
||||
import fs from 'fs'
|
||||
|
||||
import { cacheControl } from '../const'
|
||||
import { type Datalake } from '../datalake'
|
||||
import { type Datalake, wrapETag } from '../datalake'
|
||||
import { getBufferSha256, getStreamSha256 } from '../hash'
|
||||
|
||||
interface BlobParentRequest {
|
||||
@ -70,11 +70,11 @@ export async function handleBlobGet (
|
||||
)
|
||||
res.setHeader('Cache-Control', blob.cacheControl ?? cacheControl)
|
||||
res.setHeader('Last-Modified', new Date(blob.lastModified).toUTCString())
|
||||
res.setHeader('ETag', blob.etag)
|
||||
res.setHeader('ETag', wrapETag(blob.etag))
|
||||
|
||||
if (range != null && blob.bodyRange !== undefined) {
|
||||
res.setHeader('Content-Range', blob.bodyRange)
|
||||
res.setHeader('ETag', blob.bodyLength !== blob.size ? blob.bodyEtag : blob.etag)
|
||||
res.setHeader('ETag', wrapETag(blob.bodyLength !== blob.size ? blob.bodyEtag : blob.etag))
|
||||
}
|
||||
|
||||
const status = range != null && blob.bodyLength !== blob.size ? 206 : 200
|
||||
@ -120,7 +120,7 @@ export async function handleBlobHead (
|
||||
res.setHeader('Content-Disposition', filename !== undefined ? `attachment; filename="${filename}"` : 'attachment')
|
||||
res.setHeader('Cache-Control', head.cacheControl ?? cacheControl)
|
||||
res.setHeader('Last-Modified', new Date(head.lastModified).toUTCString())
|
||||
res.setHeader('ETag', head.etag)
|
||||
res.setHeader('ETag', wrapETag(head.etag))
|
||||
|
||||
res.status(200).send()
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user