{"record":{"id":"c66e0a39f429c4ea","repo":"theonedev/onedev","slug":"content-disposition-header-not-found-in-uploaded-f","errorCode":null,"errorMessage":"Content disposition header not found in uploaded file","messagePattern":"Content disposition header not found in uploaded file","errorType":"http","errorClass":"ClientException","httpStatus":400,"severity":"error","filePath":"server-plugin/server-plugin-pack-pypi/src/main/java/io/onedev/server/plugin/pack/pypi/PypiPackHandler.java","lineNumber":133,"sourceCode":"\t\t\t\t\t\t\t\tvar version = getAttribute(attributes, \"version\");\n\t\t\t\t\t\t\t\tvar sha256Hash = getAttribute(attributes, \"sha256_digest\");\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\tattributes.remove(\"name\");\n\t\t\t\t\t\t\t\tattributes.remove(\"version\");\n\t\t\t\t\t\t\t\tattributes.remove(\"filetype\");\n\t\t\t\t\t\t\t\tattributes.remove(\"metadata_version\");\n\t\t\t\t\t\t\t\tattributes.remove(\"pyversion\");\n\t\t\t\t\t\t\t\tattributes.remove(\"sha256_digest\");\n\t\t\t\t\t\t\t\tattributes.remove(\"md5_digest\");\n\t\t\t\t\t\t\t\tattributes.remove(\"blake2_256_digest\");\n\t\t\t\t\t\t\t\tattributes.remove(\":action\");\n\t\t\t\t\t\t\t\tattributes.remove(\"protocol_version\");\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\tLockUtils.run(getLockName(projectId, name), () -> transactionService.run(() -> {\n\t\t\t\t\t\t\t\t\tvar project = checkProject(projectId, true);\n\t\t\t\t\t\t\t\t\tvar contentDisposition = item.getHeaders().getHeader(\"content-disposition\"); \n\t\t\t\t\t\t\t\t\tif (contentDisposition == null)\n\t\t\t\t\t\t\t\t\t\tthrow new ClientException(SC_BAD_REQUEST, \"Content disposition header not found in uploaded file\");\n\t\t\t\t\t\t\t\t\tString fileName = null;\n\t\t\t\t\t\t\t\t\tfor (var field: Splitter.on(\";\").omitEmptyStrings().trimResults().split(contentDisposition)) {\n\t\t\t\t\t\t\t\t\t\tif (field.startsWith(\"filename=\")) {\n\t\t\t\t\t\t\t\t\t\t\tfileName = field.substring(\"filename=\".length() + 1);\n\t\t\t\t\t\t\t\t\t\t\tfileName = fileName.substring(0, fileName.length() - 1);\n\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tif (fileName == null) \n\t\t\t\t\t\t\t\t\t\tthrow new ClientException(SC_BAD_REQUEST, \"File name not found in content disposition header of uploaded file\");\n\n\t\t\t\t\t\t\t\t\tvar packBlobId = packBlobService.uploadBlob(projectId, is, sha256Hash);\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\tif (packBlobId == null)\n\t\t\t\t\t\t\t\t\t\tthrow new ClientException(SC_BAD_REQUEST, \"Digest mismatch\");\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\tPypiData data;\n\t\t\t\t\t\t\t\t\tvar pack = packService.findByNameAndVersion(project, TYPE, name, version);\n\t\t\t\t\t\t\t\t\tif (pack == null) {","sourceCodeStart":115,"sourceCodeEnd":151,"githubUrl":"https://github.com/theonedev/onedev/blob/d44925c47c37992c828ea673a5f9620539bc3ff2/server-plugin/server-plugin-pack-pypi/src/main/java/io/onedev/server/plugin/pack/pypi/PypiPackHandler.java#L115-L151","documentation":"During the PyPI package upload, the handler reads the 'content-disposition' header of the uploaded multipart file part to derive the file name. If the part has no Content-Disposition header, a ClientException (HTTP 400) is thrown.","triggerScenarios":"Uploading the package file as a raw (non-multipart) body, or constructing the multipart request manually without a Content-Disposition header on the file part.","commonSituations":"Hand-rolled curl/HTTP-client scripts that send the file without proper multipart/form-data framing; a proxy stripping headers; using a client library that posts the file as octet-stream instead of a multipart part.","solutions":["Send the upload as proper multipart/form-data so each part carries a Content-Disposition header (curl -F, requests' files=).","If building the request manually, add 'Content-Disposition: form-data; name=\"file\"; filename=\"pkg.whl\"' to the file part.","Capture the outgoing request (proxy/log) and confirm the file part includes the content-disposition header."],"exampleFix":"// before: raw body post\ncurl --data-binary @pkg.whl http://server/~pypi/upload\n// after: multipart with filename\ncurl -F \"file=@pkg.whl\" http://server/~pypi/upload","handlingStrategy":"validation","validationCode":"# verify the request is multipart/form-data with a file part\nfiles = {'file': ('pkg.whl', open('pkg.whl','rb'), 'application/octet-stream')}\nrequests.post(url, files=files, ...)","typeGuard":null,"tryCatchPattern":"if resp.status_code == 400 and 'content disposition' in resp.text:\n    raise RuntimeError('Send upload as multipart/form-data, not raw body')","preventionTips":["Always use multipart upload APIs (curl -F, requests files=) for this endpoint.","Never post the package as a raw binary body.","Test upload scripts against a dev instance first."],"tags":["pypi","http-400","multipart","headers"],"backgroundTag":"missing-required-argument","analyzedSha":"d44925c47c37992c828ea673a5f9620539bc3ff2","analyzedAt":"2026-09-06T07:18:27.995Z","contentChangedAt":"2026-09-06T07:18:27.995Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}