OSDN Git Service

OpenTween v2.4.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 #nullable enable
23
24 using System;
25 using System.Collections.Generic;
26 using System.Diagnostics.CodeAnalysis;
27 using System.Linq;
28 using System.Net;
29 using System.Net.Cache;
30 using System.Net.Http;
31 using System.Text;
32 using System.Threading;
33 using System.Threading.Tasks;
34
35 namespace OpenTween.Connection
36 {
37     public static class Networking
38     {
39         public static TimeSpan DefaultTimeout { get; set; }
40         public static TimeSpan UploadImageTimeout { get; set; }
41
42         /// <summary>
43         /// 通信に使用するプロキシの種類
44         /// </summary>
45         public static ProxyType ProxyType { get; private set; } = ProxyType.IE;
46
47         /// <summary>
48         /// 通信に使用するプロキシ
49         /// </summary>
50         public static IWebProxy? Proxy { get; private set; } = null;
51
52         /// <summary>
53         /// OpenTween 内で共通して使用する HttpClient インスタンス
54         /// </summary>
55         public static HttpClient Http => globalHttpClient;
56
57         /// <summary>
58         /// pbs.twimg.com で IPv4 を強制的に使用する
59         /// </summary>
60         public static bool ForceIPv4
61         {
62             get => forceIPv4;
63             set
64             {
65                 if (forceIPv4 == value)
66                     return;
67
68                 forceIPv4 = value;
69
70                 // Network.Http を再作成させる
71                 OnWebProxyChanged(EventArgs.Empty);
72             }
73         }
74
75         private static bool IsWindows7
76         {
77             get
78             {
79                 var os = Environment.OSVersion;
80                 return os.Platform == PlatformID.Win32NT && os.Version.Major == 6 && os.Version.Minor == 1;
81             }
82         }
83
84         /// <summary>
85         /// Webプロキシの設定が変更された場合に発生します
86         /// </summary>
87         public static event EventHandler? WebProxyChanged;
88
89         private static bool initialized = false;
90         private static HttpClient globalHttpClient;
91         private static bool forceIPv4 = false;
92
93         [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")]
94         static Networking()
95         {
96             DefaultTimeout = TimeSpan.FromSeconds(20);
97             UploadImageTimeout = TimeSpan.FromSeconds(60);
98             globalHttpClient = CreateHttpClient(new HttpClientHandler());
99         }
100
101         /// <summary>
102         /// ネットワーク接続前に行う処理。起動時に一回だけ実行する必要があります。
103         /// </summary>
104         public static void Initialize()
105         {
106             Networking.initialized = true;
107
108             ServicePointManager.Expect100Continue = false;
109             ServicePointManager.CheckCertificateRevocationList = true;
110
111             // Win7 では SystemDefault が SSL3.0 または TLS1.0 のため、明示的にバージョンを引き上げる必要がある
112             if (IsWindows7)
113                 ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;
114         }
115
116         public static void SetWebProxy(ProxyType proxyType, string proxyAddress, int proxyPort,
117             string proxyUser, string proxyPassword)
118         {
119             IWebProxy? proxy;
120             switch (proxyType)
121             {
122                 case ProxyType.None:
123                     proxy = null;
124                     break;
125                 case ProxyType.Specified:
126                     proxy = new WebProxy(proxyAddress, proxyPort);
127                     if (!MyCommon.IsNullOrEmpty(proxyUser) || !MyCommon.IsNullOrEmpty(proxyPassword))
128                         proxy.Credentials = new NetworkCredential(proxyUser, proxyPassword);
129                     break;
130                 case ProxyType.IE:
131                 default:
132                     proxy = WebRequest.GetSystemWebProxy();
133                     break;
134             }
135
136             Networking.ProxyType = proxyType;
137             Networking.Proxy = proxy;
138
139             NativeMethods.SetProxy(proxyType, proxyAddress, proxyPort);
140
141             OnWebProxyChanged(EventArgs.Empty);
142         }
143
144         /// <summary>
145         /// OpenTween で必要な設定を施した HttpClientHandler インスタンスを生成します
146         /// </summary>
147         [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")]
148         public static WebRequestHandler CreateHttpClientHandler()
149         {
150             var handler = new WebRequestHandler
151             {
152                 AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
153             };
154
155             if (Networking.Proxy != null)
156             {
157                 handler.UseProxy = true;
158                 handler.Proxy = Networking.Proxy;
159             }
160             else
161             {
162                 handler.UseProxy = false;
163             }
164
165             return handler;
166         }
167
168         /// <summary>
169         /// OpenTween で必要な設定を施した HttpClient インスタンスを生成します
170         /// </summary>
171         /// <remarks>
172         /// 通常は Networking.Http を使用すべきです。
173         /// このメソッドを使用する場合は、WebProxyChanged イベントが発生する度に HttpClient を生成し直すように実装してください。
174         /// </remarks>
175         [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")]
176         public static HttpClient CreateHttpClient(HttpMessageHandler handler)
177         {
178             HttpClient client;
179             if (ForceIPv4)
180                 client = new HttpClient(new ForceIPv4Handler(handler));
181             else
182                 client = new HttpClient(handler);
183
184             client.Timeout = Networking.DefaultTimeout;
185             client.DefaultRequestHeaders.Add("User-Agent", Networking.GetUserAgentString());
186
187             return client;
188         }
189
190         public static string GetUserAgentString(bool fakeMSIE = false)
191         {
192             if (fakeMSIE)
193                 return ApplicationSettings.AssemblyName + "/" + MyCommon.FileVersion + " (compatible; MSIE 10.0)";
194             else
195                 return ApplicationSettings.AssemblyName + "/" + MyCommon.FileVersion;
196         }
197
198         /// <summary>
199         /// Initialize() メソッドが事前に呼ばれているか確認します
200         /// </summary>
201         internal static void CheckInitialized()
202         {
203             if (!Networking.initialized)
204                 throw new InvalidOperationException("Sequence error.(not initialized)");
205         }
206
207         [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")]
208         private static void OnWebProxyChanged(EventArgs e)
209         {
210             var newClient = Networking.CreateHttpClient(Networking.CreateHttpClientHandler());
211             var oldClient = Interlocked.Exchange(ref globalHttpClient, newClient);
212             oldClient.Dispose();
213
214             WebProxyChanged?.Invoke(null, e);
215         }
216
217         private class ForceIPv4Handler : DelegatingHandler
218         {
219             private readonly IPAddress? ipv4Address;
220
221             public ForceIPv4Handler(HttpMessageHandler innerHandler)
222                 : base(innerHandler)
223             {
224                 foreach (var address in Dns.GetHostAddresses("pbs.twimg.com"))
225                     if (address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
226                         this.ipv4Address = address;
227             }
228
229             protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
230             {
231                 if (this.ipv4Address != null)
232                 {
233                     var requestUri = request.RequestUri;
234                     if (requestUri.Host == "pbs.twimg.com")
235                     {
236                         var rewriteUriStr = requestUri.GetLeftPart(UriPartial.Scheme) + this.ipv4Address + requestUri.PathAndQuery;
237                         request.RequestUri = new Uri(rewriteUriStr);
238                         request.Headers.Host = "pbs.twimg.com";
239                     }
240                 }
241
242                 return base.SendAsync(request, cancellationToken);
243             }
244         }
245     }
246
247     public enum ProxyType
248     {
249         None,
250         IE,
251         Specified,
252     }
253 }