{"record":{"id":"024166b50f261654","repo":"theonedev/onedev","slug":"invalid-http-method-for-blob-pull-method","errorCode":null,"errorMessage":"Invalid http method for blob pull: ${method}","messagePattern":"Invalid http method for blob pull: (.+?)","errorType":"http","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"server-plugin/server-plugin-pack-container/src/main/java/io/onedev/server/plugin/pack/container/ContainerServlet.java","lineNumber":271,"sourceCode":"\t\t\t\t\t\tvar hash = digest.getHash();\n\t\t\t\t\t\tPackBlob packBlob;\n\t\t\t\t\t\tif ((packBlob = packBlobService.checkPackBlob(project.getId(), hash)) != null) {\n\t\t\t\t\t\t\tresponse.setStatus(SC_OK);\t\n\t\t\t\t\t\t\tresponse.setHeader(\"Content-Length\", String.valueOf(packBlob.getSize()));\n\t\t\t\t\t\t\tresponse.setHeader(\"Docker-Content-Digest\", digestString);\n\t\t\t\t\t\t\treturn new Pair<>(packBlob.getProject().getId(), packBlob.getSha256Hash());\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tthrow new NotFoundException(ErrorCode.BLOB_UNKNOWN);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t\tif (method.equals(\"GET\")) {\n\t\t\t\t\t\tpackBlobService.downloadBlob(packBlobInfo.getLeft(), packBlobInfo.getRight(),\n\t\t\t\t\t\t\t\tresponse.getOutputStream());\n\t\t\t\t\t}\n\t\t\t\t} else if (method.equals(\"DELETE\")) {\n\t\t\t\t\tthrow new ClientException(SC_METHOD_NOT_ALLOWED, ErrorCode.UNSUPPORTED);\n\t\t\t\t} else {\n\t\t\t\t\tthrow new BadRequestException(\"Invalid http method for blob pull: \" + method);\n\t\t\t\t}\n\t\t\t} else if ((matcher = compile(\"(.+)/([^/]+)/manifests/([^/]+)\").matcher(pathInfo)).matches()) {\n\t\t\t\tvar projectPath = matcher.group(1);\n\t\t\t\tvar repository = matcher.group(2);\n\t\t\t\tvar reference = matcher.group(3);\n\t\t\t\tswitch (method) {\n\t\t\t\t\tcase \"PUT\":\n\t\t\t\t\t\tvar projectId = sessionService.call(() -> checkProject(projectPath, true).getId());\n\t\t\t\t\t\tvar baos = new ByteArrayOutputStream();\n\t\t\t\t\t\ttry (var is = request.getInputStream()) {\n\t\t\t\t\t\t\tvar copied = copyWithMaxSize(is, baos, MAX_MANIFEST_SIZE);\n\t\t\t\t\t\t\tif (copied == -1)\n\t\t\t\t\t\t\t\tthrow new ClientException(SC_NOT_ACCEPTABLE, ErrorCode.DENIED, \"Manifest exceeds maximum size: \" + MAX_MANIFEST_SIZE);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tvar bytes = baos.toByteArray();\n\t\t\t\t\t\tString hash;\n\t\t\t\t\t\tif (isTag(reference)) {","sourceCodeStart":253,"sourceCodeEnd":289,"githubUrl":"https://github.com/theonedev/onedev/blob/d44925c47c37992c828ea673a5f9620539bc3ff2/server-plugin/server-plugin-pack-container/src/main/java/io/onedev/server/plugin/pack/container/ContainerServlet.java#L253-L289","documentation":"The blob pull endpoint supports GET, HEAD, and (explicitly rejected) DELETE; any other method is rejected with this BadRequestException. DELETE on a blob returns 405 UNSUPPORTED. This guards the read-only nature of the blob resource beyond the method-not-allowed path.","triggerScenarios":"Any method other than GET or HEAD sent to /v2/<name>/blobs/<digest> — e.g. PUT attempting to push a blob at its final URL, POST, or OPTIONS. PUT on the blob URL is a common mistake because some registries tolerate monolithic PUT; OneDev requires the POST-initiated upload flow.","commonSituations":"Clients implementing the monolithic PUT upload variant (POST then PUT to /blobs/<digest>) which this registry does not support; generic REST clients defaulting to POST; tooling probing the blob endpoint with OPTIONS.","solutions":["Upload blobs via POST /v2/<name>/blobs/uploads/ then PATCH/PUT on the returned session URL; never PUT to /blobs/<digest>.","Fetch blobs with GET (or HEAD for existence checks) on /v2/<name>/blobs/<digest>.","Do not attempt DELETE on blobs; blobs are garbage-collected server-side.","Update the client to this registry's supported method set (GET/HEAD for pull)."],"exampleFix":"// before\nPUT /v2/app/blobs/sha256:abc...  body=@layer.tgz\n// after\nloc = POST /v2/app/blobs/uploads/  -> Location\nPATCH loc (chunk) ; PUT loc?digest=sha256:abc...","handlingStrategy":"validation","validationCode":"if (!['GET','HEAD'].includes(method)) {\n  throw new Error(`Blob pull supports GET/HEAD only, got ${method}`);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Pull blobs with GET, check existence with HEAD.","Never use PUT on /blobs/<digest>; use the POST-initiated upload session flow.","Do not attempt DELETE on blobs; rely on server-side GC.","Audit client method/URL templates against the OCI distribution spec."],"tags":["oci-registry","http-method","blob-pull","bad-request"],"backgroundTag":"unsupported-operation","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"}