2
0

DlnaServerController.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. using System;
  2. using System.ComponentModel.DataAnnotations;
  3. using System.Diagnostics.CodeAnalysis;
  4. using System.IO;
  5. using System.Net.Mime;
  6. using System.Threading.Tasks;
  7. using Emby.Dlna;
  8. using Emby.Dlna.Main;
  9. using Jellyfin.Api.Attributes;
  10. using Jellyfin.Api.Constants;
  11. using MediaBrowser.Controller.Dlna;
  12. using MediaBrowser.Model.Net;
  13. using Microsoft.AspNetCore.Authorization;
  14. using Microsoft.AspNetCore.Http;
  15. using Microsoft.AspNetCore.Mvc;
  16. namespace Jellyfin.Api.Controllers
  17. {
  18. /// <summary>
  19. /// Dlna Server Controller.
  20. /// </summary>
  21. [Route("Dlna")]
  22. [Authorize(Policy = Policies.AnonymousLanAccessPolicy)]
  23. public class DlnaServerController : BaseJellyfinApiController
  24. {
  25. private readonly IDlnaManager _dlnaManager;
  26. private readonly IContentDirectory _contentDirectory;
  27. private readonly IConnectionManager _connectionManager;
  28. private readonly IMediaReceiverRegistrar _mediaReceiverRegistrar;
  29. /// <summary>
  30. /// Initializes a new instance of the <see cref="DlnaServerController"/> class.
  31. /// </summary>
  32. /// <param name="dlnaManager">Instance of the <see cref="IDlnaManager"/> interface.</param>
  33. public DlnaServerController(IDlnaManager dlnaManager)
  34. {
  35. _dlnaManager = dlnaManager;
  36. _contentDirectory = DlnaEntryPoint.Current.ContentDirectory;
  37. _connectionManager = DlnaEntryPoint.Current.ConnectionManager;
  38. _mediaReceiverRegistrar = DlnaEntryPoint.Current.MediaReceiverRegistrar;
  39. }
  40. /// <summary>
  41. /// Get Description Xml.
  42. /// </summary>
  43. /// <param name="serverId">Server UUID.</param>
  44. /// <response code="200">Description xml returned.</response>
  45. /// <response code="503">DLNA is disabled.</response>
  46. /// <returns>An <see cref="OkResult"/> containing the description xml.</returns>
  47. [HttpGet("{serverId}/description")]
  48. [HttpGet("{serverId}/description.xml", Name = "GetDescriptionXml_2")]
  49. [ProducesResponseType(StatusCodes.Status200OK)]
  50. [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
  51. [Produces(MediaTypeNames.Text.Xml)]
  52. [ProducesFile(MediaTypeNames.Text.Xml)]
  53. public ActionResult GetDescriptionXml([FromRoute, Required] string serverId)
  54. {
  55. if (DlnaEntryPoint.Enabled)
  56. {
  57. var url = GetAbsoluteUri();
  58. var serverAddress = url.Substring(0, url.IndexOf("/dlna/", StringComparison.OrdinalIgnoreCase));
  59. var xml = _dlnaManager.GetServerDescriptionXml(Request.Headers, serverId, serverAddress);
  60. return Ok(xml);
  61. }
  62. return StatusCode(StatusCodes.Status503ServiceUnavailable);
  63. }
  64. /// <summary>
  65. /// Gets Dlna content directory xml.
  66. /// </summary>
  67. /// <param name="serverId">Server UUID.</param>
  68. /// <response code="200">Dlna content directory returned.</response>
  69. /// <response code="503">DLNA is disabled.</response>
  70. /// <returns>An <see cref="OkResult"/> containing the dlna content directory xml.</returns>
  71. [HttpGet("{serverId}/ContentDirectory")]
  72. [HttpGet("{serverId}/ContentDirectory/ContentDirectory", Name = "GetContentDirectory_2")]
  73. [HttpGet("{serverId}/ContentDirectory/ContentDirectory.xml", Name = "GetContentDirectory_3")]
  74. [ProducesResponseType(StatusCodes.Status200OK)]
  75. [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
  76. [Produces(MediaTypeNames.Text.Xml)]
  77. [ProducesFile(MediaTypeNames.Text.Xml)]
  78. [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
  79. public ActionResult GetContentDirectory([FromRoute, Required] string serverId)
  80. {
  81. if (DlnaEntryPoint.Enabled)
  82. {
  83. return Ok(_contentDirectory.GetServiceXml());
  84. }
  85. return StatusCode(StatusCodes.Status503ServiceUnavailable);
  86. }
  87. /// <summary>
  88. /// Gets Dlna media receiver registrar xml.
  89. /// </summary>
  90. /// <param name="serverId">Server UUID.</param>
  91. /// <response code="200">Dlna media receiver registrar xml returned.</response>
  92. /// <response code="503">DLNA is disabled.</response>
  93. /// <returns>Dlna media receiver registrar xml.</returns>
  94. [HttpGet("{serverId}/MediaReceiverRegistrar")]
  95. [HttpGet("{serverId}/MediaReceiverRegistrar/MediaReceiverRegistrar", Name = "GetMediaReceiverRegistrar_2")]
  96. [HttpGet("{serverId}/MediaReceiverRegistrar/MediaReceiverRegistrar.xml", Name = "GetMediaReceiverRegistrar_3")]
  97. [ProducesResponseType(StatusCodes.Status200OK)]
  98. [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
  99. [Produces(MediaTypeNames.Text.Xml)]
  100. [ProducesFile(MediaTypeNames.Text.Xml)]
  101. [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
  102. public ActionResult GetMediaReceiverRegistrar([FromRoute, Required] string serverId)
  103. {
  104. if (DlnaEntryPoint.Enabled)
  105. {
  106. return Ok(_mediaReceiverRegistrar.GetServiceXml());
  107. }
  108. return StatusCode(StatusCodes.Status503ServiceUnavailable);
  109. }
  110. /// <summary>
  111. /// Gets Dlna media receiver registrar xml.
  112. /// </summary>
  113. /// <param name="serverId">Server UUID.</param>
  114. /// <response code="200">Dlna media receiver registrar xml returned.</response>
  115. /// <response code="503">DLNA is disabled.</response>
  116. /// <returns>Dlna media receiver registrar xml.</returns>
  117. [HttpGet("{serverId}/ConnectionManager")]
  118. [HttpGet("{serverId}/ConnectionManager/ConnectionManager", Name = "GetConnectionManager_2")]
  119. [HttpGet("{serverId}/ConnectionManager/ConnectionManager.xml", Name = "GetConnectionManager_3")]
  120. [ProducesResponseType(StatusCodes.Status200OK)]
  121. [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
  122. [Produces(MediaTypeNames.Text.Xml)]
  123. [ProducesFile(MediaTypeNames.Text.Xml)]
  124. [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
  125. public ActionResult GetConnectionManager([FromRoute, Required] string serverId)
  126. {
  127. if (DlnaEntryPoint.Enabled)
  128. {
  129. return Ok(_connectionManager.GetServiceXml());
  130. }
  131. return StatusCode(StatusCodes.Status503ServiceUnavailable);
  132. }
  133. /// <summary>
  134. /// Process a content directory control request.
  135. /// </summary>
  136. /// <param name="serverId">Server UUID.</param>
  137. /// <response code="200">Request processed.</response>
  138. /// <response code="503">DLNA is disabled.</response>
  139. /// <returns>Control response.</returns>
  140. [HttpPost("{serverId}/ContentDirectory/Control")]
  141. [ProducesResponseType(StatusCodes.Status200OK)]
  142. [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
  143. [Produces(MediaTypeNames.Text.Xml)]
  144. [ProducesFile(MediaTypeNames.Text.Xml)]
  145. public async Task<ActionResult<ControlResponse>> ProcessContentDirectoryControlRequest([FromRoute, Required] string serverId)
  146. {
  147. if (DlnaEntryPoint.Enabled)
  148. {
  149. return await ProcessControlRequestInternalAsync(serverId, Request.Body, _contentDirectory).ConfigureAwait(false);
  150. }
  151. return StatusCode(StatusCodes.Status503ServiceUnavailable);
  152. }
  153. /// <summary>
  154. /// Process a connection manager control request.
  155. /// </summary>
  156. /// <param name="serverId">Server UUID.</param>
  157. /// <response code="200">Request processed.</response>
  158. /// <response code="503">DLNA is disabled.</response>
  159. /// <returns>Control response.</returns>
  160. [HttpPost("{serverId}/ConnectionManager/Control")]
  161. [ProducesResponseType(StatusCodes.Status200OK)]
  162. [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
  163. [Produces(MediaTypeNames.Text.Xml)]
  164. [ProducesFile(MediaTypeNames.Text.Xml)]
  165. public async Task<ActionResult<ControlResponse>> ProcessConnectionManagerControlRequest([FromRoute, Required] string serverId)
  166. {
  167. if (DlnaEntryPoint.Enabled)
  168. {
  169. return await ProcessControlRequestInternalAsync(serverId, Request.Body, _connectionManager).ConfigureAwait(false);
  170. }
  171. return StatusCode(StatusCodes.Status503ServiceUnavailable);
  172. }
  173. /// <summary>
  174. /// Process a media receiver registrar control request.
  175. /// </summary>
  176. /// <param name="serverId">Server UUID.</param>
  177. /// <response code="200">Request processed.</response>
  178. /// <response code="503">DLNA is disabled.</response>
  179. /// <returns>Control response.</returns>
  180. [HttpPost("{serverId}/MediaReceiverRegistrar/Control")]
  181. [ProducesResponseType(StatusCodes.Status200OK)]
  182. [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
  183. [Produces(MediaTypeNames.Text.Xml)]
  184. [ProducesFile(MediaTypeNames.Text.Xml)]
  185. public async Task<ActionResult<ControlResponse>> ProcessMediaReceiverRegistrarControlRequest([FromRoute, Required] string serverId)
  186. {
  187. if (DlnaEntryPoint.Enabled)
  188. {
  189. return await ProcessControlRequestInternalAsync(serverId, Request.Body, _mediaReceiverRegistrar).ConfigureAwait(false);
  190. }
  191. return StatusCode(StatusCodes.Status503ServiceUnavailable);
  192. }
  193. /// <summary>
  194. /// Processes an event subscription request.
  195. /// </summary>
  196. /// <param name="serverId">Server UUID.</param>
  197. /// <response code="200">Request processed.</response>
  198. /// <response code="503">DLNA is disabled.</response>
  199. /// <returns>Event subscription response.</returns>
  200. [HttpSubscribe("{serverId}/MediaReceiverRegistrar/Events")]
  201. [HttpUnsubscribe("{serverId}/MediaReceiverRegistrar/Events")]
  202. [ApiExplorerSettings(IgnoreApi = true)] // Ignore in openapi docs
  203. [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
  204. [ProducesResponseType(StatusCodes.Status200OK)]
  205. [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
  206. [Produces(MediaTypeNames.Text.Xml)]
  207. [ProducesFile(MediaTypeNames.Text.Xml)]
  208. public ActionResult<EventSubscriptionResponse> ProcessMediaReceiverRegistrarEventRequest(string serverId)
  209. {
  210. if (DlnaEntryPoint.Enabled)
  211. {
  212. return ProcessEventRequest(_mediaReceiverRegistrar);
  213. }
  214. return StatusCode(StatusCodes.Status503ServiceUnavailable);
  215. }
  216. /// <summary>
  217. /// Processes an event subscription request.
  218. /// </summary>
  219. /// <param name="serverId">Server UUID.</param>
  220. /// <response code="200">Request processed.</response>
  221. /// <response code="503">DLNA is disabled.</response>
  222. /// <returns>Event subscription response.</returns>
  223. [HttpSubscribe("{serverId}/ContentDirectory/Events")]
  224. [HttpUnsubscribe("{serverId}/ContentDirectory/Events")]
  225. [ApiExplorerSettings(IgnoreApi = true)] // Ignore in openapi docs
  226. [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
  227. [ProducesResponseType(StatusCodes.Status200OK)]
  228. [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
  229. [Produces(MediaTypeNames.Text.Xml)]
  230. [ProducesFile(MediaTypeNames.Text.Xml)]
  231. public ActionResult<EventSubscriptionResponse> ProcessContentDirectoryEventRequest(string serverId)
  232. {
  233. if (DlnaEntryPoint.Enabled)
  234. {
  235. return ProcessEventRequest(_contentDirectory);
  236. }
  237. return StatusCode(StatusCodes.Status503ServiceUnavailable);
  238. }
  239. /// <summary>
  240. /// Processes an event subscription request.
  241. /// </summary>
  242. /// <param name="serverId">Server UUID.</param>
  243. /// <response code="200">Request processed.</response>
  244. /// <response code="503">DLNA is disabled.</response>
  245. /// <returns>Event subscription response.</returns>
  246. [HttpSubscribe("{serverId}/ConnectionManager/Events")]
  247. [HttpUnsubscribe("{serverId}/ConnectionManager/Events")]
  248. [ApiExplorerSettings(IgnoreApi = true)] // Ignore in openapi docs
  249. [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
  250. [ProducesResponseType(StatusCodes.Status200OK)]
  251. [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
  252. [Produces(MediaTypeNames.Text.Xml)]
  253. [ProducesFile(MediaTypeNames.Text.Xml)]
  254. public ActionResult<EventSubscriptionResponse> ProcessConnectionManagerEventRequest(string serverId)
  255. {
  256. if (DlnaEntryPoint.Enabled)
  257. {
  258. return ProcessEventRequest(_connectionManager);
  259. }
  260. return StatusCode(StatusCodes.Status503ServiceUnavailable);
  261. }
  262. /// <summary>
  263. /// Gets a server icon.
  264. /// </summary>
  265. /// <param name="serverId">Server UUID.</param>
  266. /// <param name="fileName">The icon filename.</param>
  267. /// <response code="200">Request processed.</response>
  268. /// <response code="404">Not Found.</response>
  269. /// <response code="503">DLNA is disabled.</response>
  270. /// <returns>Icon stream.</returns>
  271. [HttpGet("{serverId}/icons/{fileName}")]
  272. [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
  273. [ProducesResponseType(StatusCodes.Status200OK)]
  274. [ProducesResponseType(StatusCodes.Status404NotFound)]
  275. [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
  276. [ProducesImageFile]
  277. public ActionResult GetIconId([FromRoute, Required] string serverId, [FromRoute, Required] string fileName)
  278. {
  279. if (DlnaEntryPoint.Enabled)
  280. {
  281. return GetIconInternal(fileName);
  282. }
  283. return StatusCode(StatusCodes.Status503ServiceUnavailable);
  284. }
  285. /// <summary>
  286. /// Gets a server icon.
  287. /// </summary>
  288. /// <param name="fileName">The icon filename.</param>
  289. /// <returns>Icon stream.</returns>
  290. /// <response code="200">Request processed.</response>
  291. /// <response code="404">Not Found.</response>
  292. /// <response code="503">DLNA is disabled.</response>
  293. [HttpGet("icons/{fileName}")]
  294. [ProducesResponseType(StatusCodes.Status200OK)]
  295. [ProducesResponseType(StatusCodes.Status404NotFound)]
  296. [ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
  297. [ProducesImageFile]
  298. public ActionResult GetIcon([FromRoute, Required] string fileName)
  299. {
  300. if (DlnaEntryPoint.Enabled)
  301. {
  302. return GetIconInternal(fileName);
  303. }
  304. return StatusCode(StatusCodes.Status503ServiceUnavailable);
  305. }
  306. private ActionResult GetIconInternal(string fileName)
  307. {
  308. var icon = _dlnaManager.GetIcon(fileName);
  309. if (icon == null)
  310. {
  311. return NotFound();
  312. }
  313. return File(icon.Stream, MimeTypes.GetMimeType(fileName));
  314. }
  315. private string GetAbsoluteUri()
  316. {
  317. return $"{Request.Scheme}://{Request.Host}{Request.PathBase}{Request.Path}";
  318. }
  319. private Task<ControlResponse> ProcessControlRequestInternalAsync(string id, Stream requestStream, IUpnpService service)
  320. {
  321. return service.ProcessControlRequestAsync(new ControlRequest(Request.Headers)
  322. {
  323. InputXml = requestStream,
  324. TargetServerUuId = id,
  325. RequestedUrl = GetAbsoluteUri()
  326. });
  327. }
  328. private EventSubscriptionResponse ProcessEventRequest(IDlnaEventManager dlnaEventManager)
  329. {
  330. var subscriptionId = Request.Headers["SID"];
  331. if (string.Equals(Request.Method, "subscribe", StringComparison.OrdinalIgnoreCase))
  332. {
  333. var notificationType = Request.Headers["NT"];
  334. var callback = Request.Headers["CALLBACK"];
  335. var timeoutString = Request.Headers["TIMEOUT"];
  336. if (string.IsNullOrEmpty(notificationType))
  337. {
  338. return dlnaEventManager.RenewEventSubscription(
  339. subscriptionId,
  340. notificationType,
  341. timeoutString,
  342. callback);
  343. }
  344. return dlnaEventManager.CreateEventSubscription(notificationType, timeoutString, callback);
  345. }
  346. return dlnaEventManager.CancelEventSubscription(subscriptionId);
  347. }
  348. }
  349. }