using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.IO; using System.Linq; using Jellyfin.Api.Constants; using Jellyfin.Api.Models.EnvironmentDtos; using MediaBrowser.Common.Extensions; using MediaBrowser.Model.IO; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; namespace Jellyfin.Api.Controllers { /// /// Environment Controller. /// [Authorize(Policy = Policies.FirstTimeSetupOrElevated)] public class EnvironmentController : BaseJellyfinApiController { private const char UncSeparator = '\\'; private const string UncStartPrefix = @"\\"; private readonly IFileSystem _fileSystem; private readonly ILogger _logger; /// /// Initializes a new instance of the class. /// /// Instance of the interface. /// Instance of the interface. public EnvironmentController(IFileSystem fileSystem, ILogger logger) { _fileSystem = fileSystem; _logger = logger; } /// /// Gets the contents of a given directory in the file system. /// /// The path. /// An optional filter to include or exclude files from the results. true/false. /// An optional filter to include or exclude folders from the results. true/false. /// Directory contents returned. /// Directory contents. [HttpGet("DirectoryContents")] [ProducesResponseType(StatusCodes.Status200OK)] public IEnumerable GetDirectoryContents( [FromQuery, Required] string path, [FromQuery] bool includeFiles = false, [FromQuery] bool includeDirectories = false) { if (path.StartsWith(UncStartPrefix, StringComparison.OrdinalIgnoreCase) && path.LastIndexOf(UncSeparator) == 1) { return Array.Empty(); } var entries = _fileSystem.GetFileSystemEntries(path) .Where(i => (i.IsDirectory && includeDirectories) || (!i.IsDirectory && includeFiles)) .OrderBy(i => i.FullName); return entries.Select(f => new FileSystemEntryInfo(f.Name, f.FullName, f.IsDirectory ? FileSystemEntryType.Directory : FileSystemEntryType.File)); } /// /// Validates path. /// /// Validate request object. /// Path validated. /// Path not found. /// Validation status. [HttpPost("ValidatePath")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult ValidatePath([FromBody, Required] ValidatePathDto validatePathDto) { if (validatePathDto.IsFile.HasValue) { if (validatePathDto.IsFile.Value) { if (!System.IO.File.Exists(validatePathDto.Path)) { return NotFound(); } } else { if (!Directory.Exists(validatePathDto.Path)) { return NotFound(); } } } else { if (!System.IO.File.Exists(validatePathDto.Path) && !Directory.Exists(validatePathDto.Path)) { return NotFound(); } if (validatePathDto.ValidateWritable) { if (validatePathDto.Path is null) { throw new ResourceNotFoundException(nameof(validatePathDto.Path)); } var file = Path.Combine(validatePathDto.Path, Guid.NewGuid().ToString()); try { System.IO.File.WriteAllText(file, string.Empty); } finally { if (System.IO.File.Exists(file)) { System.IO.File.Delete(file); } } } } return NoContent(); } /// /// Gets network paths. /// /// Empty array returned. /// List of entries. [Obsolete("This endpoint is obsolete.")] [HttpGet("NetworkShares")] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult> GetNetworkShares() { _logger.LogWarning("Obsolete endpoint accessed: /Environment/NetworkShares"); return Array.Empty(); } /// /// Gets available drives from the server's file system. /// /// List of entries returned. /// List of entries. [HttpGet("Drives")] [ProducesResponseType(StatusCodes.Status200OK)] public IEnumerable GetDrives() { return _fileSystem.GetDrives().Select(d => new FileSystemEntryInfo(d.Name, d.FullName, FileSystemEntryType.Directory)); } /// /// Gets the parent path of a given path. /// /// The path. /// Parent path. [HttpGet("ParentPath")] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult GetParentPath([FromQuery, Required] string path) { string? parent = Path.GetDirectoryName(path); if (string.IsNullOrEmpty(parent)) { // Check if unc share var index = path.LastIndexOf(UncSeparator); if (index != -1 && path.IndexOf(UncSeparator, StringComparison.OrdinalIgnoreCase) == 0) { parent = path.Substring(0, index); if (string.IsNullOrWhiteSpace(parent.TrimStart(UncSeparator))) { parent = null; } } } return parent; } /// /// Get Default directory browser. /// /// Default directory browser returned. /// Default directory browser. [HttpGet("DefaultDirectoryBrowser")] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult GetDefaultDirectoryBrowser() { return new DefaultDirectoryBrowserInfoDto(); } } }