|
|
|
@ -0,0 +1,233 @@
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.IO.Compression;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Net;
|
|
|
|
|
using System.Runtime.InteropServices;
|
|
|
|
|
using System.Text;
|
|
|
|
|
using CurlSharp;
|
|
|
|
|
using NLog;
|
|
|
|
|
using NzbDrone.Common.Extensions;
|
|
|
|
|
using NzbDrone.Common.Instrumentation;
|
|
|
|
|
|
|
|
|
|
namespace NzbDrone.Common.Http
|
|
|
|
|
{
|
|
|
|
|
public class CurlHttpClient
|
|
|
|
|
{
|
|
|
|
|
private static Logger Logger = NzbDroneLogger.GetLogger(typeof(CurlHttpClient));
|
|
|
|
|
|
|
|
|
|
public CurlHttpClient()
|
|
|
|
|
{
|
|
|
|
|
if (!CheckAvailability())
|
|
|
|
|
{
|
|
|
|
|
throw new ApplicationException("Curl failed to initialize.");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static bool CheckAvailability()
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
return CurlGlobalHandle.Instance.Initialize();
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Logger.TraceException("Initializing curl failed", ex);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public HttpResponse GetResponse(HttpRequest httpRequest, HttpWebRequest webRequest)
|
|
|
|
|
{
|
|
|
|
|
Stream responseStream = new MemoryStream();
|
|
|
|
|
Stream headerStream = new MemoryStream();
|
|
|
|
|
|
|
|
|
|
var curlEasy = new CurlEasy();
|
|
|
|
|
curlEasy.AutoReferer = false;
|
|
|
|
|
curlEasy.WriteFunction = (b, s, n, o) =>
|
|
|
|
|
{
|
|
|
|
|
responseStream.Write(b, 0, s * n);
|
|
|
|
|
return s * n;
|
|
|
|
|
};
|
|
|
|
|
curlEasy.HeaderFunction = (b, s, n, o) =>
|
|
|
|
|
{
|
|
|
|
|
headerStream.Write(b, 0, s * n);
|
|
|
|
|
return s * n;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
curlEasy.UserAgent = webRequest.UserAgent;
|
|
|
|
|
curlEasy.FollowLocation = webRequest.AllowAutoRedirect;
|
|
|
|
|
curlEasy.HttpGet = webRequest.Method == "GET";
|
|
|
|
|
curlEasy.Post = webRequest.Method == "POST";
|
|
|
|
|
curlEasy.Put = webRequest.Method == "PUT";
|
|
|
|
|
curlEasy.Url = webRequest.RequestUri.ToString();
|
|
|
|
|
|
|
|
|
|
if (webRequest.CookieContainer != null)
|
|
|
|
|
{
|
|
|
|
|
curlEasy.Cookie = webRequest.CookieContainer.GetCookieHeader(webRequest.RequestUri);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!httpRequest.Body.IsNullOrWhiteSpace())
|
|
|
|
|
{
|
|
|
|
|
// TODO: This might not go well with encoding.
|
|
|
|
|
curlEasy.PostFields = httpRequest.Body;
|
|
|
|
|
curlEasy.PostFieldSize = httpRequest.Body.Length;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
curlEasy.HttpHeader = SerializeHeaders(webRequest);
|
|
|
|
|
|
|
|
|
|
var result = curlEasy.Perform();
|
|
|
|
|
|
|
|
|
|
if (result != CurlCode.Ok)
|
|
|
|
|
{
|
|
|
|
|
throw new WebException(string.Format("Curl Error {0} for Url {1}", result, curlEasy.Url));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var webHeaderCollection = ProcessHeaderStream(webRequest, headerStream);
|
|
|
|
|
var responseData = ProcessResponseStream(webRequest, responseStream, webHeaderCollection);
|
|
|
|
|
|
|
|
|
|
var httpHeader = new HttpHeader(webHeaderCollection);
|
|
|
|
|
|
|
|
|
|
return new HttpResponse(httpRequest, httpHeader, responseData, (HttpStatusCode)curlEasy.ResponseCode);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private CurlSlist SerializeHeaders(HttpWebRequest webRequest)
|
|
|
|
|
{
|
|
|
|
|
if (webRequest.SendChunked)
|
|
|
|
|
{
|
|
|
|
|
throw new NotSupportedException("Chunked transfer is not supported");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (webRequest.ContentLength > 0)
|
|
|
|
|
{
|
|
|
|
|
webRequest.Headers.Add("Content-Length", webRequest.ContentLength.ToString());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (webRequest.AutomaticDecompression.HasFlag(DecompressionMethods.GZip))
|
|
|
|
|
{
|
|
|
|
|
if (webRequest.AutomaticDecompression.HasFlag(DecompressionMethods.Deflate))
|
|
|
|
|
{
|
|
|
|
|
webRequest.Headers.Add("Accept-Encoding", "gzip, deflate");
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
webRequest.Headers.Add("Accept-Encoding", "gzip");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (webRequest.AutomaticDecompression.HasFlag(DecompressionMethods.Deflate))
|
|
|
|
|
{
|
|
|
|
|
webRequest.Headers.Add("Accept-Encoding", "deflate");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var curlHeaders = new CurlSlist();
|
|
|
|
|
for (int i = 0; i < webRequest.Headers.Count; i++)
|
|
|
|
|
{
|
|
|
|
|
curlHeaders.Append(webRequest.Headers.GetKey(i) + ": " + webRequest.Headers.Get(i));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
curlHeaders.Append("Content-Type: " + webRequest.ContentType ?? string.Empty);
|
|
|
|
|
|
|
|
|
|
return curlHeaders;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private WebHeaderCollection ProcessHeaderStream(HttpWebRequest webRequest, Stream headerStream)
|
|
|
|
|
{
|
|
|
|
|
headerStream.Position = 0;
|
|
|
|
|
var headerData = headerStream.ToBytes();
|
|
|
|
|
var headerString = Encoding.ASCII.GetString(headerData);
|
|
|
|
|
|
|
|
|
|
var webHeaderCollection = new WebHeaderCollection();
|
|
|
|
|
|
|
|
|
|
foreach (var header in headerString.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).Skip(1))
|
|
|
|
|
{
|
|
|
|
|
webHeaderCollection.Add(header);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var setCookie = webHeaderCollection.Get("Set-Cookie");
|
|
|
|
|
if (setCookie != null && setCookie.Length > 0 && webRequest.CookieContainer != null)
|
|
|
|
|
{
|
|
|
|
|
webRequest.CookieContainer.SetCookies(webRequest.RequestUri, setCookie);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return webHeaderCollection;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private byte[] ProcessResponseStream(HttpWebRequest webRequest, Stream responseStream, WebHeaderCollection webHeaderCollection)
|
|
|
|
|
{
|
|
|
|
|
responseStream.Position = 0;
|
|
|
|
|
|
|
|
|
|
if (responseStream.Length != 0 && webRequest.AutomaticDecompression != DecompressionMethods.None)
|
|
|
|
|
{
|
|
|
|
|
var encoding = webHeaderCollection["Content-Encoding"];
|
|
|
|
|
if (encoding != null)
|
|
|
|
|
{
|
|
|
|
|
if (webRequest.AutomaticDecompression.HasFlag(DecompressionMethods.GZip) && encoding.IndexOf("gzip") != -1)
|
|
|
|
|
{
|
|
|
|
|
responseStream = new GZipStream(responseStream, CompressionMode.Decompress);
|
|
|
|
|
|
|
|
|
|
webHeaderCollection.Remove("Content-Encoding");
|
|
|
|
|
}
|
|
|
|
|
else if (webRequest.AutomaticDecompression.HasFlag(DecompressionMethods.Deflate) && encoding.IndexOf("deflate") != -1)
|
|
|
|
|
{
|
|
|
|
|
responseStream = new DeflateStream(responseStream, CompressionMode.Decompress);
|
|
|
|
|
|
|
|
|
|
webHeaderCollection.Remove("Content-Encoding");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return responseStream.ToBytes();
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal sealed class CurlGlobalHandle : SafeHandle
|
|
|
|
|
{
|
|
|
|
|
public static readonly CurlGlobalHandle Instance = new CurlGlobalHandle();
|
|
|
|
|
|
|
|
|
|
private bool _initialized;
|
|
|
|
|
private bool _available;
|
|
|
|
|
|
|
|
|
|
protected override void Dispose(bool disposing)
|
|
|
|
|
{
|
|
|
|
|
base.Dispose(disposing);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private CurlGlobalHandle()
|
|
|
|
|
: base(IntPtr.Zero, true)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool Initialize()
|
|
|
|
|
{
|
|
|
|
|
if (_initialized)
|
|
|
|
|
return _available;
|
|
|
|
|
|
|
|
|
|
_initialized = true;
|
|
|
|
|
_available = Curl.GlobalInit(CurlInitFlag.All) == CurlCode.Ok;
|
|
|
|
|
|
|
|
|
|
return _available;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override bool ReleaseHandle()
|
|
|
|
|
{
|
|
|
|
|
if (_initialized && _available)
|
|
|
|
|
{
|
|
|
|
|
Curl.GlobalCleanup();
|
|
|
|
|
_available = false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override bool IsInvalid
|
|
|
|
|
{
|
|
|
|
|
get { return !_initialized || !_available; }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|