What does File.resolve (and resolveSibling) do in kotlin.io, and how does it differ from new File(parent, child) and from string concatenation when building paths? What happens when the child is an absolute path?
answer
- resolve = base + child with proper separator
- Absolute child -> base ignored, child returned
- resolveSibling resolves against parent
- resolve does NOT normalize .. — use normalize()
- Validate startsWith(base) for traversal safety
basics
~10 sresolve joins a base directory with a child path safely, using the right separator. resolveSibling joins against the parent. If the child is already absolute, resolve just returns the child instead of nesting it.
solid answer
~40 skotlin.io adds File.resolve(relative): it appends a child path to a base File using the platform file separator, similar to File(base, child) but as a fluent, chainable operator (there is also a String overload). The key rule: if the child is an **absolute** path, resolve ignores the base and returns the child directly — it does not blindly concatenate. File.resolveSibling(relative) resolves against the file's parent instead of the file itself (useful to put a file next to another). Versus string concatenation with "/", resolve avoids double or missing separators and is cross-platform. Note resolve does NOT normalize ".." or collapse the path; pair it with File.normalize() if you need a canonical form, and validate the result for path-traversal safety.
code
kotlin · 7 linesimport java.io.File
val base = File("/srv/data")
base.resolve("users/42/avatar.png") // /srv/data/users/42/avatar.png
base.resolve("/etc/shadow") // /etc/shadow (absolute -> base dropped!)
base.resolve("../secret").normalize() // /srv/secret (.. collapsed only after normalize)
File("/a/b/c.txt").resolveSibling("d.txt") // /a/b/d.txtgo deeper
Knows resolve joins a base directory with a child path using the right separator.
Explains the absolute-child rule, resolveSibling, and that resolve doesn't normalize.
Connects resolve to path-traversal defense (normalize + startsWith) and to nio Path.resolve semantics.
Sets a codebase convention for safe path building, distinguishing lexical normalize() vs filesystem canonicalFile, and treats user paths as untrusted by default.
## resolve — joining paths `File.resolve(relative: File)` and `File.resolve(relative: String)` are `kotlin.io` extension functions that build a child path: ```kotlin import java.io.File val base = File("/home/user") base.resolve("docs").resolve("a.txt") // /home/user/docs/a.txt base.resolve("docs/a.txt") // same result ``` Think of it as the fluent, chainable equivalent of `File(base, child)`. It inserts the **platform `File.separator`** (`/` on Unix, `\` on Windows) so you never get `//` or a missing slash like manual `base.path + "/" + child` can. ## The absolute-child rule (the important gotcha) If `relative` is itself an **absolute** path, resolve **discards the base** and returns the child: ```kotlin File("/home/user").resolve("/etc/passwd") // /etc/passwd, NOT /home/user/etc/passwd ``` This mirrors `java.nio` `Path.resolve` semantics. It is great for "override if absolute, else relative to base" config, but it is also a **security trap**: user-supplied absolute paths escape your base directory. ## resolveSibling `File.resolveSibling(relative)` resolves against the file's **parent**, not the file itself — handy for placing an output next to an input: ```kotlin File("/var/log/app.log").resolveSibling("app.log.1") // /var/log/app.log.1 ``` ## What resolve does NOT do - It does **not** collapse `..` or `.`; `base.resolve("../x")` literally contains `..`. Use **`File.normalize()`** to canonicalize lexically (no disk access), or `File.canonicalFile`/`canonicalPath` to also resolve symlinks (touches the filesystem). - It does **not** check existence or create anything. ## Path-traversal safety Because of the absolute-child rule and the lack of normalization, after resolving an untrusted name you should normalize and verify containment: ```kotlin fun safeChild(base: File, userPath: String): File { val resolved = base.resolve(userPath).normalize() require(resolved.startsWith(base.normalize())) { "path escapes base" } return resolved } ``` ## Versus the alternatives - **String concatenation** (`a + "/" + b`): error-prone separators, not cross-platform, can produce `//`. - **`File(parent, child)` constructor**: same join semantics as resolve, but not chainable/fluent and reads less clearly in pipelines. - **`java.nio.file.Path.resolve`**: the NIO equivalent; resolve here is the `java.io.File` flavor.
- How do you make resolve safe against path traversal from user input?normalize() the resolved file and require that it startsWith the normalized base directory; reject otherwise.
- Does resolve hit the filesystem?No. It is a pure path-building operation; it doesn't check existence or resolve symlinks. canonicalFile does.
resolve is like an address builder that, if you hand it a full street address, throws away the neighborhood you gave it and uses the full one.
saying these in an interview costs you the question
- Believing resolve always concatenates even an absolute child
- Assuming resolve normalizes away .. automatically
- Using string + "/" + child and calling it equivalent
- Thinking resolve creates the directory or file
- Ignoring path-traversal validation on untrusted input