OSDN Git Service

Windows 7 で TLS1.1/TLS1.2 が有効にならない不具合を修正
[opentween/open-tween.git] / OpenTween / Connection / Networking.cs
1 // OpenTween - Client of Twitter
2 // Copyright (c) 2014 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
3 // All rights reserved.
4 //
5 // This file is part of OpenTween.
6 //
7 // This program is free software; you can redistribute it and/or modify it
8 // under the terms of the GNU General Public License as published by the Free
9 // Software Foundation; either version 3 of the License, or (at your option)
10 // any later version.
11 //
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14 // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15 // for more details.
16 //
17 // You should have received a copy of the GNU General Public License along
18 // with this program. If not, see <http://www.gnu.org/licenses/>, or write to
19 // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
20 // Boston, MA 02110-1301, USA.
21
22 using System;
23 using System.Collections.Generic;
24 using System.Diagnostics.CodeAnalysis;
25 using System.Linq;
26 using System.Net;
27 using System.Net.Cache;
28 using System.Net.Http;
29 using System.Text;
30 using System.Threading;
31 using System.Threading.Tasks;
32
33 namespace OpenTween.Connection
34 {
35     public static class Networking
36     {
37         public static TimeSpan DefaultTimeout { get; set; }
38         public static TimeSpan UploadImageTimeout { get; set; }
39
40         /// <summary>
41         /// 通信に使用するプロキシの種類
42         /// </summary>
43         public static ProxyType ProxyType { get; private set; } = ProxyType.IE;
44
45         /// <summary>
46         /// 通信に使用するプロキシ
47         /// </summary>
48         public static IWebProxy Proxy { get; private set; } = null;
49
50         /// <summary>
51         /// OpenTween 内で共通して使用する HttpClient インスタンス
52         /// </summary>
53         public static HttpClient Http => globalHttpClient;
54
55         /// <summary>
56         /// pbs.twimg.com で IPv4 を強制的に使用する
57         /// </summary>
58         public static bool ForceIPv4
59         {
60             get => forceIPv4;
61             set
62             {
63                 if (forceIPv4 == value)
64                     return;
65
66                 forceIPv4 = value;
67
68                 // Network.Http を再作成させる
69                 OnWebProxyChanged(EventArgs.Empty);
70             }
71         }
72
73         private static bool IsWindows7
74         {
75             get
76             {
77                 var os = Environment.OSVersion;
78                 return os.Platform == PlatformID.Win32NT && os.Version.Major == 6 && os.Version.Minor == 1;
79             }
80         }
81
82         /// <summary>
83         /// Webプロキシの設定が変更された場合に発生します
84         /// </summary>
85         public static event EventHandler WebProxyChanged;
86
87         private static bool initialized = false;
88         private static HttpClient globalHttpClient;
89         private static bool forceIPv4 = false;
90
91         [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")]
92         static Networking()
93         {
94             DefaultTimeout = TimeSpan.FromSeconds(20);
95             UploadImageTimeout = TimeSpan.FromSeconds(60);
96             globalHttpClient = CreateHttpClient(new HttpClientHandler());
97         }
98
99         /// <summary>
100         /// ネットワーク接続前に行う処理。起動時に一回だけ実行する必要があります。
101         /// </summary>
102         public static void Initialize()
103         {
104             Networking.initialized = true;
105
106             ServicePointManager.Expect100Continue = false;
107
108             // Win7 では SystemDefault が SSL3.0 または TLS1.0 のため、明示的にバージョンを引き上げる必要がある
109             if (IsWindows7)
110                 ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;
111         }
112
113         public static void SetWebProxy(ProxyType proxyType, string proxyAddress, int proxyPort,
114             string proxyUser, string proxyPassword)
115         {
116             IWebProxy proxy;
117             switch (proxyType)
118             {
119                 case ProxyType.None:
120                     proxy = null;
121                     break;
122                 case ProxyType.Specified:
123                     proxy = new WebProxy(proxyAddress, proxyPort);
124                     if (!string.IsNullOrEmpty(proxyUser) || !string.IsNullOrEmpty(proxyPassword))
125                         proxy.Credentials = new NetworkCredential(proxyUser, proxyPassword);
126                     break;
127                 case ProxyType.IE:
128                 default:
129                     proxy = WebRequest.GetSystemWebProxy();
130                     break;
131             }
132
133             Networking.ProxyType = proxyType;
134             Networking.Proxy = proxy;
135
136             NativeMethods.SetProxy(proxyType, proxyAddress, proxyPort, proxyUser, proxyPassword);
137
138             OnWebProxyChanged(EventArgs.Empty);
139         }
140
141         /// <summary>
142         /// OpenTween で必要な設定を施した HttpClientHandler インスタンスを生成します
143         /// </summary>
144         [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")]
145         public static WebRequestHandler CreateHttpClientHandler()
146         {
147             var handler = new WebRequestHandler
148             {
149                 AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
150             };
151
152             if (Networking.Proxy != null)
153             {
154                 handler.UseProxy = true;
155                 handler.Proxy = Networking.Proxy;
156             }
157             else
158             {
159                 handler.UseProxy = false;
160             }
161
162             return handler;
163         }
164
165         /// <summary>
166         /// OpenTween で必要な設定を施した HttpClient インスタンスを生成します
167         /// </summary>
168         /// <remarks>
169         /// 通常は Networking.Http を使用すべきです。
170         /// このメソッドを使用する場合は、WebProxyChanged イベントが発生する度に HttpClient を生成し直すように実装してください。
171         /// </remarks>
172         [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")]
173         public static HttpClient CreateHttpClient(HttpMessageHandler handler)
174         {
175             HttpClient client;
176             if (ForceIPv4)
177                 client = new HttpClient(new ForceIPv4Handler(handler));
178             else
179                 client = new HttpClient(handler);
180
181             client.Timeout = Networking.DefaultTimeout;
182             client.DefaultRequestHeaders.Add("User-Agent", Networking.GetUserAgentString());
183
184             return client;
185         }
186
187         public static string GetUserAgentString(bool fakeMSIE = false)
188         {
189             if (fakeMSIE)
190                 return ApplicationSettings.AssemblyName + "/" + MyCommon.FileVersion + " (compatible; MSIE 10.0)";
191             else
192                 return ApplicationSettings.AssemblyName + "/" + MyCommon.FileVersion;
193         }
194
195         /// <summary>
196         /// Initialize() メソッドが事前に呼ばれているか確認します
197         /// </summary>
198         internal static void CheckInitialized()
199         {
200             if (!Networking.initialized)
201                 throw new InvalidOperationException("Sequence error.(not initialized)");
202         }
203
204         [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")]
205         private static void OnWebProxyChanged(EventArgs e)
206         {
207             var newClient = Networking.CreateHttpClient(Networking.CreateHttpClientHandler());
208             var oldClient = Interlocked.Exchange(ref globalHttpClient, newClient);
209             oldClient.Dispose();
210
211             WebProxyChanged?.Invoke(null, e);
212         }
213
214         private class ForceIPv4Handler : DelegatingHandler
215         {
216             private readonly IPAddress ipv4Address;
217
218             public ForceIPv4Handler(HttpMessageHandler innerHandler)
219                 : base(innerHandler)
220             {
221                 foreach (var address in Dns.GetHostAddresses("pbs.twimg.com"))
222                     if (address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
223                         this.ipv4Address = address;
224             }
225
226             protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
227             {
228                 var requestUri = request.RequestUri;
229                 if (requestUri.Host == "pbs.twimg.com")
230                 {
231                     var rewriteUriStr = requestUri.GetLeftPart(UriPartial.Scheme) + this.ipv4Address + requestUri.PathAndQuery;
232                     request.RequestUri = new Uri(rewriteUriStr);
233                     request.Headers.Host = "pbs.twimg.com";
234                 }
235
236                 return base.SendAsync(request, cancellationToken);
237             }
238         }
239     }
240
241     public enum ProxyType
242     {
243         None,
244         IE,
245         Specified,
246     }
247 }