nativehttp

Simple interface to the native http client
Log | Files | Refs | README | LICENSE

http_windows.go (3249B)


      1 package nativehttp
      2 
      3 import (
      4 	"io"
      5 	"runtime"
      6 	"unsafe"
      7 
      8 	. "git.sr.ht/~jackmordaunt/nativehttp/core"
      9 	"git.sr.ht/~jackmordaunt/nativehttp/core/windows"
     10 	"git.sr.ht/~jackmordaunt/nativehttp/winhttp"
     11 )
     12 
     13 func get(s string) (io.ReadCloser, error) {
     14 	url, err := ParseURL(s)
     15 	if err != nil {
     16 		return nil, Err{Cause: err, Msg: "parsing url"}
     17 	}
     18 
     19 	domain, path, port := url.Domain, url.Path, url.Port
     20 
     21 	agentStr, err := windows.UTF16PtrFromString("HTTP nativehttp/1.0")
     22 	if err != nil {
     23 		return nil, err
     24 	}
     25 
     26 	session := winhttp.Open(
     27 		agentStr,
     28 		winhttp.AcessTypeDefaultProxy,
     29 		nil,
     30 		nil,
     31 		winhttp.OpenFlagAsync,
     32 	)
     33 	if session == 0 {
     34 		return nil, windows.GetLastError()
     35 	}
     36 
     37 	defer Cleanup(&err, func() error {
     38 		if ok := winhttp.CloseHandle(session); ok == 0 {
     39 			return windows.GetLastError()
     40 		}
     41 		return nil
     42 	})
     43 
     44 	domainStr, err := windows.UTF16PtrFromString(domain)
     45 	if err != nil {
     46 		return nil, err
     47 	}
     48 
     49 	connect := winhttp.Connect(session, domainStr, port, 0)
     50 	if connect == 0 {
     51 		return nil, windows.GetLastError()
     52 	}
     53 
     54 	defer Cleanup(&err, func() error {
     55 		if ok := winhttp.CloseHandle(connect); ok == 0 {
     56 			return windows.GetLastError()
     57 		}
     58 		return nil
     59 	})
     60 
     61 	pathStr, err := windows.UTF16PtrFromString(path)
     62 	if err != nil {
     63 		return nil, err
     64 	}
     65 
     66 	getStr, err := windows.UTF16PtrFromString("GET")
     67 	if err != nil {
     68 		return nil, err
     69 	}
     70 
     71 	var openRequestFlags winhttp.OpenRequestFlag
     72 	if url.Encrypted {
     73 		openRequestFlags = winhttp.OpenRequestFlagSecure
     74 	}
     75 
     76 	request := winhttp.OpenRequest(
     77 		connect,
     78 		getStr,
     79 		pathStr,
     80 		nil,
     81 		nil,
     82 		nil,
     83 		openRequestFlags|winhttp.OpenRequestFlagNullCodepage,
     84 	)
     85 	if request == 0 {
     86 		return nil, windows.GetLastError()
     87 	}
     88 
     89 	defer Cleanup(&err, func() error {
     90 		if ok := winhttp.CloseHandle(request); ok == 0 {
     91 			return windows.GetLastError()
     92 		}
     93 		return nil
     94 	})
     95 
     96 	if ok := winhttp.SendRequest(request, nil, 0, nil, 0, 0, 0); ok == 0 {
     97 		return nil, windows.GetLastError()
     98 	}
     99 
    100 	if ok := winhttp.ReceiveResponse(request, nil); ok == 0 {
    101 		return nil, windows.GetLastError()
    102 	}
    103 
    104 	runtime.KeepAlive(agentStr)
    105 	runtime.KeepAlive(domainStr)
    106 	runtime.KeepAlive(pathStr)
    107 	runtime.KeepAlive(getStr)
    108 
    109 	return &winhttpRequest{
    110 		session: session,
    111 		connect: connect,
    112 		request: request,
    113 	}, nil
    114 }
    115 
    116 // winhttpRequest adapts a request into an [io.ReadCloser].
    117 type winhttpRequest struct {
    118 	session winhttp.HINTERNET
    119 	connect winhttp.HINTERNET
    120 	request winhttp.HINTERNET
    121 	buf     []byte
    122 }
    123 
    124 var _ io.ReadCloser = (*winhttpRequest)(nil)
    125 
    126 func newRequest(h winhttp.HINTERNET) *winhttpRequest {
    127 	return &winhttpRequest{request: h}
    128 }
    129 
    130 func (r *winhttpRequest) Read(p []byte) (int, error) {
    131 	var size winhttp.DWORD
    132 	var n winhttp.DWORD
    133 
    134 	if ok := winhttp.QueryDataAvailable(r.request, &size); ok == 0 {
    135 		return 0, windows.GetLastError()
    136 	}
    137 
    138 	if size <= 0 {
    139 		return 0, io.EOF
    140 	}
    141 
    142 	if len(r.buf) < int(size+1) {
    143 		r.buf = make([]byte, size+1)
    144 	}
    145 
    146 	data := unsafe.SliceData(r.buf)
    147 
    148 	if ok := winhttp.ReadData(r.request, unsafe.Pointer(data), size, &n); ok == 0 {
    149 		return 0, windows.GetLastError()
    150 	}
    151 
    152 	nn := copy(p, r.buf[:n])
    153 
    154 	runtime.KeepAlive(data)
    155 
    156 	return nn, nil
    157 }
    158 
    159 func (r *winhttpRequest) Close() error {
    160 	if ok := winhttp.CloseHandle(r.request); ok == 0 {
    161 		return windows.GetLastError()
    162 	}
    163 	return nil
    164 }