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