{"id":34276472,"url":"https://github.com/kpmeen/clammyscan","last_synced_at":"2026-03-10T16:32:18.698Z","repository":{"id":20010644,"uuid":"23278207","full_name":"kpmeen/clammyscan","owner":"kpmeen","description":"[MIRROR] A Playframework BodyParser for scanning multipart file uploads with ClamAV","archived":false,"fork":false,"pushed_at":"2021-04-26T21:51:42.000Z","size":4681,"stargazers_count":9,"open_issues_count":0,"forks_count":5,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-12-20T09:58:20.335Z","etag":null,"topics":["akka-streams","clamav","clammyscan","playframework"],"latest_commit_sha":null,"homepage":"https://gitlab.com/kpmeen/clammyscan","language":"Scala","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kpmeen.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-08-24T10:09:29.000Z","updated_at":"2021-03-01T19:56:47.000Z","dependencies_parsed_at":"2022-08-19T07:40:59.965Z","dependency_job_id":null,"html_url":"https://github.com/kpmeen/clammyscan","commit_stats":null,"previous_names":[],"tags_count":28,"template":false,"template_full_name":null,"purl":"pkg:github/kpmeen/clammyscan","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kpmeen%2Fclammyscan","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kpmeen%2Fclammyscan/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kpmeen%2Fclammyscan/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kpmeen%2Fclammyscan/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kpmeen","download_url":"https://codeload.github.com/kpmeen/clammyscan/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kpmeen%2Fclammyscan/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30342177,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-10T15:55:29.454Z","status":"ssl_error","status_checked_at":"2026-03-10T15:54:58.440Z","response_time":106,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["akka-streams","clamav","clammyscan","playframework"],"created_at":"2025-12-16T21:37:37.318Z","updated_at":"2026-03-10T16:32:18.660Z","avatar_url":"https://github.com/kpmeen.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ClammyScan\n\nContains libraries for use in akka-streams pipelines and Play! Framework\napplications that require AV scanning. \n\nThe streams library contains the akka-streams implementation that handles the\ncommunication with clamd.\n\nThe body-parsers library contains a Play! Framework Module that provides access\nto body parsers enabling parallel anti-virus scanning of file upload streams.\n\n## Background\n\nTraditionally, AV scanning is handled by some background service running on the\nmachine(s) where files are stored. And files are typically scanned after they've\nbeen persisted. If the AV on the file server detects an infected file, it will\ntypically be placed in a quarantine of some sort. In many cases this can cause\nbroken file references and/or data inconsistencies.\n\nWith the ClammyScan body parsers this risk can be reduced since the file is scanned while it's\nbeing uploaded. This gives you control more control on how to react when a user\ntries to upload an infected file.\n\n\n## Usage\n\nAdd the dependency for ClammyScan to your `build.sbt`:\n\n```scala\nresolvers += Resolver.jcenterRepo\n\n// See latest version badge above for current release\n\n// Play! Framework body parsers\nlibraryDependencies += \"net.scalytica\" %% \"clammyscan\" % \"\u003cversion\u003e\"\n\n// Akka Streams library that enables streaming data to clamd.\nlibraryDependencies += \"net.scalytica\" %% \"clammyscan-streams\" % \"\u003cversion\u003e\"\n```\n\nOr you can clone the repository and build from source.\n\n## Configuration\n\n### ClamAV\n\nPlease refer to the official ClamAV\n[documentation](https://www.clamav.net/documents/installing-clamav) to ensure\nyou have a properly configured clamd installation.\n\n### ClammyScan\n\nClammyScan has some configurable parameters. At the moment the configurable\nparameters are defined as follows in the `reference.conf` file. Any overrides\nshould be located in the play application's `conf/application.conf` file:\n\n```hocon\nplay.modules.enabled += \"net.scalytica.clammyscan.ClammyScanModule\"\n\nclammyscan {\n  clamd {\n    host = \"localhost\"\n    host = ${?CLAM_HOST}\n    port = \"3310\"\n    port = ${?CLAM_PORT}\n    # Duration before connection timeout, where 0 means infinite.\n    # (See clamd documentation)\n    timeout = 0\n    # Configure to be the same size as the clamd StreamMaxLength parameter.\n    # (See clamd documentation) \n    streamMaxLength = 25m\n  }\n  # Defaults to true\n  removeInfected = true\n  # Defaults to true, but will be treated as false if failOnError=true\n  removeOnError = true\n  # Defaults to false...if set to true it will also set removeOnError=false\n  failOnError = false\n  # Disables the clamd scan process and just handle the upload.\n  # Defaults to false.\n  scanDisabled = false\n  # A regex for validating the filename of the file to be uploaded.\n  # Will allow anything if not set.\n  validFilenameRegex = \"\"\"(.[\\\"\\*\\\\\\\u003e\\\u003c\\?\\/\\:\\|].)|(.[\\.]?.[\\.]$)|(.*[ ]+$)\"\"\"\n}\n```\n\n## Available BodyParsers\n\nThere are `BodyParser`s implemented for both `multipart/form-data` and direct,\nchunked, file uploads. To see an example application using most of the parsers,\nplease have a look in the [sample](sample) directory.\n\n\n### Multipart parsers\n\n#### scan\n\nTakes two arguments. A function for saving the file content in parallel with the\nAV scan. And a function for handling removal of the file in case it is infected.\nThis is the most powerful of the currently available parsers.\n\nThis is also the parser to use if you need to provide custom handling for saving\nthe file.\n\n#### scanOnly\nScans your file and returns a result without persisting the file in any way.\n\n```scala\n  def scanFile: Action[ClamMultipart[Unit]] =\n    Action(clammyScan.scanOnly) { request =\u003e\n      request.body.files.head.ref.scanResponse match {\n        case vf: VirusFound =\u003e\n          NotAcceptable(Json.obj(\"message\" -\u003e vf.message))\n\n        case ce: ClamError =\u003e\n          logger.error(s\"An unknown error occurred: ${ce.message}\")\n          InternalServerError(Json.obj(\"message\" -\u003e ce.message))\n\n        case FileOk =\u003e\n          Ok(Json.obj(\"message\" -\u003e \"file is clean\"))\n      }\n    }\n```\n\n#### scanWithTmpFile\n\nScans your file and writes it to a temporary file, available for later\nprocessing in the controller.\n\n```scala\n  def scanTempFile: Action[ClamMultipart[Files.TemporaryFile]] =\n    Action(clammyScan.scanWithTmpFile) { request =\u003e\n      request.body.files.headOption.map { f =\u003e\n        val fname = f.ref.maybeRef.get.path.getFileName\n        f.ref.scanResponse match {\n          case err: ClamError =\u003e\n            Ok(Json.obj(\"message\" -\u003e s\"$fname scan result was: ${err.message}\"))\n\n          case FileOk =\u003e\n            Ok(Json.obj(\"message\" -\u003e s\"$fname uploaded successfully\"))\n        }\n      }.getOrElse {\n        BadRequest(\"could not find attached file\")\n      }\n    }\n```\n\n### Direct / Chunked parser\n\n#### directScan\n\nSimilar to the `scan` parser for multipart files. It takes two arguments. A\nfunction for saving the file content in parallel with the AV scan. And a\nfunction for handling removal of the file in case it is infected. This is the\nmost powerful of the currently available parsers.\n\nThis is also the parser to use if you need to provide custom handling for saving\nthe file.\n\n#### directScanWithTmpFile\n\nScans your file and writes it to a temporary file, available for later\nprocessing in the controller.\n\n```scala\n  def directTempFile: Action[ScannedBody[Files.TemporaryFile]] =\n    Action(clammyScan.directScanWithTmpFile) { request =\u003e\n      request.body.maybeRef.map { ref =\u003e\n        val fname = ref.path.getFileName\n        request.body.scanResponse match {\n          case err: ClamError =\u003e\n            Ok(Json.obj(\"message\" -\u003e s\"$fname scan result was: ${err.message}\"))\n\n          case FileOk =\u003e\n            Ok(Json.obj(\"message\" -\u003e s\"$fname uploaded successfully\"))\n        }\n      }.getOrElse {\n        Ok(Json.obj(\"message\" -\u003e s\"Request did not contain any files\"))\n      }\n    }\n```\n\n#### directScanOnly\n\nScans your file and returns a result without persisting the file in any way.\n\n```scala\n  def directScanFile: Action[ScannedBody[Unit]] =\n    Action(clammyScan.directScanOnly) { request =\u003e\n      request.body.scanResponse match {\n        case vf: VirusFound =\u003e\n          NotAcceptable(Json.obj(\"message\" -\u003e vf.message))\n\n        case ce: ClamError =\u003e\n          logger.error(s\"An unknown error occurred: ${ce.message}\")\n          InternalServerError(Json.obj(\"message\" -\u003e ce.message))\n\n        case FileOk =\u003e\n          Ok(Json.obj(\"message\" -\u003e \"file is clean\"))\n      }\n    }\n```\n\n## Supported clamd commands\n\nIn addition to the implemented parsers above, an instance of a ClammyScan parser\ncan execute the following commands against clamd.\n\n### ping\n\nWill just execute the `PING` command against clamd, which will respond with a\n`PONG`.\n\n### version\n\nFetches the version string for the connected clamd installation.\n\n### stats\n\nFetches a string with statistics information from the connected clamd\ninstallation.\n\nThe official clamav documentation states the following about the response\nformat:\n\n\u003e The exact reply format is subject to changes in future releases.\n\nSo there has been no extra effort to properly parse the response into meaningful\ntypes.\n\n\n# Contributing\n\nAny contributions and suggestions are very welcome!\n\n## Building and Testing\n\nCurrently the tests depend on the presence of a clamd instance running. For\nlocal testing, change the configuration in `sample/conf/application.conf` to\npoint to a running installation.\n\nThe recommended way of starting clamd for development is to execute the\n`dockerClamAV.sh` script.\n\n```bash\n# start docker-clamav\n./dockerClamAV.sh start\n\n# stop docker-clamav\n./dockerClamAV.sh stop\n\n# reset docker-clamav (stop, remove container, pull latest and start)\n./dockerClamAV.sh reset\n\n# clean docker-clamav (remove container)\n./dockerClamAV.sh clean\n```\n\nAlternatively, you can start up a Docker container using\nthe following commands:\n\n```bash\ndocker pull registry.gitlab.com/kpmeen/docker-clamav\n\n# Note the \"-m 2M\" argument. It sets the max TCP stream size for clamd to 2Mb\n# This is required for a few of the test cases that verify correct behaviour\n# when this limit is crossed.\ndocker run --name clammy -d -p 3310:3310 registry.gitlab.com/kpmeen/docker-clamav -m 2M\n\n# And to keep an eye on the clamd log\ndocker exec -it clammy tail -300f /var/log/clamav/clamav.log\n```\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkpmeen%2Fclammyscan","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkpmeen%2Fclammyscan","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkpmeen%2Fclammyscan/lists"}