OSDN Git Service

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