Validate if a filename is safe to use To validate a full path, split the path by the OS-specific path separator, and validate each part with this function
| 689 | // Validate if a filename is safe to use |
| 690 | // To validate a full path, split the path by the OS-specific path separator, and validate each part with this function |
| 691 | bool fs_validate_filename(const std::string & filename) { |
| 692 | if (!filename.length()) { |
| 693 | // Empty filename invalid |
| 694 | return false; |
| 695 | } |
| 696 | if (filename.length() > 255) { |
| 697 | // Limit at common largest possible filename on Linux filesystems |
| 698 | // to avoid unnecessary further validation |
| 699 | // (On systems with smaller limits it will be caught by the OS) |
| 700 | return false; |
| 701 | } |
| 702 | |
| 703 | std::u32string filename_utf32; |
| 704 | try { |
| 705 | #if defined(__clang__) |
| 706 | // disable C++17 deprecation warning for std::codecvt_utf8 |
| 707 | # pragma clang diagnostic push |
| 708 | # pragma clang diagnostic ignored "-Wdeprecated-declarations" |
| 709 | #endif |
| 710 | std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> converter; |
| 711 | |
| 712 | #if defined(__clang__) |
| 713 | # pragma clang diagnostic pop |
| 714 | #endif |
| 715 | |
| 716 | filename_utf32 = converter.from_bytes(filename); |
| 717 | |
| 718 | // If the reverse conversion mismatches, it means overlong UTF-8 sequences were used, |
| 719 | // or invalid encodings were encountered. Reject such attempts |
| 720 | std::string filename_reencoded = converter.to_bytes(filename_utf32); |
| 721 | if (filename_reencoded != filename) { |
| 722 | return false; |
| 723 | } |
| 724 | } catch (const std::exception &) { |
| 725 | return false; |
| 726 | } |
| 727 | |
| 728 | // Check for forbidden codepoints: |
| 729 | // - Control characters |
| 730 | // - Unicode equivalents of illegal characters |
| 731 | // - UTF-16 surrogate pairs |
| 732 | // - UTF-8 replacement character |
| 733 | // - Byte order mark (BOM) |
| 734 | // - Illegal characters: / \ : * ? " < > | |
| 735 | for (char32_t c : filename_utf32) { |
| 736 | if (c <= 0x1F // Control characters (C0) |
| 737 | || c == 0x7F // Control characters (DEL) |
| 738 | || (c >= 0x80 && c <= 0x9F) // Control characters (C1) |
| 739 | || c == 0xFF0E // Fullwidth Full Stop (period equivalent) |
| 740 | || c == 0x2215 // Division Slash (forward slash equivalent) |
| 741 | || c == 0x2216 // Set Minus (backslash equivalent) |
| 742 | || (c >= 0xD800 && c <= 0xDFFF) // UTF-16 surrogate pairs |
| 743 | || c == 0xFFFD // Replacement Character (UTF-8) |
| 744 | || c == 0xFEFF // Byte Order Mark (BOM) |
| 745 | || c == '/' || c == '\\' || c == ':' || c == '*' // Illegal characters |
| 746 | || c == '?' || c == '"' || c == '<' || c == '>' || c == '|') { |
| 747 | return false; |
| 748 | } |