Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations strongm on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

wgcs' Sendmail Program: Error Encoding Attachment File 2

Status
Not open for further replies.

codetyko

Programmer
May 26, 2001
73
MY
I have used wgcs' Sendmail program using mswinsck.ocx as per the faq given in the faq section. However, when an attachment is sent and opened in Netscape 6, the file is still encoded and of course, unreadable. When opened in Opera, the file can be opened but with a message "an error was encountered while encoding file". When retrieved from outlook, there is no problem. Any explanation? TIA.
 
That's a good question... I'm not an expert at encoding/decoding, though I think I understand it pretty well.

The first encoding I put into SendMail used the UUENCODE algorithm... I had borrowed the basic algorithm from some sample code, and converted to VFP. It worked perfectly to send emails retrieved by Eudora, however I found from others that some email programs (such as Outlook) ignored the attachments entirely. Mathematically, it seems absolutely correct, however I don't know why some programs don't like it.

So, then, I converted the attachment handling to use MIME and Base64 encoding, which has been much more successful.

However, this is part of a commercial application, and I hadn't put that code into the open SendMail.prg program....

It takes a somewhat different SMTP header, and a completely different encoding routine (which is, I think, a little slower than UUEncode... though I optimized it alot).

Here is the base code... but it's designed to be inside an object instead of just in the SendMail.PRG ( If you convert it to work in a PRG, please let me know: I'll post the improved SendMail.PRG in the FAQ ... or post your own... just give credit where credit is due....):
Code:
  PROCEDURE SendMailItem( strFrom, strTo, strSubject, strMsg, strBoundary, strAttach )  
  LOCAL lnCnt, laTO[1], lnGoodAddr, lnI, lcOutStr, lcMsg
  EXTERNAL ARRAY arrAtch

    If Not THIS.ReadWrite("RSET ")=250
      THIS.GiveFeedback( [ERROR: Could not RSET to begin.],[(Server Returned "]+THIS.SrvRet+[")])
      RETURN .f.
    endif

    If Not THIS.ReadWrite("MAIL FROM: " + alltrim(strFrom))=250
      THIS.GiveFeedback( [ERROR: "MAIL FROM: "] + alltrim(strFrom) +[" FAILED],[(Server Returned "]+THIS.SrvRet+[")])
      RETURN .f.
    endif
    
    lcMsg = strMsg
*!*	    if not empty(arrAtch) and type('arrAtch[1]')='C'
*!*	      lcMsg = lcMsg + crlf + crlf
*!*	      for lnI = 1 to ALEN(arrAtch,1)
*!*	        lcAtch = FileToStr( arrAtch[lnI] )
*!*	        lcAtch = THIS.UUEncode( arrAtch[lnI], lcAtch )
*!*	      endfor
*!*	      
*!*	      lcMsg = lcMsg + lcAtch
*!*	    endif
    
    lnCnt = aLines(laTo, ChrTran(strTo,' ,;',chr(13)))
    * once for each email address
    lnGoodAddr = 0
    for lnI = 1 to lnCnt
      if not empty(laTo[lnI])
        If THIS.ReadWrite("RCPT TO: " + alltrim(laTo[lnI])) = 250
          lnGoodAddr = lnGoodAddr + 1
        else
          THIS.GiveFeedback( [ERROR: "RCPT TO: "] + alltrim(strFrom) +[" FAILED],[(Server Returned "]+THIS.SrvRet+[")])
          *.. maybe only THIS one failed.
        endif
      endif
    endfor
    if lnGoodAddr=0
      * Throw away this message (RSET=Reset)
      If Not THIS.ReadWrite("RSET")=250
        * Error, it should always reply "250 OK"
      endif
      THIS.GiveFeedback( [ERROR: No addresses were accepted by the server!])
      RETURN .f.
    endif  
    If Not THIS.ReadWrite("DATA")=354
STRTOFILE([--ERROR: "DATA" FAILED (Server Returned "]+THIS.SrvRet+[")]+crlf,'c:\temp\EmailLog.txt',.t.)
      THIS.GiveFeedback( [ERROR: "DATA" FAILED],[(Server Returned "]+THIS.SrvRet+[")])
      RETURN .f.
    endif

    * remove any inadvertant end-of-data marks:
    lcMsg = StrTran(lcMsg, 	crlf+'.'+crlf, 			crlf+'. '+crlf)

    * Build the MIME-compliant email, starting with headers
    lcOutStr = "DATE: " + THIS.GetSmtpDateTime() +crlf;
             + "FROM: " + alltrim(strFrom) + CrLf ;
             + "TO: " + alltrim(strTo) + CrLf ;
             + "SUBJECT: " + alltrim(strSubject) + crlf
    if not empty(strAttach)
      lcOutStr = lcOutStr;            
             + "MIME-Version: 1.0" + crlf ;
             + [Content-type: multipart/mixed;]+crlf;
             + chr(9)+ [boundary="]+strBoundary+["] + crlf
    endif
    
    * Add the Message to the email
    lcOutStr = lcOutStr + crlf + lcMsg
    
    * Finally, Add the contents.
    if not empty(strAttach)
      lcOutStr = lcOutStr + crlf + strAttach
    endif
    
    lcOutStr = lcOutStr + crlf + '--'+strBoundary + '--' + crlf
    
*    THIS.GiveFeedback( [SENDING DATA...], [(Server Returned "]+THIS.SrvRet+[")])
        
    do while not empty(lcOutStr)
      INKEY(0.01)
      if NOT THIS.Write( LEFT(lcOutStr,10000) )
STRTOFILE([--ERROR: MESSAGE DATA FAILED A (Server Returned "]+THIS.SrvRet+[")]+crlf,'c:\temp\EmailLog.txt',.t.)
        THIS.GiveFeedback( [ERROR: MESSAGE DATA FAILED], [(Server Returned "]+THIS.SrvRet+[")])
        RETURN .F.
      endif

      lcOutStr = SubStr(lcOutStr,10001)
    enddo
    
    * Give a brief pause for it all to go
    inkey(0.5)
    * Place end of data mark on end:
    lcOutStr = crlf + "." && crlf always follows    
    
    lnStrt = SECONDS()
    do while lnStrt+20 > seconds()
      inkey(2)
      If THIS.ReadWrite( crlf+crlf+'.' ) = 250
        RETURN .T.
      ENDIF
      THIS.GiveFeedback( [TRYING AGAIN TO END DATA WITH <CRLF>.<CRLF>], ;
                         [(Server Returned &quot;]+THIS.SrvRet+[&quot;)] )
    enddo
    
    
STRTOFILE([--ERROR: MESSAGE DATA FAILED B (Server Returned &quot;]+THIS.SrvRet+[&quot;)]+crlf,'c:\temp\EmailLog.txt',.t.)
    THIS.GiveFeedback( [ERROR: MESSAGE DATA FAILED], [(Server Returned &quot;]+THIS.SrvRet+[&quot;)])
    RETURN .F.
  ENDPROC

 FUNCTION MimeEncode( strName, pcFileData )   
*!*	Content-Type: application/octet-stream; name=&quot;LEX.ico&quot;;
*!*	 x-mac-type=&quot;49434F00&quot;; x-mac-creator=&quot;474B4F4E&quot;
*!*	Content-Transfer-Encoding: base64
*!*	Content-Disposition: attachment; filename=&quot;LEX.ico&quot;
*!*
*!*	AAABAAEAICAQAAAAAADoAgAAFgAAACgAAAAgAAAAQAAAAAEABAAAAAAAgAIAAAAAAAAAAAAAAAAA

*!*	--=====================_14459621==_
*!*	Content-Type: application/octet-stream; name=&quot;ADrive.msk&quot;
*!*	Content-Transfer-Encoding: base64
*!*	Content-Disposition: attachment; filename=&quot;ADrive.msk&quot;
*!*
*!*	Qk1mBAAAAAAAAHYAAAAoAAAALQAAABUAAAABAAQAAAAAAPgBAAAAAAAAAAAAABAAAAAQAAAAAAAA

    LOCAL strFileName, lcOut, strFileData, lcEncoded, lcRecoded
    *Get file name
    strFileName = JustFName(strName)
    if type('pcFileData')='C'
      strFileData = pcFileData
    else
      strFileData = FileToStr(strName)
    endif
    
    lcOut = [Content-Type: application/octet-stream; name=&quot;]+strFileName+[&quot;]+crlf ;
           +[Content-Transfer-Encoding: base64] +crlf;
           +[Content-Disposition: attachment; filename=&quot;]+strFileName+[&quot;]+crlf;
           +crlf
           
    lcEncoded = THIS.EncodeStr64( strFileData )
    lcReCoded = ''
    do while not Empty(lcEncoded)
      lcReCoded = lcReCoded + left(lcEncoded,76) + crlf
      lcEncoded = Substr(lcEncoded,77)
    enddo
    
    RETURN lcOut + lcReCoded
  ENDFUNC  

	Function EncodeStr64(sInput)
	*!*	' Return radix64 encoding of string of binary values
	*!*	' Does not insert CRLFs. Just returns one long string,
	*!*	' so it's up to the user to add line breaks or other formatting.
	*!*	' Version 4: Use Byte array and StrConv - much faster
	* Converted From VB code at: [URL unfurl="true"]http://www.di-mgt.com.au/crypto.html#Base64[/URL]
	  
	  PRIVATE aEncTab
	  *                    1         2         3         1         1         1         1
	  *          01234567890123456789012345678901234567890123456789012345678901234567890
	  aEncTab = [ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/]
	  LOCAL lcOut, nLen, iIndex, lcPart, i, j, nQuants, sLast
	    lcOut = &quot;&quot;
	    nLen = Len(sInput)
	    nQuants = int(len(sInput) / 3)
	    If (nQuants > 0) Then
	        
	        *' Now start reading in 3 bytes at a time
	        For i = 0 To nQuants - 1
	          lcPart = ''
	          For j = 0 To 2
	            lcPart = lcPart + SubStr(sInput, (i*3) + j + 1, 1) 
	          Next
	          lcPart = ThIS.EncodeQuantumB( lcPart )
	          lcOut = lcOut + lcPart
	        Next
	        *EncodeStr64 = StrConv(abOutput, vbUnicode)
*	        lcOut = StrConv(lcOut, 1) && singlebyte -> Dbl
*	        lcOut = StrConv(lcOut, 5) && DblBye -> Unicode
	    EndIf
	    
	    *' Cope with odd bytes
	    *' (no real performance hit by using strings here)
	    do case
	      case nLen%3=0
	        sLast = &quot;&quot;
	      case nLen%3=1
	        sLast = SubStr(sInput, nLen, 1)+chr(0)+chr(0)
	        sLast = THIS.EncodeQuantumB(sLast)
	*        sLast = StrConv(b(, vbUnicode)
*	        sLast = StrConv(sLast,1)
*	        sLast = StrConv(sLast,5)
	        *' Replace last 2 with =
	        sLast = Left(sLast, 2) + &quot;==&quot;
	      case nLen%3=2
	        sLast = SubStr(sInput, nLen - 1, 1)+SubStr(sInput, nLen, 1)+chr(0)
	        sLast = THIS.EncodeQuantumB(sLast)
	*        sLast = StrConv(b(), vbUnicode)
*	        sLast = StrConv(sLast,1)
*	        sLast = StrConv(sLast,5)
	        *' Replace last with =
	        sLast = Left(sLast, 3) + &quot;=&quot;
	    Endcase
	    
	    RETURN lcOut+sLast
	ENDFUNC
	FUNCTION EncodeQuantumB( strIn )
	*' Expects at least 4 bytes in b, i.e. Dim b(3) As Byte
	LOCAL b0,b1,b2,b3
	    b0 = BitAnd( THIS.SHR2(asc(strIn)), 0x3F )
	    b1 = BitOr( THIS.SHL4( BitAnd( ASC(StrIn),          0x03 )), BitAnd(THIS.SHR4(ASC(SubStr(strIn,2))), 0x0F ) )
	    b2 = BitOr( THIS.SHL2( BitAnd( ASC(SubStr(StrIn,2)),0x0F )), BitAnd(THIS.SHR6(Asc(SubStr(strIn,3))), 0x03 ) )
	    b3 = BitAnd( Asc(Substr(strIn,3)), 0x3F )    
	    RETURN Substr(aEncTab,b0+1,1)+Substr(aEncTab,b1+1,1)+Substr(aEncTab,b2+1,1)+Substr(aEncTab,b3+1,1)
	EndFunc
	*' Version 3: ShiftLeft and ShiftRight functions improved.
	Function SHL2(bytValue)
	*' Shift 8-bit value to left by 2 bits
	*' i.e. VB equivalent of &quot;bytValue << 2&quot; in C
	  RETURN BitAnd(bytValue * 0x04, 0xFF)
	EndFunc

	Function SHL4(bytValue)
	*' Shift 8-bit value to left by 4 bits
	*' i.e. VB equivalent of &quot;bytValue << 4&quot; in C
	  RETURN BitAnd(bytValue * 0x10, 0xFF )
	EndFunc

	Function SHL6(bytValue )
	*' Shift 8-bit value to left by 6 bits
	*' i.e. VB equivalent of &quot;bytValue << 6&quot; in C
	  ReTURN BitAnd(bytValue * 0x40, 0xFF)
	EndFunc

	Function SHR2(bytValue )
	*' Shift 8-bit value to right by 2 bits
	*' i.e. VB equivalent of &quot;bytValue >> 2&quot; in C
	  RETURN INT( bytValue / 0x04 )
	EndFunc

	Function SHR4( bytValue )
	*' Shift 8-bit value to right by 4 bits
	*' i.e. VB equivalent of &quot;bytValue >> 4&quot; in C
	  RETURN INT( bytValue / 0x10 )
	ENDFUNC

	Function SHR6( bytValue )
	*' Shift 8-bit value to right by 6 bits
	*' i.e. VB equivalent of &quot;bytValue >> 6&quot; in C
	  RETURN INT( bytValue / 0x40 )
	ENDFUNC
 
wgcs,
sorry for the late response.been away for a while. Thanks for the code. I will have a go at it. Credit should always be given to the code originator because as programmers, only we understand the hardship of achieving a working code.
 
dear wgcs,
in the first line of code:

PROCEDURE SendMailItem( strFrom, strTo, strSubject, strMsg, strBoundary, strAttach )

what is the parameter &quot;strboundary&quot; is refering to?

what I want to do is to maintain the structure of the original sendmail program where in your new procedure above, connection has already been established. If I replace this line:

lcAtch = UUEncode( laAtch[lnI], lcAtch )

with this line

lcAtch = MimeEncode( laAtch[lnI], lcAtch )

strAttach = strAttach + lcAtch
and some minor alterations on variables,

the results are

1) the files are encoded judging by the message generated while the process is executing

2) the message was received

3) the &quot;clip&quot; (to indicate attachment) with the message was visible but the subsequent message received was something like this:

message body
C:\STORE\DATA\PDFFILES\REP1PBQ_4422.PDF

where the attachment string was embedded with the message body instead of being included as individual files.

any feedback is much appreciated.
 
Good question: in MIME, ( Multi-part Internet Main Extensions ), each &quot;part&quot; is separated by a &quot;boundary&quot;.

This is basically an arbitrary string which does not occur inside any of the &quot;parts&quot;.

You can generate something that SHOULD easily be unique enough to be a boundary with something like:

sys(2015)+sys(2015)+sys(2015)

The 'essence' of the boundary must be noted in the MIME header, so if each &quot;part&quot; is encoded separately, the boundary must be selected and passed to the encoder, so all the parts 'match'.

I had missed this bit of code showing putting the pieces together of the MIME encoded message body:
Code:
    lcBoundary = SYS(2015)+sys(2015)
            *     01234567890123 (Preceeded by Chr(13))
    lnAt = ATC(':ATTACHMENTS:',lcMsgBody)
    if lnAt>0
      lcAttach  = SUBSTR(lcMsgBody, lnAt+13)
      lcMsgBody  = LEFT(lcMsgBody,lnAt-1)
      * Validate all attachments
      * This splits several quoted LFN file-paths.
      * Spaces may appear inside the quoted strings
      lnAtch = THIS.QuoteSplit(@laAtch, lcAttach)
      for lnI = 1 to lnAtch
        if ADIR(laFiles,laAtch[lnI])=0 && Attachment doesn't exist
          THIS.CurrentAction = 'Returning Error (emi: attachment missing)'
          THIS.AddActivity('Emails Could NOT be sent: the attachment '+laAtch[lnI];
                                 +'File is missing!' )
          THIS.CurrentAction = 'Giving Feedback (emi: MessageFailed)'
          THIS.GiveFeedback( 'FAILED SENDING EMAILS', 'Attachment file '+laAtch[lnI]+' is Missing!' )
          RETURN THIS.CancelSend()
        else
          * Attachment file exists... try to load the file and add it to the lcAtchBody string.
          oErr = On('ERROR')
          Store 0 to Err,lnHnd
          ON ERROR Err=Error()
            lnHnd = FOPEN(laAtch[lnI],10)
            FCLOSE(lnHnd)
          ON ERROR &oErr

          if err<>0 or lnHnd<=0
            THIS.CurrentAction = 'Returning Error (emi: attachment locked)'
            THIS.AddActivity('Emails Could NOT be sent: the attachment '+laAtch[lnI];
                                   +'Could not be opened!' )
            THIS.CurrentAction = 'Giving Feedback (emi: MessageFailed)'
            THIS.GiveFeedback( 'FAILED SENDING EMAILS', 'Could not open attachment '+laAtch[lnI]+' Error #'+tran(err) )
            RETURN THIS.CancelSend()
          else
            lcAtch = FileToStr( laAtch[lnI] )
*            lcAtchBody = lcAtchBody+crlf+THIS.UUEncode( laAtch[lnI], lcAtch )
            lcAtchBody = lcAtchBody+crlf;
                        +'--'+lcBoundary+crlf;
                        +THIS.MimeEncode( laAtch[lnI], lcAtch ) + crlf            
          endif
        endif
      endfor
    endif
    THIS.GiveFeedback( 'ATTACHMENTS LOADED' )

    if '<HTML>' $ upper(lcMsgBody) or '<X-HTML>' $ upper(lcMsgBody)
      lcMsgBody = crlf+'--'+lcBoundary+crlf;
             +'Content-type: text/html; charset=&quot;us-ascii'+crlf;
             +crlf;
             +lcMsgBody
    else
      lcMsgBody = crlf+'--'+lcBoundary+crlf;
             +'Content-type: text/plain; charset=us-ascii'+crlf;
             +crlf;
             +lcMsgBody
    endif
        IF THIS.OpenSMTPServer(lcSmtpServer)
          THIS.GiveFeedback( 'SMTP SERVER OPENED' )
        Else
          THIS.GiveFeedback( 'ERROR OPENING SMTP SERVER' )
          RETURN THIS.CancelSend()
        ENDIF
      lcSubject = alltrim(THIS.oMsg.oRecord.Subject)
      lcMsg     = lcMsgBody
      lcAddr = ''
      for lnI = 1 to loRecip.Emails_Count
        lcAddr = lcAddr + ' ' + alltrim(loRecip.Emails[lnI])
      endfor
      Res = THIS.SendMailItem( lcFrom, lcAddr, lcSubject, lcMsg, lcBoundary, lcAtchBody )

This was buried in my code, so I missed including it at first, (and still has alot of extraneous stuff in it), but I hope it will help.
 
wgcs,

Star worthy.

Slighthaze = NULL

[ul][li]FAQ184-2483
An excellent guide to getting a fast and accurate response to your questions in this forum.[/li][/ul]
 
Thanks!

With codetyko's help, it'll all be a simple-to-use function, again soon!
 
wgcs,
will study and try to make sense of it all. I'll keep you posted on the success. Thanks for sharing.
 
I modified the sendmail program with the following code to incorporate the MimeEncode function:

lcBoundary = SYS(2015)+sys(2015)
* 01234567890123 (Preceeded by Chr(13))

* Load Attachments
If TYPE('lcAttachments')='C' AND NOT EMPTY(lcAttachments)
lnAtchCnt = ALINES( laAtch, STRTRAN(lcAttachments,',',CHR(13)) )
lcMsg = lcMsg + crlf + crlf
For lnI = 1 TO lnAtchCnt
If ADIR(laFiles,laAtch[lnI])=0
GiveFeedBack( loFB, &quot;ERROR: Attachment Not Found:&quot;+laAtch[lnI] )
failed=.T.
*thisform.errmsg=&quot;ERROR: Attachment Not Found:&quot;+laAtch[lnI]
oProgBar.olecontrol1.stop()
oProgBar.RELEASE()
Release CLASSLIB progress

Return .F.
Endif
lcAtch = FILETOSTR( laAtch[lnI] )
If EMPTY(lcAtch)
GiveFeedBack( loFB, &quot;ERROR: Attachment Empty/Could not ;
be Read:&quot;+laAtch[lnI] )
failed=.T.
oProgBar.olecontrol1.stop()
oProgBar.RELEASE()
Release CLASSLIB progress
Return .F.
Endif

GiveFeedBack( loFB, &quot;Encoding file: &quot;+justfname(laAtch[lnI]) )

strAttach = strAttach+crlf;
+'--'+lcBoundary+crlf;
+MimeEncode( laAtch[lnI], lcAtch ) + crlf
Endfor
GiveFeedBack( loFB, 'ATTACHMENTS LOADED' )
Endif


after the following codes were added:

* remove any inadvertant end-of-data marks:
lcOutStr = STRTRAN(lcOutStr, crlf+'.'+crlf, crlf+'. '+crlf)
* Place end of data mark on end:
lcOutStr = lcOutStr + crlf + &quot;.&quot;

If NOT ReadWrite(Sock,lcOutStr, 354 )
GiveFeedBack( loFB, &quot;Failed DATA (Cont'd)&quot; )
failed=.T.
oProgBar.olecontrol1.stop()
oProgBar.RELEASE()
Release CLASSLIB progress
Exit && Leave Control Loop
Endif


GiveFeedBack( loFB, &quot;Almost Finished&quot; )
If NOT ReadWrite(Sock,&quot;QUIT&quot;, 250)
GiveFeedBack( loFB, &quot;Failed QUIT&quot; )
failed=.T.
*thisform.errmsg=&quot;Failed QUIT&quot;
oProgBar.olecontrol1.stop()
oProgBar.RELEASE()
Release CLASSLIB progress
Exit && Leave Control Loop
Endif

GiveFeedBack( loFB, &quot;Email Sent!&quot; )
failed=.F.
llRet = .T.
Exit && Leave Control Loop
Enddo



this is the resultant lcOutstr :



DATE: Fri, 19 Sep 2003 14:28:35 +0.05
FROM: ma-rw09@tm.net.my
TO: codetyko@time.net.my
SUBJECT: subject of email
MIME-Version: 1.0
Content-type: multipart/mixed;
boundary=&quot;_11V0V0Y9C_11V0V0Y9D&quot;

&quot;the message&quot;



--_11V0V0YE2_11V0V0YE3
Content-Type: application/octet-stream; name=&quot;_X_UNIQ.CDX&quot;
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename=&quot;_X_UNIQ.CDX&quot;

AAQAAAAAAAAAAAAACgDgAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA


--_11V0V0Y9C_11V0V0Y9D--

.


(note the &quot;period&quot; above)


the question is:

the message went through but only the subject appears but no message body or content. But the paper clip to indicate attachment was visible.

if there is no attachment sent, the message went through with an additional &quot;boundary&quot; like this:

the message
--_11V0V0YE2_11V0V0YE3



any comments? another point worth mentioning is the use of the &quot;write&quot; function like the following:

do while not empty(lcOutStr)
INKEY(0.01)

if NOT THIS.Write( LEFT(lcOutStr,10000) )
STRTOFILE([--ERROR: MESSAGE DATA FAILED A (Server Returned &quot;]+THIS.SrvRet+[&quot;)]+crlf,'c:\temp\EmailLog.txt',.t.)
THIS.GiveFeedback( [ERROR: MESSAGE DATA FAILED], [(Server Returned &quot;]+THIS.SrvRet+[&quot;)])
RETURN .F.
endif

lcOutStr = SubStr(lcOutStr,10001)
enddo


why the requirement to extract 10001 characters from lcOutstr? TIA
 
wgcs,
relooked at the code and found out that the boundary was not the same. It seems that there are two types of boundaries being declared. Rectified the error and the attachment went through. However, the &quot;message body&quot; will not be shown if there is any attachment sent. No problem found when no attachment sent. I reproduced the output string for lcOutput(shortened where necessary) :

DATE: Sat, 20 Sep 2003 16:49:11 +0.05
FROM: mx-rw09@tm.net.my
TO: max-rw09@tm.net.my
SUBJECT: this is the email subject
MIME-Version: 1.0
Content-type: multipart/mixed;
boundary=&quot;_11W101SNR_11W101SNS&quot;

this is the email message body that did not show up



--_11W101SNR_11W101SNS
Content-Type: application/octet-stream; name=&quot;_X_UNIQ.CDX&quot;
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename=&quot;_X_UNIQ.CDX&quot;

AAQAAAAAAAAAAAAACgDgAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AQD//////////+AB//8AAA8PEAQEAwAGUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgABAAAABgBpZHRh
ZwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAD/
/////////+gB//8AAA8PEAQEAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA


--_11W101SNR_11W101SNS
Content-Type: application/octet-stream; name=&quot;_X_UNIQ.DBF&quot;
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename=&quot;_X_UNIQ.DBF&quot;

MAMGGgAAAABoARgAAAAAAAAAAAAAAAAAAAAAAAADAABJRFRBRwAAAAAAAEMBAAAACgAAAAAAAAAA
AAAAAAAAAElETlVNAAAAAAAATgsAAAANAAAAAAAAAAAAAAAAAAAADQAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAA


--_11W101SNR_11W101SNS--

.


Any feedback would be much appreciated. The question on the use of the &quot;write&quot; function like the following will still apply:

do while not empty(lcOutStr)
INKEY(0.01)
if NOT THIS.Write( LEFT(lcOutStr,10000) )
STRTOFILE([--ERROR: MESSAGE DATA FAILED A (Server Returned &quot;]+THIS.SrvRet+[&quot;)]+crlf,'c:\temp\EmailLog.txt',.t.)
THIS.GiveFeedback( [ERROR: MESSAGE DATA FAILED], [(Server Returned &quot;]+THIS.SrvRet+[&quot;)])
RETURN .F.
endif

lcOutStr = SubStr(lcOutStr,10001)
enddo


why the requirement to extract 10001 characters from lcOutstr? TIA
 
The reason the body of the message doesn't show up is because once you use MIME, you have to MIME the whole email. The text before the first MIME boundary will only show up in non-MIME compliant email clients.

The message body is the first MIME part, and must start with the boundary and the MIME header, like this:

(See after the email sample for the answer to the SUBSTR question)
Code:
DATE: Sat, 20 Sep 2003 16:49:11 +0.05
FROM: mx-rw09@tm.net.my
TO: max-rw09@tm.net.my
SUBJECT: this is the email subject
MIME-Version: 1.0
Content-type: multipart/mixed;
    boundary=&quot;_11W101SNR_11W101SNS&quot;

this is the email message body that will only show up in non-MIME email clients

--_11W101SNR_11W101SNS
Content-type: text/plain; charset=us-ascii

this is the email message body that will show up


--_11W101SNR_11W101SNS
Content-Type: application/octet-stream; name=&quot;_X_UNIQ.CDX&quot;
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename=&quot;_X_UNIQ.CDX&quot;

AAQAAAAAAAAAAAAACgDgAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

-------------
As for the SUBSTR(lcOutStr,10001), the reason for the loop at all is so you can provide feedback during the send-mail operation. Otherwise, VFP ends up blocked on the SendData call in the Write() method for however long it takes to send the one big chunk (which could easily be 10minutes or longer with dial-up) and there wouldn't even be a way to cancel the send.

As for it being 10001 instead of 10000... it might not be intuitive, but this is how it works:

LEFT(str,X) takes the first X number of characters.

SUBSTR(str,X,Y) takes Y characters starting at char X
SUBSTR(str,X) takes ALL remaining characters, starting at char X

So, if you send: LEFT(str,1), then the next to go is SUBSTR(str,2).

If you send: LEFT(str,10), the next to go is SUBSTR(str,11)

And, similarly, if you send the first 10,000 chars
( LEFT(str,10000)
the next to go is SUBSTR(str,10001)

(I'm sure this explanation is MORE than sufficient... at some point, probably early in it, the &quot;Oh, Yeah!&quot; hit....)
 
wgcs,

many thanks for the reply. My mistake of not checking variable references properly. Now it works perfectly. Doing further testing before posting the revised version. With full credit to you, probably we could make this an &quot;open source&quot; project for the foxpro community. Anybody can contribute to improve it such as adding email retrieval capability, autodial if there is no connection and so on and create an alternative to MS outlook. Regards.
 
Sounds good: There's already a page I started for SendMail at:

Which might be a good collaboration point.

AnatoliyMogylevets contributed a version that doesn't require the MsWinSck.OCX at all, but uses winSock directly, and is class-based instead of function-based.
 

If anyone has any sample code to enable "bcc"'s with this sendmail, i would be most grateful.

cheers
 
All that BCC's should need is an additional header line, starting with BCC. You can do this by simply appending a CRLF (chr(13)+chr(10)) to the subject, followed by the additional headers. Essentially, the directions given at for another SMTP tool should work here (the only difference being that the subject in SendMail is a parameter, not a property):
How do I send "cc" and "bcc" with your SMTP control?
You can add "cc", "bcc" and any other headers at the end of MessageSubject. For example:
Code:
SMTP1.MessageSubject = "Order Confirmation" & vbCrLf & _
      "CC: orders@mycompany.com" & vbCrLf & _
      "BCC: admin@mycompany.com" & vbCrLf & _
      "Reply-To: webmaster@mycompany.com"
 
Hi,

I think there is an bug in the UUEncode routine for attachments. The bug effects the last line of encoded data. It will only manifest itself when the last line length of the data is not divisible by 3:

Code:
    *Add first symbol to encoded string that informs
    *about quantity of symbols in encoded string.
    *More often "M" symbol is used.
    
    strTempLine = Chr(Len(strChunk) + 32)

In the code above you insert a character to indicate the length of the encoded line but then, straight after:

Code:
    If i = lEncodedLines And (Len(strChunk) % 3<>0) Then
      *If the last line is processed and length of
      *source data is not a number divisible by 3,
      *add one or two blankspace symbols
      strChunk = strChunk + Space( 3 -(Len(strChunk) % 3) )
    endif

... you increase the length of the string (if not divisible by 3). From what I know of UUEncoding (which, to be honest, you could write on the back of a stamp) this invalidates the last line.

I swapped these two pieces of code around and it fixed a corruption problem I was having opening attachments in outlook.

Hope that is of some help to someone.
Hugs n kisses,
ATTITW
 
Thanks for helping with this: I found that the original VB code that I adapted is designed identically (so that the length character indicates actual data instead of the 6-bit characters)
and I just found some pascal source that seems to support your findings:
This page indicates that there is no formal specification, but acceptance by Outlook (since it's so common) is probably a good enough judgement of correctness. I fixed the FAQ.

According to this page: it would be worth converting to using Base64 instead of uuencode....
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top