Program Hypermail C Hypermail provides a web interface to OpenVMS mail index files C and list archives by reading the index directly and generating C HTML on the fly. It interacts as a DECnet task with the OSU C DECthreads web server and is accessed and controlled via URLs. C C All occurrances of <, >, and & within mail messages and most file C or path names are escaped. For displays of a specific message, C the previous and following messages in the folder (if any) are C provided as a link. Obvious hypertext references, i.e. those C starting with a recognized scheme, such as "http://", are linked C as well as displayed. C C Written by: C C Jonathan S. Boswell , April, 1997, while performing C duties for the US Food and Drug Administration. This code may be C used for any purposes whatsoever, but the author disclaims any C responsibility for the use or abuse of HYPERMAIL, including but C not limited to failure of the software to perform as advertised, C or any/all compromised security and/or corruption of databases, C or loss of small countries or other apocalyptic events. (That is, C you're on your own.) C C Special acknowlegement goes to Kevin Ashley, author of MLSEARCH.FOR C for many excellent pointers and his framework for accessing the VMS C callable mail interface from Fortran. C C Modification history: C C V0.8 April 4, 1997 IOC C V0.9 April 7, 1997 Added forward, backward, and MAILTO links to C mail message. C V1.0 April 8, 1997 Corrected zero-length string bug in Get_Records. C Corrected message number field length bug in C code for adding links. Added "Return" link to C subject list on all message displays. Added C buffer to Print_Folder_Name to prevent string C overflow while escaping dangerous chars. C V1.1 April 14, 1997 Added subject (thread) search capability. C V1.2 April 16, 1997 Added process logical name translation code for C replacement of banners (Level 1,2 headers) with C user-supplied values, which could include any C markup, including . C HYPERMAIL$H1 and HYPERMAIL$H2 are translated. C Rearrange COMMON block to avoid alignment C warnings on Alpha. C V1.3 April 29, 1997 Add ~username URL syntax to return specified C user's default mailfile directory. Detect and C provide links for likely hypertext references C within mail message body. Prohibit access to C WASTEBASKET folder. Highlight marked mail on C subject list. C V1.4 July 22, 1997 Added links to bottom of message display. Added C link for some types of DECnet mail, including a C DECnet subnet addresses with embedded quotes. C Added new customization facility via special header/ C footer email messages in folder "Archive Customization". C Added escape/unescape code to handle spaces, %, #, and C quote characters in foldernames. C C Installation: C C Place this program and it's include file in your webserver account's C SCRIPT_CODE directory (e.g. [http_server.script_code]) and compile it. C Example: $ FORTRAN/EXTEND_SOURCE HYPERMAIL C $ LINK/NOTRACEBACK HYPERMAIL C If no errors are encountered, place the executable in the webserver C account's HTBIN directory (e.g. [http_server.htbin]). You're done. C C Example use: C C HYPERMAIL can be used to access OpenVMS mail archives with the C following sample URLs and their resulting action. C C http://server.domain.name/htbin/hypermail C http://server.domain.name/htbin/hypermail/ C http://server.domain.name/htbin/hypermail/. C http://server.domain.name/htbin/hypermail/./ C C Access webserver account's default mail index file C in that account's default mail subdirectory, and C return a list of folders therein. Notice that for C better archive security and (silent) administration C by listserver managers, the folders MAIL, NEWMAIL, C and WASTEBASKET are not accessible through HYPERMAIL. C C http://server.domain.name/htbin/hypermail/folder C http://server.domain.name/htbin/hypermail/folder/ C C Access default mail file as above, but return a list C of message authors/subjects in folder . C C http://server.domain.name/htbin/hypermail/folder/messageID C http://server.domain.name/htbin/hypermail/folder/messageID/ C C Access default mail file as above, but return the C specific message in folder . C C http://server.domain.name/htbin/hypermail/folder?thread C http://server.domain.name/htbin/hypermail/folder?thread/ C http://server.domain.name/htbin/hypermail/folder?thread/messageID C http://server.domain.name/htbin/hypermail/folder?thread/messageID/ C C Access default mail file as above, but return a list of C only those message subjects (or single message ) C in folder which contain the string . C C http://server.domain.name/htbin/hypermail/folder/?search C http://server.domain.name/htbin/hypermail/folder/?search/ C C Access default mail file as above, but return a list C of message subjects in folder which contain C the message string anywhere in any message C body in that folder. C ***MESSAGE BODY SEARCH NOT YET AVAILABLE**** C C http://server.domain.name/htbin/hypermail/.alt C http://server.domain.name/htbin/hypermail/.alt/ C http://server.domain.name/htbin/hypermail/.alt/folder C http://server.domain.name/htbin/hypermail/.alt/folder/ C http://server.domain.name/htbin/hypermail/.alt/folder?thread C http://server.domain.name/htbin/hypermail/.alt/folder?thread/ C http://server.domain.name/htbin/hypermail/.alt/folder/message C http://server.domain.name/htbin/hypermail/.alt/folder/message/ C http://server.domain.name/htbin/hypermail/.alt/folder?thread/message C http://server.domain.name/htbin/hypermail/.alt/folder?thread/message/ C C Access alternate mail file index in webserver C account's default mailfile directory, and return either C list of folders, subjects in specified folder , C subjects in specified thread , or specific message C . C C http://server.domain.name/htbin/hypermail/.. C http://server.domain.name/htbin/hypermail/../ C C Access webserver account's login directory, open mail.mai C and return list of folders. C C http://server.domain.name/htbin/hypermail/..dev:[dir.subdir.etc]alt C http://server.domain.name/htbin/hypermail/..dev:[dir.subdir.etc]alt/ C http://server.domain.name/htbin/hypermail/..dev:[dir.subdir.etc]alt/folder C http://server.domain.name/htbin/hypermail/..dev:[dir.subdir.etc]alt/folder/ C http://server.domain.name/htbin/hypermail/..dev:[dir.subdir.etc]alt/folder?thread C http://server.domain.name/htbin/hypermail/..dev:[dir.subdir.etc]alt/folder?thead/ C http://server.domain.name/htbin/hypermail/..dev:[dir.subdir.etc]alt/folder/message C http://server.domain.name/htbin/hypermail/..dev:[dir.subdir.etc]alt/folder/message/ C http://server.domain.name/htbin/hypermail/..dev:[dir.subdir.etc]alt/folder?thread/message C http://server.domain.name/htbin/hypermail/..dev:[dir.subdir.etc]alt/folder?thread/message/ C C Access alternate mailfile index located in , C and return list of folders, or subjects in specified folder , or C subjects in specified thread , or specific message . C C http://server.domain.name/htbin/hypermail/~username C http://server.domain.name/htbin/hypermail/~username/ C http://server.domain.name/htbin/hypermail/~username/folder C http://server.domain.name/htbin/hypermail/~username/folder/ C http://server.domain.name/htbin/hypermail/~username/folder?thread C http://server.domain.name/htbin/hypermail/~username/folder?thread/ C http://server.domain.name/htbin/hypermail/~username/folder/message C http://server.domain.name/htbin/hypermail/~username/folder/message/ C http://server.domain.name/htbin/hypermail/~username/folder?thread/message C http://server.domain.name/htbin/hypermail/~username/folder?thread/message/ C C Access alternate mailfile index located in default mailfile directory C of user and return list of folders, or subjects in specified C folder , or subjects in specified thread , or specific C message . Whew! (This operation requires SYSNAM privilege.) C C SECURITY NOTE: The OpenVMS callable mail interface is insufficiently C flexible in the sense that it will not open mail index files READONLY. C You would like to do this if you are going to have Hypermail read another C user's mail. C C There are several workarounds: C C 1) Give ownership of all mail archives to the webserver account C 2) Patch a local copy of MAILSHR such that it will open files READONLY C 3) Set up ACLs for all mail archives and grant RW access from webserver C 5) Install the HYPERMAIL image with privilege C 6) Give the webserver account elevated privilege C 7) Open up mail archive for RW to group which includes webserver C C The author has gone with #2 since it's easy, works well, is C trivial to manage, and most importantly does not give write C access to important mail archives from a possibly hacked or C otherwise compromised webserver account. C C Main program opens DECnet channel back to web server, parses the URL, C and calls the relevant subroutines to process each request. Implicit None Common/hypermail/RqurlL,FolderL,QueryL,Requested_URL,Folder,Query Character*4096 Requested_URL Character*255 def_dir,mailfile,folder,query Character*8 message Character*16 Folder_Type/'threaded folder '/ Data Mailfile,Folder,Query,Message/4*' '/ Integer I,J,RqurlL,def_dirL,mailfileL,folderL,queryl,messageL,messageID,Str$Len,Str$Element,Status,File_Context Include '($strdef)' C Connect to network link established by WWWEXEC.COM Open (unit=1, file='net_link:', status='old', 1 carriagecontrol='list', recl=4096) Write(1,10) '' 10 Format(a) Write(1,10) '' Read (1,20,end=30) RqurlL,Requested_URL 20 Format(q,a) 30 Write(1,10) '' Write(1,10) 'status: 200 Sending document' Write(1,10) 'content-type: text/html' Write(1,10) !Yes, that's right! A null record... Take THAT, C. Write(1,10) 'HYPERMAIL V1.4' Call Banner !Discover if logicals HYPERMAIL$H1,2 are set, put up headers. C Add trailing slash for convenience with str$element function processing C and use in forming links. If (Requested_URL(RqurlL:RqurlL) .ne. '/') then RqurlL=RqurlL+1 If(RqurlL .gt. len(Requested_URL)) call abort('Pathologically long URL. Aborting.') Requested_URL(RqurlL:RqurlL)='/' Endif C Assume 1st part of path is /htbin/hypermail/. Get next string. Status = str$element(mailfile,3,'/',Requested_URL(:RqurlL)) If (status .eq. str$_noelem) then !No delimiters left. Call Abort('Bad (confusingly mapped?) URL.') !This can't happen with trailing "/" added above. Call exit Elseif (.not. status) then Call StatusCheck(status) Else C 1st parse went OK. This string could be either an alternate C mailfile path or a folder name. If (mailfile(1:2) .eq. '..') then C This should be a complete mailfile path. Get rid of dots. Mailfile=mailfile(3:) C User could have entered nothing after .. preamble. MailfileL = Str$Len( mailfile) If(mailfileL .eq. 0) then !Specify default mailfile. Mailfile='sys$login:mail.mai' Endif Elseif (mailfile(1:1) .eq. '.') then C This is partial mailfile path. Get rid of dot. Mailfile=mailfile(2:) Call GetDefMailDir(Def_Dir,Def_DirL) C User could have entered nothing after . preamble. MailfileL = Str$Len( mailfile) If(mailfileL .eq. 0) then Mailfile=Def_Dir(:Def_DirL)//'mail.mai' Else !Borrow Def_Dir for temp storage. Def_dir(Def_DirL+1:)=mailfile(:mailfileL) Mailfile=Def_Dir Endif Elseif (mailfile(1:1) .eq. '~') then C This is ~username path, not mailfile. Find out where user's default mailfile is. Mailfile = mailfile(2:) !Get rid of tilda. C Trap for case nothing entered after ~ preamble. MailfileL = Str$Len(mailfile) If(MailfileL .eq. 0) Call Abort('No username specified in URL. A valid username after ~ is required.') Call GetUserMailDir(Mailfile(:MailfileL),Def_Dir,Def_DirL) MailFile = Def_Dir(:Def_DirL)//'mail.mai' Elseif (mailfile(1:1) .eq. ' ') then C Nothing left! (No mailfile path nor folder name.) Call GetDefMailDir(Def_Dir,Def_DirL) MailFile = Def_Dir(:Def_DirL)//'mail.mai' Else !Folder string present in mailfile string. Folder=mailfile Call GetDefMailDir(Def_Dir,Def_DirL) MailFile = Def_Dir(:Def_DirL)//'mail.mai' Endif Endif MailfileL = Str$Len( mailfile) C At this point, we have the desired mail index file stored in the C "mailfile" variable. Attempt to open it. Call Open_Mail(MailFile(:mailfileL),File_Context) C Parse the URL string again for folder and message number. FolderL = Str$Len(folder) If (folderL .eq. 0) then C Either we haven't yet parsed for folder following mailfile, C or else user entered nothing beyond call to HYPERMAIL. C (Or else they entered a null path element "//", somehow.) C In any case, parse 4th element in URL. Status = str$element(folder,4,'/',Requested_URL(:RqurlL)) If (status .eq. str$_noelem) then !No delimiters left. C We're done. Insure that folder is still cleared. Folder=' ' Elseif (status) then !Folder now holds selection. C But we may still have message number in 5th element. Status = str$element(message,5,'/',Requested_URL(:RqurlL)) If (status .eq. str$_noelem) then !No delimiters left. Message = ' ' Elseif (status) then !We now have message. Else Call StatusCheck(status) Endif Else Call StatusCheck(status) Endif Else !Folder already loaded. C Get message number from 4th element in URL. Status = str$element(message,4,'/',Requested_URL(:RqurlL)) If (status .eq. str$_noelem) then !No delimiters left. Message = ' ' Elseif (status) then !We now have message. Else Call StatusCheck(status) Endif Endif C Process thread query string, if any. This is indicated by C presence of "?" in Folder string. I = Index(Folder,'?') If (I .ne. 0) then !Thread query is present. J = Index(Folder,'=') !Locate beginning of query string. If (J .eq. 0) then !Alternate syntax w/o "thread=" Query = Folder (I+1:) !Load query string. Else !Normal syntax from submitted form. Query = Folder (J+1:) !Load query string. Endif Folder = Folder(:I-1) !Clear folder string of query. Endif C Trap for blank query string, remove spurious "?" from Requested_URL. QueryL = Str$Len(Query) I = Index(Requested_URL,'?') If (QueryL .eq. 0 .and. I .ne. 0) then J = max(Index(Requested_URL,'='),I) Requested_URL (I:) = Requested_URL (J+1:) RqurlL = RqurlL - 1 - J + I Endif C Replace the "+" with spaces. Do I=1,QueryL If (Query(I:I) .eq. '+') Query(I:I) = ' ' Enddo C Process message body search string, if any. This is indicated C by presence of "?" in Message string. MessageL=Str$Len(message) If(message(:1) .eq. '?') then !Body search requested. Call Abort('Body search not yet implemented.') Message = ' ' MessageL = 0 Endif C Trap requests for forbidden folders. If(Folder.eq.'MAIL') Call Abort('Access to old (read) mail is forbidden.') If(Folder.eq.'NEWMAIL') Call Abort('Access to new mail is forbidden.') If(Folder.eq.'WASTEBASKET') Call Abort('Access to wastebasket is forbidden.') Call Unescape_URL(Folder) FolderL=Str$Len(Folder) D Write(1,*)'
File:',Mailfile(:MailfileL) D Write(1,*)'
Folder:',Folder(:FolderL) D Write(1,*)'
Query:',Query(:QueryL) D Write(1,*)'
Message:',Message(:MessageL) C Inject header info, unescaped, from body of message 1 in this file's C Archive Customization folder, if found. I = -1 Call Get_Message(File_Context,I) C And select "folder" or "threaded folder" message. If (QueryL .eq. 0) then J=10 Else J=1 Endif C Finally get down to business. If (folderL .eq. 0) then !Return list of folders. Write(1,*)'

Returning list of folders in archive ',mailfile(:mailfileL),'.
' Call Get_Folders(File_Context) Elseif (messageL .eq. 0) then !Return list of subjects in folder. Write(1,*)'

Returning list of subjects in ',folder_type(J:),folder(:folderL),' of archive ',mailfile(:mailfileL),'.
' Call Get_Message_Subjects(File_Context) Else !Return specific message. Write(1,*)'

Returning message ',Message(:MessageL),' in ',folder_type(J:),folder(:folderL),'.
' Read (message(:messageL),*, err=100) messageID If (MessageID.le.0)Call Abort('MessageID must be greater than zero.') Call Get_Message(File_Context,messageID) Endif C Inject footer info, unescaped, from body of message 2 in this file's C Archive Customization folder, if found. If (I .gt. 1) then !Footer present in folder. I = -2 Call Get_Message(File_Context,I) Endif Call Close_mail(File_Context) Write(1,10) '' Write(1,10) '' Call exit 100 Call Abort('MessageID must be numeric.') End Subroutine Banner C Customize presentation by replacing the default headers with C user specified input via the process logical names HYPERMAIL$H1 C and HYPERMAIL$H2. These strings are deliberately not escaped C to allow injection of valid markup, such as inlined GIFs. Implicit None Character*255 Buffer Integer BufferL,Status,Sys$Trnlnm Include '($LNMDEF)' Include '($SSDEF)' Include 'Hypermail.inc' InputItem(1).Code = LNM$_String InputItem(1).Blen = Len(Buffer) InputItem(1).Bufadr = %Loc(Buffer) InputItem(1).Retlenadr = %loc(BufferL) InputItem(2) = NullItem Status = sys$trnlnm (,'LNM$PROCESS_TABLE','HYPERMAIL$H1',,InputItem) If (Status .eq. SS$_NOLOGNAM) then !Write default H1 header. Write (1,*) '

Hypermail

' Else !Use replacement. Call StatusCheck (Status) Write (1,*) '

',Buffer(:BufferL),'

' Endif Status = sys$trnlnm (,'LNM$PROCESS_TABLE','HYPERMAIL$H2',,InputItem) If (Status .eq. SS$_NOLOGNAM) then !Write default H2. Write (1,*) '

Hypertext Access to OpenVMS Mail Archives

' Else !Use replacement. Call StatusCheck (Status) Write (1,*) '

',Buffer(:BufferL),'

' Endif Return End Subroutine GetDefMailDir(Maildir,MaildirL) Implicit None Character*(*) Maildir Integer MaildirL,Mail$User_Begin,Mail$User_End,UserContext Include '($MAILDEF)' Include 'Hypermail.inc' UserContext = 0 InputItem(1) = NullItem OutputItem(1).Bufadr = %Loc(Maildir) OutputItem(1).Blen = Len(Maildir) OutputItem(1).Retlenadr = %Loc(MaildirL) OutputItem(1).Code = Mail$_User_Full_Directory OutputItem(2) = NullItem Call StatusCheck(Mail$User_Begin(UserContext,InputItem,OutputItem)) Call StatusCheck(Mail$User_End(UserContext,NullItem,NullItem)) Return End Subroutine GetUserMailDir(Username,Maildir,MaildirL) Implicit None Character*(*) Username,Maildir Integer MaildirL,Mail$User_Begin,Mail$User_End,Mail$User_Get_Info,UserContext Include '($MAILDEF)' Include 'Hypermail.inc' UserContext = 0 Call StatusCheck(Mail$User_Begin(UserContext,NullItem,NullItem)) InputItem(1).Code = Mail$_User_Username InputItem(1).Bufadr = %Loc(Username) InputItem(1).Blen = Len(Username) InputItem(2) = NullItem OutputItem(1).Code = Mail$_User_Full_Directory OutputItem(1).Bufadr = %Loc(Maildir) OutputItem(1).Blen = Len(Maildir) OutputItem(1).Retlenadr = %Loc(MaildirL) OutputItem(2) = NullItem Call StatusCheck(Mail$User_Get_Info(UserContext,InputItem,OutputItem)) Call StatusCheck(Mail$User_End(UserContext,NullItem,NullItem)) Return End Subroutine Open_Mail(Filename,File_context) Implicit None Character*(*) Filename Integer File_context,Mail$Mailfile_Begin,Mail$Mailfile_Open Include '($MAILDEF)' Include 'Hypermail.inc' InputItem(1).Code = Mail$_Mailfile_Name InputItem(1).Blen = Len(Filename) InputItem(1).Bufadr = %Loc(Filename) InputItem(2) = NullItem OutputItem(1) = NullItem Call StatusCheck(Mail$Mailfile_Begin(File_context,NullItem,NullItem)) Call StatusCheck(Mail$Mailfile_Open(File_context,InputItem,OutputItem)) Return End Subroutine Close_mail(File_context) Implicit None Integer File_context,Mail$Mailfile_Close,Mail$Mailfile_End Include '($MAILDEF)' Include 'Hypermail.inc' C Close up current file Call StatusCheck(Mail$Mailfile_Close(File_context,NullItem,NullItem)) Call StatusCheck(Mail$Mailfile_End(File_context,NullItem,NullItem)) Return End Subroutine Get_Folders(File_Context) Implicit None External Print_Folder_Name Integer Status,File_Context,Mail$Mailfile_Info_File Include '($MAILDEF)' Include 'Hypermail.inc' C Ask the mailer to call Print_Folder_Name for each folder. InputItem(1).Code = Mail$_Mailfile_Folder_Routine InputItem(1).Blen = 4 InputItem(1).Bufadr = %Loc(Print_Folder_Name) InputItem(2).Code = Mail$_Mailfile_User_Data InputItem(2).Blen = 4 InputItem(2).Bufadr = %Loc(File_Context) InputItem(3) = NullItem Write(1,*)'

Archive folders:

    ' Status = Mail$Mailfile_Info_File(File_Context,InputItem,NullItem) Call StatusCheck(Status) Write(1,*)'
' Return End Integer Function Print_Folder_Name(File_Context,Folder_String) Implicit None Common/hypermail/RqurlL,FolderL,QueryL,Requested_URL,Folder,Query Character*4096 Requested_URL Character*255 Folder,Query Character Folder_String*(*) Integer File_Context,RqurlL,folderL,QueryL,Str$Len If (len(Folder_String) .ne. 0) Then Folder = Folder_String !Borrow Folder to avoid overflowing Folder_String. Call Escape_Special_Chars (Folder) !Replace HTML-unsafe characters. FolderL = Str$Len(Folder) If (Folder.eq.'MAIL'.or.folder.eq.'NEWMAIL'.or.folder.eq.'WASTEBASKET') then Write(1,*)'
  • ',Folder(:folderL) Elseif (Folder.ne.'Archive Customization') then Query = Folder_String !Borrow Query string since it's not needed now. Call Escape_URL (Query) !Replace URL-unsafe characters. QueryL = Str$Len(Query) Write(1,*)'
  • ',Folder(:folderL),'' Endif Endif Print_Folder_Name = 1 Return End Subroutine Get_Message_Subjects(File_Context) Implicit None Common/hypermail/RqurlL,FolderL,QueryL,Requested_URL,Folder,Query Character*4096 Requested_URL Character*2048 Output Character*255 Folder,Query,From,Subj,Date Character*6 Str$LJ_Integer Integer File_Context,Message_Context,RqurlL,FolderL,QueryL Integer FromL,SubjL,DateL,Str$Len,Nmsg,Message,Size,I Integer*2 Flags Integer Mail$Message_end,Mail$Message_Info Include '($MAILDEF)' Include 'Hypermail.inc' C Find the end of this URL, sans query if any, or trailing slash. I = Index(Requested_URL, '?') If (I .eq. 0) then I = RqurlL - 1 Else I = I-1 Endif C Add thread query form. Write(1,*)'
    ' Write(1,*)'Select subjects including ' Write(1,*)'
    ' C Add link back to full subject display. If (QueryL .ne. 0) Write(1,*)'Reselect all subjects.' C Locate end of last field for backlink. Do I=RqurlL-1,1,-1 !Scan backwards. If(Requested_URL(I:I) .eq. '/') goto 40 Enddo 40 Write(1,*)'Go back to ' Write(1,*)'list of folders in current file.' Nmsg = 0 !Insure normal (not header/footer) selection. Call Select_Messages(File_Context,Message_Context,Nmsg) InputItem(1).Code = Mail$_Message_ID InputItem(1).Blen = 4 InputItem(1).Bufadr = %Loc(Message) InputItem(2) = NullItem OutputItem(1).Code = Mail$_Message_From OutputItem(1).Blen = Len(From) OutputItem(1).Bufadr = %Loc(From) OutputItem(1).Retlenadr = %loc(Froml) OutputItem(2).Code = Mail$_Message_Subject OutputItem(2).Blen = Len(Subj) OutputItem(2).Bufadr = %Loc(Subj) OutputItem(2).Retlenadr = %loc(Subjl) OutputItem(3).Code = Mail$_Message_Date OutputItem(3).Blen = Len(Date) OutputItem(3).Bufadr = %loc(date) OutputItem(3).Retlenadr = %loc(Datel) OutputItem(4).Code = Mail$_Message_Size OutputItem(4).Blen = 4 OutputItem(4).Bufadr = %loc(Size) OutputItem(4).Retlenadr = 0 OutputItem(5).Code = Mail$_Message_Return_Flags OutputItem(5).Blen = 2 OutputItem(5).Bufadr = %loc(Flags) OutputItem(5).Retlenadr = 0 OutputItem(6) = NullItem If (QueryL .eq. 0) then Write(1,*)'
    Number of messages: ',Nmsg,'
    ' Else Write(1,*)'
    Number of message subjects containing "',Query,'":',Nmsg,'
    ' Endif Write(1,*)'
      ' Do Message = 1,Nmsg Output = Requested_URL(:RqurlL)//Str$LJ_Integer(message) Write(1,*)'
    1. Subject: ' Call StatusCheck(Mail$Message_Info(Message_context,InputItem,OutputItem)) If(subjL.gt.0)then Output= Subj(:subjL) Call Escape_Special_Chars (Output) If((flags.and.MAIL$M_MARKED) .ne. 0)then Write(1,*)'',Output(:Str$Len(Output)),'
      ' Else Write(1,*)Output(:Str$Len(Output)),'
      ' Endif Else Write(1,*)'[No subject]
      ' Endif Output= 'From: '//From(:FromL) Call Escape_Special_Chars (Output) Call Add_Mailto_Link(Output) Write(1,*)Output(:Str$Len(Output)),'
      Date: ',Date(:DateL),'
      Lines:',Size Enddo Write(1,*)'
    ' C Close message context. Call StatusCheck(Mail$Message_End(Message_context,NullItem,NullItem)) Return End Subroutine Get_Message(File_Context,Message) C Retrieve from folder Folder. Special header/footer C processing indicated by Message = -1 for header, -2 for footer. Implicit None Common/hypermail/RqurlL,FolderL,QueryL,Requested_URL,Folder,Query Character*4096 Requested_URL Character*255 folder,query Character*(6) Str$LJ_Integer, Buffer Integer File_Context,Message_Context,Message,RqurlL,FolderL,QueryL,Size,Nmsg,ID,Str$Len Integer Mail$Message_end,Mail$Message_Get Logical Customized Include '($MAILDEF)' Include 'Hypermail.inc' If (Message .lt. 0) then Customized = .true. Nmsg = Message !Flag for Select_Messages Message = abs(Message) Else Customized = .false. Nmsg = 0 !Insure normal operation. Endif Call Select_Messages(File_Context,Message_Context,Nmsg) If (Message .gt. Nmsg) then If (Customized) return !No customization messages present. Call Abort('Folder doesn''t contain that many messages.') Endif If (Customized) Goto 2 !Skip link addition. We're doing headers/footers. C Add link to previous message. First, trim URL of current message number. Do RqurlL=RqurlL-1,1,-1 !Scan backwards. If(Requested_URL(RqurlL:RqurlL) .eq. '/') goto 1 Enddo 1 If (Message .ne. 1) then Buffer = Str$LJ_Integer(message-1) Write(1,*)'Go to previous message in folder.
    ' Endif If (QueryL .eq. 0) then Write(1,*)'Go back to current folder''s subject list.
    ' Else Write(1,*)'Go back to current folder''s threaded subject list.
    ' Endif If (Message .ne. Nmsg) then Buffer = Str$LJ_Integer(message+1) Write(1,*)'Go to next message in folder.
    ' Endif 2 InputItem(1) = NullItem InputItem(1).Code = Mail$_Message_ID InputItem(1).Blen = 4 InputItem(1).Bufadr = %Loc(Message) InputItem(2) = NullItem OutputItem(1).Code = Mail$_Message_Size OutputItem(1).Blen = 4 OutputItem(1).Bufadr = %loc(Size) OutputItem(1).Retlenadr = 0 OutputItem(2).Code = Mail$_Message_Current_ID OutputItem(2).Blen = 4 OutputItem(2).Bufadr = %loc(ID) OutputItem(2).Retlenadr = 0 OutputItem(3) = NullItem Call StatusCheck(Mail$Message_Get(Message_context,InputItem,OutputItem)) Call Get_Records(Message_Context,Size,Customized) C Close message context. Call StatusCheck(Mail$Message_End(Message_context,NullItem,NullItem)) If (Customized) then !Don't add navigation footers twice. Message = Nmsg !Report Nmsg back to main program. Return Elseif (Message .ne. Nmsg) then !Add navigation links to bottom. Write(1,*)'
    Go to next message in folder.
    ' Endif Return End Subroutine Select_Messages(File_Context,Message_Context,Nmsg) C Select specific messages from open mailfile. Special operating C mode for subject threads indicated by non-zero QueryL. Special C operating mode for header/footer retrieval indicated by negative C Nmsg. Nmsg = -1 indicates header; Nmsg = -2 indictes footer. Implicit None Common/hypermail/RqurlL,FolderL,QueryL,Requested_URL,Folder,Query Character*4096 Requested_URL Character*255 Folder,Query Character*21 Customize_Folder/'Archive Customization'/ Integer File_Context,Nmsg,Message_Context,Status,RqurlL,FolderL,QueryL Integer Mail$Message_Begin,Mail$Message_Select Logical Customized Include '($MAILDEF)' Include 'Hypermail.inc' C Set up message context. Message_context = 0 InputItem(1).Code = Mail$_Message_file_ctx InputItem(1).Blen = 4 InputItem(1).Bufadr = %loc(File_Context) InputItem(1).Retlenadr = 0 InputItem(2) = NullItem OutputItem(1) = NullItem Call StatusCheck(Mail$Message_Begin(Message_context,InputItem,OutputItem)) C Select all messages in folder, or only specific messages with given C subject substring. InputItem(1).Code = Mail$_Message_Folder If (Nmsg.lt.0) then !We're in header/footer mode. Customized = .true. InputItem(1).Blen = 21 InputItem(1).Bufadr = %loc(Customize_Folder) Else !Normal mode. Customized = .false. InputItem(1).Blen = FolderL InputItem(1).Bufadr = %loc(Folder) Endif If (Customized) then !Select all messages. InputItem(2).Code = Mail$_NoSignal InputItem(3) = NullItem Elseif (QueryL.eq.0) then !Select all messages. InputItem(2) = NullItem Else !Select only subjects with . InputItem(2).Code = Mail$_Message_Subj_Substring InputItem(2).Blen = QueryL InputItem(2).Bufadr = %Loc(Query) InputItem(3) = NullItem Endif OutputItem(1).Code = Mail$_Message_selected OutputItem(1).Blen = 4 OutputItem(1).Bufadr = %loc(Nmsg) OutputItem(1).Retlenadr = 0 OutputItem(2) = Nullitem Status = Mail$Message_Select(Message_Context,InputItem,OutputItem) If (.not. status) then !No such folder. If (Customized) then !No customization folder. Continue silently. Nmsg = 0 !Folder w/o messages not possible. Else !Specified folder not found. Drop dead. Call Abort('Folder not found. Possibly case is wrong.') Endif Endif Return End Subroutine Get_Records(Message_context,Nrecs,Customized) C Grab the next record from the message. Implicit None Character Rec*255, Output*2048 Integer Message_context,Nrecs,MRtype,RecL,I Logical Customized Include '($Maildef)' Integer Mail$Message_Get, Str$Len Include 'Hypermail.inc' If (.not. Customized) then Write(1,*)'
    Lines:',Nrecs,'
    ' Write(1,10)'
    '
    	Endif
    
    	InputItem(1).Code = Mail$_Message_Continue
    	InputItem(2) = NullItem
    	OutputItem(1).Code = Mail$_Message_Record
    	OutputItem(1).Blen = Len(Rec)
    	OutputItem(1).Bufadr = %Loc(Rec)
    	OutputItem(1).Retlenadr = %Loc(RecL)
    	OutputItem(2).Code = Mail$_Message_Record_Type
    	OutputItem(2).Blen = 2
    	OutputItem(2).Bufadr = %Loc(MRtype)
    	OutputItem(3) = NullItem
    
    	Do I=1,Nrecs
    1	   Call StatusCheck(Mail$Message_Get(Message_context,InputItem,OutputItem))
    
    	   If (RecL .gt. 0) then
    	      If (Str$Len(Rec) .gt. 0) then
    	         Output = Rec(:RecL)	!Need larger buffer for adding hypertext links.
    
    	         If (Customized) then	!Skip mail headers; this is a header/footer message.
    	            If (MRtype .eq. Mail$_Message_Header) Goto 1
    	         Else			!Regular message.
    	            Call Escape_Special_Chars (Output)	!Escape HTML characters.
    	            If (MRtype .eq. Mail$_Message_Header. and. Output(:5) .eq. 'From:') then
    	               Call Add_Mailto_Link (Output)	!Removes SMTP% string.
    	            Else
    	               Call Add_Links (Output)		!All other links.  SMTP% remains.
    	            Endif
    	         Endif
    	         Write(1,10)Output(:max(Str$Len(Output),1))
    	      Else	!Null record.
    	         Write(1,10)
    	      Endif
    	   Else	!Null record.
    	      Write(1,10)
    	   Endif
    
    	   If (MRtype .eq. Mail$_Message_Header) Go to 1 !Not part of Nrecs count.
    	Enddo
    
    	If (.not. Customized) Write(1,10)'
    ' Return 10 Format(a) End Subroutine Add_Links (String) Implicit none Character*2048 String,Upcase Integer I, Iskip, Ibegin, Iend, Scheme, Status, SchemeL(11) Integer Str$Find_First_Substring,Str$Concat,Str$Upcase Data SchemeL/9,9,7,6,5,7,7,8,7,7,1/ Iskip = 1 !Initialize for loop over multiple links/line. C See if there are any of the following likely URL strings (left) on this line. 1 Status = str$find_first_substring(String(Iskip:), Ibegin, Scheme, 1 'telnet://', 'gopher://', 'http://', 'ftp://', 'news:', 2 'mailto:', 'wais://', 'https://', 'nntp://', 'file://', '@') If (Ibegin .eq. 0) then !No more lowercase URLs left. C Check to make sure we didn't miss something due to case sensitivity. Status = Str$Upcase(Upcase,String) Status = str$find_first_substring(Upcase(Iskip:), Ibegin, Scheme, 1 'TELNET://', 'GOPHER://', 'HTTP://', 'FTP://', 'NEWS:', 2 'MAILTO:', 'WAIS://', 'HTTPS://', 'NNTP://', 'FILE://') If (Ibegin .eq. 0) then !No more URLs left. Return Endif Endif C We have a likely URL. Correct Ibegin for offset. Ibegin = Iskip + Ibegin - 1 C Find the end of this URL by scanning for termination characters. Status = Str$Find_First_Substring(String(Ibegin+SchemeL(Scheme):),Iend, I, 1 '! ', '. ', ', ', ': ', '; ', '] ', !Punctuation followed by 2 '! ', '. ', ', ', ': ', '; ', '] ', ! ...by 3 ' ', ' ', ! or themselves 4 ')', '}', '>', '"', '''') If (Iend .eq. 0) then !Good heavens! We've overflowed string. Return !Give up (but continue silently). Elseif (Iend .eq. 1) then !Bare scheme. Skip this incomplete URL. Iskip = Ibegin + SchemeL(Scheme) Goto 1 Else !Characters present. Correct Iend for offset. Iend = Ibegin + SchemeL(Scheme) + Iend - 2 Endif If (Scheme .eq. 11) then !We've got a possible email address. C Scan backwards to find beginning of address. Do Ibegin=Ibegin,1,-1 If(String(Ibegin:Ibegin) .eq. ' ' .or. 1 String(Ibegin:Ibegin) .eq. ' ' .or. 2 String(Ibegin:Ibegin) .eq. ';' .or. 3 String(Ibegin:Ibegin) .eq. '"' .or. 4 String(Ibegin:Ibegin) .eq. '''' .or. 5 String(Ibegin:Ibegin) .eq. '{' .or. 6 String(Ibegin:Ibegin) .eq. '[' .or. 7 String(Ibegin:Ibegin) .eq. '(') Goto 2 Enddo Ibegin = 0 !Oops. Hit beginning of string. 2 Ibegin = Ibegin + 1 !Get past the terminator we just found. If (String(Ibegin:Ibegin) .eq. '@') then !Assume address is invalid. Iskip = Iend + 1 Goto 1 Endif C Do special check for Message-ID header line and skip it. If (Index(String(:Ibegin),'Message-ID:') .ne. 0) then Iskip = Iend + 1 Goto 1 Endif Endif C Let's insert the terminating anchor while we still know where it goes. Call StatusCheck(str$concat(String,String(:Iend),'',String(Iend+1:Len(String)-4))) C Finally add href anchor ahead of this URL. If (Scheme .eq. 11) then !We have to add mailto: link ahead of address. If (Ibegin .eq. 1) then !We're starting out in column 1. Iskip = Iend + 22 !Set up to skip this portion of line. Call StatusCheck(Str$Concat(String,'',String(:Len(String)-Iskip))) Else !Concatenate beginning of string too. Iskip = Iend - Ibegin + 23!Set up to skip this portion of line. Call StatusCheck(Str$Concat(String,String(:Ibegin-1), 1 '', 2 String(Ibegin:Len(String)-Iskip))) Endif Else if (Ibegin .eq. 1) then !We're starting out in column 1. Iskip = Iend + 15 !Set up to skip this portion of line. Call StatusCheck(Str$Concat(String,'', 1 String(:Len(String)-Iskip))) Else !Concatenate beginning of string too. Iskip = Iend - Ibegin + 16 !Set up to skip this portion of line. Call StatusCheck(Str$Concat(String,String(:Ibegin-1), 1 '', 2 String(Ibegin:Len(String)-Iskip))) Endif Iskip = Iskip + Iend !Start next scan beyond current link. Goto 1 !Check for more URLs on this line. End Subroutine Add_Mailto_Link (String) Implicit none Character String*(*), Address*80 Integer I, Ibegin, Iend, Status Integer Str$Find_First_Substring,Str$Replace,Str$Len C Get rid of the evil SMTP%, and all it's demon spawn. Status = Str$Find_First_Substring(String,Ibegin,I,'SMTP%"','MX%"','IN%"') If (Ibegin .eq. 0) then !Possibly a DECnet node address. Ibegin = Index(String,'::') If (Ibegin .eq. 0) Return !Give up. Don't know what this is. C Flip DECnet address around so that node::user becomes user@node. C The latter will still work for 1-hop DECnet nodes and even IP nodes C in the same domain as "node". Iend = Index(String(7:),' ')+5 !Stop before personal name If (Iend.eq.0) Iend = Str$Len(String)!No personal name after address. C Rearrange ordering of username and nodename. String(7:) = String(Ibegin+2:Iend)//'@'//String(7:Ibegin-1)//String(Iend+1:Str$Len(string)) C Add a terminating anchor while we still know where it goes. Status = Str$Replace(String,String,Iend,Iend,' ') Address=String(7:Iend-1) !We shortened string by 1 character. C Finally add mailto link anchor. Status = Str$Replace(String,String,6,6,' ') Return Else !We have an email address. Find the final quote. Iend = Index(String(Ibegin+6:),'"') + Ibegin + 5 If (String(Iend+1:Iend+1) .eq. '@') then !Probable DECnet subaddress. Iend = Index(String(Iend+1:),'"') + Iend !Find final quote (we hope). Endif Endif C Let's replace the last quote with a terminating anchor while we still know where it is. Status = Str$Replace(String,String,Iend,Iend,'') C Next replace the beginning SMTP%" preamble with the address anchor. If (I .eq. 1) then !Replace SMTP%". Address=String(Ibegin+6:Iend-1) Call Escape_URL(Address) Status = Str$Replace(String,String,Ibegin,Ibegin+5,'') Else !Replace MX%" or IN%". Address=String(Ibegin+4:Iend-1) Call Escape_URL(Address) Status = Str$Replace(String,String,Ibegin,Ibegin+3,'') Endif Return End Subroutine Escape_Special_Chars (String) C Escape dangerous Characters <, >, and &. Implicit none Character*(*) String Integer I, Status, Ibegin, Iend Integer Str$Len,str$find_first_in_set,Str$Replace Include '($strdef)' Iend = Str$Len(String) If (Iend .eq. 0) Return !If string is all blanks. Ibegin = 1 1 I=str$find_first_in_set(String(Ibegin:Iend),'<>&') If (I .eq. 0) Return Ibegin = Ibegin+I-1 if (String(Ibegin:Ibegin) .eq. '<') then Status = Str$Replace(String,String,Ibegin,Ibegin,'<') Iend = Iend+3 else if (String(Ibegin:Ibegin) .eq. '>') then Status = Str$Replace(String,String,Ibegin,Ibegin,'>') Iend = Iend+3 else Status = Str$Replace(String,String,Ibegin,Ibegin,'&') Iend = Iend+4 endif C Get out past the ampersand we just added! Ibegin = Ibegin+3 Goto 1 End Subroutine Escape_URL (String) C Escape (some) dangerous URL characters: , %, #, and ". Implicit none Character*(*) String Integer I, Status, Ibegin, Iend Integer Str$Len,str$find_first_in_set,Str$Replace Include '($strdef)' Iend = Str$Len(String) If (Iend .eq. 0) Return !If string is all blanks. Ibegin = 1 1 I=str$find_first_in_set(String(Ibegin:Iend),' "#%') If (I .eq. 0) Return Ibegin = Ibegin+I-1 if (String(Ibegin:Ibegin) .eq. ' ') then Status = Str$Replace(String,String,Ibegin,Ibegin,'%20') Iend = Iend+2 else if (String(Ibegin:Ibegin) .eq. '"') then Status = Str$Replace(String,String,Ibegin,Ibegin,'"') Iend = Iend+5 else if (String(Ibegin:Ibegin) .eq. '#') then Status = Str$Replace(String,String,Ibegin,Ibegin,'%23') Iend = Iend+2 else !Must be % itself. Status = Str$Replace(String,String,Ibegin,Ibegin,'%25') Iend = Iend+2 endif C Get out past the % we just added. Ibegin = Ibegin+2 Goto 1 End Subroutine Unescape_URL (String) Implicit none Character*(*) String Character*(1) Replacement(4)/' ','#','%','"'/ Integer I, Ibegin, Iskip, Status Integer Str$Find_First_Substring, Str$Replace Iskip = 1 !Initialize for loop over multiple escaped chars/line. C See if there are any escaped characters on this line. 1 Status = str$find_first_substring(String(Iskip:), Ibegin, I, '%20','%23','%25','"') If (Ibegin .eq. 0) Return !Done. C We have another escaped char. Ibegin = Iskip + Ibegin - 1 !Correct Ibegin for offset. If (I .ne. 4) then !Skip 3 chars. Status = Str$Replace(String,String,Ibegin,Ibegin+2,Replacement(I)(:)) Else !Skip 6 chars. Status = Str$Replace(String,String,Ibegin,Ibegin+5,Replacement(I)(:)) Endif Iskip = Iskip + 1 !Start next scan beyond current char. Goto 1 !Check for more escaped chars on this line. End Character*(*) Function Str$LJ_Integer(N) C Function returns left-justified character string converted from integer argument. Integer I, N, str$find_first_in_set Write(Str$LJ_Integer,'(i6)')N I = str$find_first_in_set(Str$LJ_Integer,'0123456789-') Str$LJ_Integer=Str$LJ_Integer(I:) Return End Integer Function Str$Len(String) C Return length of string to last non-blank character. C If input string is all blanks, return zero. Character*(*) String Do Str$Len=len(string),1,-1 If ((string(Str$Len:Str$Len) .ne. ' ') .and. 1 (string(Str$Len:Str$Len) .ne. ' ')) Return Enddo End Subroutine Abort(Error_Message) Implicit None Character*(*) Error_Message Write(1,10)'
    Fatal error:' Write(1,10)Error_Message Write(1,10)'
    ' Write(1,10)'' Call Exit 10 Format(a) End Subroutine StatusCheck(Status) Implicit None Integer Status,MessL Character*132 Message If(.Not. Status) then Write(1,10)'
    Fatal error:' Call Sys$Getmsg(%val(Status),MessL,Message,,) Write(1,10),Message(:MessL) Write(1,10),'
    ' Write(1,10) '' Call Exit Endif Return 10 Format(a) End