Windows has a few API libraries to interact with the internet:
WinSock
WinINet
WinHttp
WinSock is the lowest level you can go unless you write a custom driver. It is also the fastest since you only use what you need. After WinSock, each adds an extra layer between you and Sockets. They all in turn use sockets (AFAIK), and make life easier. In other words, you can do anything you want with sockets - client/server, SSL, FTP, HTTP, IPv4, IPv6, cookies, cache, sessions, etc... but you have to do it all yourself.
WinINet builds upon WinSock to make life easier in using FTP, HTTP, Getting Headers, Response codes, etc...
From what I understand (as I don't use it) WinHTTP builds upon WinINet and makes SSL and a few other things easy/er to use.
There are 2 types of sockets: Blocking and Non-Blocking (Async), we will be using Blocking Sockets for this tutorial.
The basic way to interact with a server is:
Get the hostname/address and resolve it.
Create a socket, then connect to the server.
Send a header (Which tells the server what you want and how)
Receive the data
Close the socket.
Anything you can do on the Internet/Intranet, IRC, Email, HTTP, whatever, all have communication standards created by The Internet Engineering Task Force (IETF). The documents are called RFC's. Want to write an email client? There are MIME, SMTP, UUEncoding, Header and response RFC's all describe how to interact with a server and what a client should send.
For HTTP we are interested in these RFC's
First thing we must do is initialize the Winsock dll for our app, we do this by calling WSAStartup
invoke WSAStartup, 101H, offset wsdata
Where 101H is the version of the dll to use, wsdata is a pointer to a WSADATA structure that on return from the call will be filled in with various info.
Important field filled in would be .wVersion or wHighVersion. Let's say you requested version 2.2, and the WS2_32.dll on the users OS did not support it, then .wVersion would not match 2.2 so you would call WSA_Cleanup and initialize again with a lower version number.
Before our program closes (when we are done using Winsock), we must call WSACleanup. You must call WSACleanup for every call to WSAStartup that you make.
Important to note:
Winsock does not use NULL terminated strings to find the end of data (Why you ask? Well binary data can contain multiple NULLS), instead you pass functions the length of the string.
Headers are terminated by 4 characters - 13,10,13,10 so after the last header it will look like this:
LastHeader: SomeValue 13, 10
13,10
Optional data to send (for POST maybe)
For everything to work nicely, we need a buffer large enought to receive the header and data. How do we do that? Have you used InternetQueryDataAvailable or HttpQueryInfo? What they do (I am guessing here) is instead of a GET, they send a HEAD request and read the Content-Length header or if that is not available, they do a GET and do a recv with a size of HEADERSIZE + 12 to retrieve the chunked data size. We can do the same thing or we can just create a buffer large enough to handle any data we might receive.
Ok, let's go through our code:
.elseif eax==WM_COMMAND
mov edx,wParam
movzx eax,dx
shr edx,16
.if edx == BN_CLICKED
.if eax==BTN_SEND
mov FlashTotal, -1
invoke SendMessage, hHeaderOut, WM_GETTEXTLENGTH, 0, 0
.if eax == 0
mov FlashTotal, 0
invoke SetTimer, hWin, 1, 100, 0
mov bHaveHeader, FALSE
.else
mov bHaveHeader, TRUE
.endif
invoke SendMessage, hHost, WM_GETTEXTLENGTH, 0, 0
.if eax == 0
mov FlashTotal2, 0
invoke SetTimer, hWin, 2, 100, 0
mov bHaveHost, FALSE
.else
mov bHaveHost, TRUE
.endif
.if bHaveHeader == TRUE && bHaveHost == TRUE
invoke CreateThread, NULL, NULL, addr SendReceive, 1, NULL, NULL
invoke CloseHandle, eax
.endif
.endif
.endif
Uh, you tell me what it does :-)What this does is check to see if our header text is empty, and if it is, creates a timer to flash the edit control 2 times and goes ding ding. Same if our host edit control is empty.
If they are not empty, we create a thread to send and receive our data.
The "Magic" happens here:
SendReceive proc uses esi ebx
Local s:SOCKET
local peer:sockaddr_in
Local RecvOffset, RecvSpaceLeft:DWORD
invoke SendMessage, hDataIn, WM_SETTEXT, 0, NULL
invoke SendMessage, hHeaderIn, WM_SETTEXT, 0, NULL
invoke SendMessage, hHost, WM_GETTEXTLENGTH, 0, 0
inc eax
push eax
invoke HeapAlloc, hHeap, HEAP_ZERO_MEMORY, eax
mov esi, eax
pop eax
invoke SendMessage, hHost, WM_GETTEXT, eax, esi
invoke SendMessage, hInfo, WM_SETTEXT, 0, offset szResolving
invoke inet_addr, esi
.if eax == INADDR_NONE
invoke gethostbyname, esi
.else
invoke gethostbyaddr, eax, 4, AF_INET
.endif
push eax
invoke HeapFree, hHeap, 0, esi
pop eax
.if eax == 0
invoke SendMessage, hInfo, WM_SETTEXT, 0, offset szNoResolve
ret
.endif
mov eax, [eax].hostent.h_list
mov eax, [eax]
mov eax, [eax]
mov peer.sin_addr, eax
mov peer.sin_family, AF_INET
mov peer.sin_port, 05000h
invoke SendMessage, hInfo, WM_SETTEXT, 0, offset szSocket
invoke socket, AF_INET, SOCK_STREAM, 0
.if eax == INVALID_SOCKET
invoke SendMessage, hInfo, WM_SETTEXT, 0, offset szNoSocket
ret
.endif
mov s, eax
invoke SendMessage, hInfo, WM_SETTEXT, 0, offset szConnecting
invoke connect, s, addr peer, sizeof peer
.if eax == SOCKET_ERROR
invoke closesocket, s
invoke SendMessage, hInfo, WM_SETTEXT, 0, offset szNoConnect
ret
.endif
invoke SendMessage, hInfo, WM_SETTEXT, 0, offset szConnected
invoke SendMessage, hHeaderOut, WM_GETTEXTLENGTH, 0, 0
mov ebx, eax
inc eax
push eax
invoke HeapAlloc, hHeap, HEAP_ZERO_MEMORY, eax
mov esi, eax
pop eax
invoke SendMessage, hHeaderOut, WM_GETTEXT, eax, esi
invoke send, s, esi, ebx, 0
.if eax == SOCKET_ERROR
invoke SendMessage, hInfo, WM_SETTEXT, 0, offset szSendError
invoke HeapFree, hHeap, 0, esi
ret
.endif
invoke HeapFree, hHeap, 0, esi
invoke SendMessage, hInfo, WM_SETTEXT, 0, offset szReply
mov RecvOffset, 0
mov RecvSpaceLeft, MAX_RECV_BUFFER_SIZE - 1
invoke HeapAlloc, hHeap, HEAP_ZERO_MEMORY, MAX_RECV_BUFFER_SIZE
mov esi, eax
Receive:
mov eax, esi
add eax, RecvOffset
invoke recv, s, eax, RecvSpaceLeft, 0
.if eax == SOCKET_ERROR
invoke HeapFree, hHeap, 0, esi
invoke closesocket, s
ret
.endif
add RecvOffset, eax
sub RecvSpaceLeft, eax
test eax, eax
jnz Receive
invoke SendMessage, hInfo, WM_SETTEXT, 0, offset szGotReply
push esi
call GetHeader
invoke closesocket, s
ret
SendReceive endp
First two things we do, is clear the text from our HeaderIn and DataIn controls
Then we get the text length of the host control contents, add 1 for the NULL and create a buffer to hold the text.
Then we grab the host text.
Next we call inet_addr on the host text to convert the ip address (if entered) into a binary representation of the IP in Network Byte Order.
If inet_addr returns INADDR_NONE it is either a host name or invalid IP.
We call gethostbyname to return a pointer to a hostent structure.
or it was a void IP so we call gethostbyaddr to return a pointer to the hostent structure
next we dereference the pointer 3 times to get the address of site and fill in the sockaddr_in structure.
Next we create a socket and connect to the server.
After we successfully created a socket and connected to the server, we create a buffer for and get the header to send.
Now, we are ready to send the header. The whole header might get sent at once, but chances are it won't because of traffic on the net or server so we have to keep sending our buffer adjusting its pointer till we are done.
Next we call GetHeader:
GetHeader proc uses esi edi lpData:DWORD
invoke szLen, esi
invoke HeapAlloc, hHeap, HEAP_ZERO_MEMORY, eax
mov edi, eax
invoke BinSearch, 0, esi, eax, offset szHeadEnd, 4
push eax
invoke szLeft, esi, edi, eax
invoke SendMessage, hHeaderIn, WM_SETTEXT, 0, edi
pop eax
add eax, 4
add esi, eax
invoke SendMessage, hDataIn, WM_SETTEXT, 0, esi
invoke HeapFree, hHeap, 0, edi
invoke HeapFree, hHeap, 0, lpData
ret
GetHeader endp
Here I split the received data into 2 parts and display it: First I find 4 characters - 13,10,13,10 which signifies the end of the header, get the header and display it. Then I add 4 to buffer pointer to skip the end of the header and display our data. That is it, simple.
If you use HTTP/1.1 in the header then you will see a hex number before the data, that is the "chunked size" of the data, I don't care about that, so I just use version 1.0 which does not return that.
Look through the RFCs to see all header fields you can use.
A static page is a page that the contents does not change and the server knows the file size and can send it to you (for example the txt file in the sample)
A dynamic page (chunked data) is a page where the size could be different with each get (the dreamincode pages)
If you want to get the /forums page from DiC comment out
szHeaderToSend BYTE "GET /hello.txt HTTP/1.0", 13, 10
BYTE "Host: www.gunnerinc.com", 13, 10
BYTE "Connection: close", 13, 10
BYTE "User-Agent: GunnerInc DreamInCode Ineternet Tutorial Thingie/1.0", 13, 10, 13, 10, 0
szHost BYTE "www.gunnerinc.com", 0
and un comment:
;szHeaderToSend BYTE "GET /forums/ HTTP/1.0", 13, 10 ; BYTE "Host: www.dreamincode.net", 13, 10 ; BYTE "Connection: close", 13, 10 ; BYTE "User-Agent: GunnerInc DreamInCode Ineternet Tutorial Thingie/1.0", 13, 10, 13, 10, 0 ;szHost BYTE "www.dreamincode.net", 0
Why did I choose the forums page? I am going to write a little app that will grab that page and display all usernames that are online.
Most of the time you will be using GET or POST to get data from a server (yes you can get data with POST)
If you are sending data to a php page, the header would be:
GET/POST /somephppage.php?HTTP/1.x
Or
POST /somephppage.php HTTP/1.x
header1: something
header2: somethingelse
content-length: SizeOfDataThatFollows
SomeField=SomeData&AnotherField=MoreData
Again, look at the RFCs/Wikki to learn about the headers and repsonses.
Hope this helps!!!
*** EDIT ***
Fixed a few typos and added a dummy parameter to the SendReceive proc as per ThreadProc
Attached File(s)
-
Internet Functions.zip (17.34K)
Number of downloads: 578
This post has been edited by GunnerInc: 13 February 2012 - 05:02 PM







MultiQuote




|