Skip to content

Commit da157a7

Browse files
committed
Fix multi-part uploads
1 parent ce8156f commit da157a7

File tree

1 file changed

+49
-30
lines changed

1 file changed

+49
-30
lines changed

src/index.ts

Lines changed: 49 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -107,17 +107,17 @@ async function createPostRequest(
107107
}
108108

109109
function createRequestBodyForFilepaths(
110-
filepaths: string[]
110+
filepaths: string[],
111+
basePath: string
111112
): Array<string | ReadStream> {
112113
const requestBody = []
113-
for (const p of filepaths) {
114-
// Multipart header for each file.
114+
for (const absPath of filepaths) {
115+
const relPath = path.relative(basePath, absPath)
116+
const filename = path.basename(absPath)
115117
requestBody.push(
116-
`Content-Disposition: form-data; name="file"; filename="${path.basename(p)}"\n`,
117-
`Content-Type: application/octet-stream\n\n`,
118-
createReadStream(p),
119-
// New line after file content.
120-
'\n'
118+
`Content-Disposition: form-data; name="${relPath}"; filename="${filename}"\r\n`,
119+
`Content-Type: application/octet-stream\r\n\r\n`,
120+
createReadStream(absPath)
121121
)
122122
}
123123
return requestBody
@@ -130,29 +130,32 @@ function createRequestBodyForJson(
130130
const ext = path.extname(basename)
131131
const name = path.basename(basename, ext)
132132
return [
133-
`Content-Disposition: form-data; name="${name}"; filename="${basename}"\n`,
134-
'Content-Type: application/json\n\n',
133+
`Content-Disposition: form-data; name="${name}"; filename="${basename}"\r\n`,
134+
'Content-Type: application/json\r\n\r\n',
135135
JSON.stringify(jsonData),
136136
// New line after file content.
137-
'\n'
137+
'\r\n'
138138
]
139139
}
140140

141141
async function createUploadRequest(
142142
baseUrl: string,
143143
urlPath: string,
144-
requestBodyNoBoundaries: Array<string | ReadStream>,
144+
requestBodyNoBoundaries: Array<
145+
string | ReadStream | Array<string | ReadStream>
146+
>,
145147
options: RequestOptions
146148
): Promise<IncomingMessage> {
147149
// Generate a unique boundary for multipart encoding.
148-
const boundary = `----NodeMultipartBoundary${Date.now()}`
149-
const boundarySep = `--${boundary}\n`
150-
// Create request body as a stream.
150+
const boundary = `NodeMultipartBoundary${Date.now()}`
151+
const boundarySep = `--${boundary}\r\n`
152+
const finalBoundary = `--${boundary}--\r\n`
151153
const requestBody = [
152-
...(requestBodyNoBoundaries.length
153-
? requestBodyNoBoundaries.flatMap(e => [boundarySep, e])
154-
: [boundarySep]),
155-
`--${boundary}--\n`
154+
...requestBodyNoBoundaries.flatMap(part => [
155+
boundarySep,
156+
...(Array.isArray(part) ? part : [part])
157+
]),
158+
finalBoundary
156159
]
157160
const req = getHttpModule(baseUrl).request(`${baseUrl}${urlPath}`, {
158161
method: 'POST',
@@ -167,13 +170,15 @@ async function createUploadRequest(
167170
for (const part of requestBody) {
168171
if (typeof part === 'string') {
169172
req.write(part)
170-
} else {
173+
} else if (typeof part?.pipe === 'function') {
171174
part.pipe(req, { end: false })
172175
// Wait for file streaming to complete.
173176
// eslint-disable-next-line no-await-in-loop
174177
await events.once(part, 'end')
175178
// Ensure a new line after file content.
176-
req.write('\n')
179+
req.write('\r\n')
180+
} else {
181+
throw new TypeError('Invalid multipart part: expected string or stream')
177182
}
178183
}
179184
} finally {
@@ -224,15 +229,26 @@ function isResponseOk(response: IncomingMessage): boolean {
224229
)
225230
}
226231

227-
function resolveAbsPaths(filepaths: string[], pathsRelativeTo = '.'): string[] {
232+
function resolveAbsPaths(
233+
filepaths: string[],
234+
pathsRelativeTo?: string
235+
): string[] {
236+
const basePath = resolveBasePath(pathsRelativeTo)
228237
// Node's path.resolve will process path segments from right to left until
229238
// it creates a valid absolute path. So if `pathsRelativeTo` is an absolute
230239
// path, process.cwd() is not used, which is the common expectation. If none
231240
// of the paths resolve then it defaults to process.cwd().
232-
const basePath = path.resolve(process.cwd(), pathsRelativeTo)
233241
return filepaths.map(p => path.resolve(basePath, p))
234242
}
235243

244+
function resolveBasePath(pathsRelativeTo = '.'): string {
245+
// Node's path.resolve will process path segments from right to left until
246+
// it creates a valid absolute path. So if `pathsRelativeTo` is an absolute
247+
// path, process.cwd() is not used, which is the common expectation. If none
248+
// of the paths resolve then it defaults to process.cwd().
249+
return path.resolve(process.cwd(), pathsRelativeTo)
250+
}
251+
236252
/**
237253
* Package.json data to base the User-Agent on
238254
*/
@@ -460,13 +476,14 @@ export class SocketSdk {
460476
filepaths: string[],
461477
pathsRelativeTo = '.'
462478
): Promise<SocketSdkResultType<'createDependenciesSnapshot'>> {
463-
const absFilepaths = resolveAbsPaths(filepaths, pathsRelativeTo)
479+
const basePath = resolveBasePath(pathsRelativeTo)
480+
const absFilepaths = resolveAbsPaths(filepaths, basePath)
464481
try {
465482
const data = await getResponseJson(
466483
await createUploadRequest(
467484
this.#baseUrl,
468485
`dependencies/upload?${new URLSearchParams(params)}`,
469-
createRequestBodyForFilepaths(absFilepaths),
486+
createRequestBodyForFilepaths(absFilepaths, basePath),
470487
this.#reqOptions
471488
)
472489
)
@@ -482,13 +499,14 @@ export class SocketSdk {
482499
filepaths: string[],
483500
pathsRelativeTo: string = '.'
484501
): Promise<SocketSdkResultType<'CreateOrgFullScan'>> {
485-
const absFilepaths = resolveAbsPaths(filepaths, pathsRelativeTo)
502+
const basePath = resolveBasePath(pathsRelativeTo)
503+
const absFilepaths = resolveAbsPaths(filepaths, basePath)
486504
try {
487505
const data = await getResponseJson(
488506
await createUploadRequest(
489507
this.#baseUrl,
490508
`orgs/${encodeURIComponent(orgSlug)}/full-scans?${new URLSearchParams(queryParams ?? '')}`,
491-
createRequestBodyForFilepaths(absFilepaths),
509+
createRequestBodyForFilepaths(absFilepaths, basePath),
492510
this.#reqOptions
493511
)
494512
)
@@ -522,15 +540,16 @@ export class SocketSdk {
522540
pathsRelativeTo: string = '.',
523541
issueRules?: Record<string, boolean>
524542
): Promise<SocketSdkResultType<'createReport'>> {
525-
const absFilepaths = resolveAbsPaths(filepaths, pathsRelativeTo)
543+
const basePath = resolveBasePath(pathsRelativeTo)
544+
const absFilepaths = resolveAbsPaths(filepaths, basePath)
526545
try {
527546
const data = await createUploadRequest(
528547
this.#baseUrl,
529548
'report/upload',
530549
[
531-
...createRequestBodyForFilepaths(absFilepaths),
550+
...createRequestBodyForFilepaths(absFilepaths, basePath),
532551
...(issueRules
533-
? createRequestBodyForJson(issueRules, 'issueRules.json')
552+
? createRequestBodyForJson(issueRules, 'issueRules')
534553
: [])
535554
],
536555
{

0 commit comments

Comments
 (0)