Skip to content

Latest commit

 

History

History
341 lines (285 loc) · 8.99 KB

sample_388.md

File metadata and controls

341 lines (285 loc) · 8.99 KB

Home

Winsock: reading email messages (POP3, port 110)

Before you begin:

This code returns number of incoming email messages waiting on a POP3 server without opening your default email client (Outlook Express, GoupWise, Eudora etc.).

Also it contains basic GetMsg and GetMsgTop methods being good enough for reading those messages.

Note that even after being read all messages stay on your server (unless you decide to issue the DELE command), so they will be available for retrieving in your regular email program.

In the beginning part of the code replace connection attributes with valid ones from your email account. This is where you can find them in Microsoft Outlook Express:

See also:

FoxTalk articles:


Code:

LOCAL cServer, cUser, cPwd, obj

* provide connection attributes from your email account:
* Incoming mail (POP3)
* Account name
* Password
cServer = "pop3.fastmail.com"
cUser = "UncleJoe"
cPwd = "checkmail"

obj = CreateObject("Tpop3", cServer)
IF VARTYPE(obj) = "O"
	IF obj.Authorize(cUser, cPwd)
		obj.SendCmd("STAT")
		obj.GetResp(5000)
*		obj.GetMsg(1)
*		obj.GetMsgTop(1,0)
	ENDIF
ELSE
	? "Not connected"
ENDIF
* end of main

DEFINE CLASS Tpop3 As Custom
#DEFINE CrLf  Chr(13)+Chr(10)
#DEFINE POP3_PORT  110
#DEFINE AF_INET        2
#DEFINE SOCK_STREAM    1
#DEFINE IPPROTO_TCP    6
#DEFINE SOCKET_ERROR  -1
#DEFINE READ_SIZE      16384

	HostIP=""
	Pop3Socket=0
	DataSocket=0
	LastResp=""
	LastValidResp=""
	CrLfTrim=.T.

PROCEDURE Init(cHost)
	THIS.decl
	IF WSAStartup(0x202, Repli(Chr(0),512)) <> 0
	* unable to initialize Winsock on this computer
		RETURN .F.
	ENDIF
	IF Not THIS.OpenConnect(cHost)
		= WSACleanup()
		RETURN .F.
	ENDIF
	SET MEMOWIDTH TO 250

PROCEDURE Destroy
	THIS.CloseConnect
	= WSACleanup()

PROCEDURE CloseConnect
	IF THIS.Pop3Socket > 0
		THIS.SendCmd("QUIT")
		THIS.GetResp(5000)
		THIS.GetRespAll(500)
		= closesocket(THIS.Pop3Socket)
		THIS.Pop3Socket = 0
	ENDIF

PROTECTED FUNCTION OpenConnect(cHost)
	LOCAL lResult
	THIS.CloseConnect
	IF Not THIS.CheckHost(cHost)
		RETURN .F.
	ENDIF
	THIS.Pop3Socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
	? "Connecting to " + cHost + ", " + THIS.HostIP + " ..."
	lResult = THIS.cn(THIS.Pop3Socket, THIS.HostIP, POP3_PORT)
	IF lResult
		THIS.GetResp(5000)
		lResult = LEFT(THIS.LastValidResp,1) = "+"
		THIS.GetRespAll(500)
	ENDIF
	IF Not lResult
		= closesocket(THIS.Pop3Socket)
		THIS.Pop3Socket = 0
		?? " failed"
	ENDIF
RETURN lResult

PROTECTED FUNCTION cn(hSocket, cIP, nPort)
	LOCAL cBuffer, nResult
	cBuffer = num2word(AF_INET) +;
		num2word(htons(nPort)) +;
		num2dword(inet_addr(cIP)) + Repli(Chr(0),8)
	nResult = ws_connect(hSocket, @cBuffer, Len(cBuffer))
RETURN (nResult = 0)

FUNCTION GetMsg(nIndex)
	THIS.SendCmd("RETR " + LTRIM(STR(nIndex)))
	IF Not THIS.GetRespOk(5000)
		RETURN .F.
	ENDIF

	LOCAL lResult
	THIS.CrLfTrim = .F.
	lResult = THIS.GetRespDot(5000)
	THIS.CrLfTrim = .T.
RETURN lResult

FUNCTION GetMsgTop(nIndex, nLines)
	THIS.SendCmd("TOP " + LTRIM(STR(nIndex)) + " " + LTRIM(STR(nLines)))
	IF Not THIS.GetRespOk(5000)
		RETURN .F.
	ENDIF

	LOCAL lResult
	THIS.CrLfTrim = .F.
	lResult = THIS.GetRespDot(5000)
	THIS.CrLfTrim = .T.
RETURN lResult

FUNCTION GetRespAll(nWait)
* receive all available data from the control socket
	LOCAL cLine
	DO WHILE .T.
		cLine = THIS.GetResp(nWait)
		IF EMPTY(cLine)
			RETURN .F.
		ENDIF
	ENDDO

FUNCTION GetRespDot(nWait)
* reads response lines until single dot (termination char)
	LOCAL cLine
	DO WHILE .T.
		cLine = THIS.GetResp(nWait)
		IF cLine = "."
			RETURN .T.
		ENDIF
		IF EMPTY(cLine)
			RETURN .F.
		ENDIF
	ENDDO

FUNCTION GetRespOk(nWait)
* reads one line; return true on a positive response
	LOCAL cLine
	cLine = THIS.GetResp(nWait)
RETURN (LEFT(cLine,1) = "+")

FUNCTION GetResp(nWait)
* receive a portion of data from the control socket
	LOCAL hEventRead, cRead, nBytesRead

	hEventRead = WSACreateEvent()
	= WSAEventSelect(THIS.Pop3Socket, hEventRead, 1)
	nEventResult = WSAWaitForMultipleEvents(1, @hEventRead, 0, nWait, 0)
	= WSACloseEvent(hEventRead)

	IF nEventResult = 0
		cRead = Repli(Chr(0), READ_SIZE)
		nBytesRead = recv(THIS.Pop3Socket, @cRead, READ_SIZE, 0)
		cRead = LEFT(cRead, nBytesRead)
		IF THIS.CrLfTrim
			cRead = STRTRAN(cRead, CrLf,"")
		ENDIF
		? cRead
	ELSE
		cRead = ""
	ENDIF
	THIS.LastResp = cRead
	IF Not EMPTY(cRead)
		THIS.LastValidResp = cRead
	ENDIF
RETURN cRead

PROCEDURE SendCmd(cmd)
	? CrLf + ">> " + cmd
	LOCAL cBuffer, nResult, cResponse
	cBuffer = cmd + CrLf
	nResult = send(THIS.Pop3Socket, @cBuffer, Len(cBuffer), 0)
RETURN (nResult <> SOCKET_ERROR)

FUNCTION Authorize(cUser, cPwd)
	LOCAL lReturn
	THIS.SendCmd("USER " + cUser)
	IF THIS.GetRespOk(5000)
		THIS.SendCmd("PASS " + cPwd)
		lReturn = THIS.GetRespOk(5000)
		THIS.GetRespAll(500)
		RETURN lReturn
	ENDIF
RETURN .F.

PROTECTED FUNCTION CheckHost(cHost)
	LOCAL nStruct, nSize, cBuffer, nAddr, cIP
	nStruct = gethostbyname(cHost)
	IF nStruct = 0
		THIS.HostIP = ""
		RETURN .F.
	ENDIF
	cBuffer = Repli(Chr(0), 16)  && HOSTENT_SIZE
	cIP = Repli(Chr(0), 4)
	= CopyMemory(@cBuffer, nStruct, Len(cBuffer))
	= CopyMemory(@cIP, buf2dword(SUBS(cBuffer,13,4)),4)
	= CopyMemory(@cIP, buf2dword(cIP),4)
	THIS.HostIP = inet_ntoa(buf2dword(cIP))
RETURN Not EMPTY(THIS.HostIP)

PROCEDURE decl
	DECLARE INTEGER WSACleanup IN ws2_32
	DECLARE INTEGER WSAStartup IN ws2_32 INTEGER wVerRq, STRING lpWSAData
	DECLARE INTEGER socket IN ws2_32 INTEGER af, INTEGER tp, INTEGER pt
	DECLARE INTEGER closesocket IN ws2_32 INTEGER s
	DECLARE STRING inet_ntoa IN ws2_32 INTEGER in_addr
	DECLARE INTEGER gethostbyname IN ws2_32 STRING host
	DECLARE INTEGER inet_addr IN ws2_32 STRING cp
	DECLARE INTEGER htons IN ws2_32 INTEGER hostshort
	DECLARE INTEGER WSACreateEvent IN ws2_32
	DECLARE INTEGER WSACloseEvent IN ws2_32 INTEGER hEvent
	DECLARE INTEGER WSASetEvent IN ws2_32 INTEGER hEvent

	DECLARE INTEGER send IN ws2_32;
		INTEGER s, STRING @buf, INTEGER buflen, INTEGER flags

	DECLARE INTEGER recv IN ws2_32;
		INTEGER s, STRING @buf, INTEGER buflen, INTEGER flags

	DECLARE INTEGER connect IN ws2_32 AS ws_connect;
		INTEGER s, STRING @sname, INTEGER namelen

	DECLARE INTEGER WSAEventSelect IN ws2_32;
		INTEGER s, INTEGER hEventObject, INTEGER lNetworkEvents

	DECLARE INTEGER WSAWaitForMultipleEvents IN ws2_32;
		INTEGER cEvents, INTEGER @lphEvents, INTEGER fWaitAll,;
		INTEGER dwTimeout, INTEGER fAlertable

	DECLARE RtlMoveMemory IN kernel32 As CopyMemory;
		STRING @Dest, INTEGER Src, INTEGER nLength
ENDDEFINE

FUNCTION buf2dword(lcBuffer)
RETURN Asc(SUBSTR(lcBuffer, 1,1)) + ;
	BitLShift(Asc(SUBSTR(lcBuffer, 2,1)),  8) +;
	BitLShift(Asc(SUBSTR(lcBuffer, 3,1)), 16) +;
	BitLShift(Asc(SUBSTR(lcBuffer, 4,1)), 24)

FUNCTION num2dword(lnValue)
#DEFINE m0  256
#DEFINE m1  65536
#DEFINE m2  16777216
	IF lnValue < 0
		lnValue = 0x100000000 + lnValue
	ENDIF
	LOCAL b0, b1, b2, b3
	b3 = Int(lnValue/m2)
	b2 = Int((lnValue - b3*m2)/m1)
	b1 = Int((lnValue - b3*m2 - b2*m1)/m0)
	b0 = Mod(lnValue, m0)
RETURN Chr(b0)+Chr(b1)+Chr(b2)+Chr(b3)

FUNCTION num2word(lnValue)
RETURN Chr(MOD(m.lnValue,256)) + CHR(INT(m.lnValue/256))  

Listed functions:

WSACleanup
WSACloseEvent
WSACreateEvent
WSAEventSelect
WSAStartup
WSAWaitForMultipleEvents
closesocket
gethostbyname
htons
inet_addr
inet_ntoa
recv
send
socket

Comment:

A screen output will be similar to the following:

Connecting to pop3.fastmail.com, 192.168.0.1 ...
+OK POP3 server ready.

USER UncleJoe
+OK Password required.

PASS checkmail
+OK logged in.

STAT 5 67312

QUIT
+OK POP3 server signing off.

"STAT 5" means that there is 5 messages waiting.


There is a couple of links that might be interesting:

  • STD/STD53. Post Office Protocol - Version 3
  • How to receive an e-mail (Introduction to POP3 protocol)

    #kwd: sln_reademail, sln_winsock