OSDN Git Service

SecurityProtocolTypeの明示的な指定を削除
[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         /// <summary>
74         /// Webプロキシの設定が変更された場合に発生します
75         /// </summary>
76         public static event EventHandler WebProxyChanged;
77
78         private static bool initialized = false;
79         private static HttpClient globalHttpClient;
80         private static bool forceIPv4 = false;
81
82         [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")]
83         static Networking()
84         {
85             DefaultTimeout = TimeSpan.FromSeconds(20);
86             UploadImageTimeout = TimeSpan.FromSeconds(60);
87             globalHttpClient = CreateHttpClient(new HttpClientHandler());
88         }
89
90         /// <summary>
91         /// ネットワーク接続前に行う処理。起動時に一回だけ実行する必要があります。
92         /// </summary>
93         public static void Initialize()
94         {
95             Networking.initialized = true;
96
97             ServicePointManager.Expect100Continue = false;
98         }
99
100         public static void SetWebProxy(ProxyType proxyType, string proxyAddress, int proxyPort,
101             string proxyUser, string proxyPassword)
102         {
103             IWebProxy proxy;
104             switch (proxyType)
105             {
106                 case ProxyType.None:
107                     proxy = null;
108                     break;
109                 case ProxyType.Specified:
110                     proxy = new WebProxy(proxyAddress, proxyPort);
111                     if (!string.IsNullOrEmpty(proxyUser) || !string.IsNullOrEmpty(proxyPassword))
112                         proxy.Credentials = new NetworkCredential(proxyUser, proxyPassword);
113                     break;
114                 case ProxyType.IE:
115                 default:
116                     proxy = WebRequest.GetSystemWebProxy();
117                     break;
118             }
119
120             Networking.ProxyType = proxyType;
121             Networking.Proxy = proxy;
122
123             NativeMethods.SetProxy(proxyType, proxyAddress, proxyPort, proxyUser, proxyPassword);
124
125             OnWebProxyChanged(EventArgs.Empty);
126         }
127
128         /// <summary>
129         /// OpenTween で必要な設定を施した HttpClientHandler インスタンスを生成します
130         /// </summary>
131         [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")]
132         public static WebRequestHandler CreateHttpClientHandler()
133         {
134             var handler = new WebRequestHandler
135             {
136                 AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
137             };
138
139             if (Networking.Proxy != null)
140             {
141                 handler.UseProxy = true;
142                 handler.Proxy = Networking.Proxy;
143             }
144             else
145             {
146                 handler.UseProxy = false;
147             }
148
149             return handler;
150         }
151
152         /// <summary>
153         /// OpenTween で必要な設定を施した HttpClient インスタンスを生成します
154         /// </summary>
155         /// <remarks>
156         /// 通常は Networking.Http を使用すべきです。
157         /// このメソッドを使用する場合は、WebProxyChanged イベントが発生する度に HttpClient を生成し直すように実装してください。
158         /// </remarks>
159         [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")]
160         public static HttpClient CreateHttpClient(HttpMessageHandler handler)
161         {
162             HttpClient client;
163             if (ForceIPv4)
164                 client = new HttpClient(new ForceIPv4Handler(handler));
165             else
166                 client = new HttpClient(handler);
167
168             client.Timeout = Networking.DefaultTimeout;
169             client.DefaultRequestHeaders.Add("User-Agent", Networking.GetUserAgentString());
170
171             return client;
172         }
173
174         public static string GetUserAgentString(bool fakeMSIE = false)
175         {
176             if (fakeMSIE)
177                 return ApplicationSettings.AssemblyName + "/" + MyCommon.FileVersion + " (compatible; MSIE 10.0)";
178             else
179                 return ApplicationSettings.AssemblyName + "/" + MyCommon.FileVersion;
180         }
181
182         /// <summary>
183         /// Initialize() メソッドが事前に呼ばれているか確認します
184         /// </summary>
185         internal static void CheckInitialized()
186         {
187             if (!Networking.initialized)
188                 throw new InvalidOperationException("Sequence error.(not initialized)");
189         }
190
191         [SuppressMessage("Microsoft.Reliability", "CA2000:DisposeObjectsBeforeLosingScope")]
192         private static void OnWebProxyChanged(EventArgs e)
193         {
194             var newClient = Networking.CreateHttpClient(Networking.CreateHttpClientHandler());
195             var oldClient = Interlocked.Exchange(ref globalHttpClient, newClient);
196             oldClient.Dispose();
197
198             WebProxyChanged?.Invoke(null, e);
199         }
200
201         private class ForceIPv4Handler : DelegatingHandler
202         {
203             private readonly IPAddress ipv4Address;
204
205             public ForceIPv4Handler(HttpMessageHandler innerHandler)
206                 : base(innerHandler)
207             {
208                 foreach (var address in Dns.GetHostAddresses("pbs.twimg.com"))
209                     if (address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
210                         this.ipv4Address = address;
211             }
212
213             protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
214             {
215                 var requestUri = request.RequestUri;
216                 if (requestUri.Host == "pbs.twimg.com")
217                 {
218                     var rewriteUriStr = requestUri.GetLeftPart(UriPartial.Scheme) + this.ipv4Address + requestUri.PathAndQuery;
219                     request.RequestUri = new Uri(rewriteUriStr);
220                     request.Headers.Host = "pbs.twimg.com";
221                 }
222
223                 return base.SendAsync(request, cancellationToken);
224             }
225         }
226     }
227
228     public enum ProxyType
229     {
230         None,
231         IE,
232         Specified,
233     }
234 }