diff --git a/README.md b/README.md index f2b66b8..a8e5a3d 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,11 @@ ![Language](https://img.shields.io/badge/language-C%23-purple) ![License](https://img.shields.io/badge/license-Apache%202.0-green) +[Website](https://novasystemslab.org) · +[Organization](https://novasystemslab.org) · +[Discussions](https://github.com/Nova-Systems-Lab/WinDroid-Runtime/discussions) · +[Issues](https://github.com/Nova-Systems-Lab/WinDroid-Runtime/issues) + **WinDroid Runtime** is an independent Android-compatible runtime and toolkit for Windows. The long-term goal of this project is to build a modern platform that allows Android applications to run, integrate, and feel natural on Windows. The project is being designed from scratch, starting with developer tooling, APK management, ADB integration, and runtime research before moving toward experimental Android runtime and desktop integration research. @@ -347,6 +352,8 @@ For official project work: - Use GitHub Issues for confirmed bugs and actionable tasks. - Use Pull Requests for code and documentation changes. +For private security reports, follow the [security policy](SECURITY.md) or contact [security@novasystemslab.org](mailto:security@novasystemslab.org). + ## Current First Milestone The first practical milestone is to build WinDroid Studio v0.1, a native Windows application that can: diff --git a/src/WinDroid.Adb/Models/AdbPathResult.cs b/src/WinDroid.Adb/Models/AdbPathResult.cs new file mode 100644 index 0000000..f20cfe7 --- /dev/null +++ b/src/WinDroid.Adb/Models/AdbPathResult.cs @@ -0,0 +1,71 @@ +namespace WinDroid.Adb.Models; + +/// +/// Represents the outcome of attempting to locate an ADB executable. +/// +/// +/// A successful result carries a non-empty and a +/// . A failed result carries a +/// and a non-empty +/// . Use and +/// to construct values that honour these invariants. +/// +public sealed class AdbPathResult +{ + /// + /// Gets a value indicating whether an ADB executable was located. + /// + public bool Found { get; init; } + + /// + /// Gets the resolved absolute path to the ADB executable when + /// is ; otherwise + /// . + /// + public string? Path { get; init; } + + /// + /// Gets a concise, user-safe explanation of why resolution failed when + /// is ; otherwise + /// . + /// + public string? ErrorMessage { get; init; } + + /// + /// Creates a successful result for the given resolved executable path. + /// + /// The resolved absolute path to the ADB executable. + /// + /// is , empty, or whitespace. + /// + public static AdbPathResult Success(string path) + { + ArgumentException.ThrowIfNullOrWhiteSpace(path); + + return new AdbPathResult + { + Found = true, + Path = path, + ErrorMessage = null, + }; + } + + /// + /// Creates a failed result with the given user-safe error message. + /// + /// A concise explanation of the failure. + /// + /// is , empty, or whitespace. + /// + public static AdbPathResult Failure(string errorMessage) + { + ArgumentException.ThrowIfNullOrWhiteSpace(errorMessage); + + return new AdbPathResult + { + Found = false, + Path = null, + ErrorMessage = errorMessage, + }; + } +} diff --git a/src/WinDroid.Adb/Services/AdbPathResolver.cs b/src/WinDroid.Adb/Services/AdbPathResolver.cs new file mode 100644 index 0000000..87cfa96 --- /dev/null +++ b/src/WinDroid.Adb/Services/AdbPathResolver.cs @@ -0,0 +1,173 @@ +using WinDroid.Adb.Models; + +namespace WinDroid.Adb.Services; + +/// +/// Locates an ADB executable by checking an optional custom path and then the +/// directories listed in the system PATH environment variable. +/// +/// +/// This type only determines whether an ADB executable exists on disk. It does +/// not launch ADB and does not verify that a located file is a genuine ADB +/// binary. The system PATH is read on every call so that environment +/// changes during the application lifetime are honoured. +/// +public sealed class AdbPathResolver +{ + // The active application target is Windows, where the executable is named + // "adb.exe". The plain "adb" name is also checked so the portable library + // behaves sensibly on non-Windows hosts. "adb.exe" is checked first so + // resolution is deterministic when both names exist in the same directory. + private static readonly string[] ExecutableNames = ["adb.exe", "adb"]; + + /// + /// Attempts to locate an ADB executable. + /// + /// + /// Optional path to an ADB executable, typically supplied from + /// AdbSettings.CustomAdbPath. , empty, or + /// whitespace values are ignored and resolution falls back to the system + /// PATH. + /// + /// + /// A successful containing the resolved path, or + /// a failed result whose explains + /// why ADB could not be found. A missing ADB is an expected outcome and is + /// reported through the result rather than by throwing. + /// + public AdbPathResult Resolve(string? customAdbPath = null) + { + string? normalizedCustomPath = NormalizeToken(customAdbPath); + bool customPathSupplied = normalizedCustomPath is not null; + + if (customPathSupplied && File.Exists(normalizedCustomPath)) + { + string? resolved = GetFullPathSafe(normalizedCustomPath!); + if (resolved is not null) + { + return AdbPathResult.Success(resolved); + } + + // The file exists but its path could not be normalized to an + // absolute path, so fall through to the system PATH search. + } + + string? pathMatch = SearchSystemPath(); + if (pathMatch is not null) + { + return AdbPathResult.Success(pathMatch); + } + + return AdbPathResult.Failure(BuildNotFoundMessage(customPathSupplied)); + } + + /// + /// Searches the directories listed in the system PATH for a known ADB + /// executable name, returning the first match as an absolute path. + /// + private static string? SearchSystemPath() + { + string? pathVariable = Environment.GetEnvironmentVariable("PATH"); + if (string.IsNullOrEmpty(pathVariable)) + { + return null; + } + + var visitedDirectories = new HashSet(StringComparer.OrdinalIgnoreCase); + + foreach (string rawEntry in pathVariable.Split(Path.PathSeparator)) + { + string? directory = NormalizeToken(rawEntry); + if (directory is null || !visitedDirectories.Add(directory)) + { + // Blank or duplicate entry; skip without touching the filesystem. + continue; + } + + string? match = FindExecutableInDirectory(directory); + if (match is not null) + { + return match; + } + } + + return null; + } + + /// + /// Returns the absolute path to the first known ADB executable found in the + /// given directory, or if none is present or the + /// entry cannot be inspected. + /// + private static string? FindExecutableInDirectory(string directory) + { + foreach (string executableName in ExecutableNames) + { + string candidate = Path.Combine(directory, executableName); + + // File.Exists is exception-free and returns false for malformed or + // inaccessible paths, so unusable PATH entries are skipped safely. + if (File.Exists(candidate)) + { + string? resolved = GetFullPathSafe(candidate); + if (resolved is not null) + { + return resolved; + } + + // Exists but cannot be normalized to an absolute path; skip it. + } + } + + return null; + } + + /// + /// Trims surrounding whitespace and a single pair of matching surrounding + /// quotation marks, returning when nothing usable + /// remains. + /// + private static string? NormalizeToken(string? value) + { + if (string.IsNullOrWhiteSpace(value)) + { + return null; + } + + string trimmed = value.Trim(); + + if (trimmed.Length >= 2 && trimmed[0] == '"' && trimmed[^1] == '"') + { + trimmed = trimmed[1..^1].Trim(); + } + + return string.IsNullOrEmpty(trimmed) ? null : trimmed; + } + + /// + /// Produces an absolute, normalized path, returning + /// if the path cannot be normalized. + /// + private static string? GetFullPathSafe(string path) + { + try + { + return Path.GetFullPath(path); + } + catch (Exception ex) when ( + ex is ArgumentException + or PathTooLongException + or NotSupportedException + or System.Security.SecurityException) + { + return null; + } + } + + private static string BuildNotFoundMessage(bool customPathSupplied) => + customPathSupplied + ? "ADB could not be found. The supplied custom path did not point to a " + + "valid ADB executable file, and no ADB executable was found through " + + "the system PATH." + : "ADB could not be found through the system PATH."; +}