Alaska Software Inc. - WAA session manager
Username: Password:
AuthorTopic: WAA session manager
Allen Lee WAA session manager
on Wed, 13 Apr 2016 23:00:55 -0700
Given that:
1. the WAA 1.90.331 session manager will assign a WAA session to the 
wrong user (PDR 6483)
2. under certain circumstances, information that is stored in a WAA 
session carries information that stems from a wrong connection
3. the problem occurs when the same WAA package is requested from more 
then one client within a short time frame

Has anyone developed their own session manager that replaces the default 
WAA session manager?

Can anyone, who experienced this problem in WAA, say that the problem no 
longer exists in CXP?
Thomas BraunRe: WAA session manager
on Thu, 14 Apr 2016 08:38:15 +0200
Allen Lee wrote:

> Has anyone developed their own session manager that replaces the default 
> WAA session manager?

Yes. I did.

But AFAIK, the WAA session manage uses cookies to identify the client,
while I don't, I use session keys that are posted with each web page, so it
is no drop-in replacement.

Thomas
Allen Lee Re: WAA session manager
on Thu, 14 Apr 2016 01:01:59 -0700
On 4/13/2016 11:38 PM, Thomas Braun wrote:
> Allen Lee wrote:
>
>> Has anyone developed their own session manager that replaces the default
>> WAA session manager?
>
> Yes. I did.
>
> But AFAIK, the WAA session manage uses cookies to identify the client,
> while I don't, I use session keys that are posted with each web page, so it
> is no drop-in replacement.
>
> Thomas
>
By session keys, do you mean that you store parameters in an array via 
oContext:setCargo and then pass the parameters to the next page via 
oContext:getCargo?

If so, what method do you use to create a unique identifier for each user?
Thomas BraunRe: WAA session manager
on Thu, 14 Apr 2016 17:41:13 +0200
Allen Lee wrote:

> By session keys, do you mean that you store parameters in an array via 

No, the session key is a unique identifier which is part of each page
submitted:

<input type="hidden" value="6VkLK5Hs4txCrMxc2Y3fKanDZQCLMKe9Z6A" name="SID">

If not part of the page, a new one is created and then stored in a
"session.dbf" file at the first contact/login of the user:

{"KEY", "C",64,0},;
{"EXPIRES","C",12,0},;
{"USERID","C",3,0},;
{"DATA","M",4,0} }

"DATA" holds the cargo contents.

This is the code I use to replace setCargo/getCargo etc., maybe you can use
parts of it:

"base" is the base class for my own HTML3 class... at the bottom are some
helper function you will need as well...hope this helps 

Thomas

METHOD Base:openSession(nExpireMinutes, cUserId)
   local cCookie := ::GetVar("SID")
   local nArea := Select()
   local lFound := FALSE
   local cAlias := "SESSION"
   LOCAL cIpAddr := ""

   DEFAULT nExpireMinutes TO 60
   DEFAULT cUserID        TO ""

   IF ::context != NIL
      cIpAddr := ::context:getRemoteAddr()
   ENDIF

   if !Empty(cCookie)
      lFound := (cAlias)->(dbSeek(cCookie,.f.,"KEY"))
   ELSE
      cCookie := CreateNewSession( cIpAddr, nExpireMinutes, cUserId)
   endif

   if lFound .and. !Empty((cAlias)->DATA)
      ::cargoData := Bin2Var((cAlias)->DATA)
   else
      ::cargoData := {}
   endif

   ::SessionID := cCookie

   dbSelectArea(nArea)
return lFound

METHOD Base:CloseSession()

   if !Empty(::SessionID)
      DeleteSession(::SessionID)
   endif

   ::SessionID := ""

return NIL


METHOD Base:setCargo(x1,x2)
   local cCookie := ::GetVar("SID")
   local lRet := FALSE
   local nArea := Select()
   local cAlias := "SESSION"
   local i := 0

   IF EMPTY(cCookie)
      cCookie := ::SessionID
   ENDIF

   if !(x1 == NIL)
      if !Empty(cCookie) .and. (cAlias)->(dbSeek(cCookie))
         if (cAlias)->(RecLock())
            if x2 == NIL
               ::cargoData := x1
            else
               x1 := ALLTRIM(x1)
               if !ValType(::cargoData) == 'A'
                  ::cargoData := {}
               else
                  i := AScan( ::cargoData, {|e| lower(e[1]) == lower(x1) }
)
               endif
               if i == 0
                  aadd(::cargoData, {x1,x2})
               else
                  ::cargoData[i][2] := x2
               endif
            endif
            (cAlias)->DATA := Var2Bin(::cargoData)
            (cAlias)->(dbrUnlock())
            lRet := TRUE
         endif
      endif
   endif
   dbSelectArea(nArea)
return lRet


METHOD Base:delCargo(x1)
   local cCookie := ::GetVar("SID")
   local lRet := FALSE
   local nArea := Select()
   local cAlias := "SESSION"
   local i := 0

   IF EMPTY(cCookie)
      cCookie := ::SessionID
   ENDIF

   if !(x1 == NIL)
      if !Empty(cCookie) .and. (cAlias)->(dbSeek(cCookie))
         if (cAlias)->(RecLock())
            x1 := ALLTRIM(x1)
            if !ValType(::cargoData) == 'A'
               ::cargoData := {}
            else
               i := AScan( ::cargoData, {|e| lower(e[1]) == lower(x1) } )
            endif
            if i != 0
               AREMOVE(::cargoData, i, 1)
            endif
            (cAlias)->DATA := Var2Bin(::cargoData)
            (cAlias)->(dbrUnlock())
            lRet := TRUE
         endif
      endif
   endif
   dbSelectArea(nArea)
return lRet

METHOD Base:ClearCargo()
   local cCookie := ::GetVar("SID")
   local lRet := FALSE
   local nArea := Select()
   local cAlias := "SESSION"

   IF EMPTY(cCookie)
      cCookie := ::SessionID
   ENDIF

   if !Empty(cCookie) .and. (cAlias)->(dbSeek(cCookie))
      ::cargoData := {}
      if (cAlias)->(RecLock())
         (cAlias)->DATA := Var2Bin(::cargoData)
         (cAlias)->(dbrUnlock())
         lRet := TRUE
      endif
   ENDIF

   dbSelectArea(nArea)
return lRet


METHOD Base:setAllCargo()
   local cCookie := ::GetVar("SID")
   local lRet := FALSE
   local nArea := Select()
   local cAlias := "SESSION"
   local j:= 0, i := 0
   LOCAL aVars := ::GetAllVars(), x1, x2

   IF EMPTY(cCookie)
      cCookie := ::SessionID
   ENDIF

   if !Empty(cCookie) .and. (cAlias)->(dbSeek(cCookie))
      FOR j := 1 TO LEN(aVars)
         x1 := aVars[j,1]
         x2 := aVars[j,2,1]
         if !(x1 == NIL)
            x1 := ALLTRIM(x1)
            if !ValType(::cargoData) == 'A'
               ::cargoData := {}
            else
               i := AScan( ::cargoData, {|e| lower(e[1]) == lower(x1) } )
            endif
            if i == 0
               aadd(::cargoData, {x1,x2})
            else
               ::cargoData[i][2] := x2
            endif
         endif
      NEXT

      if (cAlias)->(RecLock())
         (cAlias)->DATA := Var2Bin(::cargoData)
         (cAlias)->(dbrUnlock())
         lRet := TRUE
      endif
   ENDIF

   dbSelectArea(nArea)
return lRet


METHOD Base:getCargo(cName)
   local xRet
   local i

   if Valtype(cName) == 'C' .and. ValType(::cargoData) == 'A'
      cName := ALLTRIM(cName)
      if (i := AScan(::cargoData,{|e| lower(e[1]) == lower(cName) } )) > 0
         xRet := ::cargoData[i][2]
      endif
   elseif cName == NIL
      xRet := ::cargoData
   endif

return xRet

METHOD Base:getAllCargo()
return ::cargoData

METHOD Base:IsCargo(cName)
   local lRet := FALSE

   if Valtype(cName) == 'C' .and. ValType(::cargoData) == 'A'
      lRet := (AScan(::cargoData,{|e| lower(e[1]) == lower(cName) } ) > 0)
   endif
return lRet

-------------- HELPERS -------------------


FUNCTION CreateNewSession(cIpAddr, nExpiry, cUserId)
LOCAL nSel := SELECT(), cSID := ""
LOCAL lNewRec := .T., nNewRec := 0
LOCAL cNow := DTOS(DATE()) + STRTRAN(TIME(), ":", "" )

   DEFAULT cIpAddr TO ""
   DEFAULT nExpiry TO EXPIRE_MINUTES
   DEFAULT cUserID TO ""

   SELECT SESSION
   IF FilLock(10)
      cSID := RandomPass(64)

       Falls Session-ID bereits vorhanden, solange versuchen, bis
       eine neue herauskommt.
      DO WHILE dbSeek( cSID, .F. , "KEY" )
         cSID := RandomPass(64)
      ENDDO

      /* Session-Datensatz recyclen */
      IF LASTREC() != 0
         OrdSetFocus("EXPIRES")
         GO TOP
         IF ! EOF() .AND. SESSION->expires < cNow
            IF RecLock(5)
               lNewRec := .F.
               nNewRec := RECNO()
            ENDIF
         ENDIF
         OrdSetFocus("KEY")
      ENDIF

      IF lNewRec
         APPEND BLANK
      ELSE
         dbGoto(nNewRec)
      ENDIF

      SESSION->KEY      := cSID
      SESSION->EXPIRES  := CalcExpiration( nExpiry )
      SESSION->ipaddr   := cIpAddr
      SESSION->userid   := cUserID
      SESSION->data     := ""
      SESSION->loggedin := TRUE
      dbUnlock()

   ENDIF

   SELECT (nSel)

RETURN cSID

FUNCTION CalcExpiration(nMinutes)
LOCAL cExpiration, nExpire
LOCAL dExpire := DATE()

   nExpire := SECONDS() + nMinutes * 60  Ablaufzeit

    Expiration-Zeit bei jedem Zugriff neu berechnen, Tageswechsel
berücksichtigen
   IF nExpire >= 86400   24 Stunden
      dExpire := dExpire + 1
      nExpire := nExpire - 86400
   ENDIF

   nExpire := ROUND(nExpire / 60,0)

   cExpiration := DTOS(dExpire) + STRZERO(INT(nExpire/60),2,0) +
STRZERO(nExpire%60,2,0)

RETURN cExpiration

FUNCTION DeleteSession(cSid)
LOCAL nSel := SELECT(), lRet := TRUE
LOCAL cNow := DTOS(DATE()) + "0000"

   SELECT SESSION

    Session vorhanden?
   IF dbSeek( cSID, .F. , "KEY" )

       Logout
      IF RecLock(5)
         SESSION->key      := ""
         SESSION->userid   := ""
         SESSION->loggedin := FALSE
         SESSION->ipaddr   := ""
         SESSION->data     := ""
         SESSION->expires  := cNow
         dbrUnlock()
      ENDIF

   ENDIF

   SELECT (nSel)

RETURN lRet

FUNCTION SessionExpired(cSid, oCon)
LOCAL nSel := SELECT(), lRet := TRUE
LOCAL cNow := DTOS(DATE()) + SUBSTR(STRTRAN(TIME(), ":", "" ),1,4)

   SELECT SESSION

    Session vorhanden?
   IF dbSeek( cSID, .F. , "KEY" )

      lRet := SESSION->expires < cNow .OR. !SESSION->loggedin .OR.
!(TRIM(oCon:getRemoteAddr()) = TRIM(SESSION->ipaddr))

      IF !lRet
         EVTUSER->(dbSeek(SESSION->userid,.F.,"ID"))

          Wenn OK, Session verlängern
         IF RecLock(5)
            SESSION->EXPIRES := CalcExpiration( EXPIRE_MINUTES )
            dbrUnlock()
         ENDIF
      ELSE
          Logout
         DeleteSession(cSid, oCon)
      ENDIF

   ENDIF

   SELECT (nSel)

RETURN lRet

FUNCTION RandomPass(nLen, cFormat)
LOCAL nI, cChar, nZahl := 64
LOCAL cRet := ""

   DEFAULT cFormat TO ""

   FOR nI := 1 TO nLen

      DO WHILE nZahl >= 58 .AND. nZahl <= 64
         nZahl := Random(42)+48
         /*
            Wegen Verwechslungsgefahr 1iI 0Oo entfernen
         */
         IF nZahl == 48 .OR. nZahl == 49  .OR. nZahl == 73 .OR.;
            nZahl == 79 .OR. nZahl == 105 .OR. nZahl == 111

            nZahl := 64

         ENDIF
      ENDDO
      cChar := CHR(nZahl)
      IF Random(1)=0
         cChar := LOWER(cChar)
      ENDIF
      cRet += cChar
      nZahl := 64

   NEXT

   IF ! EMPTY(cFormat)
      cRet := Transform( cRet , cFormat )
   ENDIF

RETURN cRet
Allen Lee Re: WAA session manager
on Thu, 14 Apr 2016 11:06:47 -0700
Thank you very much, Thomas.
I know this is going help a great deal.
I'm going to begin testing an implementation based on your model.
Your sharing is greatly appreciated.
If you vacation in Vancouver, Canada then I invite you stay as a guest 
in my home.

On 4/14/2016 8:41 AM, Thomas Braun wrote:
> Allen Lee wrote:
>
>> By session keys, do you mean that you store parameters in an array via
>
> No, the session key is a unique identifier which is part of each page
> submitted:
>
> <input type="hidden" value="6VkLK5Hs4txCrMxc2Y3fKanDZQCLMKe9Z6A" name="SID">
>
> If not part of the page, a new one is created and then stored in a
> "session.dbf" file at the first contact/login of the user:
>
> {"KEY", "C",64,0},;
> {"EXPIRES","C",12,0},;
> {"USERID","C",3,0},;
> {"DATA","M",4,0} }
>
> "DATA" holds the cargo contents.
>
> This is the code I use to replace setCargo/getCargo etc., maybe you can use
> parts of it:
>
> "base" is the base class for my own HTML3 class... at the bottom are some
> helper function you will need as well...hope this helps 
>
> Thomas
>
> METHOD Base:openSession(nExpireMinutes, cUserId)
>     local cCookie := ::GetVar("SID")
>     local nArea := Select()
>     local lFound := FALSE
>     local cAlias := "SESSION"
>     LOCAL cIpAddr := ""
>
>     DEFAULT nExpireMinutes TO 60
>     DEFAULT cUserID        TO ""
>
>     IF ::context != NIL
>        cIpAddr := ::context:getRemoteAddr()
>     ENDIF
>
>     if !Empty(cCookie)
>        lFound := (cAlias)->(dbSeek(cCookie,.f.,"KEY"))
>     ELSE
>        cCookie := CreateNewSession( cIpAddr, nExpireMinutes, cUserId)
>     endif
>
>     if lFound .and. !Empty((cAlias)->DATA)
>        ::cargoData := Bin2Var((cAlias)->DATA)
>     else
>        ::cargoData := {}
>     endif
>
>     ::SessionID := cCookie
>
>     dbSelectArea(nArea)
> return lFound
>
> METHOD Base:CloseSession()
>
>     if !Empty(::SessionID)
>        DeleteSession(::SessionID)
>     endif
>
>     ::SessionID := ""
>
> return NIL
>
>
> METHOD Base:setCargo(x1,x2)
>     local cCookie := ::GetVar("SID")
>     local lRet := FALSE
>     local nArea := Select()
>     local cAlias := "SESSION"
>     local i := 0
>
>     IF EMPTY(cCookie)
>        cCookie := ::SessionID
>     ENDIF
>
>     if !(x1 == NIL)
>        if !Empty(cCookie) .and. (cAlias)->(dbSeek(cCookie))
>           if (cAlias)->(RecLock())
>              if x2 == NIL
>                 ::cargoData := x1
>              else
>                 x1 := ALLTRIM(x1)
>                 if !ValType(::cargoData) == 'A'
>                    ::cargoData := {}
>                 else
>                    i := AScan( ::cargoData, {|e| lower(e[1]) == lower(x1) }
> )
>                 endif
>                 if i == 0
>                    aadd(::cargoData, {x1,x2})
>                 else
>                    ::cargoData[i][2] := x2
>                 endif
>              endif
>              (cAlias)->DATA := Var2Bin(::cargoData)
>              (cAlias)->(dbrUnlock())
>              lRet := TRUE
>           endif
>        endif
>     endif
>     dbSelectArea(nArea)
> return lRet
>
>
> METHOD Base:delCargo(x1)
>     local cCookie := ::GetVar("SID")
>     local lRet := FALSE
>     local nArea := Select()
>     local cAlias := "SESSION"
>     local i := 0
>
>     IF EMPTY(cCookie)
>        cCookie := ::SessionID
>     ENDIF
>
>     if !(x1 == NIL)
>        if !Empty(cCookie) .and. (cAlias)->(dbSeek(cCookie))
>           if (cAlias)->(RecLock())
>              x1 := ALLTRIM(x1)
>              if !ValType(::cargoData) == 'A'
>                 ::cargoData := {}
>              else
>                 i := AScan( ::cargoData, {|e| lower(e[1]) == lower(x1) } )
>              endif
>              if i != 0
>                 AREMOVE(::cargoData, i, 1)
>              endif
>              (cAlias)->DATA := Var2Bin(::cargoData)
>              (cAlias)->(dbrUnlock())
>              lRet := TRUE
>           endif
>        endif
>     endif
>     dbSelectArea(nArea)
> return lRet
>
> METHOD Base:ClearCargo()
>     local cCookie := ::GetVar("SID")
>     local lRet := FALSE
>     local nArea := Select()
>     local cAlias := "SESSION"
>
>     IF EMPTY(cCookie)
>        cCookie := ::SessionID
>     ENDIF
>
>     if !Empty(cCookie) .and. (cAlias)->(dbSeek(cCookie))
>        ::cargoData := {}
>        if (cAlias)->(RecLock())
>           (cAlias)->DATA := Var2Bin(::cargoData)
>           (cAlias)->(dbrUnlock())
>           lRet := TRUE
>        endif
>     ENDIF
>
>     dbSelectArea(nArea)
> return lRet
>
>
> METHOD Base:setAllCargo()
>     local cCookie := ::GetVar("SID")
>     local lRet := FALSE
>     local nArea := Select()
>     local cAlias := "SESSION"
>     local j:= 0, i := 0
>     LOCAL aVars := ::GetAllVars(), x1, x2
>
>     IF EMPTY(cCookie)
>        cCookie := ::SessionID
>     ENDIF
>
>     if !Empty(cCookie) .and. (cAlias)->(dbSeek(cCookie))
>        FOR j := 1 TO LEN(aVars)
>           x1 := aVars[j,1]
>           x2 := aVars[j,2,1]
>           if !(x1 == NIL)
>              x1 := ALLTRIM(x1)
>              if !ValType(::cargoData) == 'A'
>                 ::cargoData := {}
>              else
>                 i := AScan( ::cargoData, {|e| lower(e[1]) == lower(x1) } )
>              endif
>              if i == 0
>                 aadd(::cargoData, {x1,x2})
>              else
>                 ::cargoData[i][2] := x2
>              endif
>           endif
>        NEXT
>
>        if (cAlias)->(RecLock())
>           (cAlias)->DATA := Var2Bin(::cargoData)
>           (cAlias)->(dbrUnlock())
>           lRet := TRUE
>        endif
>     ENDIF
>
>     dbSelectArea(nArea)
> return lRet
>
>
> METHOD Base:getCargo(cName)
>     local xRet
>     local i
>
>     if Valtype(cName) == 'C' .and. ValType(::cargoData) == 'A'
>        cName := ALLTRIM(cName)
>        if (i := AScan(::cargoData,{|e| lower(e[1]) == lower(cName) } )) > 0
>           xRet := ::cargoData[i][2]
>        endif
>     elseif cName == NIL
>        xRet := ::cargoData
>     endif
>
> return xRet
>
> METHOD Base:getAllCargo()
> return ::cargoData
>
> METHOD Base:IsCargo(cName)
>     local lRet := FALSE
>
>     if Valtype(cName) == 'C' .and. ValType(::cargoData) == 'A'
>        lRet := (AScan(::cargoData,{|e| lower(e[1]) == lower(cName) } ) > 0)
>     endif
> return lRet
>
> -------------- HELPERS -------------------
>
>
> FUNCTION CreateNewSession(cIpAddr, nExpiry, cUserId)
> LOCAL nSel := SELECT(), cSID := ""
> LOCAL lNewRec := .T., nNewRec := 0
> LOCAL cNow := DTOS(DATE()) + STRTRAN(TIME(), ":", "" )
>
>     DEFAULT cIpAddr TO ""
>     DEFAULT nExpiry TO EXPIRE_MINUTES
>     DEFAULT cUserID TO ""
>
>     SELECT SESSION
>     IF FilLock(10)
>        cSID := RandomPass(64)
>
>         Falls Session-ID bereits vorhanden, solange versuchen, bis
>         eine neue herauskommt.
>        DO WHILE dbSeek( cSID, .F. , "KEY" )
>           cSID := RandomPass(64)
>        ENDDO
>
>        /* Session-Datensatz recyclen */
>        IF LASTREC() != 0
>           OrdSetFocus("EXPIRES")
>           GO TOP
>           IF ! EOF() .AND. SESSION->expires < cNow
>              IF RecLock(5)
>                 lNewRec := .F.
>                 nNewRec := RECNO()
>              ENDIF
>           ENDIF
>           OrdSetFocus("KEY")
>        ENDIF
>
>        IF lNewRec
>           APPEND BLANK
>        ELSE
>           dbGoto(nNewRec)
>        ENDIF
>
>        SESSION->KEY      := cSID
>        SESSION->EXPIRES  := CalcExpiration( nExpiry )
>        SESSION->ipaddr   := cIpAddr
>        SESSION->userid   := cUserID
>        SESSION->data     := ""
>        SESSION->loggedin := TRUE
>        dbUnlock()
>
>     ENDIF
>
>     SELECT (nSel)
>
> RETURN cSID
>
> FUNCTION CalcExpiration(nMinutes)
> LOCAL cExpiration, nExpire
> LOCAL dExpire := DATE()
>
>     nExpire := SECONDS() + nMinutes * 60  Ablaufzeit
>
>      Expiration-Zeit bei jedem Zugriff neu berechnen, Tageswechsel
> berücksichtigen
>     IF nExpire >= 86400   24 Stunden
>        dExpire := dExpire + 1
>        nExpire := nExpire - 86400
>     ENDIF
>
>     nExpire := ROUND(nExpire / 60,0)
>
>     cExpiration := DTOS(dExpire) + STRZERO(INT(nExpire/60),2,0) +
> STRZERO(nExpire%60,2,0)
>
> RETURN cExpiration
>
> FUNCTION DeleteSession(cSid)
> LOCAL nSel := SELECT(), lRet := TRUE
> LOCAL cNow := DTOS(DATE()) + "0000"
>
>     SELECT SESSION
>
>      Session vorhanden?
>     IF dbSeek( cSID, .F. , "KEY" )
>
>         Logout
>        IF RecLock(5)
>           SESSION->key      := ""
>           SESSION->userid   := ""
>           SESSION->loggedin := FALSE
>           SESSION->ipaddr   := ""
>           SESSION->data     := ""
>           SESSION->expires  := cNow
>           dbrUnlock()
>        ENDIF
>
>     ENDIF
>
>     SELECT (nSel)
>
> RETURN lRet
>
> FUNCTION SessionExpired(cSid, oCon)
> LOCAL nSel := SELECT(), lRet := TRUE
> LOCAL cNow := DTOS(DATE()) + SUBSTR(STRTRAN(TIME(), ":", "" ),1,4)
>
>     SELECT SESSION
>
>      Session vorhanden?
>     IF dbSeek( cSID, .F. , "KEY" )
>
>        lRet := SESSION->expires < cNow .OR. !SESSION->loggedin .OR.
> !(TRIM(oCon:getRemoteAddr()) = TRIM(SESSION->ipaddr))
>
>        IF !lRet
>           EVTUSER->(dbSeek(SESSION->userid,.F.,"ID"))
>
>            Wenn OK, Session verlängern
>           IF RecLock(5)
>              SESSION->EXPIRES := CalcExpiration( EXPIRE_MINUTES )
>              dbrUnlock()
>           ENDIF
>        ELSE
>            Logout
>           DeleteSession(cSid, oCon)
>        ENDIF
>
>     ENDIF
>
>     SELECT (nSel)
>
> RETURN lRet
>
> FUNCTION RandomPass(nLen, cFormat)
> LOCAL nI, cChar, nZahl := 64
> LOCAL cRet := ""
>
>     DEFAULT cFormat TO ""
>
>     FOR nI := 1 TO nLen
>
>        DO WHILE nZahl >= 58 .AND. nZahl <= 64
>           nZahl := Random(42)+48
>           /*
>              Wegen Verwechslungsgefahr 1iI 0Oo entfernen
>           */
>           IF nZahl == 48 .OR. nZahl == 49  .OR. nZahl == 73 .OR.;
>              nZahl == 79 .OR. nZahl == 105 .OR. nZahl == 111
>
>              nZahl := 64
>
>           ENDIF
>        ENDDO
>        cChar := CHR(nZahl)
>        IF Random(1)=0
>           cChar := LOWER(cChar)
>        ENDIF
>        cRet += cChar
>        nZahl := 64
>
>     NEXT
>
>     IF ! EMPTY(cFormat)
>        cRet := Transform( cRet , cFormat )
>     ENDIF
>
> RETURN cRet
>