{"record":{"id":"1118d33e897ada06","repo":"instructure/canvas-lms","slug":"zip-file-cannot-have-more-than-max-file-count-entries","errorCode":null,"errorMessage":"Zip File cannot have more than #{MAX_FILE_COUNT} entries","messagePattern":"Zip File cannot have more than #(.+?) entries","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"lib/unzip_attachment.rb","lineNumber":279,"sourceCode":"# quite seem appropriate to move it to it's own file\n# since it's such an integral part of the unzipping\n# process\nclass ZipFileStats\n  MAX_FILE_COUNT = 250_000\n  attr_reader :file_count, :total_size, :paths, :filename, :quota_remaining\n\n  def initialize(filename)\n    @filename = filename\n    @paths = []\n    @file_count = 0\n    @total_size = 0\n    @quota_remaining = nil\n    process!\n  end\n\n  def validate_against(context)\n    if file_count > MAX_FILE_COUNT\n      raise ArgumentError, \"Zip File cannot have more than #{MAX_FILE_COUNT} entries\"\n    end\n\n    # check whether the nominal size of the zip's contents would exceed\n    # quota, and reject the zip immediately if so\n    quota_hash = Attachment.get_quota(context)\n    if quota_hash[:quota] > 0\n      if (quota_hash[:quota_used] + total_size) > quota_hash[:quota]\n        raise Attachment::OverQuotaError, \"Zip file would exceed quota limit\"\n      end\n\n      @quota_remaining = quota_hash[:quota] - quota_hash[:quota_used]\n    end\n  end\n\n  # since the central directory can lie, track quota during extraction as well\n  # to prevent zip bomb denial-of-service attacks\n  def charge_quota(size)\n    return if @quota_remaining.nil?","sourceCodeStart":261,"sourceCodeEnd":297,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/lib/unzip_attachment.rb#L261-L297","documentation":"validate_against in lib/unzip_attachment.rb:279 raises ArgumentError when the uploaded zip contains more than MAX_FILE_COUNT entries. Canvas rejects the archive up front before extraction to prevent resource exhaustion from very large zips.","triggerScenarios":"Uploading a zip attachment to a course/folder whose entry count exceeds MAX_FILE_COUNT; calling UnzipAttachment with a zip containing thousands of small files.","commonSituations":"Instructors bulk-uploading course exports or LMS migration archives with many files; automated imports of content packages; students uploading zip assignments that explode into many entries.","solutions":["Split the zip into smaller archives each under MAX_FILE_COUNT entries","Upload the files in batches instead of one large zip","Increase MAX_FILE_COUNT if the deployment legitimately needs larger zips (with capacity review)","Inspect the zip's entry count locally (unzip -l) before uploading"],"exampleFix":"// before\n# course_export_with_10000_files.zip uploaded directly\n// after\nzip -r batch1.zip files_0001-0500/\nzip -r batch2.zip files_0501-1000/ # each batch <= MAX_FILE_COUNT entries","handlingStrategy":"validation","validationCode":"raise TooManyEntries if zip_entry_count(zip_path) > MAX_FILE_COUNT","typeGuard":null,"tryCatchPattern":"begin\n  UnzipAttachment.process(context, zip)\nrescue ArgumentError => e\n  render json: {error: e.message}, status: :bad_request\nend","preventionTips":["Count zip entries (unzip -l) before upload","Split large archives client-side","Surface MAX_FILE_COUNT in UI guidance"],"tags":["ruby","zip","file-upload","limit"],"backgroundTag":"file-size-limit-exceeded","analyzedSha":"1c9f0bb8013ed69c4f2efe11fd483025469b7e6c","analyzedAt":"2026-09-15T20:33:18.891Z","contentChangedAt":"2026-09-15T20:33:18.891Z","schemaVersion":2},"datasetVersion":"2026-09-23T02:17:17.105Z"}