PageRenderTime 63ms CodeModel.GetById 32ms RepoModel.GetById 1ms app.codeStats 0ms

/GitSharp.Core/Transport/Daemon.cs

https://github.com/stschake/GitSharp
C# | 399 lines | 251 code | 42 blank | 106 comment | 29 complexity | 99e52ea0e77e6d2f49bc2fb80332ddd2 MD5 | raw file
  1. /*
  2. * Copyright (C) 2008, Google Inc.
  3. *
  4. * All rights reserved.
  5. *
  6. * Redistribution and use in source and binary forms, with or
  7. * without modification, are permitted provided that the following
  8. * conditions are met:
  9. *
  10. * - Redistributions of source code must retain the above copyright
  11. * notice, this list of conditions and the following disclaimer.
  12. *
  13. * - Redistributions in binary form must reproduce the above
  14. * copyright notice, this list of conditions and the following
  15. * disclaimer in the documentation and/or other materials provided
  16. * with the distribution.
  17. *
  18. * - Neither the name of the Git Development Community nor the
  19. * names of its contributors may be used to endorse or promote
  20. * products derived from this software without specific prior
  21. * written permission.
  22. *
  23. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  24. * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  25. * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  26. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  27. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  28. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  29. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  30. * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  31. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  32. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  33. * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  34. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  35. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  36. */
  37. using System;
  38. using System.Collections.Generic;
  39. using System.IO;
  40. using System.Linq;
  41. using System.Net;
  42. using System.Net.Sockets;
  43. using System.Runtime.CompilerServices;
  44. using System.Threading;
  45. namespace GitSharp.Core.Transport
  46. {
  47. /// <summary>
  48. /// Basic daemon for the anonymous <code>git://</code> transport protocol.
  49. /// </summary>
  50. public class Daemon
  51. {
  52. public const int DEFAULT_PORT = 9418;
  53. private const int BACKLOG = 5;
  54. public IPEndPoint MyAddress { get; private set; }
  55. public DaemonService[] Services { get; private set; }
  56. public Dictionary<string, Thread> Processors { get; private set; }
  57. public bool ExportAll { get; set; }
  58. public Dictionary<string, Repository> Exports { get; private set; }
  59. public ICollection<DirectoryInfo> ExportBase { get; private set; }
  60. public bool Run { get; private set; }
  61. private Thread acceptThread;
  62. private Object locker = new Object();
  63. /// <summary>
  64. /// Configure a daemon to listen on any available network port.
  65. /// </summary>
  66. public Daemon()
  67. : this(null)
  68. {
  69. }
  70. /// <summary>
  71. /// Configure a new daemon for the specified network address.
  72. /// </summary>
  73. /// <param name="addr">
  74. /// Address to listen for connections on. If null, any available
  75. /// port will be chosen on all network interfaces.
  76. /// </param>
  77. public Daemon(IPEndPoint addr)
  78. {
  79. MyAddress = addr;
  80. Exports = new Dictionary<string, Repository>();
  81. ExportBase = new List<DirectoryInfo>();
  82. Processors = new Dictionary<string, Thread>();
  83. Services = new DaemonService[] { new UploadPackService(), new ReceivePackService() };
  84. }
  85. /// <summary> * Lookup a supported service so it can be reconfigured.
  86. /// </summary>
  87. /// <param name="name">
  88. /// Name of the service; e.g. "receive-pack"/"git-receive-pack" or
  89. /// "upload-pack"/"git-upload-pack".
  90. /// </param>
  91. /// <returns>
  92. /// The service; null if this daemon implementation doesn't support
  93. /// the requested service type.
  94. /// </returns>
  95. public DaemonService GetService(string name)
  96. {
  97. lock(locker)
  98. {
  99. if (!name.StartsWith("git-"))
  100. name = "git-" + name;
  101. foreach (DaemonService s in Services)
  102. {
  103. if (s.Command.Equals(name))
  104. return s;
  105. }
  106. return null;
  107. }
  108. }
  109. /// <summary>
  110. /// Add a single repository to the set that is exported by this daemon.
  111. /// <para />
  112. /// The existence (or lack-thereof) of <code>git-daemon-export-ok</code> is
  113. /// ignored by this method. The repository is always published.
  114. /// </summary>
  115. /// <param name="name">
  116. /// name the repository will be published under.
  117. /// </param>
  118. /// <param name="db">the repository instance. </param>
  119. public void ExportRepository(string name, Repository db)
  120. {
  121. if (!name.EndsWith(".git"))
  122. name = name + ".git";
  123. Exports.Add(name, db);
  124. RepositoryCache.register(db);
  125. }
  126. /// <summary>
  127. /// Recursively export all Git repositories within a directory.
  128. /// </summary>
  129. /// <param name="dir">
  130. /// the directory to export. This directory must not itself be a
  131. /// git repository, but any directory below it which has a file
  132. /// named <code>git-daemon-export-ok</code> will be published.
  133. /// </param>
  134. public void ExportDirectory(DirectoryInfo dir)
  135. {
  136. ExportBase.Add(dir);
  137. }
  138. /// <summary>
  139. /// Start this daemon on a background thread.
  140. /// </summary>
  141. /// <exception cref="IOException">
  142. /// the server socket could not be opened.
  143. /// </exception>
  144. /// <exception cref="InvalidOperationException">
  145. /// the daemon is already running.
  146. /// </exception>
  147. public void Start()
  148. {
  149. if (acceptThread != null)
  150. {
  151. throw new InvalidOperationException("Daemon already running");
  152. }
  153. var listenSock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
  154. listenSock.Bind(MyAddress ?? new IPEndPoint(IPAddress.Any, 0));
  155. listenSock.Listen(BACKLOG);
  156. MyAddress = (IPEndPoint)listenSock.LocalEndPoint;
  157. Run = true;
  158. acceptThread = new Thread(new ThreadStart(delegate
  159. {
  160. while (Run)
  161. {
  162. try
  163. {
  164. startClient(listenSock.Accept());
  165. }
  166. catch (ThreadInterruptedException)
  167. {
  168. }
  169. catch (SocketException)
  170. {
  171. break;
  172. }
  173. }
  174. try
  175. {
  176. listenSock.Close();
  177. }
  178. catch (SocketException)
  179. {
  180. }
  181. finally
  182. {
  183. lock (this)
  184. {
  185. acceptThread = null;
  186. }
  187. }
  188. }));
  189. acceptThread.Start();
  190. }
  191. /// <returns>
  192. /// true if this daemon is receiving connections.
  193. /// </returns>
  194. public virtual bool isRunning()
  195. {
  196. lock(locker)
  197. {
  198. return Run;
  199. }
  200. }
  201. private void startClient(Socket s)
  202. {
  203. var dc = new DaemonClient(this) { Peer = s.RemoteEndPoint };
  204. // [caytchen] TODO: insanse anonymous methods were ported 1:1 from jgit, do properly sometime
  205. var t = new Thread(
  206. new ThreadStart(delegate
  207. {
  208. using(NetworkStream stream = new NetworkStream(s))
  209. {
  210. try
  211. {
  212. dc.Execute(new BufferedStream(stream));
  213. }
  214. catch (IOException)
  215. {
  216. }
  217. catch (SocketException)
  218. {
  219. }
  220. finally
  221. {
  222. try
  223. {
  224. s.Close();
  225. }
  226. catch (IOException)
  227. {
  228. }
  229. catch (SocketException)
  230. {
  231. }
  232. }
  233. }
  234. }));
  235. t.Start();
  236. Processors.Add("Git-Daemon-Client " + s.RemoteEndPoint, t);
  237. }
  238. public DaemonService MatchService(string cmd)
  239. {
  240. foreach (DaemonService d in Services)
  241. {
  242. if (d.Handles(cmd))
  243. return d;
  244. }
  245. return null;
  246. }
  247. /// <summary>
  248. /// Stop this daemon.
  249. /// </summary>
  250. public void Stop()
  251. {
  252. if (acceptThread != null)
  253. {
  254. Run = false;
  255. // [caytchen] behaviour probably doesn't match
  256. //acceptThread.Interrupt();
  257. }
  258. }
  259. public Repository OpenRepository(string name)
  260. {
  261. // Assume any attempt to use \ was by a Windows client
  262. // and correct to the more typical / used in Git URIs.
  263. //
  264. name = name.Replace('\\', '/');
  265. // git://thishost/path should always be name="/path" here
  266. //
  267. if (!name.StartsWith("/")) return null;
  268. // Forbid Windows UNC paths as they might escape the base
  269. //
  270. if (name.StartsWith("//")) return null;
  271. // Forbid funny paths which contain an up-reference, they
  272. // might be trying to escape and read /../etc/password.
  273. //
  274. if (name.Contains("/../")) return null;
  275. name = name.Substring(1);
  276. Repository db = Exports[name];
  277. if (db != null) return db;
  278. db = Exports[name + ".git"];
  279. if (db != null) return db;
  280. DirectoryInfo[] search = ExportBase.ToArray();
  281. foreach (DirectoryInfo f in search)
  282. {
  283. string p = f.ToString();
  284. if (!p.EndsWith("/")) p = p + '/';
  285. db = OpenRepository(new DirectoryInfo(p + name));
  286. if (db != null) return db;
  287. db = OpenRepository(new DirectoryInfo(p + name + ".git"));
  288. if (db != null) return db;
  289. db = OpenRepository(new DirectoryInfo(p + name + "/.git"));
  290. if (db != null) return db;
  291. }
  292. return null;
  293. }
  294. private Repository OpenRepository(DirectoryInfo f)
  295. {
  296. if (Directory.Exists(f.ToString()) && CanExport(f))
  297. {
  298. try
  299. {
  300. return new Repository(f);
  301. }
  302. catch (IOException)
  303. {
  304. }
  305. }
  306. return null;
  307. }
  308. private bool CanExport(DirectoryInfo d)
  309. {
  310. if (ExportAll) return true;
  311. string p = d.ToString();
  312. if (!p.EndsWith("/")) p = p + '/';
  313. return File.Exists(p + "git-daemon-export-ok");
  314. }
  315. #region Nested Types
  316. // [caytchen] note these two were actually done anonymously in the original jgit
  317. class UploadPackService : DaemonService
  318. {
  319. public UploadPackService()
  320. : base("upload-pack", "uploadpack")
  321. {
  322. Enabled = true;
  323. }
  324. public override void Execute(DaemonClient client, Repository db)
  325. {
  326. var rp = new UploadPack(db);
  327. Stream stream = client.Stream;
  328. rp.Upload(stream, null, null);
  329. }
  330. }
  331. class ReceivePackService : DaemonService
  332. {
  333. public ReceivePackService()
  334. : base("receive-pack", "receivepack")
  335. {
  336. Enabled = false;
  337. }
  338. public override void Execute(DaemonClient client, Repository db)
  339. {
  340. EndPoint peer = client.Peer;
  341. var ipEndpoint = peer as IPEndPoint;
  342. if (ipEndpoint == null)
  343. {
  344. throw new InvalidOperationException("peer must be a IPEndPoint");
  345. }
  346. string host = Dns.GetHostEntry(ipEndpoint.Address).HostName ?? ipEndpoint.Address.ToString();
  347. var rp = new ReceivePack(db);
  348. Stream stream = client.Stream;
  349. const string name = "anonymous";
  350. string email = name + "@" + host;
  351. rp.setRefLogIdent(new PersonIdent(name, email));
  352. rp.receive(stream, null);
  353. }
  354. }
  355. #endregion
  356. }
  357. }