1. --****************************************************************************
  2. -- Software: POP3_CLASS
  3. -- Version:  0.5
  4. -- Date:     2011-11-04
  5. -- Author:   Valentin Schmidt
  6. -- Contact:  fluxus@freenet.de
  7. -- License:  Freeware
  8. --
  9. -- Requirements/Dependencies:
  10. -- + Multiuser Xtra
  11. -- + Base64 encoder class (internally either using a fast xtra like e.g. Crypto Xtra or slow lingo code)
  12. --
  13. --****************************************************************************
  14.  
  15. -- public
  16. property pServer
  17. property pPort
  18. property pUser
  19. property pPass
  20.  
  21. property pDeleteFlag
  22. property pHeaderOnly
  23.  
  24. property pCallbackHandler
  25. property pCallbackTarget
  26.  
  27. -- private
  28. property pMUXtra
  29.  
  30. property pMailCount
  31. property pMailNum
  32. property pMails
  33.  
  34. property pStatus           -- Status of messaging
  35.  
  36. property pEOL
  37. property pLF
  38. property pCR
  39.  
  40. property pMaxLines -- lines of msg when header only
  41.  
  42. property pBase64Encoder
  43.  
  44. ----------------------------------------------------
  45. -- CONSTRUCTOR
  46. ----------------------------------------------------
  47. on new (me, server, port, user, pw)
  48.   pServer = server
  49.   pPort = port
  50.   pUser = user
  51.   pPass = pw
  52.  
  53.   pLF = numToChar(10)
  54.   pCR = numToChar(13) -- RETURN
  55.   pEOL = pCR & pLF
  56.  
  57.   pMaxLines = 10
  58.  
  59.   pBase64Encoder = script("BASE64_CLASS").new()
  60.  
  61.   return me
  62. end
  63.  
  64. ----------------------------------------------------
  65. -- Start connection
  66. ----------------------------------------------------
  67. on getMails (me, deleteFlag, headerOnly, callbackHandler, callbackTarget)
  68.   pDeleteFlag = deleteFlag
  69.   pHeaderOnly = headerOnly
  70.  
  71.   pCallbackHandler = callbackHandler
  72.   pCallbackTarget = callbackTarget
  73.   if voidP(pCallbackTarget) then pCallbackTarget = _movie
  74.  
  75.   pMails=[]
  76.   pMailCount = 0
  77.   pMailNum = 0
  78.   pMUXtra = new(xtra "Multiuser")
  79.  
  80.   pStatus = "START"
  81.  
  82.   -- Set default message handler for ALL messages
  83.   errCode = pMUXtra.setNetMessageHandler( #defaultMsg, me)
  84.   me.CheckError( errCode )
  85.  
  86.   -- Set a message handler to get the initial logon reponse, keyword is the last param
  87.   errCode = pMUXtra.setNetMessageHandler( #firstConnect, me, "connectToNetServer" )
  88.   me.CheckError( errCode )
  89.  
  90.   -- Connect to the server in TEXT mode (note the 1 as the last parameter)
  91.   errCode = pMUXtra.connectToNetServer( "x", "x", pServer, pPort, "x", 1)
  92.   me.CheckError( errCode )
  93. end
  94.  
  95. ----------------------------------------------------
  96. -- Send messages after we first connect
  97. ----------------------------------------------------
  98. on firstConnect (me)
  99.   netMessage = pMUXtra.getNetMessage()
  100.   me.dbg("Connection Established")
  101. end
  102.  
  103. ----------------------------------------------------
  104. -- process received messages
  105. ----------------------------------------------------
  106. on defaultMsg (me)
  107.   netMessage = pMUXtra.getNetMessage()  
  108.   msg = str_replace(pLF, "", netMessage.content) -- CR only
  109.   if the last char of msg=pCR then delete the last char of msg
  110.   if msg="x" then return
  111.  
  112.   case (pStatus) of
  113.     "LIST":      
  114.       if the last char of msg="." then
  115.         --if msg.line.count>2 then pMailCount = msg.line.count-2
  116.         pMailCount = msg.line.count-1
  117.         if msg.line[1] starts "+OK" then pMailCount=pMailCount-1
  118.         pMailNum=0
  119.         me.dbg("NUMBER OF MAILS:" && pMailCount)
  120.         repeat with i = 1 to pMailCount
  121.           pMails[i]=""
  122.         end repeat
  123.         me.nextMessage()
  124.       end if
  125.      
  126.     "RETR","TOP":
  127.       pMails[pMailNum]=pMails[pMailNum] & msg
  128.      
  129.       if the last char of msg="." then
  130.         mail=pMails[pMailNum]
  131.        
  132.         delete the last line of mail
  133.         if mail starts "+OK" then delete char 1 to 3 of mail
  134.         if mail.line[1].word.count=0 then delete line 1 of mail
  135.         --        if (mail.line[1] starts "+OK ") then delete line 1 of mail --msg
  136.        
  137.         pMails[pMailNum]=mail
  138.        
  139.         header=me.getHeader(pMailNum)
  140.         me.dbg("--------------------")
  141.         me.dbg("MAIL" &&  pMailNum)
  142.         me.dbg("FROM:" && me.getFrom(pMailNum))
  143.         me.dbg("SUBJECT:" && me.getSubject(pMailNum))
  144.         -- me.dbg("BODY:" && me.getBody(pMailNum))
  145.         me.dbg("--------------------")
  146.        
  147.         me.nextMessage()
  148.        
  149.       end if
  150.      
  151.     "QUIT":
  152.       me.clearMU()
  153.       if NOT voidP(pCallbackHandler) then
  154.         call(pCallbackHandler, [pCallbackTarget], me)
  155.       end if
  156.      
  157.     otherwise:
  158.       me.nextMessage()
  159.   end case
  160.  
  161. end
  162.  
  163. ----------------------------------------------------
  164. --  send messages
  165. ----------------------------------------------------
  166. on nextMessage (me)
  167.   -- "START","USER" && pUser,"PASS" && pPass,"LIST","RETR","DELE","QUIT"
  168.  
  169.   case (pStatus) of
  170.     "START":
  171.       com = "USER" && pUser
  172.       pStatus = "USER"
  173.      
  174.     "USER":
  175.       com = "PASS" && pPass
  176.       pStatus = "PASS"
  177.      
  178.     "PASS":
  179.       com = "LIST"
  180.       pStatus = "LIST"
  181.      
  182.     "LIST":
  183.       if pMailCount>0 then
  184.         pMailNum=1
  185.         if pHeaderOnly then
  186.           com = "TOP 1" && pMaxLines
  187.           pStatus = "TOP"
  188.         else
  189.           com = "RETR 1"
  190.           pStatus = "RETR"
  191.         end if
  192.       else
  193.         com = "QUIT"
  194.         pStatus = "QUIT"
  195.       end if
  196.      
  197.     "RETR","TOP":
  198.       if pDeleteFlag then
  199.         com =  "DELE" && pMailNum
  200.         pStatus = "DELE"
  201.       else
  202.         if pMailNum<pMailCount then
  203.           pMailNum=pMailNum+1
  204.           if pHeaderOnly then
  205.             com = "TOP" && pMailNum && pMaxLines
  206.             pStatus = "TOP"
  207.           else
  208.             com = "RETR" && pMailNum
  209.             pStatus = "RETR"
  210.           end if
  211.         else
  212.           com = "QUIT"
  213.           pStatus = "QUIT"
  214.         end if
  215.       end if
  216.      
  217.     "DELE":
  218.       if pMailNum<pMailCount then
  219.         pMailNum=pMailNum+1
  220.         if pHeaderOnly then
  221.           com = "TOP" && pMailNum && pMaxLines
  222.           pStatus = "TOP"
  223.         else
  224.           com = "RETR" && pMailNum
  225.           pStatus = "RETR"
  226.         end if
  227.       else
  228.         com = "QUIT"
  229.         pStatus = "QUIT"
  230.       end if
  231.   end case
  232.  
  233.   errCode = pMUXtra.sendNetMessage( "", "", com & pEOL)
  234.   me.dbg("SEND" && com)
  235.   me.CheckError( errCode )
  236. end
  237.  
  238. ----------------------------------------------------
  239. -- clear out netMessageHandler callbacks and void instance
  240. ----------------------------------------------------
  241. on clearMU (me)
  242.   -- clear all callbacks by setting #handlerSymbol parameter to 0
  243.   errCode = pMUXtra.setNetMessageHandler(0, me)
  244.   errCode = pMUXtra.setNetMessageHandler(0, me, "ConnectToNetServer")
  245.  
  246.   -- clear instance of the Multiuser xtra
  247.   pMUXtra = VOID
  248.   me.dbg("CLOSE MUXtra")
  249. end
  250.  
  251. ----------------------------------------------------
  252. -- Display errors
  253. ----------------------------------------------------
  254. on CheckError (me, errCode)
  255.   if ( errCode <> 0 ) then
  256.     me.dbg("Error " & string( errCode ) & " or: " & pMUXtra.GetNetErrorString( errCode ))
  257.   end if
  258. end
  259.  
  260. ----------------------------------------------------
  261. -- shows network status information
  262. ----------------------------------------------------
  263. on dbg (me, s)
  264.   put "POP:" && s -- delete/comment line for silent execution
  265. end
  266.  
  267. ----------------------------------------------------
  268. -- MAIL UTILITIES
  269. ----------------------------------------------------
  270.  
  271. ----------------------------------------------------
  272. -- returns full email
  273. ----------------------------------------------------
  274. on getMsg (me, msgNum)
  275.   return pMails[msgNum]
  276. end
  277.  
  278. ----------------------------------------------------
  279. -- returns mail header
  280. ----------------------------------------------------
  281. on getHeader (me, msgNum)
  282.   msg=pMails[msgNum]
  283.   return msg.char[1..offset(pCR&pCR,msg)-1]
  284. end
  285.  
  286. ----------------------------------------
  287. -- returns mail body
  288. ----------------------------------------
  289. on getBody (me, msgNum)
  290.   msg=pMails[msgNum]
  291.   return msg.char[offset(pCR&pCR,msg)+2..msg.length] -- -2]
  292. end
  293.  
  294. ----------------------------------------
  295. -- returns mail from
  296. ----------------------------------------
  297. on getFrom (me, msgNum)
  298.   msg=pMails[msgNum]
  299.   von = offset("From:",msg)+6
  300.   msg=msg.char[von..msg.length-2]
  301.   from = msg.line[1]
  302.   if from contains "<" then
  303.     from=from.char[offset("<",from)+1..offset(">",from)-1]
  304.   end if
  305.   return from
  306. end
  307.  
  308. ----------------------------------------
  309. -- returns mail subject
  310. ----------------------------------------
  311. on getSubject (me, msgNum)
  312.   msg=pMails[msgNum]
  313.   von = offset("Subject:",msg)+9
  314.   msg=msg.char[von..msg.length-2]
  315.   sub = msg.line[1]
  316.   return sub
  317. end
  318.  
  319. ----------------------------------------
  320. --
  321. ----------------------------------------
  322. on isMimeMail (me, msgNum)
  323.   msg=pMails[msgNum]
  324.   return (msg contains "MIME-Version:")
  325. end
  326.  
  327. ----------------------------------------
  328. -- extracts mail attachments from MIME mail
  329. -- returns list of files as property list: filename - > data (bytearray)
  330. ----------------------------------------
  331. on getMimeFiles (me, msgNum)
  332.   msg=pMails[msgNum]
  333.   r=numtochar(13)
  334.  
  335.   -- get boundary
  336.   von = offset("boundary=",msg)+10
  337.   bis = von + offset(QUOTE,msg.char[von..msg.length])-2
  338.   bound = "--" & msg.char[von..bis]
  339.  
  340.   files = [:]
  341.   parts = []
  342.  
  343.   repeat with i = 1 to msg.line.count
  344.     if msg.line[i] starts bound then parts.add(i)
  345.   end repeat
  346.  
  347.   repeat with i = 1 to (parts.count-1)
  348.     part=msg.line[parts[i]+1..parts[i+1]-1]
  349.     if part contains "Content-Disposition: attachment;" then
  350.       von = offset("filename=",part)+10
  351.       bis = von + offset(QUOTE,part.char[von..part.length])-2
  352.       fn = part.char[von..bis]
  353.      
  354.       von = offset(r&r,part)+2
  355.       data = part.char[von..part.length-1]
  356.      
  357.       data = str_replace(pCR, "", data) -- ???
  358.      
  359.       ba = pBase64Encoder.decode(data)
  360.      
  361.       files.addProp(fn, ba)
  362.      
  363.     end if
  364.   end repeat
  365.   return files
  366. end
  367.  
  368. ----------------------------------------
  369. -- extracts Text from MIME mail
  370. ----------------------------------------
  371. on getMimeText (me, msgNum)
  372.   msg=pMails[msgNum]
  373.   r=numtochar(13)
  374.  
  375.   -- get boundary
  376.   von = offset("boundary=",msg)+10
  377.   bis = von + offset(QUOTE,msg.char[von..msg.length])-2
  378.   bound = "--" & msg.char[von..bis]
  379.  
  380.   parts=[]
  381.   repeat with i = 1 to msg.line.count
  382.     if msg.line[i] starts bound then parts.add(i)
  383.   end repeat
  384.  
  385.   repeat with i = 1 to (parts.count-1)
  386.     part=msg.line[parts[i]+1..parts[i+1]-1]
  387.     if part contains "Content-Type: text/plain;" then
  388.       return part.char[(offset(r&r,part)+2)..part.length-1]
  389.     end if
  390.   end repeat
  391.  
  392.   return ""
  393. end
  394.  
  395. ----------------------------------------
  396. -- extracts HTML from MIME mail
  397. ----------------------------------------
  398. on getMimeHtml (me, msgNum)
  399.   msg=pMails[msgNum]
  400.   r=numtochar(13)
  401.  
  402.   -- get boundary
  403.   von = offset("boundary=",msg)+10
  404.   bis = von + offset(QUOTE,msg.char[von..msg.length])-2
  405.   bound = "--" & msg.char[von..bis]
  406.  
  407.   parts=[]
  408.   repeat with i = 1 to msg.line.count
  409.     if msg.line[i] starts bound then parts.add(i)
  410.   end repeat
  411.  
  412.   repeat with i = 1 to (parts.count-1)
  413.     part=msg.line[parts[i]+1..parts[i+1]-1]
  414.     if part contains "Content-Type: text/html;" then
  415.       return part.char[(offset(r&r,part)+2)..part.length-1]
  416.     end if
  417.   end repeat
  418.  
  419.   return ""
  420. end
  421.  
  422. ----------------------------------------
  423. -- replace in string
  424. ----------------------------------------
  425. on str_replace (stringToFind, stringToInsert, input)
  426.   output = ""
  427.   findLen = stringToFind.length - 1
  428.   repeat while true
  429.     currOffset = offset(stringToFind, input)
  430.     if currOffset=0 then exit repeat
  431.     put input.char [1..currOffset] after output
  432.     delete the last char of output
  433.     put stringToInsert after output
  434.     delete input.char [1.. (currOffset + findLen)]
  435.   end repeat
  436.   put input after output
  437.   return output
  438. end
  439.  
  440.  
[raw code]