EnvironmentController.cs 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel.DataAnnotations;
  4. using System.IO;
  5. using System.Linq;
  6. using Jellyfin.Api.Constants;
  7. using Jellyfin.Api.Models.EnvironmentDtos;
  8. using MediaBrowser.Model.IO;
  9. using Microsoft.AspNetCore.Authorization;
  10. using Microsoft.AspNetCore.Http;
  11. using Microsoft.AspNetCore.Mvc;
  12. using Microsoft.Extensions.Logging;
  13. namespace Jellyfin.Api.Controllers
  14. {
  15. /// <summary>
  16. /// Environment Controller.
  17. /// </summary>
  18. [Authorize(Policy = Policies.RequiresElevation)]
  19. public class EnvironmentController : BaseJellyfinApiController
  20. {
  21. private const char UncSeparator = '\\';
  22. private const string UncStartPrefix = @"\\";
  23. private readonly IFileSystem _fileSystem;
  24. private readonly ILogger<EnvironmentController> _logger;
  25. /// <summary>
  26. /// Initializes a new instance of the <see cref="EnvironmentController"/> class.
  27. /// </summary>
  28. /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
  29. /// <param name="logger">Instance of the <see cref="ILogger{EnvironmentController}"/> interface.</param>
  30. public EnvironmentController(IFileSystem fileSystem, ILogger<EnvironmentController> logger)
  31. {
  32. _fileSystem = fileSystem;
  33. _logger = logger;
  34. }
  35. /// <summary>
  36. /// Gets the contents of a given directory in the file system.
  37. /// </summary>
  38. /// <param name="path">The path.</param>
  39. /// <param name="includeFiles">An optional filter to include or exclude files from the results. true/false.</param>
  40. /// <param name="includeDirectories">An optional filter to include or exclude folders from the results. true/false.</param>
  41. /// <response code="200">Directory contents returned.</response>
  42. /// <returns>Directory contents.</returns>
  43. [HttpGet("DirectoryContents")]
  44. [ProducesResponseType(StatusCodes.Status200OK)]
  45. public IEnumerable<FileSystemEntryInfo> GetDirectoryContents(
  46. [FromQuery, Required] string path,
  47. [FromQuery] bool includeFiles = false,
  48. [FromQuery] bool includeDirectories = false)
  49. {
  50. if (path.StartsWith(UncStartPrefix, StringComparison.OrdinalIgnoreCase)
  51. && path.LastIndexOf(UncSeparator) == 1)
  52. {
  53. return Array.Empty<FileSystemEntryInfo>();
  54. }
  55. var entries =
  56. _fileSystem.GetFileSystemEntries(path)
  57. .Where(i => (i.IsDirectory && includeDirectories) || (!i.IsDirectory && includeFiles))
  58. .OrderBy(i => i.FullName);
  59. return entries.Select(f => new FileSystemEntryInfo(f.Name, f.FullName, f.IsDirectory ? FileSystemEntryType.Directory : FileSystemEntryType.File));
  60. }
  61. /// <summary>
  62. /// Validates path.
  63. /// </summary>
  64. /// <param name="validatePathDto">Validate request object.</param>
  65. /// <response code="200">Path validated.</response>
  66. /// <response code="404">Path not found.</response>
  67. /// <returns>Validation status.</returns>
  68. [HttpPost("ValidatePath")]
  69. [ProducesResponseType(StatusCodes.Status200OK)]
  70. [ProducesResponseType(StatusCodes.Status404NotFound)]
  71. public ActionResult ValidatePath([FromBody, Required] ValidatePathDto validatePathDto)
  72. {
  73. if (validatePathDto.IsFile.HasValue)
  74. {
  75. if (validatePathDto.IsFile.Value)
  76. {
  77. if (!System.IO.File.Exists(validatePathDto.Path))
  78. {
  79. return NotFound();
  80. }
  81. }
  82. else
  83. {
  84. if (!Directory.Exists(validatePathDto.Path))
  85. {
  86. return NotFound();
  87. }
  88. }
  89. }
  90. else
  91. {
  92. if (!System.IO.File.Exists(validatePathDto.Path) && !Directory.Exists(validatePathDto.Path))
  93. {
  94. return NotFound();
  95. }
  96. if (validatePathDto.ValidateWritable)
  97. {
  98. var file = Path.Combine(validatePathDto.Path, Guid.NewGuid().ToString());
  99. try
  100. {
  101. System.IO.File.WriteAllText(file, string.Empty);
  102. }
  103. finally
  104. {
  105. if (System.IO.File.Exists(file))
  106. {
  107. System.IO.File.Delete(file);
  108. }
  109. }
  110. }
  111. }
  112. return Ok();
  113. }
  114. /// <summary>
  115. /// Gets network paths.
  116. /// </summary>
  117. /// <response code="200">Empty array returned.</response>
  118. /// <returns>List of entries.</returns>
  119. [Obsolete("This endpoint is obsolete.")]
  120. [HttpGet("NetworkShares")]
  121. [ProducesResponseType(StatusCodes.Status200OK)]
  122. public ActionResult<IEnumerable<FileSystemEntryInfo>> GetNetworkShares()
  123. {
  124. _logger.LogWarning("Obsolete endpoint accessed: /Environment/NetworkShares");
  125. return Array.Empty<FileSystemEntryInfo>();
  126. }
  127. /// <summary>
  128. /// Gets available drives from the server's file system.
  129. /// </summary>
  130. /// <response code="200">List of entries returned.</response>
  131. /// <returns>List of entries.</returns>
  132. [HttpGet("Drives")]
  133. [ProducesResponseType(StatusCodes.Status200OK)]
  134. public IEnumerable<FileSystemEntryInfo> GetDrives()
  135. {
  136. return _fileSystem.GetDrives().Select(d => new FileSystemEntryInfo(d.Name, d.FullName, FileSystemEntryType.Directory));
  137. }
  138. /// <summary>
  139. /// Gets the parent path of a given path.
  140. /// </summary>
  141. /// <param name="path">The path.</param>
  142. /// <returns>Parent path.</returns>
  143. [HttpGet("ParentPath")]
  144. [ProducesResponseType(StatusCodes.Status200OK)]
  145. public ActionResult<string?> GetParentPath([FromQuery, Required] string path)
  146. {
  147. string? parent = Path.GetDirectoryName(path);
  148. if (string.IsNullOrEmpty(parent))
  149. {
  150. // Check if unc share
  151. var index = path.LastIndexOf(UncSeparator);
  152. if (index != -1 && path.IndexOf(UncSeparator, StringComparison.OrdinalIgnoreCase) == 0)
  153. {
  154. parent = path.Substring(0, index);
  155. if (string.IsNullOrWhiteSpace(parent.TrimStart(UncSeparator)))
  156. {
  157. parent = null;
  158. }
  159. }
  160. }
  161. return parent;
  162. }
  163. /// <summary>
  164. /// Get Default directory browser.
  165. /// </summary>
  166. /// <response code="200">Default directory browser returned.</response>
  167. /// <returns>Default directory browser.</returns>
  168. [HttpGet("DefaultDirectoryBrowser")]
  169. [ProducesResponseType(StatusCodes.Status200OK)]
  170. public ActionResult<DefaultDirectoryBrowserInfoDto> GetDefaultDirectoryBrowser()
  171. {
  172. return new DefaultDirectoryBrowserInfoDto();
  173. }
  174. }
  175. }