Build a SQLite "file:" URI with immutable=1 from a filesystem path. * immutable=1 bypasses WAL and locking and reads the main DB file directly — * used only as a fallback for read-only filesystems where the wal-index * (-shm) cannot be created. URI-special characters are percent-encoded. * Windows backslashes are normalized to '/', and a leading '/' is inserted * for drive-letter paths so the
| 782 | * for drive-letter paths so the drive is not parsed as a URI authority. |
| 783 | * Returns false if the output buffer is too small. */ |
| 784 | static bool build_immutable_uri(const char *path, char *out, size_t out_sz) { |
| 785 | static const char PREFIX[] = "file://"; |
| 786 | static const char SUFFIX[] = "?immutable=1"; |
| 787 | static const char HEX[] = "0123456789ABCDEF"; |
| 788 | size_t prefix_len = sizeof(PREFIX) - 1; |
| 789 | size_t suffix_len = sizeof(SUFFIX) - 1; |
| 790 | if (prefix_len + 1 > out_sz) { |
| 791 | return false; |
| 792 | } |
| 793 | memcpy(out, PREFIX, prefix_len); |
| 794 | size_t pos = prefix_len; |
| 795 | |
| 796 | /* Ensure the path component begins with '/' (POSIX absolute paths already |
| 797 | * do; Windows "C:\..." gets a leading '/' -> "/C:/..."). */ |
| 798 | if (path[0] != '/') { |
| 799 | if (pos + 1 >= out_sz) { |
| 800 | return false; |
| 801 | } |
| 802 | out[pos++] = '/'; |
| 803 | } |
| 804 | |
| 805 | for (const unsigned char *p = (const unsigned char *)path; *p != '\0'; p++) { |
| 806 | unsigned char c = *p; |
| 807 | if (c == '\\') { |
| 808 | c = '/'; /* normalize Windows separators */ |
| 809 | } |
| 810 | bool safe = (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || |
| 811 | c == '/' || c == '.' || c == '-' || c == '_' || c == '~' || c == ':'; |
| 812 | if (safe) { |
| 813 | if (pos + 1 >= out_sz) { |
| 814 | return false; |
| 815 | } |
| 816 | out[pos++] = (char)c; |
| 817 | } else { |
| 818 | if (pos + 3 >= out_sz) { |
| 819 | return false; |
| 820 | } |
| 821 | out[pos++] = '%'; |
| 822 | out[pos++] = HEX[(c >> 4) & 0xF]; |
| 823 | out[pos++] = HEX[c & 0xF]; |
| 824 | } |
| 825 | } |
| 826 | |
| 827 | if (pos + suffix_len + 1 > out_sz) { |
| 828 | return false; |
| 829 | } |
| 830 | memcpy(out + pos, SUFFIX, suffix_len); |
| 831 | pos += suffix_len; |
| 832 | out[pos] = '\0'; |
| 833 | return true; |
| 834 | } |
| 835 | |
| 836 | cbm_store_t *cbm_store_open_path_query(const char *db_path) { |
| 837 | if (!db_path) { |
no outgoing calls
no test coverage detected