OSDN Git Service

OAuth2Sessionを使用したAPIアクセスに対応
[opentween/open-tween.git] / OpenTween / Connection / Networking.cs
index dd8b012..b75013c 100644 (file)
 // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
 // Boston, MA 02110-1301, USA.
 
+#nullable enable
+
 using System;
 using System.Collections.Generic;
 using System.Diagnostics.CodeAnalysis;
 using System.Linq;
 using System.Net;
+using System.Net.Cache;
 using System.Net.Http;
 using System.Text;
 using System.Threading;
@@ -35,44 +38,55 @@ namespace OpenTween.Connection
     {
         public static TimeSpan DefaultTimeout { get; set; }
 
+        public static TimeSpan UploadImageTimeout { get; set; }
+
         /// <summary>
         /// 通信に使用するプロキシの種類
         /// </summary>
-        public static ProxyType ProxyType
-        {
-            get { return proxyType; }
-        }
+        public static ProxyType ProxyType { get; private set; } = ProxyType.IE;
 
         /// <summary>
         /// 通信に使用するプロキシ
         /// </summary>
-        public static IWebProxy Proxy
-        {
-            get { return proxy; }
-        }
+        public static IWebProxy? Proxy { get; private set; } = null;
 
         /// <summary>
         /// OpenTween 内で共通して使用する HttpClient インスタンス
         /// </summary>
-        public static HttpClient Http
+        public static HttpClient Http => globalHttpClient;
+
+        /// <summary>
+        /// pbs.twimg.com で IPv4 を強制的に使用する
+        /// </summary>
+        public static bool ForceIPv4
         {
-            get { return globalHttpClient; }
+            get => forceIPv4;
+            set
+            {
+                if (forceIPv4 == value)
+                    return;
+
+                forceIPv4 = value;
+
+                // Network.Http を再作成させる
+                OnWebProxyChanged(EventArgs.Empty);
+            }
         }
 
         /// <summary>
         /// Webプロキシの設定が変更された場合に発生します
         /// </summary>
-        public static event EventHandler WebProxyChanged;
+        public static event EventHandler? WebProxyChanged;
 
         private static bool initialized = false;
         private static HttpClient globalHttpClient;
-        private static ProxyType proxyType = ProxyType.IE;
-        private static IWebProxy proxy = null;
+        private static bool forceIPv4 = false;
 
         [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")]
         static Networking()
         {
             DefaultTimeout = TimeSpan.FromSeconds(20);
+            UploadImageTimeout = TimeSpan.FromSeconds(60);
             globalHttpClient = CreateHttpClient(new HttpClientHandler());
         }
 
@@ -84,12 +98,17 @@ namespace OpenTween.Connection
             Networking.initialized = true;
 
             ServicePointManager.Expect100Continue = false;
+            ServicePointManager.CheckCertificateRevocationList = true;
         }
 
-        public static void SetWebProxy(ProxyType proxyType, string proxyAddress, int proxyPort,
-            string proxyUser, string proxyPassword)
+        public static void SetWebProxy(
+            ProxyType proxyType,
+            string proxyAddress,
+            int proxyPort,
+            string proxyUser,
+            string proxyPassword)
         {
-            IWebProxy proxy;
+            IWebProxy? proxy;
             switch (proxyType)
             {
                 case ProxyType.None:
@@ -97,7 +116,7 @@ namespace OpenTween.Connection
                     break;
                 case ProxyType.Specified:
                     proxy = new WebProxy(proxyAddress, proxyPort);
-                    if (!string.IsNullOrEmpty(proxyUser) || !string.IsNullOrEmpty(proxyPassword))
+                    if (!MyCommon.IsNullOrEmpty(proxyUser) || !MyCommon.IsNullOrEmpty(proxyPassword))
                         proxy.Credentials = new NetworkCredential(proxyUser, proxyPassword);
                     break;
                 case ProxyType.IE:
@@ -106,24 +125,26 @@ namespace OpenTween.Connection
                     break;
             }
 
-            Networking.proxyType = proxyType;
-            Networking.proxy = proxy;
+            Networking.ProxyType = proxyType;
+            Networking.Proxy = proxy;
 
-            NativeMethods.SetProxy(proxyType, proxyAddress, proxyPort, proxyUser, proxyPassword);
+            NativeMethods.SetProxy(proxyType, proxyAddress, proxyPort);
 
             OnWebProxyChanged(EventArgs.Empty);
         }
 
         /// <summary>
-        /// プロキシ等の設定を施した HttpClient インスタンスを生成します
+        /// OpenTween で必要な設定を施した HttpClientHandler インスタンスを生成します
         /// </summary>
-        /// <remarks>
-        /// 通常は Networking.Http を使用すべきです。
-        /// このメソッドを使用する場合は、WebProxyChanged イベントが発生する度に HttpClient を生成し直すように実装してください。
-        /// </remarks>
         [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")]
-        public static HttpClient CreateHttpClient(HttpClientHandler handler)
+        public static WebRequestHandler CreateHttpClientHandler()
         {
+            var handler = new WebRequestHandler
+            {
+                UseCookies = false,
+                AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
+            };
+
             if (Networking.Proxy != null)
             {
                 handler.UseProxy = true;
@@ -134,9 +155,26 @@ namespace OpenTween.Connection
                 handler.UseProxy = false;
             }
 
-            var client = new HttpClient(handler);
+            return handler;
+        }
+
+        /// <summary>
+        /// OpenTween で必要な設定を施した HttpClient インスタンスを生成します
+        /// </summary>
+        /// <remarks>
+        /// 通常は Networking.Http を使用すべきです。
+        /// このメソッドを使用する場合は、WebProxyChanged イベントが発生する度に HttpClient を生成し直すように実装してください。
+        /// </remarks>
+        [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")]
+        public static HttpClient CreateHttpClient(HttpMessageHandler handler)
+        {
+            HttpClient client;
+            if (ForceIPv4)
+                client = new HttpClient(new ForceIPv4Handler(handler));
+            else
+                client = new HttpClient(handler);
+
             client.Timeout = Networking.DefaultTimeout;
-            client.DefaultRequestHeaders.Add("User-Agent", Networking.GetUserAgentString());
 
             return client;
         }
@@ -144,9 +182,9 @@ namespace OpenTween.Connection
         public static string GetUserAgentString(bool fakeMSIE = false)
         {
             if (fakeMSIE)
-                return MyCommon.GetAssemblyName() + "/" + MyCommon.FileVersion + " (compatible; MSIE 10.0)";
+                return ApplicationSettings.AssemblyName + "/" + MyCommon.FileVersion + " (compatible; MSIE 10.0)";
             else
-                return MyCommon.GetAssemblyName() + "/" + MyCommon.FileVersion;
+                return ApplicationSettings.AssemblyName + "/" + MyCommon.FileVersion;
         }
 
         /// <summary>
@@ -161,12 +199,42 @@ namespace OpenTween.Connection
         [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")]
         private static void OnWebProxyChanged(EventArgs e)
         {
-            var newClient = Networking.CreateHttpClient(new HttpClientHandler());
+            var newClient = Networking.CreateHttpClient(Networking.CreateHttpClientHandler());
             var oldClient = Interlocked.Exchange(ref globalHttpClient, newClient);
             oldClient.Dispose();
 
-            if (WebProxyChanged != null)
-                WebProxyChanged(null, e);
+            WebProxyChanged?.Invoke(null, e);
+        }
+
+        private class ForceIPv4Handler : DelegatingHandler
+        {
+            private readonly IPAddress? ipv4Address;
+
+            public ForceIPv4Handler(HttpMessageHandler innerHandler)
+                : base(innerHandler)
+            {
+                foreach (var address in Dns.GetHostAddresses("pbs.twimg.com"))
+                {
+                    if (address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
+                        this.ipv4Address = address;
+                }
+            }
+
+            protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
+            {
+                if (this.ipv4Address != null)
+                {
+                    var requestUri = request.RequestUri;
+                    if (requestUri.Host == "pbs.twimg.com")
+                    {
+                        var rewriteUriStr = requestUri.GetLeftPart(UriPartial.Scheme) + this.ipv4Address + requestUri.PathAndQuery;
+                        request.RequestUri = new Uri(rewriteUriStr);
+                        request.Headers.Host = "pbs.twimg.com";
+                    }
+                }
+
+                return base.SendAsync(request, cancellationToken);
+            }
         }
     }