WebServiceAppsApi.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539
  1. /*
  2. Technitium DNS Server
  3. Copyright (C) 2022 Shreyas Zare (shreyas@technitium.com)
  4. This program is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program. If not, see <http://www.gnu.org/licenses/>.
  14. */
  15. using DnsServerCore.ApplicationCommon;
  16. using DnsServerCore.Dns.Applications;
  17. using Newtonsoft.Json;
  18. using System;
  19. using System.Collections.Generic;
  20. using System.IO;
  21. using System.Net;
  22. using System.Net.Http;
  23. using System.Threading.Tasks;
  24. using TechnitiumLibrary;
  25. namespace DnsServerCore
  26. {
  27. class WebServiceAppsApi
  28. {
  29. #region variables
  30. readonly DnsWebService _dnsWebService;
  31. readonly Uri _appStoreUri;
  32. string _storeAppsJsonData;
  33. DateTime _storeAppsJsonDataUpdatedOn;
  34. const int STORE_APPS_JSON_DATA_CACHE_TIME_SECONDS = 300;
  35. #endregion
  36. #region constructor
  37. public WebServiceAppsApi(DnsWebService dnsWebService, Uri appStoreUri)
  38. {
  39. _dnsWebService = dnsWebService;
  40. _appStoreUri = appStoreUri;
  41. }
  42. #endregion
  43. #region private
  44. private async Task<string> GetStoreAppsJsonData()
  45. {
  46. if ((_storeAppsJsonData == null) || (DateTime.UtcNow > _storeAppsJsonDataUpdatedOn.AddSeconds(STORE_APPS_JSON_DATA_CACHE_TIME_SECONDS)))
  47. {
  48. SocketsHttpHandler handler = new SocketsHttpHandler();
  49. handler.Proxy = _dnsWebService.DnsServer.Proxy;
  50. handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
  51. using (HttpClient http = new HttpClient(handler))
  52. {
  53. _storeAppsJsonData = await http.GetStringAsync(_appStoreUri);
  54. _storeAppsJsonDataUpdatedOn = DateTime.UtcNow;
  55. }
  56. }
  57. return _storeAppsJsonData;
  58. }
  59. #endregion
  60. #region public
  61. public async Task ListInstalledAppsAsync(JsonTextWriter jsonWriter)
  62. {
  63. List<string> apps = new List<string>(_dnsWebService.DnsServer.DnsApplicationManager.Applications.Keys);
  64. apps.Sort();
  65. dynamic jsonStoreAppsArray = null;
  66. if (apps.Count > 0)
  67. {
  68. try
  69. {
  70. string storeAppsJsonData = await GetStoreAppsJsonData().WithTimeout(5000);
  71. jsonStoreAppsArray = JsonConvert.DeserializeObject(storeAppsJsonData);
  72. }
  73. catch
  74. { }
  75. }
  76. jsonWriter.WritePropertyName("apps");
  77. jsonWriter.WriteStartArray();
  78. foreach (string app in apps)
  79. {
  80. if (_dnsWebService.DnsServer.DnsApplicationManager.Applications.TryGetValue(app, out DnsApplication application))
  81. {
  82. jsonWriter.WriteStartObject();
  83. jsonWriter.WritePropertyName("name");
  84. jsonWriter.WriteValue(application.Name);
  85. jsonWriter.WritePropertyName("version");
  86. jsonWriter.WriteValue(DnsWebService.GetCleanVersion(application.Version));
  87. if (jsonStoreAppsArray != null)
  88. {
  89. foreach (dynamic jsonStoreApp in jsonStoreAppsArray)
  90. {
  91. string name = jsonStoreApp.name.Value;
  92. if (name.Equals(application.Name))
  93. {
  94. string version = jsonStoreApp.version.Value;
  95. string url = jsonStoreApp.url.Value;
  96. jsonWriter.WritePropertyName("updateVersion");
  97. jsonWriter.WriteValue(version);
  98. jsonWriter.WritePropertyName("updateUrl");
  99. jsonWriter.WriteValue(url);
  100. jsonWriter.WritePropertyName("updateAvailable");
  101. jsonWriter.WriteValue(new Version(version) > application.Version);
  102. break;
  103. }
  104. }
  105. }
  106. jsonWriter.WritePropertyName("dnsApps");
  107. {
  108. jsonWriter.WriteStartArray();
  109. foreach (KeyValuePair<string, IDnsApplication> dnsApp in application.DnsApplications)
  110. {
  111. jsonWriter.WriteStartObject();
  112. jsonWriter.WritePropertyName("classPath");
  113. jsonWriter.WriteValue(dnsApp.Key);
  114. jsonWriter.WritePropertyName("description");
  115. jsonWriter.WriteValue(dnsApp.Value.Description);
  116. if (dnsApp.Value is IDnsAppRecordRequestHandler appRecordHandler)
  117. {
  118. jsonWriter.WritePropertyName("isAppRecordRequestHandler");
  119. jsonWriter.WriteValue(true);
  120. jsonWriter.WritePropertyName("recordDataTemplate");
  121. jsonWriter.WriteValue(appRecordHandler.ApplicationRecordDataTemplate);
  122. }
  123. else
  124. {
  125. jsonWriter.WritePropertyName("isAppRecordRequestHandler");
  126. jsonWriter.WriteValue(false);
  127. }
  128. jsonWriter.WritePropertyName("isRequestController");
  129. jsonWriter.WriteValue(dnsApp.Value is IDnsRequestController);
  130. jsonWriter.WritePropertyName("isAuthoritativeRequestHandler");
  131. jsonWriter.WriteValue(dnsApp.Value is IDnsAuthoritativeRequestHandler);
  132. jsonWriter.WritePropertyName("isQueryLogger");
  133. jsonWriter.WriteValue(dnsApp.Value is IDnsQueryLogger);
  134. jsonWriter.WriteEndObject();
  135. }
  136. jsonWriter.WriteEndArray();
  137. }
  138. jsonWriter.WriteEndObject();
  139. }
  140. }
  141. jsonWriter.WriteEndArray();
  142. }
  143. public async Task ListStoreApps(JsonTextWriter jsonWriter)
  144. {
  145. string storeAppsJsonData = await GetStoreAppsJsonData();
  146. dynamic jsonStoreAppsArray = JsonConvert.DeserializeObject(storeAppsJsonData);
  147. jsonWriter.WritePropertyName("storeApps");
  148. jsonWriter.WriteStartArray();
  149. foreach (dynamic jsonStoreApp in jsonStoreAppsArray)
  150. {
  151. string name = jsonStoreApp.name.Value;
  152. string version = jsonStoreApp.version.Value;
  153. string description = jsonStoreApp.description.Value;
  154. string url = jsonStoreApp.url.Value;
  155. string size = jsonStoreApp.size.Value;
  156. jsonWriter.WriteStartObject();
  157. jsonWriter.WritePropertyName("name");
  158. jsonWriter.WriteValue(name);
  159. jsonWriter.WritePropertyName("version");
  160. jsonWriter.WriteValue(version);
  161. jsonWriter.WritePropertyName("description");
  162. jsonWriter.WriteValue(description);
  163. jsonWriter.WritePropertyName("url");
  164. jsonWriter.WriteValue(url);
  165. jsonWriter.WritePropertyName("size");
  166. jsonWriter.WriteValue(size);
  167. bool installed = _dnsWebService.DnsServer.DnsApplicationManager.Applications.TryGetValue(name, out DnsApplication installedApp);
  168. jsonWriter.WritePropertyName("installed");
  169. jsonWriter.WriteValue(installed);
  170. if (installed)
  171. {
  172. jsonWriter.WritePropertyName("installedVersion");
  173. jsonWriter.WriteValue(DnsWebService.GetCleanVersion(installedApp.Version));
  174. jsonWriter.WritePropertyName("updateAvailable");
  175. jsonWriter.WriteValue(new Version(version) > installedApp.Version);
  176. }
  177. jsonWriter.WriteEndObject();
  178. }
  179. jsonWriter.WriteEndArray();
  180. }
  181. public async Task DownloadAndInstallAppAsync(HttpListenerRequest request)
  182. {
  183. string name = request.QueryString["name"];
  184. if (string.IsNullOrEmpty(name))
  185. throw new DnsWebServiceException("Parameter 'name' missing.");
  186. name = name.Trim();
  187. string url = request.QueryString["url"];
  188. if (string.IsNullOrEmpty(url))
  189. throw new DnsWebServiceException("Parameter 'url' missing.");
  190. if (!url.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
  191. throw new DnsWebServiceException("Parameter 'url' value must start with 'https://'.");
  192. string tmpFile = Path.GetTempFileName();
  193. try
  194. {
  195. using (FileStream fS = new FileStream(tmpFile, FileMode.Create, FileAccess.ReadWrite))
  196. {
  197. //download to temp file
  198. SocketsHttpHandler handler = new SocketsHttpHandler();
  199. handler.Proxy = _dnsWebService.DnsServer.Proxy;
  200. handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
  201. using (HttpClient http = new HttpClient(handler))
  202. {
  203. using (Stream httpStream = await http.GetStreamAsync(url))
  204. {
  205. await httpStream.CopyToAsync(fS);
  206. }
  207. }
  208. //install app
  209. fS.Position = 0;
  210. await _dnsWebService.DnsServer.DnsApplicationManager.InstallApplicationAsync(name, fS);
  211. _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] DNS application '" + name + "' was installed successfully from: " + url);
  212. }
  213. }
  214. finally
  215. {
  216. try
  217. {
  218. File.Delete(tmpFile);
  219. }
  220. catch (Exception ex)
  221. {
  222. _dnsWebService.Log.Write(ex);
  223. }
  224. }
  225. }
  226. public async Task DownloadAndUpdateAppAsync(HttpListenerRequest request)
  227. {
  228. string name = request.QueryString["name"];
  229. if (string.IsNullOrEmpty(name))
  230. throw new DnsWebServiceException("Parameter 'name' missing.");
  231. name = name.Trim();
  232. string url = request.QueryString["url"];
  233. if (string.IsNullOrEmpty(url))
  234. throw new DnsWebServiceException("Parameter 'url' missing.");
  235. if (!url.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
  236. throw new DnsWebServiceException("Parameter 'url' value must start with 'https://'.");
  237. string tmpFile = Path.GetTempFileName();
  238. try
  239. {
  240. using (FileStream fS = new FileStream(tmpFile, FileMode.Create, FileAccess.ReadWrite))
  241. {
  242. //download to temp file
  243. SocketsHttpHandler handler = new SocketsHttpHandler();
  244. handler.Proxy = _dnsWebService.DnsServer.Proxy;
  245. handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
  246. using (HttpClient http = new HttpClient(handler))
  247. {
  248. using (Stream httpStream = await http.GetStreamAsync(url))
  249. {
  250. await httpStream.CopyToAsync(fS);
  251. }
  252. }
  253. //update app
  254. fS.Position = 0;
  255. await _dnsWebService.DnsServer.DnsApplicationManager.UpdateApplicationAsync(name, fS);
  256. _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] DNS application '" + name + "' was updated successfully from: " + url);
  257. }
  258. }
  259. finally
  260. {
  261. try
  262. {
  263. File.Delete(tmpFile);
  264. }
  265. catch (Exception ex)
  266. {
  267. _dnsWebService.Log.Write(ex);
  268. }
  269. }
  270. }
  271. public async Task InstallAppAsync(HttpListenerRequest request)
  272. {
  273. string name = request.QueryString["name"];
  274. if (string.IsNullOrEmpty(name))
  275. throw new DnsWebServiceException("Parameter 'name' missing.");
  276. name = name.Trim();
  277. #region skip to content
  278. int crlfCount = 0;
  279. int byteRead;
  280. while (crlfCount != 4)
  281. {
  282. byteRead = request.InputStream.ReadByte();
  283. switch (byteRead)
  284. {
  285. case -1:
  286. throw new EndOfStreamException();
  287. case 13: //CR
  288. case 10: //LF
  289. crlfCount++;
  290. break;
  291. default:
  292. crlfCount = 0;
  293. break;
  294. }
  295. }
  296. #endregion
  297. string tmpFile = Path.GetTempFileName();
  298. try
  299. {
  300. using (FileStream fS = new FileStream(tmpFile, FileMode.Create, FileAccess.ReadWrite))
  301. {
  302. //write to temp file
  303. await request.InputStream.CopyToAsync(fS);
  304. //install app
  305. fS.Position = 0;
  306. await _dnsWebService.DnsServer.DnsApplicationManager.InstallApplicationAsync(name, fS);
  307. _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] DNS application '" + name + "' was installed successfully.");
  308. }
  309. }
  310. finally
  311. {
  312. try
  313. {
  314. File.Delete(tmpFile);
  315. }
  316. catch (Exception ex)
  317. {
  318. _dnsWebService.Log.Write(ex);
  319. }
  320. }
  321. }
  322. public async Task UpdateAppAsync(HttpListenerRequest request)
  323. {
  324. string name = request.QueryString["name"];
  325. if (string.IsNullOrEmpty(name))
  326. throw new DnsWebServiceException("Parameter 'name' missing.");
  327. name = name.Trim();
  328. #region skip to content
  329. int crlfCount = 0;
  330. int byteRead;
  331. while (crlfCount != 4)
  332. {
  333. byteRead = request.InputStream.ReadByte();
  334. switch (byteRead)
  335. {
  336. case -1:
  337. throw new EndOfStreamException();
  338. case 13: //CR
  339. case 10: //LF
  340. crlfCount++;
  341. break;
  342. default:
  343. crlfCount = 0;
  344. break;
  345. }
  346. }
  347. #endregion
  348. string tmpFile = Path.GetTempFileName();
  349. try
  350. {
  351. using (FileStream fS = new FileStream(tmpFile, FileMode.Create, FileAccess.ReadWrite))
  352. {
  353. //write to temp file
  354. await request.InputStream.CopyToAsync(fS);
  355. //update app
  356. fS.Position = 0;
  357. await _dnsWebService.DnsServer.DnsApplicationManager.UpdateApplicationAsync(name, fS);
  358. _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] DNS application '" + name + "' was updated successfully.");
  359. }
  360. }
  361. finally
  362. {
  363. try
  364. {
  365. File.Delete(tmpFile);
  366. }
  367. catch (Exception ex)
  368. {
  369. _dnsWebService.Log.Write(ex);
  370. }
  371. }
  372. }
  373. public void UninstallApp(HttpListenerRequest request)
  374. {
  375. string name = request.QueryString["name"];
  376. if (string.IsNullOrEmpty(name))
  377. throw new DnsWebServiceException("Parameter 'name' missing.");
  378. name = name.Trim();
  379. _dnsWebService.DnsServer.DnsApplicationManager.UninstallApplication(name);
  380. _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] DNS application '" + name + "' was uninstalled successfully.");
  381. }
  382. public async Task GetAppConfigAsync(HttpListenerRequest request, JsonTextWriter jsonWriter)
  383. {
  384. string name = request.QueryString["name"];
  385. if (string.IsNullOrEmpty(name))
  386. throw new DnsWebServiceException("Parameter 'name' missing.");
  387. name = name.Trim();
  388. if (!_dnsWebService.DnsServer.DnsApplicationManager.Applications.TryGetValue(name, out DnsApplication application))
  389. throw new DnsWebServiceException("DNS application was not found: " + name);
  390. string config = await application.GetConfigAsync();
  391. jsonWriter.WritePropertyName("config");
  392. jsonWriter.WriteValue(config);
  393. }
  394. public async Task SetAppConfigAsync(HttpListenerRequest request)
  395. {
  396. string name = request.QueryString["name"];
  397. if (string.IsNullOrEmpty(name))
  398. throw new DnsWebServiceException("Parameter 'name' missing.");
  399. name = name.Trim();
  400. if (!_dnsWebService.DnsServer.DnsApplicationManager.Applications.TryGetValue(name, out DnsApplication application))
  401. throw new DnsWebServiceException("DNS application was not found: " + name);
  402. string formRequest;
  403. using (StreamReader sR = new StreamReader(request.InputStream, request.ContentEncoding))
  404. {
  405. formRequest = sR.ReadToEnd();
  406. }
  407. string[] formParts = formRequest.Split('&');
  408. foreach (string formPart in formParts)
  409. {
  410. if (formPart.StartsWith("config="))
  411. {
  412. string config = formPart.Substring(7);
  413. if (config.Length == 0)
  414. config = null;
  415. await application.SetConfigAsync(config);
  416. _dnsWebService.Log.Write(DnsWebService.GetRequestRemoteEndPoint(request), "[" + _dnsWebService.GetSession(request).Username + "] DNS application '" + name + "' app config was saved successfully.");
  417. return;
  418. }
  419. }
  420. throw new DnsWebServiceException("Missing POST parameter: config");
  421. }
  422. #endregion
  423. }
  424. }