(command: IOpenFileCommand)
| 821 | } |
| 822 | |
| 823 | private async executeOpenFile(command: IOpenFileCommand) { |
| 824 | try { |
| 825 | const formatter = new CompleteFormatter( |
| 826 | this.app, |
| 827 | getQuickAddInstance(), |
| 828 | this.choiceExecutor |
| 829 | ); |
| 830 | |
| 831 | const resolvedPath = await formatter.formatFileName(command.filePath, ""); |
| 832 | const normalizedPath = resolvedPath.replace(/\\/g, "/"); |
| 833 | |
| 834 | // Validate path segments to prevent traversal attacks. A substring check |
| 835 | // would wrongly reject legitimate filenames that merely contain ".." (e.g. |
| 836 | // 'log..2024.md') or "//"; only a literal '..' path segment or an empty |
| 837 | // segment (from '//') is an actual traversal/malformed path. |
| 838 | const segments = normalizedPath.split("/"); |
| 839 | const hasTraversal = segments.some( |
| 840 | (segment, index) => |
| 841 | segment === ".." || |
| 842 | // An empty segment is a doubled slash; the leading slash of an |
| 843 | // absolute path is allowed (index 0), as is a single trailing slash. |
| 844 | (segment === "" && index !== 0 && index !== segments.length - 1) |
| 845 | ); |
| 846 | if (hasTraversal) { |
| 847 | log.logError(`OpenFile: Path traversal not allowed in '${normalizedPath}'`); |
| 848 | return; |
| 849 | } |
| 850 | |
| 851 | const file = this.app.vault.getAbstractFileByPath(normalizedPath); |
| 852 | |
| 853 | if (!file || !(file instanceof TFile)) { |
| 854 | log.logError(`OpenFile: '${normalizedPath}' does not exist or is not a file`); |
| 855 | return; |
| 856 | } |
| 857 | |
| 858 | const openOptions = buildOpenFileOptions(command); |
| 859 | |
| 860 | await openFile(this.app, file, { |
| 861 | ...openOptions, |
| 862 | originLeaf: this.originLeaf, |
| 863 | }); |
| 864 | } catch (error) { |
| 865 | log.logError(`OpenFile: Failed to open file '${command.filePath}': ${error.message}`); |
| 866 | } |
| 867 | } |
| 868 | } |
no test coverage detected