2019-06-23 05:21:30 +03:00
|
|
|
// Copyright (c) 2019 Alexander Medvednikov. All rights reserved.
|
|
|
|
// Use of this source code is governed by an MIT license
|
|
|
|
// that can be found in the LICENSE file.
|
|
|
|
|
2019-06-22 21:20:28 +03:00
|
|
|
module http
|
|
|
|
|
2019-07-12 17:26:02 +03:00
|
|
|
import time
|
|
|
|
|
|
|
|
#flag -lwininet -lShlwapi
|
2019-06-22 21:20:28 +03:00
|
|
|
#flag -lurlmon
|
2019-07-12 17:26:02 +03:00
|
|
|
#include <WinInet.h>
|
2019-06-22 21:20:28 +03:00
|
|
|
#include "urlmon.h"
|
|
|
|
#include <shlwapi.h>
|
2019-07-31 23:27:39 +03:00
|
|
|
|
2019-06-22 21:20:28 +03:00
|
|
|
import const (
|
|
|
|
INTERNET_OPEN_TYPE_PRECONFIG
|
|
|
|
INTERNET_DEFAULT_HTTP_PORT
|
|
|
|
INTERNET_DEFAULT_HTTPS_PORT
|
|
|
|
INTERNET_SERVICE_HTTP
|
2019-07-31 14:38:24 +03:00
|
|
|
INTERNET_MAX_URL_LENGTH
|
|
|
|
URL_ESCAPE_PERCENT
|
2019-07-31 23:38:31 +03:00
|
|
|
URL_ESCAPE_SEGMENT_ONLY
|
2019-07-31 23:48:10 +03:00
|
|
|
HTTP_QUERY_RAW_HEADERS_CRLF
|
|
|
|
// flags
|
|
|
|
INTERNET_FLAG_HYPERLINK
|
|
|
|
INTERNET_FLAG_IGNORE_CERT_CN_INVALID
|
|
|
|
INTERNET_FLAG_IGNORE_CERT_DATE_INVALID
|
|
|
|
INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP
|
|
|
|
INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS
|
|
|
|
INTERNET_FLAG_NO_AUTH
|
|
|
|
INTERNET_FLAG_NO_CACHE_WRITE
|
|
|
|
INTERNET_FLAG_NO_UI
|
|
|
|
INTERNET_FLAG_NO_COOKIES
|
|
|
|
INTERNET_FLAG_KEEP_CONNECTION
|
|
|
|
INTERNET_FLAG_PRAGMA_NOCACHE
|
|
|
|
INTERNET_FLAG_SECURE
|
|
|
|
INTERNET_FLAG_RELOAD
|
2019-06-22 21:20:28 +03:00
|
|
|
)
|
|
|
|
|
2019-07-16 16:29:27 +03:00
|
|
|
const (
|
|
|
|
BUF_MAX = 1024
|
2019-07-31 14:38:24 +03:00
|
|
|
URL_ESCAPE_AS_UTF8 = 0x00040000 // missing in mingw, require Windows 7
|
|
|
|
URL_ESCAPE_ASCII_URI_COMPONENT = 0x00080000 // missing in mingw, require Windows 8
|
2019-07-31 23:38:31 +03:00
|
|
|
)
|
2019-07-16 16:29:27 +03:00
|
|
|
|
2019-07-15 17:06:23 +03:00
|
|
|
pub fn (req &Request) do() Response {
|
2019-06-22 21:20:28 +03:00
|
|
|
emptyresp := Response{}
|
|
|
|
mut url := req.url
|
2019-07-12 17:26:02 +03:00
|
|
|
//println('\n\nhttp.do() WIN URL="$url" TYP=$req.typ data="$req.data" headers.len=req.headers.len"')
|
2019-06-22 21:20:28 +03:00
|
|
|
is_ssl := req.url.starts_with('https://')
|
2019-07-13 14:51:11 +03:00
|
|
|
mut pos := url.index('://')
|
|
|
|
if pos == -1 {return emptyresp}
|
|
|
|
url = url.right(pos + 3)
|
2019-06-22 21:20:28 +03:00
|
|
|
mut host := url
|
|
|
|
mut path := '/'
|
|
|
|
pos = url.index('/')
|
|
|
|
if pos > -1 {
|
|
|
|
host = url.left(pos)
|
|
|
|
host = host.clone()
|
|
|
|
path = url.right(pos)
|
|
|
|
}
|
|
|
|
mut headers := ''
|
|
|
|
mut resp_headers := ''
|
2019-07-14 23:26:21 +03:00
|
|
|
for key, val in req.headers {
|
2019-06-22 21:20:28 +03:00
|
|
|
headers += '$key: $val\r\n'
|
|
|
|
}
|
|
|
|
if req.typ == 'POST' {
|
|
|
|
headers += 'Content-Type: application/x-www-form-urlencoded'
|
|
|
|
}
|
|
|
|
data := req.data
|
|
|
|
// Retrieve default http user agent
|
|
|
|
user_agent := ''
|
2019-07-31 23:38:31 +03:00
|
|
|
internet := C.InternetOpen(user_agent.to_wide(), INTERNET_OPEN_TYPE_PRECONFIG, 0, 0, 0)
|
2019-06-22 21:20:28 +03:00
|
|
|
if isnil(internet) {
|
|
|
|
println('InternetOpen() failed')
|
|
|
|
return emptyresp
|
|
|
|
}
|
|
|
|
port := int(if is_ssl{INTERNET_DEFAULT_HTTPS_PORT} else { INTERNET_DEFAULT_HTTP_PORT})
|
2019-07-31 23:38:31 +03:00
|
|
|
connect := C.InternetConnect(internet, host.to_wide(), port, 0, 0, INTERNET_SERVICE_HTTP, 0, 0)
|
2019-06-22 21:20:28 +03:00
|
|
|
if isnil(connect) {
|
|
|
|
e := C.GetLastError()
|
|
|
|
println('[windows] InternetConnect() failed')
|
|
|
|
C.printf('err=%d\n', e)
|
|
|
|
return emptyresp
|
|
|
|
}
|
2019-07-31 23:48:10 +03:00
|
|
|
mut flags :=
|
|
|
|
INTERNET_FLAG_HYPERLINK | INTERNET_FLAG_IGNORE_CERT_CN_INVALID |
|
|
|
|
INTERNET_FLAG_IGNORE_CERT_DATE_INVALID |
|
|
|
|
INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP |
|
|
|
|
INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS | INTERNET_FLAG_NO_AUTH |
|
|
|
|
INTERNET_FLAG_NO_CACHE_WRITE | INTERNET_FLAG_NO_UI |
|
|
|
|
INTERNET_FLAG_NO_COOKIES | // ...
|
|
|
|
INTERNET_FLAG_KEEP_CONNECTION |
|
|
|
|
INTERNET_FLAG_PRAGMA_NOCACHE | INTERNET_FLAG_RELOAD
|
2019-06-22 21:20:28 +03:00
|
|
|
if is_ssl {
|
2019-07-31 23:48:10 +03:00
|
|
|
flags = flags | INTERNET_FLAG_SECURE
|
2019-06-22 21:20:28 +03:00
|
|
|
}
|
2019-07-31 23:38:31 +03:00
|
|
|
request := C.HttpOpenRequest(connect, req.typ.to_wide(), path.to_wide(), 'HTTP/1.1'.to_wide(), 0, 0, flags, 0)
|
2019-06-22 21:20:28 +03:00
|
|
|
if isnil(request) {
|
|
|
|
println('HttpOpenRequest() failed')
|
|
|
|
return emptyresp
|
|
|
|
}
|
2019-07-31 23:38:31 +03:00
|
|
|
ret := C.HttpSendRequest(request, headers.to_wide(), -1, data.str, data.len)
|
|
|
|
// Get response headers
|
|
|
|
// Todo call twice to get len
|
2019-07-31 23:48:10 +03:00
|
|
|
size := 1024
|
|
|
|
h_buf := malloc(size)
|
|
|
|
|
|
|
|
C.HttpQueryInfo(request, HTTP_QUERY_RAW_HEADERS_CRLF, h_buf, &size, 0)
|
2019-06-22 21:20:28 +03:00
|
|
|
// Get response body
|
|
|
|
mut buf := [1025]byte
|
|
|
|
mut nr_read := 0
|
2019-07-12 19:42:14 +03:00
|
|
|
mut s := ''
|
2019-07-31 23:27:39 +03:00
|
|
|
for {
|
2019-06-22 21:20:28 +03:00
|
|
|
ok := C.InternetReadFile(request, buf, BUF_MAX, &nr_read)
|
|
|
|
if !ok {
|
|
|
|
println('InternetReadFile() not ok ')
|
|
|
|
}
|
|
|
|
if ok && nr_read == 0 {
|
|
|
|
if req.url.contains('websocket') {
|
|
|
|
println('win sleeping 2')
|
|
|
|
time.sleep(2)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
buf[nr_read] = 0
|
2019-07-31 23:38:31 +03:00
|
|
|
s += tos(buf, nr_read) // TODO perf
|
2019-06-22 21:20:28 +03:00
|
|
|
nr_read = 0
|
|
|
|
}
|
|
|
|
C.InternetCloseHandle(request)
|
|
|
|
C.InternetCloseHandle(connect)
|
|
|
|
C.InternetCloseHandle(internet)
|
2019-07-31 23:48:10 +03:00
|
|
|
resp_headers = string(h_buf)
|
2019-06-22 21:20:28 +03:00
|
|
|
hh := resp_headers.split('\n')
|
|
|
|
mut resp := Response {
|
2019-07-31 23:27:39 +03:00
|
|
|
text: s
|
2019-06-22 21:20:28 +03:00
|
|
|
headers: map[string]string{}
|
|
|
|
// headers: resp_headers
|
|
|
|
}
|
|
|
|
for h in hh {
|
|
|
|
vals := h.split(':')
|
2019-07-12 17:26:02 +03:00
|
|
|
hpos := h.index(':')
|
2019-07-12 19:42:14 +03:00
|
|
|
if hpos == -1 {
|
2019-06-22 21:20:28 +03:00
|
|
|
continue
|
|
|
|
}
|
2019-07-12 17:26:02 +03:00
|
|
|
key := h.left(hpos)
|
|
|
|
val := h.right(hpos + 1)
|
2019-06-22 21:20:28 +03:00
|
|
|
// println('$key => $val')
|
|
|
|
resp.headers[key] = val.trim_space()
|
|
|
|
}
|
|
|
|
return resp
|
|
|
|
}
|
|
|
|
|
2019-07-31 21:53:07 +03:00
|
|
|
pub fn escape_url(s string) string {
|
2019-07-31 14:38:24 +03:00
|
|
|
mut buf := &u16(malloc(INTERNET_MAX_URL_LENGTH * 2)) // INTERNET_MAX_URL_LENGTH * sizeof(wchar_t)
|
|
|
|
mut nr_chars := INTERNET_MAX_URL_LENGTH
|
|
|
|
res := C.UrlEscape(s.to_wide(), buf, &nr_chars, URL_ESCAPE_PERCENT | URL_ESCAPE_AS_UTF8 | URL_ESCAPE_ASCII_URI_COMPONENT)
|
|
|
|
return string_from_wide2(buf, nr_chars)
|
|
|
|
}
|
|
|
|
|
2019-07-31 21:53:07 +03:00
|
|
|
pub fn unescape_url(s string) string {
|
2019-07-31 14:38:24 +03:00
|
|
|
mut buf := &u16(malloc(INTERNET_MAX_URL_LENGTH * 2))
|
2019-07-31 23:38:31 +03:00
|
|
|
mut nr_chars := INTERNET_MAX_URL_LENGTH
|
2019-08-01 14:23:36 +03:00
|
|
|
res := C.UrlUnescape(s.to_wide(), buf, &nr_chars, URL_ESCAPE_AS_UTF8 | URL_ESCAPE_ASCII_URI_COMPONENT)
|
2019-07-31 14:38:24 +03:00
|
|
|
return string_from_wide2(buf, nr_chars)
|
2019-06-22 21:20:28 +03:00
|
|
|
}
|
|
|
|
|
2019-07-31 21:53:07 +03:00
|
|
|
pub fn unescape(s string) string {
|
2019-07-31 23:38:31 +03:00
|
|
|
panic('http.unescape() was replaced with http.unescape_url()')
|
|
|
|
return ''
|
2019-07-31 21:53:07 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn escape(s string) string {
|
2019-07-31 23:38:31 +03:00
|
|
|
panic('http.escape() was replaced with http.escape_url()')
|
|
|
|
return ''
|
2019-07-31 21:53:07 +03:00
|
|
|
}
|
|
|
|
|
2019-06-22 21:20:28 +03:00
|
|
|
fn C.InternetReadFile(voidptr, voidptr, int, intptr) bool
|
|
|
|
|