Something went wrong. Try again.
Uploads documents to Paperless-ngx from the command line or from a Send to Paperless item in Nautilus.
Something went wrong. Try again.
6.0 kB · 130 lines
Zig
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131// SPDX-FileCopyrightText: © 2026 Jeffrey C. Ollie <jeff@ocjtech.us>// SPDX-License-Identifier: MIT
//! The kinds of file Paperless-ngx will consume, taken from its parsers'//! own tables (`src/paperless/parsers/*.py` in Paperless-ngx 3.2.1).//!//! A file is refused here, before anything is sent, if it is not one of//! these: Paperless would only refuse it after the upload. The office//! formats and mail are consumed only by a server that has Tika (and, for//! the office formats, Gotenberg) enabled; one without refuses them with an//! error that says so.
const std = @import("std");
pub const Kind = struct { /// Lowercase, without the dot. extension: []const u8, /// What is sent as the file's `Content-Type`. media_type: []const u8,};
/// By extension. Several extensions can share a media type.pub const kinds = [_]Kind{ // The OCR parser. .{ .extension = "pdf", .media_type = "application/pdf" }, .{ .extension = "png", .media_type = "image/png" }, .{ .extension = "jpg", .media_type = "image/jpeg" }, .{ .extension = "jpeg", .media_type = "image/jpeg" }, .{ .extension = "jpe", .media_type = "image/jpeg" }, .{ .extension = "tif", .media_type = "image/tiff" }, .{ .extension = "tiff", .media_type = "image/tiff" }, .{ .extension = "gif", .media_type = "image/gif" }, .{ .extension = "bmp", .media_type = "image/bmp" }, .{ .extension = "webp", .media_type = "image/webp" }, .{ .extension = "heic", .media_type = "image/heic" }, // The text parser. .{ .extension = "txt", .media_type = "text/plain" }, .{ .extension = "text", .media_type = "text/plain" }, .{ .extension = "csv", .media_type = "text/csv" }, // The mail parser. .{ .extension = "eml", .media_type = "message/rfc822" }, // The Tika parser. .{ .extension = "doc", .media_type = "application/msword" }, .{ .extension = "docx", .media_type = "application/vnd.openxmlformats-officedocument.wordprocessingml.document" }, .{ .extension = "xls", .media_type = "application/vnd.ms-excel" }, .{ .extension = "xlsx", .media_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" }, .{ .extension = "ppt", .media_type = "application/vnd.ms-powerpoint" }, .{ .extension = "pptx", .media_type = "application/vnd.openxmlformats-officedocument.presentationml.presentation" }, .{ .extension = "ppsx", .media_type = "application/vnd.openxmlformats-officedocument.presentationml.slideshow" }, .{ .extension = "odt", .media_type = "application/vnd.oasis.opendocument.text" }, .{ .extension = "ods", .media_type = "application/vnd.oasis.opendocument.spreadsheet" }, .{ .extension = "odp", .media_type = "application/vnd.oasis.opendocument.presentation" }, .{ .extension = "odg", .media_type = "application/vnd.oasis.opendocument.graphics" }, .{ .extension = "rtf", .media_type = "text/rtf" },};
/// Other names for the same media types: what shared-mime-info, and so/// Nautilus, calls some of them. Paperless sniffs the content itself, so/// these only decide whether a file is offered.const aliases = [_][]const u8{ "application/csv", "application/rtf", "image/x-bmp", "image/x-ms-bmp", "image/heif",};
/// The media type to send for a file, from the extension of its name, or/// null when Paperless would not consume it.pub fn forFilename(name: []const u8) ?[]const u8 { const ext = std.fs.path.extension(std.fs.path.basename(name)); if (ext.len < 2) return null; for (kinds) |kind| { if (std.ascii.eqlIgnoreCase(kind.extension, ext[1..])) return kind.media_type; } return null;}
/// Whether Paperless consumes files of a media type, as a file manager/// reports it. Parameters (`; charset=...`) are ignored.pub fn isSupported(media_type: []const u8) bool { const bare = std.mem.trim(u8, media_type[0 .. std.mem.findScalar(u8, media_type, ';') orelse media_type.len], " "); for (kinds) |kind| { if (std.ascii.eqlIgnoreCase(kind.media_type, bare)) return true; } for (aliases) |alias| { if (std.ascii.eqlIgnoreCase(alias, bare)) return true; } return false;}
const testing = std.testing;
test forFilename { try testing.expectEqualStrings("application/pdf", forFilename("scan.pdf").?); try testing.expectEqualStrings("application/pdf", forFilename("/a/b/SCAN.PDF").?); try testing.expectEqualStrings("image/jpeg", forFilename("photo.JPEG").?); try testing.expectEqualStrings("image/tiff", forFilename("fax.tif").?); try testing.expectEqualStrings("message/rfc822", forFilename("receipt.eml").?); try testing.expectEqualStrings("text/csv", forFilename("bank.csv").?); try testing.expectEqualStrings( "application/vnd.openxmlformats-officedocument.wordprocessingml.document", forFilename("letter.docx").?, ); try testing.expectEqual(null, forFilename("program.exe")); try testing.expectEqual(null, forFilename("archive.tar.gz")); try testing.expectEqual(null, forFilename("README")); try testing.expectEqual(null, forFilename(".pdf")); try testing.expectEqual(null, forFilename("dir.pdf/file")); try testing.expectEqual(null, forFilename("trailing."));}
test isSupported { try testing.expect(isSupported("application/pdf")); try testing.expect(isSupported("text/plain; charset=utf-8")); try testing.expect(isSupported("application/rtf")); try testing.expect(isSupported("IMAGE/PNG")); try testing.expect(!isSupported("application/zip")); try testing.expect(!isSupported("inode/directory")); try testing.expect(!isSupported(""));}
test "every kind is supported by its own media type" { var buffer: [32]u8 = undefined; for (kinds) |kind| { try testing.expect(isSupported(kind.media_type)); const name = try std.fmt.bufPrint(&buffer, "file.{s}", .{kind.extension}); try testing.expectEqualStrings(kind.media_type, forFilename(name).?); }}