Windows has a few API libraries to interact with the internet:
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
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 .endifUh, 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:
POST /somephppage.php HTTP/1.x
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
Internet Functions.zip (17.34K)
Number of downloads: 923
This post has been edited by GunnerInc: 13 February 2012 - 05:02 PM