CommandLineArguments.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. // **********************************************************************************
  2. // CassiniDev - http://cassinidev.codeplex.com
  3. //
  4. // Copyright (c) 2010 Sky Sanders. All rights reserved.
  5. //
  6. // This source code is subject to terms and conditions of the Microsoft Public
  7. // License (Ms-PL). A copy of the license can be found in the license.txt file
  8. // included in this distribution.
  9. //
  10. // You must not remove this notice, or any other, from this software.
  11. //
  12. // **********************************************************************************
  13. #region
  14. using System;
  15. using System.Collections.Generic;
  16. using System.IO;
  17. using System.Net;
  18. using System.Text;
  19. using System.Threading;
  20. #endregion
  21. namespace CassiniDev
  22. {
  23. /// <summary>
  24. /// Command line arguments
  25. ///
  26. /// fixed 5/24/10 - quoted embedded spaces in ToString
  27. /// </summary>
  28. public class CommandLineArguments
  29. {
  30. #region Properties
  31. [Argument(ArgumentType.AtMostOnce, ShortName = "ah", DefaultValue = false,
  32. HelpText = "If true add entry to Windows hosts file. Requires write permissions to hosts file.")] public
  33. bool AddHost;
  34. [Argument(ArgumentType.AtMostOnce, ShortName = "a", LongName = "path",
  35. HelpText = "Physical location of content.")] public string
  36. ApplicationPath;
  37. [Argument(ArgumentType.AtMostOnce, LongName = "log", DefaultValue = false, HelpText = "Enable logging.")] public
  38. bool EnableLogging;
  39. [Argument(ArgumentType.AtMostOnce, ShortName = "h", LongName = "host",
  40. HelpText = "Host name used for app root url. Optional unless AddHost is true.")] public string HostName;
  41. [Argument(ArgumentType.AtMostOnce, ShortName = "i", LongName = "ip",
  42. HelpText = "IP address to listen to. Ignored if IPMode != Specific")] public string IPAddress;
  43. [Argument(ArgumentType.AtMostOnce, ShortName = "im", DefaultValue = IPMode.Loopback, HelpText = "",
  44. LongName = "ipMode")] public
  45. IPMode IPMode;
  46. [Argument(ArgumentType.AtMostOnce, ShortName = "v6", DefaultValue = false,
  47. HelpText = "If IPMode 'Any' or 'LoopBack' are specified use the V6 address", LongName = "ipV6")] public bool
  48. IPv6;
  49. [Argument(ArgumentType.AtMostOnce, LongName = "nodirlist", DefaultValue = false,
  50. HelpText = "Disable diretory listing")] public bool Nodirlist;
  51. [Argument(ArgumentType.AtMostOnce, LongName = "ntlm", DefaultValue = false, HelpText = "Run as current identity"
  52. )] public bool Ntlm;
  53. [Argument(ArgumentType.AtMostOnce, ShortName = "p", LongName = "port",
  54. HelpText = "Port to listen to. Ignored if PortMode=FirstAvailable.", DefaultValue = 0)] public int Port;
  55. [Argument(ArgumentType.AtMostOnce, ShortName = "pm", HelpText = "", LongName = "portMode",
  56. DefaultValue = PortMode.FirstAvailable)] public PortMode PortMode;
  57. [Argument(ArgumentType.AtMostOnce, ShortName = "pre", DefaultValue = 65535, LongName = "highPort",
  58. HelpText = "End of port range. Ignored if PortMode != FirstAvailable")] public int PortRangeEnd = 9000;
  59. [Argument(ArgumentType.AtMostOnce, ShortName = "prs", DefaultValue = 32768, LongName = "lowPort",
  60. HelpText = "Start of port range. Ignored if PortMode != FirstAvailable")] public int PortRangeStart =
  61. 8080;
  62. [DefaultArgument(ArgumentType.AtMostOnce, DefaultValue = RunMode.Server, HelpText = "[Server|Hostsfile]")] public RunMode RunMode;
  63. [Argument(ArgumentType.AtMostOnce, LongName = "silent", DefaultValue = false, HelpText = "Fail silently")] public bool Silent;
  64. [Argument(ArgumentType.AtMostOnce, ShortName = "t", DefaultValue = 0, LongName = "timeout",
  65. HelpText = "Length of time, in ms, to wait for a request before stopping the server. 0 = no timeout.")] public int TimeOut;
  66. [Argument(ArgumentType.AtMostOnce, ShortName = "v", LongName = "vpath", DefaultValue = "/",
  67. HelpText = "Optional. default value '/'"
  68. )] public string VirtualPath = "/";
  69. [Argument(ArgumentType.AtMostOnce, ShortName = "vs", DefaultValue = false,
  70. HelpText = "If true run in Visual Studio Development Server mode - readonly UI with single option to quit.."
  71. )] public
  72. bool VisualStudio;
  73. [Argument(ArgumentType.AtMostOnce, ShortName = "w", DefaultValue = 0, LongName = "wait",
  74. HelpText =
  75. "Length of time, in ms, to wait for a specific port before throwing an exception or exiting. 0 = don't wait."
  76. )] public int WaitForPort;
  77. #endregion
  78. public string[] ToArgs()
  79. {
  80. List<string> result = new List<string>();
  81. if (RunMode != RunMode.Server)
  82. {
  83. result.Add(string.Format("{0}", RunMode));
  84. }
  85. if (!string.IsNullOrEmpty(ApplicationPath))
  86. {
  87. result.Add(string.Format("/a:{0}", ApplicationPath.Contains("") ? String.Format("\"{0}\"", ApplicationPath) : ApplicationPath));
  88. }
  89. result.Add(string.Format("/v:{0}", VirtualPath.Contains("") ? String.Format("\"{0}\"", VirtualPath) : VirtualPath));
  90. if (!string.IsNullOrEmpty(HostName))
  91. {
  92. result.Add(string.Format("/h:{0}", HostName.Contains("") ? String.Format("\"{0}\"", HostName) : HostName));
  93. }
  94. if (AddHost)
  95. {
  96. result.Add("/ah");
  97. }
  98. if (IPMode != IPMode.Loopback)
  99. {
  100. result.Add(string.Format("/im:{0}", IPMode));
  101. }
  102. if (!string.IsNullOrEmpty(IPAddress))
  103. {
  104. result.Add(string.Format("/i:{0}", IPAddress));
  105. }
  106. if (IPv6)
  107. {
  108. result.Add("/v6");
  109. }
  110. if (VisualStudio)
  111. {
  112. result.Add("/vs");
  113. }
  114. if (PortMode != PortMode.FirstAvailable)
  115. {
  116. result.Add(string.Format("/pm:{0}", PortMode));
  117. }
  118. if (Port != 0)
  119. {
  120. result.Add(string.Format("/p:{0}", Port));
  121. }
  122. if (PortRangeStart != 32768)
  123. {
  124. result.Add(string.Format("/prs:{0}", PortRangeStart));
  125. }
  126. if (PortRangeEnd != 65535)
  127. {
  128. result.Add(string.Format("/pre:{0}", PortRangeEnd));
  129. }
  130. if (TimeOut > 0)
  131. {
  132. result.Add(string.Format("/t:{0}", TimeOut));
  133. }
  134. if (WaitForPort > 0)
  135. {
  136. result.Add(string.Format("/w:{0}", WaitForPort));
  137. }
  138. if (Ntlm)
  139. {
  140. result.Add("/ntlm");
  141. }
  142. if (Silent)
  143. {
  144. result.Add("/silent");
  145. }
  146. if (Nodirlist)
  147. {
  148. result.Add("/nodirlist");
  149. }
  150. if (EnableLogging)
  151. {
  152. result.Add("/log");
  153. }
  154. return result.ToArray();
  155. }
  156. public override string ToString()
  157. {
  158. return string.Join(" ", ToArgs());
  159. //StringBuilder sb = new StringBuilder();
  160. //if (RunMode != RunMode.Server)
  161. //{
  162. // sb.AppendFormat("{0}", RunMode);
  163. //}
  164. //if (!string.IsNullOrEmpty(ApplicationPath))
  165. //{
  166. // sb.AppendFormat(" /a:{0}", ApplicationPath.Contains(" ") ? String.Format("\"{0}\"", ApplicationPath) : ApplicationPath);
  167. //}
  168. //sb.AppendFormat(" /v:{0}", VirtualPath.Contains(" ") ? String.Format("\"{0}\"", VirtualPath) : VirtualPath);
  169. //if (!string.IsNullOrEmpty(HostName))
  170. //{
  171. // sb.AppendFormat(" /h:{0}", HostName.Contains(" ") ? String.Format("\"{0}\"", HostName) : HostName);
  172. //}
  173. //if (AddHost)
  174. //{
  175. // sb.Append(" /ah");
  176. //}
  177. //if (IPMode != IPMode.Loopback)
  178. //{
  179. // sb.AppendFormat(" /im:{0}", IPMode);
  180. //}
  181. //if (!string.IsNullOrEmpty(IPAddress))
  182. //{
  183. // sb.AppendFormat(" /i:{0}", IPAddress);
  184. //}
  185. //if (IPv6)
  186. //{
  187. // sb.Append(" /v6");
  188. //}
  189. //if (VisualStudio)
  190. //{
  191. // sb.Append(" /vs");
  192. //}
  193. //if (PortMode != PortMode.FirstAvailable)
  194. //{
  195. // sb.AppendFormat(" /pm:{0}", PortMode);
  196. //}
  197. //if (Port != 0)
  198. //{
  199. // sb.AppendFormat(" /p:{0}", Port);
  200. //}
  201. //if (PortRangeStart != 32768)
  202. //{
  203. // sb.AppendFormat(" /prs:{0}", PortRangeStart);
  204. //}
  205. //if (PortRangeEnd != 65535)
  206. //{
  207. // sb.AppendFormat(" /pre:{0}", PortRangeEnd);
  208. //}
  209. //if (TimeOut > 0)
  210. //{
  211. // sb.AppendFormat(" /t:{0}", TimeOut);
  212. //}
  213. //if (WaitForPort > 0)
  214. //{
  215. // sb.AppendFormat(" /w:{0}", WaitForPort);
  216. //}
  217. //if (Ntlm)
  218. //{
  219. // sb.Append(" /ntlm");
  220. //}
  221. //if (Silent)
  222. //{
  223. // sb.Append(" /silent");
  224. //}
  225. //if (Nodirlist)
  226. //{
  227. // sb.Append(" /nodirlist");
  228. //}
  229. //if (EnableLogging)
  230. //{
  231. // sb.Append(" /log");
  232. //}
  233. //return sb.ToString().Trim();
  234. }
  235. /// <summary>
  236. /// </summary>
  237. internal void Validate()
  238. {
  239. if (string.IsNullOrEmpty(ApplicationPath))
  240. {
  241. throw new CassiniException(SR.ErrApplicationPathIsNull, ErrorField.ApplicationPath);
  242. }
  243. try
  244. {
  245. ApplicationPath = Path.GetFullPath(ApplicationPath);
  246. }
  247. catch
  248. {
  249. }
  250. if (!Directory.Exists(ApplicationPath))
  251. {
  252. throw new CassiniException(SR.WebdevDirNotExist, ErrorField.ApplicationPath);
  253. }
  254. ApplicationPath = ApplicationPath.Trim('\"').TrimEnd('\\');
  255. if (!string.IsNullOrEmpty(VirtualPath))
  256. {
  257. VirtualPath = VirtualPath.Trim('\"');
  258. VirtualPath = VirtualPath.Trim('/');
  259. VirtualPath = "/" + VirtualPath;
  260. }
  261. else
  262. {
  263. VirtualPath = "/";
  264. }
  265. if (!VirtualPath.StartsWith("/"))
  266. {
  267. VirtualPath = "/" + VirtualPath;
  268. }
  269. if (AddHost && string.IsNullOrEmpty(HostName))
  270. {
  271. throw new CassiniException(SR.ErrInvalidHostname, ErrorField.HostName);
  272. }
  273. IPAddress = ParseIP(IPMode, IPv6, IPAddress).ToString();
  274. if (VisualStudio) // then STOP HERE.
  275. {
  276. // It is fortunate that in order to provide api parity with WebDev
  277. // we do not need to port scan. Visual Studio balks and refuses to
  278. // attach if we monkey around and open ports.
  279. Port = Port == 0 ? 80 : Port;
  280. PortMode = PortMode.Specific;
  281. return;
  282. }
  283. switch (PortMode)
  284. {
  285. case PortMode.FirstAvailable:
  286. if (PortRangeStart < 1)
  287. {
  288. throw new CassiniException(SR.ErrInvalidPortRangeValue, ErrorField.PortRangeStart);
  289. }
  290. if (PortRangeEnd < 1)
  291. {
  292. throw new CassiniException(SR.ErrInvalidPortRangeValue, ErrorField.PortRangeEnd);
  293. }
  294. if (PortRangeStart > PortRangeEnd)
  295. {
  296. throw new CassiniException(SR.ErrPortRangeEndMustBeEqualOrGreaterThanPortRangeSta,
  297. ErrorField.PortRange);
  298. }
  299. Port = CassiniNetworkUtils.GetAvailablePort(PortRangeStart, PortRangeEnd,
  300. System.Net.IPAddress.Parse(IPAddress), true);
  301. if (Port == 0)
  302. {
  303. throw new CassiniException(SR.ErrNoAvailablePortFound, ErrorField.PortRange);
  304. }
  305. break;
  306. case PortMode.Specific:
  307. if ((Port < 1) || (Port > 0xffff))
  308. {
  309. throw new CassiniException(SR.ErrPortOutOfRange, ErrorField.Port);
  310. }
  311. // start waiting....
  312. //TODO: design this hack away.... why am I waiting in a validation method?
  313. int now = Environment.TickCount;
  314. // wait until either 1) the specified port is available or 2) the specified amount of time has passed
  315. while (Environment.TickCount < now + WaitForPort &&
  316. CassiniNetworkUtils.GetAvailablePort(Port, Port, System.Net.IPAddress.Parse(IPAddress), true) !=
  317. Port)
  318. {
  319. Thread.Sleep(100);
  320. }
  321. // is the port available?
  322. if (CassiniNetworkUtils.GetAvailablePort(Port, Port, System.Net.IPAddress.Parse(IPAddress), true) !=
  323. Port)
  324. {
  325. throw new CassiniException(SR.ErrPortIsInUse, ErrorField.Port);
  326. }
  327. break;
  328. default:
  329. throw new CassiniException(SR.ErrInvalidPortMode, ErrorField.None);
  330. }
  331. }
  332. /// <summary>
  333. /// Converts CommandLineArgument values to an IP address if possible.
  334. /// Throws Exception if not.
  335. /// </summary>
  336. /// <param name="ipmode"></param>
  337. /// <param name="v6"></param>
  338. /// <param name="ipString"></param>
  339. /// <returns></returns>
  340. /// <exception cref="CassiniException">If IPMode is invalid</exception>
  341. /// <exception cref="CassiniException">If IPMode is 'Specific' and ipString is invalid</exception>
  342. public static IPAddress ParseIP(IPMode ipmode, bool v6, string ipString)
  343. {
  344. IPAddress ip;
  345. switch (ipmode)
  346. {
  347. case IPMode.Loopback:
  348. ip = v6 ? System.Net.IPAddress.IPv6Loopback : System.Net.IPAddress.Loopback;
  349. break;
  350. case IPMode.Any:
  351. ip = v6 ? System.Net.IPAddress.IPv6Any : System.Net.IPAddress.Any;
  352. break;
  353. case IPMode.Specific:
  354. if (!System.Net.IPAddress.TryParse(ipString, out ip))
  355. {
  356. throw new CassiniException(SR.ErrInvalidIPAddress, ErrorField.IPAddress);
  357. }
  358. break;
  359. default:
  360. throw new CassiniException(SR.ErrInvalidIPMode, ErrorField.None);
  361. }
  362. return ip;
  363. }
  364. }
  365. }