1 #Region "Microsoft.VisualBasic::2ba0871779282725d13c43e9a4cc68e0, Microsoft.VisualBasic.Core\Extensions\WebServices\WebServiceUtils.vb"
2
3     ' Author:
4     
5     '       asuka (amethyst.asuka@gcmodeller.org)
6     '       xie (genetics@smrucc.org)
7     '       xieguigang (xie.guigang@live.com)
8     
9     ' Copyright (c) 2018 GPL3 Licensed
10     
11     
12     ' GNU GENERAL PUBLIC LICENSE (GPL3)
13     
14     
15     ' This program is free software: you can redistribute it and/or modify
16     ' it under the terms of the GNU General Public License as published by
17     ' the Free Software Foundation, either version 3 of the License, or
18     ' (at your option) any later version.
19     
20     ' This program is distributed in the hope that it will be useful,
21     ' but WITHOUT ANY WARRANTY; without even the implied warranty of
22     ' MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23     ' GNU General Public License for more details.
24     
25     ' You should have received a copy of the GNU General Public License
26     ' along with this program. If not, see <http://www.gnu.org/licenses/>.
27
28
29
30     ' /********************************************************************************/
31
32     ' Summaries:
33
34     Module WebServiceUtils
35     
36     '     Properties: DefaultUA, Protocols, Proxy
37     
38     '     Constructor: (+1 OverloadsSub New
39     
40     '     Function: __getMyIPAddress, BuildArgs, (+2 Overloads) BuildReqparm, BuildUrlData, CheckValidationResult
41     '               (+2 OverloadsDownloadFile, GenerateDictionary, GetDownload, GetMyIPAddress, GetProxy
42     '               (+2 OverloadsGetRequest, GetRequestRaw, IsSocketPortOccupied, isURL, (+2 Overloads) POST
43     '               POSTFile, (+2 Overloads) PostRequest, PostUrlDataParser, QueryStringParameters, UrlDecode
44     '               UrlEncode, UrlPathEncode
45     
46     '     Sub: (+2 OverloadsSetProxy, UrlDecode, UrlEncode
47     
48     ' /********************************************************************************/
49
50 #End Region
51
52 Imports System.Collections.Specialized
53 Imports System.IO
54 Imports System.Net
55 Imports System.Net.Security
56 Imports System.Reflection
57 Imports System.Runtime.CompilerServices
58 Imports System.Security.Cryptography.X509Certificates
59 Imports System.Text
60 Imports System.Text.RegularExpressions
61 Imports System.Web
62 Imports Microsoft.VisualBasic.CommandLine.Reflection
63 Imports Microsoft.VisualBasic.Language
64 Imports Microsoft.VisualBasic.Language.Default
65 Imports Microsoft.VisualBasic.Linq.Extensions
66 Imports Microsoft.VisualBasic.Net.Http
67 Imports Microsoft.VisualBasic.Scripting.MetaData
68
69 ''' <summary>
70 ''' The extension module for web services works.
71 ''' </summary>
72 '''
73 <Package("Utils.WebServices",
74                   Description:="The extension module for web services programming in your scripting.",
75                   Category:=APICategories.UtilityTools,
76                   Publisher:="<a href=""mailto://xie.guigang@gmail.com"">xie.guigang@gmail.com</a>")>
77 Public Module WebServiceUtils
78
79     ''' <summary>
80     ''' Web protocols enumeration
81     ''' </summary>
82     ''' <returns></returns>
83     Public ReadOnly Property Protocols As String() = {"http://""https://""ftp://""sftp://"}
84
85     ''' <summary>
86     ''' Determine that is this uri string is a network location?
87     ''' (判断这个uri字符串是否是一个网络位置)
88     ''' </summary>
89     ''' <param name="url"></param>
90     ''' <returns></returns>
91     <Extension> Public Function isURL(url As StringAs Boolean
92         Return url.InStrAny(Protocols) > -1
93     End Function
94
95     ''' <summary>
96     ''' Build the request parameters for the HTTP POST
97     ''' </summary>
98     ''' <param name="dict"></param>
99     ''' <returns></returns>
100     <ExportAPI("Build.Reqparm",
101                Info:="Build the request parameters for the HTTP POST")>
102     <Extension> Public Function BuildReqparm(dict As Dictionary(Of StringString)) As Specialized.NameValueCollection
103         Dim reqparm As New Specialized.NameValueCollection
104         For Each Value As KeyValuePair(Of StringStringIn dict
105             Call reqparm.Add(Value.Key, Value.Value)
106         Next
107         Return reqparm
108     End Function
109
110     ''' <summary>
111     ''' Build the request parameters for the HTTP POST
112     ''' </summary>
113     ''' <param name="data"></param>
114     ''' <returns></returns>
115     <ExportAPI("Build.Reqparm"Info:="Build the request parameters for the HTTP POST")>
116     <Extension>
117     Public Function BuildReqparm(data As IEnumerable(Of KeyValuePair(Of StringString))) As Specialized.NameValueCollection
118         Dim reqparm As New Specialized.NameValueCollection
119         For Each Value As KeyValuePair(Of StringStringIn data
120             Call reqparm.Add(Value.Key, Value.Value)
121         Next
122         Return reqparm
123     End Function
124
125     Const PortOccupied As String = "Only one usage of each socket address (protocol/network address/port) Is normally permitted"
126
127     ''' <summary>
128     ''' Only one usage of each socket address (protocol/network address/port) Is normally permitted
129     ''' </summary>
130     ''' <param name="ex"></param>
131     ''' <returns></returns>
132     <Extension> Public Function IsSocketPortOccupied(ex As Exception) As Boolean
133         If TypeOf ex Is System.Net.Sockets.SocketException AndAlso
134             InStr(ex.ToString, PortOccupied, CompareMethod.Text) Then
135             Return True
136         Else
137             Return False
138         End If
139     End Function
140
141     ''' <summary>
142     ''' Create a parameter dictionary from the request parameter tokens.
143     ''' (请注意,字典的key默认为转换为小写的形式)
144     ''' </summary>
145     ''' <param name="tokens">
146     ''' 元素的个数必须要大于1,因为从url里面解析出来的元素之中第一个元素是url本身,则不再对url做字典解析
147     ''' </param>
148     ''' <returns>
149     ''' ###### 2016-11-21
150     ''' 因为post可能会传递数组数据进来,则这个时候就会出现重复的键名,则已经不再适合字典类型了,这里改为返回<see cref="NameValueCollection"/>
151     ''' </returns>
152     <ExportAPI("CreateDirectory"Info:="Create a parameter dictionary from the request parameter tokens.")>
153     <Extension>
154     Public Function GenerateDictionary(tokens As String(), Optional lowercase As Boolean = TrueAs NameValueCollection
155         Dim out As New NameValueCollection
156
157         If tokens.IsNullOrEmpty Then
158             Return out
159         End If
160         If tokens.Length = 1 Then  ' 只有url,没有附带的参数,则返回一个空的字典集合
161             If InStr(tokens(Scan0), "=") = 0 Then
162                 Return out
163             End If
164         End If
165
166         Dim LQuery = (From s As String
167                       In tokens
168                       Let p As Integer = InStr(s, "="c)
169                       Let Key As String = Mid(s, 1, p - 1)
170                       Let value = Mid(s, p + 1)
171                       Select Key,
172                           value).ToArray
173
174         For Each x In LQuery
175             Dim name As String = If(lowercase,
176                 x.Key.ToLower,
177                 x.Key)
178             Call out.Add(name, x.value)
179         Next
180
181         Return out
182     End Function
183
184     ''' <summary>
185     ''' 不像<see cref="PostUrlDataParser(String, Boolean)"/>函数,这个函数不会替换掉转义字符,并且所有的Key都已经被默认转换为小写形式的了
186     ''' </summary>
187     ''' <param name="url">URL parameters</param>
188     ''' <returns></returns>
189     <ExportAPI("Request.Parser")>
190     <Extension> Public Function QueryStringParameters(url$, Optional transLower As Boolean = TrueAs NameValueCollection
191         Dim tokens$()
192
193         With InStr(url, "://")
194             If .ByRef < 10 AndAlso .ByRef > 0 Then
195                 url = url.GetTagValue("?").Value
196             End If
197
198             tokens = url.Split("&"c)
199         End With
200
201         Return GenerateDictionary(tokens, transLower)
202     End Function
203
204     ReadOnly urlEscaping As DefaultValue(Of Func(Of StringString)) = New Func(Of StringString)(AddressOf UrlEncode)
205     Friend ReadOnly noEscaping As DefaultValue(Of Func(Of StringString)) = New Func(Of StringString)(Function(s) s)
206
207     ''' <summary>
208     ''' 生成URL请求的参数
209     ''' </summary>
210     ''' <param name="data"></param>
211     ''' <param name="escaping">是否进行对value部分的字符串数据进行转义</param>
212     ''' <returns></returns>
213     ''' 
214     <MethodImpl(MethodImplOptions.AggressiveInlining)>
215     <Extension> Public Function BuildUrlData(data As IEnumerable(Of KeyValuePair(Of StringString)), Optional escaping As Boolean = FalseAs String
216         Return data.Select(Function(x) $"{x.Key}={(noEscaping Or urlEscaping.When(escaping))(x.Value) }").JoinBy("&")
217     End Function
218
219     <ExportAPI("Build.Args")>
220     Public Function BuildArgs(ParamArray params As String()()) As String
221         If params.IsNullOrEmpty Then
222             Return ""
223         Else
224             Dim values = params.Select(Function(arg) $"{arg(Scan0)}={arg(1)}").ToArray
225             Return String.Join("&", values)
226         End If
227     End Function
228
229     ''' <summary>
230     ''' 在服务器端对URL进行解码还原
231     ''' </summary>
232     ''' <param name="s"></param>
233     ''' <param name="encoding"></param>
234     ''' <returns></returns>
235     <Extension> <ExportAPI("URL.Decode")>
236     Public Function UrlDecode(s$, Optional encoding As Encoding = NothingAs String
237         If s.StringEmpty Then
238             Return ""
239         End If
240         If encoding IsNot Nothing Then
241             Return HttpUtility.UrlDecode(s, encoding)
242         Else
243             Return HttpUtility.UrlDecode(s)
244         End If
245     End Function
246
247     <ExportAPI("URL.Decode")>
248     Public Sub UrlDecode(s As StringByRef output As TextWriter)
249         If s IsNot Nothing Then
250             output.Write(UrlDecode(s))
251         End If
252     End Sub
253
254     ''' <summary>
255     ''' 进行url编码,将特殊字符进行转码
256     ''' </summary>
257     ''' <param name="s"></param>
258     ''' <param name="encoding"></param>
259     ''' <returns></returns>
260     ''' 
261     <MethodImpl(MethodImplOptions.AggressiveInlining)>
262     <ExportAPI("URL.Encode")>
263     <Extension>
264     Public Function UrlEncode(s As StringOptional encoding As Encoding = NothingAs String
265         If encoding IsNot Nothing Then
266             Return HttpUtility.UrlEncode(s, encoding)
267         Else
268             Return HttpUtility.UrlEncode(s)
269         End If
270     End Function
271
272     <ExportAPI("URL.Encode")>
273     Public Sub UrlEncode(s As StringByRef output As TextWriter)
274         If s IsNot Nothing Then
275             output.Write(UrlEncode(s))
276         End If
277     End Sub
278
279     ''' <summary>
280     ''' 编码整个URL,这个函数会自动截取出query string parameter部分,然后对截取出来的query string parameter进行编码
281     ''' </summary>
282     ''' <param name="s"></param>
283     ''' <returns></returns>
284     <ExportAPI("URL.PathEncode")>
285     <Extension>
286     Public Function UrlPathEncode(s As StringAs String
287         If s Is Nothing Then
288             Return Nothing
289         End If
290
291         Dim idx As Integer = s.IndexOf("?"c)
292         Dim s2 As String = Nothing
293
294         If idx <> -1 Then
295             s2 = s.Substring(0, idx)
296             s2 = HttpUtility.UrlEncode(s2) & s.Substring(idx)
297         Else
298             s2 = HttpUtility.UrlEncode(s)
299         End If
300
301         Return s2
302     End Function
303
304     ''' <summary>
305     ''' 假若你的数据之中包含有SHA256的加密数据,则非常不推荐使用这个函数进行解析。因为请注意,这个函数会替换掉一些转义字符的,所以会造成一些非常隐蔽的BUG
306     ''' </summary>
307     ''' <param name="data">转义的时候大小写无关</param>
308     ''' <returns></returns>
309     '''
310     <ExportAPI("PostRequest.Parsing")>
311     <Extension> Public Function PostUrlDataParser(data$, Optional toLower As Boolean = TrueAs NameValueCollection
312         If String.IsNullOrEmpty(data) Then
313             Return New NameValueCollection
314         End If
315
316         Dim params$() = data.UrlDecode.Split("&"c)
317         Dim table = GenerateDictionary(params, toLower)
318         Return table
319     End Function
320
321     <ExportAPI("GET"Info:="GET http request")>
322     <Extension> Public Function GetRequest(strUrl$, ParamArray args As String()()) As String
323         If args.IsNullOrEmpty Then
324             Return GetRequest(strUrl)
325         Else
326             Dim params As String = BuildArgs(args)
327
328             If String.IsNullOrEmpty(params) Then
329                 Return GetRequest(strUrl)
330             Else
331                 Return GetRequest($"{strUrl}?{params}")
332             End If
333         End If
334     End Function
335
336     ''' <summary>
337     ''' GET http request
338     ''' </summary>
339     ''' <param name="url"></param>
340     ''' <returns></returns>
341     <ExportAPI("GET"Info:="GET http request")>
342     <Extension> Public Function GetRequest(url$, Optional https As Boolean = FalseOptional userAgent As String = "Microsoft.VisualBasic.[HTTP/GET]"As String
343         Dim strData As String = ""
344         Dim strValue As New List(Of String)
345         Dim reader As New StreamReader(GetRequestRaw(url, https, userAgent), Encoding.UTF8)
346
347         Do While True
348             strData = reader.ReadLine()
349             If strData Is Nothing Then
350                 Exit Do
351             Else
352                 strValue += strData
353             End If
354         Loop
355
356         strData = String.Join(vbCrLf, strValue.ToArray)
357         Return strData
358     End Function
359
360     Sub New()
361         ServicePointManager.ServerCertificateValidationCallback = New RemoteCertificateValidationCallback(AddressOf CheckValidationResult)
362     End Sub
363
364     Private Function CheckValidationResult(sender As Object,
365                                            certificate As X509Certificate,
366                                            chain As X509Chain,
367                                            errors As SslPolicyErrors) As Boolean
368         Return True
369     End Function
370
371     ''' <summary>
372     ''' Example for xx-net tool:
373     ''' 
374     ''' ```
375     ''' http://127.0.0.1:8087/
376     ''' ```
377     ''' </summary>
378     ''' <returns></returns>
379     Public Property Proxy As String
380
381     ''' <summary>
382     ''' 
383     ''' </summary>
384     ''' <param name="url"></param>
385     ''' <param name="https"></param>
386     ''' <param name="userAgent">
387     ''' 
388     ''' fix a bug for github API:
389     ''' 
390     ''' Protocol violation using Github api
391     ''' 
392     ''' You need to set UserAgent like this:
393     ''' webRequest.UserAgent = "YourAppName"
394     ''' Otherwise it will give The server committed a protocol violation. Section=ResponseStatusLine Error.
395     ''' </param>
396     ''' <returns></returns>
397     <ExportAPI("GET.Raw"Info:="GET http request")>
398     <Extension> Public Function GetRequestRaw(url As String,
399                                               Optional https As Boolean = False,
400                                               Optional userAgent As String = "Microsoft.VisualBasic.[HTTP/GET]"As Stream
401         Dim request As HttpWebRequest
402         If https Then
403             request = WebRequest.CreateDefault(New Uri(url))
404         Else
405             request = DirectCast(WebRequest.Create(url), HttpWebRequest)
406         End If
407
408         request.Method = "GET"
409         request.KeepAlive = False
410         request.ServicePoint.Expect100Continue = False
411         request.UserAgent = userAgent
412
413         Dim response As HttpWebResponse = DirectCast(request.GetResponse, HttpWebResponse)
414         Dim s As Stream = response.GetResponseStream()
415         Return s
416     End Function
417
418     <ExportAPI("POST"Info:="POST http request")>
419     Public Function PostRequest(url As StringOptional params As IEnumerable(Of KeyValuePair(Of StringString)) = NothingAs String
420         Return url.POST(params.BuildReqparm)
421     End Function
422
423     <ExportAPI("POST"Info:="POST http request")>
424     Public Function PostRequest(url As StringParamArray params As String()()) As String
425         Dim post As KeyValuePair(Of StringString)()
426         If params Is Nothing Then
427             post = Nothing
428         Else
429             post = params.Select(Function(value) New KeyValuePair(Of StringString)(value(0), value(1))).ToArray
430         End If
431         Return PostRequest(url, post)
432     End Function
433
434     ''' <summary>
435     ''' POST http request for get html.
436     ''' (请注意,假若<paramref name="params"/>之中含有字符串数组的话,则会出错,这个时候需要使用
437     ''' <see cref="Post(String, Dictionary(Of StringString()), StringStringString)"/>方法)
438     ''' </summary>
439     ''' <param name="url$"></param>
440     ''' <param name="params"></param>
441     ''' <param name="Referer$"></param>
442     ''' <returns></returns>
443     <ExportAPI("POST"Info:="POST http request")>
444     <Extension> Public Function POST(url$,
445                                      Optional params As NameValueCollection = Nothing,
446                                      Optional headers As Dictionary(Of StringString) = Nothing,
447                                      Optional Referer$ = "",
448                                      Optional proxy$ = NothingAs String
449
450         Static emptyBody As New DefaultValue(Of NameValueCollection) With {
451             .Value = New NameValueCollection,
452             .assert = Function(c)
453                           Return c Is Nothing OrElse DirectCast(c, NameValueCollection).Count = 0
454                       End Function
455         }
456
457         Using request As New WebClient
458
459             Call request.Headers.Add("User-Agent", UserAgent.GoogleChrome)
460             Call request.Headers.Add(NameOf(Referer), Referer)
461
462             For Each header In headers.SafeQuery
463                 If Not request.Headers.ContainsKey(header.Key) Then
464                     request.Headers.Add(header.Key, header.Value)
465                 End If
466             Next
467
468             If String.IsNullOrEmpty(proxy) Then
469                 proxy = WebServiceUtils.Proxy
470             End If
471             If Not String.IsNullOrEmpty(proxy) Then
472                 Call request.SetProxy(proxy)
473             End If
474
475             Call $"[POST] {url}....".__DEBUG_ECHO
476
477             Dim response As Byte() = request.UploadValues(url, "POST", params Or emptyBody)
478             Dim strData As String = Encoding.UTF8.GetString(response)
479
480             Call $"[GET] {response.Length} bytes...".__DEBUG_ECHO
481
482             Return strData
483         End Using
484     End Function
485
486     ''' <summary>
487     ''' 通过post上传文件
488     ''' </summary>
489     ''' <param name="url$"></param>
490     ''' <param name="name$"></param>
491     ''' <param name="referer$"></param>
492     ''' <returns></returns>
493     <Extension>
494     Public Function POSTFile(url$, buffer As Byte(), Optional name$ = ""Optional referer$ = NothingAs String
495         Dim request As HttpWebRequest = DirectCast(WebRequest.Create(url), HttpWebRequest)
496
497         request.Method = "POST"
498         request.Accept = "application/json"
499         request.ContentLength = buffer.Length
500         request.ContentType = "multipart/form-data; boundary=------WebKitFormBoundaryBpijhG6dKsQpCMdN--;"
501         request.UserAgent = UserAgent.GoogleChrome
502         request.Referer = referer
503         '  request.Headers("fileName") = name Or File.FileName.AsDefault
504
505         If Not String.IsNullOrEmpty(Proxy) Then
506             Call request.SetProxy(Proxy)
507         End If
508
509         Call $"[POST] {url}....".__DEBUG_ECHO
510
511         ' post data Is sent as a stream
512         With request.GetRequestStream()
513             Dim buffer = File.ReadBinary
514
515             Call New StreamWriter(.ByRef).Write(vbCrLf)
516             Call .Flush()
517             Call .Write(buffer, Scan0, buffer.Length)
518             Call .Flush()
519         End With
520
521         ' returned values are returned as a stream, then read into a string
522         Dim response = DirectCast(request.GetResponse(), HttpWebResponse)
523
524         Using responseStream As New StreamReader(response.GetResponseStream())
525             Dim html As New StringBuilder
526             Dim s As New Value(Of String)
527
528             Do While Not (s = responseStream.ReadLine) Is Nothing
529                 Call html.AppendLine(+s)
530             Loop
531
532             Call $"Get {html.Length} bytes from server response...".__DEBUG_ECHO
533
534             Return html.ToString
535         End Using
536     End Function
537
538     ''' <summary>
539     ''' POST http request for get html
540     ''' </summary>
541     ''' <param name="url$"></param>
542     ''' <param name="data"></param>
543     ''' <param name="Referer$"></param>
544     ''' <returns></returns>
545     <ExportAPI("POST"Info:="POST http request")>
546     <Extension> Public Function POST(url$, data As Dictionary(Of StringString()),
547                                      Optional Referer$ = "",
548                                      Optional proxy$ = Nothing,
549                                      Optional ua As String = UserAgent.GoogleChrome) As String
550
551         Dim postString As New List(Of String)
552
553         For Each postValue As KeyValuePair(Of StringString()) In data
554             postString += postValue.Value _
555                 .Select(Function(v) postValue.Key & "=" & HttpUtility.UrlEncode(v))
556         Next
557
558         Dim postData As String = postString.JoinBy("&")
559         Dim request As HttpWebRequest = DirectCast(WebRequest.Create(url), HttpWebRequest)
560
561         request.Method = "POST"
562         request.Accept = "application/json"
563         request.ContentLength = postData.Length
564         request.ContentType = "application/x-www-form-urlencoded; charset=utf-8"
565         request.UserAgent = ua
566         request.Referer = Referer
567
568         If Not String.IsNullOrEmpty(proxy) Then
569             Call request.SetProxy(proxy)
570         End If
571
572         Call $"[POST] {url}....".__DEBUG_ECHO
573
574         ' post data Is sent as a stream
575         Using sender As New StreamWriter(request.GetRequestStream())
576             sender.Write(postData)
577         End Using
578
579         ' returned values are returned as a stream, then read into a string
580         Dim response = DirectCast(request.GetResponse(), HttpWebResponse)
581         Using responseStream As New StreamReader(response.GetResponseStream())
582             Dim html As New StringBuilder
583             Dim s As New Value(Of String)
584
585             Do While Not (s = responseStream.ReadLine) Is Nothing
586                 Call html.AppendLine(+s)
587             Loop
588
589             Call $"[GET] {html.Length} bytes...".__DEBUG_ECHO
590
591             Return html.ToString
592         End Using
593     End Function
594
595     <Extension>
596     Public Sub SetProxy(ByRef request As HttpWebRequest, proxy As String)
597         request.Proxy = proxy.GetProxy
598     End Sub
599
600     <Extension>
601     Public Sub SetProxy(ByRef request As WebClient, proxy As String)
602         request.Proxy = proxy.GetProxy
603     End Sub
604
605     <Extension>
606     Public Function GetProxy(proxy As StringAs WebProxy
607         Return New WebProxy With {
608             .Address = New Uri(proxy),
609             .Credentials = New NetworkCredential()
610         }
611     End Function
612
613     Public Property DefaultUA As String = UserAgent.GoogleChrome
614
615 #If FRAMEWORD_CORE Then
616     ''' <summary>
617     ''' download the file from <paramref name="strUrl"></paramref> to <paramref name="save">local file</paramref>.
618     ''' </summary>
619     ''' <param name="strUrl"></param>
620     ''' <param name="save">The file path of the file saved</param>
621     ''' <returns></returns>
622     ''' <remarks></remarks>
623     <ExportAPI("wget"Info:="Download data from the specific URL location.")>
624     <Extension> Public Function DownloadFile(<Parameter("url")> strUrl$,
625                                              <Parameter("Path.Save""The saved location of the downloaded file data.")>
626                                              save$,
627                                              Optional proxy$ = Nothing,
628                                              Optional ua$ = UserAgent.FireFox,
629                                              Optional retry% = 0,
630                                              Optional progressHandle As DownloadProgressChangedEventHandler = Nothing,
631                                              Optional refer$ = Nothing,
632                                              <CallerMemberName>
633                                              Optional trace$ = NothingAs Boolean
634 #Else
635     ''' <summary>
636     ''' download the file from <paramref name="strUrl"></paramref> to <paramref name="SavedPath">local file</paramref>.
637     ''' </summary>
638     ''' <param name="strUrl"></param>
639     ''' <param name="SavedPath"></param>
640     ''' <returns></returns>
641     ''' <remarks></remarks>
642     <Extension> Public Function DownloadFile(strUrl As String, SavedPath As StringAs Boolean
643 #End If
644 RE0:
645         Try
646             Using browser As New WebClient()
647                 If Not String.IsNullOrEmpty(proxy) Then
648                     Call browser.SetProxy(proxy)
649                 End If
650                 If Not refer.StringEmpty Then
651                     browser.Headers.Add(NameOf(refer), refer)
652                 End If
653                 If Not progressHandle Is Nothing Then
654                     AddHandler browser.DownloadProgressChanged, progressHandle
655                 End If
656
657                 Call browser.Headers.Add(UserAgent.UAheader, ua)
658                 Call save.ParentPath.MkDIR
659                 Call $"{strUrl} --> {save}".__DEBUG_ECHO
660                 Call browser.DownloadFile(strUrl, save)
661             End Using
662
663             Return True
664         Catch ex As Exception
665             Call App.LogException(New Exception(strUrl, ex), trace)
666             Call ex.PrintException
667
668             If retry > 0 Then
669                 retry -= 1
670                 GoTo RE0
671             Else
672
673             End If
674
675             Return False
676         Finally
677             If save.FileExists Then
678                 Call $"[{FileIO.FileSystem.GetFileInfo(save).Length} Bytes]".__DEBUG_ECHO
679             Else
680                 Call $"Download failure!".__DEBUG_ECHO
681             End If
682         End Try
683     End Function
684
685     ''' <summary>
686     ''' 使用GET方法下载文件
687     ''' </summary>
688     ''' <param name="url"></param>
689     ''' <param name="savePath"></param>
690     ''' <returns></returns>
691     '''
692     <ExportAPI("GET.Download"Info:="Download file from http request and save to a specific location.")>
693     <Extension> Public Function GetDownload(url As String, savePath As StringAs Boolean
694         Try
695             Dim responseStream As Stream = GetRequestRaw(url)
696             Dim localBuffer As Stream = responseStream.CopyStream
697             Call $"[{localBuffer.Length} Bytes]".__DEBUG_ECHO
698             Return localBuffer.FlushStream(savePath)
699         Catch ex As Exception
700             ex = New Exception(url, ex)
701             Call ex.PrintException
702             Call App.LogException(ex)
703             Return False
704         End Try
705     End Function
706
707     Public Const IPAddress As String = "http://ipaddress.com/"
708     ''' <summary>
709     ''' Microsoft DNS Server
710     ''' </summary>
711     Public Const MicrosoftDNS As String = "4.2.2.1"
712
713     ''' <summary>
714     ''' 获取我的公网IP地址,假若没有连接互联网的话则会返回局域网IP地址
715     ''' </summary>
716     ''' <returns></returns>
717     Public Function GetMyIPAddress() As String
718         Dim hasInternet As Boolean
719
720         Try
721             hasInternet = Not Net.PingUtility.Ping(System.Net.IPAddress.Parse(MicrosoftDNS)) > Integer.MaxValue
722         Catch ex As Exception
723             hasInternet = False
724         End Try
725
726         If hasInternet Then
727             Return __getMyIPAddress()   'IPAddress on Internet
728         Else
729             Return Net.AsynInvoke.LocalIPAddress  'IPAddress in LAN
730         End If
731     End Function
732
733     Public Const RegexIPAddress As String = "\d{1,3}(\.\d{1,3}){3}"
734
735     Private Function __getMyIPAddress() As String
736         Dim page As String = IPAddress.GET
737         Dim ipResult As String = Regex.Match(page, $"IP[:] {RegexIPAddress}<br><img", RegexOptions.IgnoreCase).Value
738         ipResult = Regex.Match(ipResult, RegexIPAddress).Value
739         Return ipResult
740     End Function
741 End Module