PageRenderTime 39ms CodeModel.GetById 11ms RepoModel.GetById 1ms app.codeStats 0ms

/InterceptNuGet/InterceptNuGet/InterceptChannel.cs

https://github.com/johnataylor/InterceptNuGet
C# | 381 lines | 301 code | 75 blank | 5 comment | 33 complexity | d07f36063dfa8a887ccbfd55daf4cdaa MD5 | raw file
  1. using Newtonsoft.Json.Linq;
  2. using NuGet.Versioning;
  3. using System;
  4. using System.Collections.Generic;
  5. using System.IO;
  6. using System.Linq;
  7. using System.Net.Http;
  8. using System.Reflection;
  9. using System.Threading.Tasks;
  10. using System.Xml;
  11. using System.Xml.Linq;
  12. namespace InterceptNuGet
  13. {
  14. class InterceptChannel
  15. {
  16. string _baseAddress;
  17. string _searchBaseAddress;
  18. string _passThroughAddress;
  19. public InterceptChannel(string baseAddress, string searchBaseAddress, string passThroughAddress)
  20. {
  21. _baseAddress = baseAddress.TrimEnd('/');
  22. _searchBaseAddress = searchBaseAddress.TrimEnd('/');
  23. _passThroughAddress = passThroughAddress.TrimEnd('/');
  24. }
  25. public static async Task<InterceptChannel> Create(string source)
  26. {
  27. HttpClient client = new HttpClient();
  28. HttpResponseMessage response = await client.GetAsync(source);
  29. if (response.StatusCode == System.Net.HttpStatusCode.NotFound)
  30. {
  31. HttpResponseMessage rootResponse = await client.GetAsync(source + "/root.xml");
  32. if (response.IsSuccessStatusCode)
  33. {
  34. string text = await rootResponse.Content.ReadAsStringAsync();
  35. XNamespace shim = XNamespace.Get("http://schema.nuget.org/shim");
  36. XElement interceptionSpecification = XElement.Parse(text);
  37. string baseAddress = interceptionSpecification.Elements(shim + "baseAddress").First().Value;
  38. string searchBaseAddress = interceptionSpecification.Elements(shim + "searchBaseAddress").First().Value;
  39. string passThroughAddress = interceptionSpecification.Elements(shim + "passThroughAddress").First().Value;
  40. return new InterceptChannel(baseAddress, searchBaseAddress, passThroughAddress);
  41. }
  42. }
  43. return null;
  44. }
  45. public async Task Root(InterceptCallContext context, string feedName = null)
  46. {
  47. context.Log(string.Format("Root: {0}", feedName ?? string.Empty), ConsoleColor.Magenta);
  48. if (feedName == null)
  49. {
  50. Stream stream = GetResourceStream("xml.Root.xml");
  51. XElement xml = XElement.Load(stream);
  52. await context.WriteResponse(xml);
  53. }
  54. else
  55. {
  56. Stream stream = GetResourceStream("xml.FeedRoot.xml");
  57. string s = (new StreamReader(stream)).ReadToEnd();
  58. string t = string.Format(s, feedName);
  59. XElement xml = XElement.Load(new StringReader(t), LoadOptions.SetBaseUri);
  60. await context.WriteResponse(xml);
  61. }
  62. }
  63. public async Task Metadata(InterceptCallContext context, string feed = null)
  64. {
  65. context.Log(string.Format("Metadata: {0}", feed ?? string.Empty), ConsoleColor.Magenta);
  66. Stream stream = GetResourceStream(feed == null ? "xml.Metadata.xml" : "xml.FeedMetadata.xml");
  67. XElement xml = XElement.Load(stream);
  68. await context.WriteResponse(xml);
  69. }
  70. public async Task Count(InterceptCallContext context, string searchTerm, bool isLatestVersion, string targetFramework, bool includePrerelease, string feedName)
  71. {
  72. context.Log(string.Format("Count: {0}", searchTerm), ConsoleColor.Magenta);
  73. JObject obj = await FetchJson(context, MakeCountAddress(searchTerm, isLatestVersion, targetFramework, includePrerelease, feedName));
  74. string count = obj["totalHits"].ToString();
  75. await context.WriteResponse(count);
  76. }
  77. public async Task Search(InterceptCallContext context, string searchTerm, bool isLatestVersion, string targetFramework, bool includePrerelease, int skip, int take, string feedName)
  78. {
  79. context.Log(string.Format("Search: {0} ({1},{2})", searchTerm, skip, take), ConsoleColor.Magenta);
  80. JObject obj = await FetchJson(context, MakeSearchAddress(searchTerm, isLatestVersion, targetFramework, includePrerelease, skip, take, feedName));
  81. XElement feed = InterceptFormatting.MakeFeedFromSearch(_passThroughAddress, "Packages", obj["data"], "");
  82. await context.WriteResponse(feed);
  83. }
  84. public async Task GetPackage(InterceptCallContext context, string id, string version, string feedName)
  85. {
  86. context.Log(string.Format("GetPackage: {0} {1}", id, version), ConsoleColor.Magenta);
  87. JObject resolverBlob = await FetchJson(context, MakeResolverAddress(id));
  88. NuGetVersion desiredVersion = NuGetVersion.Parse(version);
  89. JToken desiredPackage = null;
  90. foreach (JToken package in resolverBlob["package"])
  91. {
  92. NuGetVersion currentVersion = NuGetVersion.Parse(package["version"].ToString());
  93. if (currentVersion == desiredVersion)
  94. {
  95. desiredPackage = package;
  96. break;
  97. }
  98. }
  99. if (desiredPackage == null)
  100. {
  101. throw new Exception(string.Format("unable to find version {0} of package {1}", version, id));
  102. }
  103. XElement feed = InterceptFormatting.MakeFeed(_passThroughAddress, "Packages", new List<JToken> { desiredPackage }, id);
  104. await context.WriteResponse(feed);
  105. }
  106. public async Task GetLatestVersionPackage(InterceptCallContext context, string id, bool includePrerelease)
  107. {
  108. context.Log(string.Format("GetLatestVersionPackage: {0} {1}", id, includePrerelease ? "[include prerelease]" : ""), ConsoleColor.Magenta);
  109. JObject resolverBlob = await FetchJson(context, MakeResolverAddress(id));
  110. JToken latest = ExtractLatestVersion(resolverBlob, includePrerelease);
  111. if (latest == null)
  112. {
  113. throw new Exception(string.Format("package {0} not found", id));
  114. }
  115. XElement feed = InterceptFormatting.MakeFeed(_passThroughAddress, "Packages", new List<JToken> { latest }, id);
  116. await context.WriteResponse(feed);
  117. }
  118. public async Task GetAllPackageVersions(InterceptCallContext context, string id)
  119. {
  120. context.Log(string.Format("GetAllPackageVersions: {0}", id), ConsoleColor.Magenta);
  121. JObject resolverBlob = await FetchJson(context, MakeResolverAddress(id));
  122. XElement feed = InterceptFormatting.MakeFeed(_passThroughAddress, "Packages", resolverBlob["package"], id);
  123. await context.WriteResponse(feed);
  124. }
  125. public async Task GetListOfPackageVersions(InterceptCallContext context, string id)
  126. {
  127. context.Log(string.Format("GetListOfPackageVersions: {0}", id), ConsoleColor.Magenta);
  128. JObject resolverBlob = await FetchJson(context, MakeResolverAddress(id));
  129. List<NuGetVersion> versions = new List<NuGetVersion>();
  130. foreach (JToken package in resolverBlob["package"])
  131. {
  132. versions.Add(NuGetVersion.Parse(package["version"].ToString()));
  133. }
  134. versions.Sort();
  135. JArray array = new JArray();
  136. foreach (NuGetVersion version in versions)
  137. {
  138. array.Add(version.ToString());
  139. }
  140. await context.WriteResponse(array);
  141. }
  142. public async Task ListAllVersion(InterceptCallContext context)
  143. {
  144. await PassThrough(context);
  145. }
  146. public async Task ListLatestVersion(InterceptCallContext context)
  147. {
  148. await PassThrough(context);
  149. }
  150. public async Task GetUpdates(InterceptCallContext context, string[] packageIds, string[] versions, string[] versionConstraints, string[] targetFrameworks, bool includePrerelease, bool includeAllVersions)
  151. {
  152. context.Log(string.Format("GetUpdates: {0}", string.Join("|", packageIds)), ConsoleColor.Magenta);
  153. List<JToken> packages = new List<JToken>();
  154. for (int i = 0; i < packageIds.Length; i++)
  155. {
  156. VersionRange range = null;
  157. VersionRange.TryParse(versionConstraints[i], out range);
  158. JObject resolverBlob = await FetchJson(context, MakeResolverAddress(packageIds[i]));
  159. JToken latest = ExtractLatestVersion(resolverBlob, includePrerelease, range);
  160. if (latest == null)
  161. {
  162. throw new Exception(string.Format("package {0} not found", packageIds[i]));
  163. }
  164. packages.Add(latest);
  165. }
  166. XElement feed = InterceptFormatting.MakeFeed(_passThroughAddress, "GetUpdates", packages, packageIds);
  167. await context.WriteResponse(feed);
  168. }
  169. public async Task PassThrough(InterceptCallContext context, bool log = false)
  170. {
  171. context.Log(_passThroughAddress + context.RequestUri.PathAndQuery, ConsoleColor.Cyan);
  172. await InterceptChannel.PassThrough(context, _passThroughAddress, log);
  173. }
  174. public static async Task PassThrough(InterceptCallContext context, string baseAddress, bool log = false)
  175. {
  176. string pathAndQuery = context.RequestUri.PathAndQuery;
  177. Uri forwardAddress = new Uri(baseAddress + pathAndQuery);
  178. Tuple<string, byte[]> content = await Forward(forwardAddress, log);
  179. context.ResponseContentType = content.Item1;
  180. await context.WriteResponseAsync(content.Item2);
  181. }
  182. static JToken ExtractLatestVersion(JObject resolverBlob, bool includePrerelease, VersionRange range = null)
  183. {
  184. // firstly just pick the first one (or the first in range)
  185. JToken candidateLatest = null;
  186. if (range == null)
  187. {
  188. candidateLatest = resolverBlob["package"].FirstOrDefault();
  189. }
  190. else
  191. {
  192. foreach (JToken package in resolverBlob["package"])
  193. {
  194. NuGetVersion currentVersion = NuGetVersion.Parse(package["version"].ToString());
  195. if (range.Satisfies(currentVersion))
  196. {
  197. candidateLatest = package;
  198. break;
  199. }
  200. }
  201. }
  202. if (candidateLatest == null)
  203. {
  204. return null;
  205. }
  206. // secondly iterate through package to see if we have a later package
  207. NuGetVersion candidateLatestVersion = NuGetVersion.Parse(candidateLatest["version"].ToString());
  208. foreach (JToken package in resolverBlob["package"])
  209. {
  210. NuGetVersion currentVersion = NuGetVersion.Parse(package["version"].ToString());
  211. if (range != null && !range.Satisfies(currentVersion))
  212. {
  213. continue;
  214. }
  215. if (includePrerelease)
  216. {
  217. if (currentVersion > candidateLatestVersion)
  218. {
  219. candidateLatest = package;
  220. candidateLatestVersion = currentVersion;
  221. }
  222. }
  223. else
  224. {
  225. if (!currentVersion.IsPrerelease && currentVersion > candidateLatestVersion)
  226. {
  227. candidateLatest = package;
  228. candidateLatestVersion = currentVersion;
  229. }
  230. }
  231. }
  232. if (candidateLatestVersion.IsPrerelease && !includePrerelease)
  233. {
  234. return null;
  235. }
  236. return candidateLatest;
  237. }
  238. Uri MakeResolverAddress(string id)
  239. {
  240. id = id.ToLowerInvariant();
  241. Uri resolverBlobAddress = new Uri(string.Format("{0}/{1}.json", _baseAddress, id));
  242. return resolverBlobAddress;
  243. }
  244. Uri MakeCountAddress(string searchTerm, bool isLatestVersion, string targetFramework, bool includePrerelease, string feedName)
  245. {
  246. string feedArg = feedName == null ? string.Empty : string.Format("&feed={0}", feedName);
  247. Uri searchAddress = new Uri(string.Format("{0}?q={1}&targetFramework={2}&includePrerelease={3}&countOnly=true{4}",
  248. _searchBaseAddress, searchTerm, targetFramework, includePrerelease, feedArg));
  249. return searchAddress;
  250. }
  251. Uri MakeSearchAddress(string searchTerm, bool isLatestVersion, string targetFramework, bool includePrerelease, int skip, int take, string feedName)
  252. {
  253. string feedArg = feedName == null ? string.Empty : string.Format("&feed={0}", feedName);
  254. Uri searchAddress = new Uri(string.Format("{0}?q={1}&targetFramework={2}&includePrerelease={3}&skip={4}&take={5}{6}",
  255. _searchBaseAddress, searchTerm, targetFramework, includePrerelease, skip, take, feedArg));
  256. return searchAddress;
  257. }
  258. async Task<JObject> FetchJson(InterceptCallContext context, Uri address)
  259. {
  260. context.Log(address.ToString(), ConsoleColor.Yellow);
  261. HttpClient client = new HttpClient();
  262. HttpResponseMessage response = await client.GetAsync(address);
  263. string json = await response.Content.ReadAsStringAsync();
  264. JObject obj = JObject.Parse(json);
  265. return obj;
  266. }
  267. static async Task<Tuple<string, byte[]>> Forward(Uri forwardAddress, bool log)
  268. {
  269. HttpClient client = new HttpClient();
  270. HttpResponseMessage response = await client.GetAsync(forwardAddress);
  271. string contentType = response.Content.Headers.ContentType.ToString();
  272. byte[] data = await response.Content.ReadAsByteArrayAsync();
  273. if (log)
  274. {
  275. Dump(contentType, data);
  276. }
  277. return new Tuple<string, byte[]>(contentType, data);
  278. }
  279. public static Stream GetResourceStream(string resName)
  280. {
  281. return Assembly.GetExecutingAssembly().GetManifestResourceStream(Assembly.GetExecutingAssembly().GetName().Name + "." + resName);
  282. }
  283. // Just for debugging
  284. static void Dump(string contentType, byte[] data)
  285. {
  286. using (TextReader reader = new StreamReader(new MemoryStream(data)))
  287. {
  288. string s = reader.ReadToEnd();
  289. if (contentType.IndexOf("xml") > -1)
  290. {
  291. XElement xml = XElement.Parse(s);
  292. using (XmlWriter writer = XmlWriter.Create(Console.Out, new XmlWriterSettings { Indent = true }))
  293. {
  294. xml.WriteTo(writer);
  295. //int count = xml.Elements(XName.Get("entry", "http://www.w3.org/2005/Atom")).Count();
  296. //Console.WriteLine(count);
  297. }
  298. }
  299. else
  300. {
  301. Console.WriteLine(s);
  302. }
  303. }
  304. }
  305. }
  306. }