EdgeAIG's picture
download
raw
6.89 kB
import { Config, MultipartError, PartInfo } from "../index.js"
import * as CT from "./contentType.js"
import * as HP from "./headers.js"
import * as Search from "./search.js"
const enum State {
headers,
body,
}
const errInvalidDisposition: MultipartError = { _tag: "InvalidDisposition" }
const errEndNotReached: MultipartError = { _tag: "EndNotReached" }
const errMaxParts: MultipartError = { _tag: "ReachedLimit", limit: "MaxParts" }
const errMaxTotalSize: MultipartError = {
_tag: "ReachedLimit",
limit: "MaxTotalSize",
}
const errMaxPartSize: MultipartError = {
_tag: "ReachedLimit",
limit: "MaxPartSize",
}
const errMaxFieldSize: MultipartError = {
_tag: "ReachedLimit",
limit: "MaxFieldSize",
}
const constCR = new TextEncoder().encode("\r\n")
export function defaultIsFile(info: PartInfo) {
return (
info.filename !== undefined ||
info.contentType === "application/octet-stream"
)
}
function parseBoundary(headers: Record<string, string>) {
const contentType = CT.parse(headers["content-type"])
return contentType.parameters.boundary
}
function noopOnChunk(_chunk: Uint8Array | null) {}
export function make({
headers,
onFile: onPart,
onField,
onError,
onDone,
isFile = defaultIsFile,
maxParts = Infinity,
maxTotalSize = Infinity,
maxPartSize = Infinity,
maxFieldSize = 1024 * 1024,
}: Config) {
const boundary = parseBoundary(headers)
if (boundary === undefined) {
onError({ _tag: "InvalidBoundary" })
return {
write: noopOnChunk,
end() {},
}
}
const state = {
state: State.headers,
index: 0,
parts: 0,
onChunk: noopOnChunk,
info: undefined as any as PartInfo,
headerSkip: 0,
partSize: 0,
totalSize: 0,
isFile: false,
fieldChunks: [] as Array<Uint8Array>,
fieldSize: 0,
}
function skipBody() {
state.state = State.body
state.isFile = true
state.onChunk = noopOnChunk
}
const headerParser = HP.make()
const split = Search.make(
`\r\n--${boundary}`,
function (index, chunk) {
if (index === 0) {
// data before the first boundary
skipBody()
return
} else if (index !== state.index) {
if (state.index > 0) {
if (state.isFile) {
state.onChunk(null)
state.partSize = 0
} else {
if (state.fieldChunks.length === 1) {
onField(state.info, state.fieldChunks[0])
} else {
const buf = new Uint8Array(state.fieldSize)
let offset = 0
for (let i = 0; i < state.fieldChunks.length; i++) {
const chunk = state.fieldChunks[i]
buf.set(chunk, offset)
offset += chunk.length
}
onField(state.info, buf)
}
state.fieldSize = 0
state.fieldChunks = []
}
}
state.state = State.headers
state.index = index
state.headerSkip = 2 // skip the first \r\n
// trailing --
if (chunk[0] === 45 && chunk[1] === 45) {
return onDone()
}
state.parts++
if (state.parts > maxParts) {
onError(errMaxParts)
}
}
if ((state.partSize += chunk.length) > maxPartSize) {
onError(errMaxPartSize)
}
if (state.state === State.headers) {
const result = headerParser(chunk, state.headerSkip)
state.headerSkip = 0
if (result._tag === "Continue") {
return
} else if (result._tag === "Failure") {
skipBody()
return onError({ _tag: "BadHeaders", error: result })
}
const contentType = CT.parse(result.headers["content-type"] as string)
const contentDisposition = CT.parse(
result.headers["content-disposition"] as string,
true,
)
if (
"form-data" === contentDisposition.value &&
!("name" in contentDisposition.parameters)
) {
skipBody()
return onError(errInvalidDisposition)
}
let encodedFilename: string | undefined
if ("filename*" in contentDisposition.parameters) {
const parts = contentDisposition.parameters["filename*"].split("''")
if (parts.length === 2) {
encodedFilename = decodeURIComponent(parts[1])
}
}
state.info = {
name: contentDisposition.parameters.name ?? "",
filename: encodedFilename ?? contentDisposition.parameters.filename,
contentType:
contentType.value === ""
? contentDisposition.parameters.filename !== undefined
? "application/octet-stream"
: "text/plain"
: contentType.value,
contentTypeParameters: contentType.parameters,
contentDisposition: contentDisposition.value,
contentDispositionParameters: contentDisposition.parameters as any,
headers: result.headers,
}
state.state = State.body
state.isFile = isFile(state.info)
if (state.isFile) {
state.onChunk = onPart(state.info)
}
if (result.endPosition < chunk.length) {
if (state.isFile) {
state.onChunk(chunk.subarray(result.endPosition))
} else {
const buf = chunk.subarray(result.endPosition)
if ((state.fieldSize += buf.length) > maxFieldSize) {
onError(errMaxFieldSize)
}
state.fieldChunks.push(buf)
}
}
} else if (state.isFile) {
state.onChunk(chunk)
} else {
if ((state.fieldSize += chunk.length) > maxFieldSize) {
onError(errMaxFieldSize)
}
state.fieldChunks.push(chunk)
}
},
constCR,
)
return {
write(chunk: Uint8Array) {
if ((state.totalSize += chunk.length) > maxTotalSize) {
return onError(errMaxTotalSize)
}
return split.write(chunk)
},
end() {
split.end()
if (state.state === State.body) {
onError(errEndNotReached)
}
state.state = State.headers
state.index = 0
state.parts = 0
state.onChunk = noopOnChunk
state.info = undefined as any as PartInfo
state.totalSize = 0
state.partSize = 0
state.fieldChunks = []
state.fieldSize = 0
},
} as const
}
const utf8Decoder = new TextDecoder("utf-8")
function getDecoder(charset: string) {
if (charset === "utf-8" || charset === "utf8" || charset === "") {
return utf8Decoder
}
try {
return new TextDecoder(charset)
} catch (error) {
return utf8Decoder
}
}
export function decodeField(info: PartInfo, value: Uint8Array): string {
return getDecoder(info.contentTypeParameters.charset ?? "utf-8").decode(value)
}

Xet Storage Details

Size:
6.89 kB
·
Xet hash:
f46896ee381e2e703607cef64ca925350b709301e1eb9934da39fc3ea47c0254

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.