Page 1 of 1

MASM - Winsock interacting with webpages.

#1 GunnerInc  Icon User is offline

  • "Hurry up and wait"
  • member icon




Reputation: 858
  • View blog
  • Posts: 2,284
  • Joined: 28-March 11

Posted 12 February 2012 - 07:31 PM

I will teach you the basics of using WinSock to communicate with a server and get a file.
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)


This post has been edited by GunnerInc: 13 February 2012 - 05:02 PM


Is This A Good Question/Topic? 2
  • +

Replies To: MASM - Winsock interacting with webpages.

#2 v0rtex  Icon User is offline

  • Caffeine: db "Never Enough!"
  • member icon

Reputation: 223
  • View blog
  • Posts: 773
  • Joined: 02-June 10

Posted 13 February 2012 - 08:03 AM

Thanks really well done. Precise and to the point, helped a lot :)
Was This Post Helpful? 0
  • +
  • -

#3 GunnerInc  Icon User is offline

  • "Hurry up and wait"
  • member icon




Reputation: 858
  • View blog
  • Posts: 2,284
  • Joined: 28-March 11

Posted 13 February 2012 - 04:53 PM

Glad it could help!
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1