DIRECTORY BasicTime, Convert, FTP, FTPInternal, IO, PrincOps, Process, PupDefs, PupRouterDefs, PupStream, PupTypes, RefText, Rope, RuntimeError; FTPServer: CEDAR MONITOR IMPORTS BasicTime, Convert, FTP, FTPInternal, IO, Process, PupDefs, PupRouterDefs, PupStream, RefText, Rope, RuntimeError EXPORTS FTP = BEGIN ROPE: TYPE = Rope.ROPE; CARD: TYPE = LONG CARDINAL; ReverseCardinal: TYPE = MACHINE DEPENDENT RECORD [high, low: CARDINAL]; LookupFileType: PupTypes.PupType = LOOPHOLE[200B]; LookupFileReplyType: PupTypes.PupType = LOOPHOLE[201B]; LookupFileErrorType: PupTypes.PupType = LOOPHOLE[202B]; Connections: INT _ 0; -- number of existing connections that have not yet been destroyed debugging: BOOL _ TRUE; versionRope: ROPE = Convert.RopeFromCard[from: FTPInternal.ftpVersion, showRadix: FALSE] ; versionDate: ROPE = "March 24, 1985 12:32:02 pm PST"; Object: PUBLIC TYPE = FTPInternal.Object; Handle: TYPE = FTPInternal.Handle; MyName: ROPE _ NIL; ProcessSocketPair: TYPE = RECORD[ process: PROCESS, fileInfoPupSocket: PupDefs.PupSocket ]; Listener: TYPE = REF ListenerObject; ListenerObject: PUBLIC TYPE = RECORD [ socket: PupDefs.PupSocket, timeout: PupDefs.Tocks, stop: BOOL, check: FTP.AcceptProc, defalut: BOOL, proc: PROCESS, serverProcs: FTP.ServerProcs, acceptProc: FTP.AcceptProc, processSocketPairs: LIST OF REF ProcessSocketPair _ NIL, fileInfo: FTP.FileInfoProc _ NIL ]; CreateListener: PUBLIC PROC [socket: PupStream.PupSocketID _ PupTypes.ftpSoc, procs: FTP.ServerProcs, accept: FTP.AcceptProc _ NIL, timeoutSeconds: INT _ 30, fileInfo: FTP.FileInfoProc, fileInfoSocket: PupStream.PupSocketID, fileInfoProcesses: CARDINAL [1..10]] RETURNS [l: Listener] = { him: Listener = NEW[ListenerObject]; him.fileInfo _ fileInfo; him.socket _ PupDefs.PupSocketMake[socket, PupTypes.fillInPupAddress, PupDefs.veryLongWait]; him.timeout _ PupDefs.SecondsToTocks[timeoutSeconds]; him.stop _ FALSE; him.check _ accept; him.serverProcs _ procs; him.defalut _ TRUE; him.proc _ FORK Listen[him, socket]; IF fileInfo # NIL THEN { FOR processes: CARDINAL IN [0..fileInfoProcesses) DO p: PROCESS; lookupSocket: PupDefs.PupSocket; pair: REF ProcessSocketPair; lookupSocket _ PupDefs.PupSocketMake[fileInfoSocket, PupTypes.fillInPupAddress, PupDefs.MsToTocks[200]]; p _ FORK FileInfoServer[him, lookupSocket]; pair _ NEW [ProcessSocketPair _ [p, lookupSocket]]; him.processSocketPairs _ CONS[pair, him.processSocketPairs]; ENDLOOP; }; RETURN[him]; }; DestroyListener: PUBLIC PROC [l: Listener] = TRUSTED { l.stop _ TRUE; PupDefs.PupSocketKick[l.socket]; JOIN l.proc; PupDefs.PupSocketDestroy[l.socket]; IF l.fileInfo # NIL AND l.processSocketPairs # NIL THEN { FOR pairs: LIST OF REF ProcessSocketPair _ l.processSocketPairs, pairs.rest UNTIL pairs = NIL DO PupDefs.PupSocketKick[pairs.first.fileInfoPupSocket]; JOIN pairs.first.process; PupDefs.PupSocketDestroy[pairs.first.fileInfoPupSocket]; ENDLOOP; }; }; Server: PUBLIC PROC [stream: IO.STREAM, procs: FTP.ServerProcs] = { him: Listener = NEW[ListenerObject]; him.serverProcs _ procs; him.defalut _ FALSE; innerServer[stream, him]; }; Listen: PROC [listener: Listener, local: PupDefs.PupSocketID] = TRUSTED { soc: PupRouterDefs.PupRouterSocket; arg: IO.STREAM; b: PupDefs.PupBuffer; UNTIL listener.stop DO b _ listener.socket.get[]; IF b # NIL THEN { SELECT b.pupType FROM rfc => { OPEN PupStream; FOR soc _ PupRouterDefs.GetFirstPupSocket[], soc.next UNTIL soc = NIL DO IF soc.remote # b.source THEN LOOP; IF soc.id # b.pupID THEN LOOP; b.address _ soc.local; PupDefs.SwapPupSourceAndDest[b]; PupDefs.PupRouterSendThis[b]; EXIT; ENDLOOP; IF soc = NIL THEN { him: PupDefs.PupAddress _ b.address; accept: BOOL _ TRUE; reason: ROPE _ NIL; IF listener.check # NIL THEN [accept: accept, reason: reason] _ listener.check[him, Connections]; IF ~accept THEN { b.pupType _ abort; PupDefs.SwapPupSourceAndDest[b]; PupDefs.SetPupContentsBytes[b, 2]; PupDefs.AppendRopeToPupBuffer[b, reason]; PupDefs.PupRouterSendThis[b]; GOTO Reject; }; PupDefs.ReturnFreePupBuffer[b]; arg _ PupByteStreamMake[ local, him, listener.timeout, alreadyOpened, b.pupID]; Process.Detach[FORK defaultServerRoot[arg, him, listener, PupDefs.AnyLocalPupAddress[local]]]; }; EXITS Reject => NULL; }; echoMe => { b.pupType _ iAmEcho; PupDefs.SwapPupSourceAndDest[b]; PupDefs.PupRouterSendThis[b]; }; ENDCASE => PupDefs.SendErrorPup[b, LOOPHOLE[100B], "RFC expected"]; }; ENDLOOP; }; defaultServerRoot: PROC [stream: IO.STREAM, pupAddress: PupStream.PupAddress, listener: Listener, listenerPupAddress: PupDefs.PupAddress] = { bumpConnections[]; innerServer[stream, listener]; debumpConnections[]; stream.Close[ ]; }; innerServer: PROC [stream: IO.STREAM, listener: Listener] = { closing: BOOLEAN _ FALSE; closeReason: PupStream.CloseReason _ localAbort; { ENABLE { PupStream.StreamClosing => {closeReason _ why; closing _ TRUE; GOTO Exit}; PupStream.TimeOut => {closeReason _ transmissionTimeout; closing _ TRUE; GOTO Exit}; RuntimeError.UNCAUGHT => IF NOT debugging THEN {closeReason _ localAbort; GOTO Exit}; ABORTED, UNWIND => {closeReason _ localAbort; GOTO Exit}; }; ftpHandle: Handle; remoteHerald: ROPE; ftpHandle _ NEW[Object _ []]; ftpHandle.pList[local] _ NEW[FTPInternal.PListObject]; ftpHandle.byteStream _ stream; remoteHerald _ AwaitCallingMessage[ftpHandle]; IF NOT closing THEN SendHerald[ftpHandle, listener, remoteHerald]; UNTIL closing DO mark: FTPInternal.Mark; code: FTP.FailureCode; [mark, code] _ ftpHandle.GetCommand[]; SELECT mark FROM retrieve => { Retrieve[ftpHandle, listener]; }; store => { [] _ ftpHandle.GetText[gobbleEOC: TRUE]; ftpHandle.GenerateNo[badCommand, "Command superceded by NewStore"]; }; comment => { [] _ ftpHandle.GetText[gobbleEOC: FALSE]; }; newStore => { Store[ftpHandle, listener]; }; enumerate => { Enumeration[ftpHandle, listener, enumerate]; }; newEnumerate => { Enumeration[ftpHandle, listener, newEnumerate]; }; delete => { Delete[ftpHandle, listener]; }; rename => { Rename[ftpHandle, listener] }; ENDCASE => { [] _ ftpHandle.GetText[gobbleEOC: TRUE]; ftpHandle.GenerateNo[badCommand, "Command undefined or unimplemented"]; }; ENDLOOP; EXITS Exit => NULL; }; }; AwaitCallingMessage: PROC [h: Handle] RETURNS [msg: ROPE] = { mark: FTPInternal.Mark; code: FTP.FailureCode; [mark, code] _ h.GetCommand[]; WHILE mark # version OR code # LOOPHOLE[FTPInternal.ftpVersion] DO IF mark # version THEN { h.GenerateFailed[protocolError, "First command must be version"]; [mark, code] _ h.GetCommand[]; LOOP; }; h.GenerateFailed[protocolError, "Incompatible protocol version"]; ENDLOOP; msg _ h.GetText[gobbleEOC: TRUE]; }; SendHerald: PROC [h: Handle, l: Listener, remoteHerald: ROPE] = { localHerald: ROPE _ Rope.Cat[MyName, " Cedar FTP Version ", versionRope, "File server of ", versionDate]; IF l.serverProcs.version # NIL THEN localHerald _ l.serverProcs.version[h, remoteHerald]; h.PutCommand[mark: version, code: LOOPHOLE[FTPInternal.ftpVersion], text: localHerald, sendEOC: TRUE]; }; Retrieve: PROC [ftpHandle: Handle, listener: Listener] = { xferOK: BOOL; comp: FTP.ServerCompleteProc = { IF xferOK THEN ftpHandle.PutCommand[mark: yes, text: "Transfer complete", sendEOC: FALSE]; RETURN[xferOK]; }; confirm: FTP.ConfirmTransferProc = { mark: FTPInternal.Mark; code: FTP.FailureCode; ftpHandle.PutCommandAndPList[mark: hereIsPList, pList: ftpHandle.pList[local], sendEOC: TRUE]; [mark, code] _ ftpHandle.GetCommand[]; SELECT mark FROM yes => { xferOK _ TRUE; [] _ ftpHandle.GetText[gobbleEOC: TRUE]; }; no => { [] _ ftpHandle.GetText[gobbleEOC: TRUE]; xferOK _ FALSE; }; ENDCASE => ftpHandle.GenerateFailed[protocolError]; IF xferOK THEN { ftpHandle.PutCommand[mark: hereIsFile]; RETURN [ftpHandle.byteStream]; } ELSE { RETURN [NIL]; }; }; ftpHandle.pList[remote] _ ftpHandle.GetPList[gobbleEOC: TRUE]; IF listener.serverProcs.checkCredentials # NIL THEN { listener.serverProcs.checkCredentials[ftpHandle ! FTP.Failed => { ftpHandle.GenerateNo[code: IF code = protocolError THEN transientError ELSE code, text: text, sendEOC: TRUE]; GOTO return; }; ]; }; listener.serverProcs.retrieve[h: ftpHandle, confirm: confirm, complete: comp ! FTP.Failed => { ftpHandle.PutCommand[mark: no, code: IF code = protocolError THEN transientError ELSE code, text: text, sendEOC: ~resumable]; IF resumable THEN RESUME ELSE GOTO return; }; ]; ftpHandle.PutEOC[]; EXITS return => RETURN; }; Store: PROC [ftpHandle: Handle, listener: Listener] = { xferOK: BOOL; comp: FTP.ServerCompleteProc = { xferOK _ ftpHandle.GetYesNo[gobbleEOC: TRUE]; IF xferOK THEN ftpHandle.PutCommand[mark: yes, text: "Transfer Completed", sendEOC: FALSE]; RETURN[xferOK]; }; confirm: FTP.ConfirmTransferProc = { mark: FTPInternal.Mark; code: FTP.FailureCode; ftpHandle.PutCommandAndPList[mark: hereIsPList, pList: ftpHandle.pList[local], sendEOC: TRUE]; [mark, code] _ ftpHandle.GetCommand[]; SELECT mark FROM hereIsFile => { RETURN [ftpHandle.byteStream]; }; no => { [] _ ftpHandle.GetText[gobbleEOC: TRUE]; RETURN [NIL]; }; ENDCASE => ftpHandle.GenerateFailed[protocolError]; }; ftpHandle.pList[remote] _ ftpHandle.GetPList[gobbleEOC: TRUE]; IF listener.serverProcs.checkCredentials # NIL THEN { listener.serverProcs.checkCredentials[ftpHandle ! FTP.Failed => { ftpHandle.GenerateNo[code: IF code = protocolError THEN transientError ELSE code, text: text, sendEOC: TRUE]; GOTO return; }; ]; }; listener.serverProcs.store[h: ftpHandle, confirm: confirm, complete: comp ! FTP.Failed => { ftpHandle.PutCommand[mark: no, code: IF code = protocolError THEN transientError ELSE code, text: text, sendEOC: ~resumable]; IF resumable THEN RESUME ELSE GOTO return; }; ]; ftpHandle.PutEOC[]; EXITS return => RETURN; }; Enumeration: PROC [ftpHandle: Handle, listener: Listener, protocol: {enumerate, newEnumerate}] = { firstTime: BOOL _ TRUE; noteFileProc: PROC[h: Handle] = { IF firstTime OR protocol= enumerate THEN h.PutCommand[mark: hereIsPList]; firstTime _ FALSE; h.PutPList[pList: h.pList[local]]; }; ftpHandle.pList[remote] _ ftpHandle.GetPList[gobbleEOC: TRUE]; IF listener.serverProcs.checkCredentials # NIL THEN { listener.serverProcs.checkCredentials[ftpHandle ! FTP.Failed => { ftpHandle.GenerateNo[code: IF code = protocolError THEN transientError ELSE code, text: text, sendEOC: TRUE]; GOTO return; }; ]; }; listener.serverProcs.enumerate[h: ftpHandle, noteFile: noteFileProc ! FTP.Failed => { ftpHandle.PutCommand[mark: no, code: IF code = protocolError THEN transientError ELSE code, text: text, sendEOC: ~resumable]; IF resumable THEN RESUME ELSE GOTO return; }; ]; IF firstTime THEN { ftpHandle.GenerateNo[code: fileNotFound, text: "File not found.", sendEOC: TRUE]; } ELSE ftpHandle.PutEOC[]; EXITS return => RETURN; }; Delete: PROC [ftpHandle: Handle, listener: Listener] = { deleteOK: BOOL; comp: FTP.ServerCompleteProc = { IF deleteOK THEN ftpHandle.PutCommand[mark: yes, text: "Delete OK", sendEOC: FALSE]; RETURN[deleteOK]; }; confirm: FTP.ConfirmProc = { mark: FTPInternal.Mark; code: FTP.FailureCode; ftpHandle.PutCommandAndPList[mark: hereIsPList, pList: ftpHandle.pList[local], sendEOC: TRUE]; [mark, code] _ ftpHandle.GetCommand[]; deleteOK _ TRUE; SELECT mark FROM yes => { [] _ ftpHandle.GetText[gobbleEOC: TRUE]; RETURN [TRUE]; }; no => { [] _ ftpHandle.GetText[gobbleEOC: TRUE]; RETURN [FALSE]; }; ENDCASE => ftpHandle.GenerateFailed[protocolError]; }; ftpHandle.pList[remote] _ ftpHandle.GetPList[gobbleEOC: TRUE]; IF listener.serverProcs.checkCredentials # NIL THEN { listener.serverProcs.checkCredentials[ftpHandle ! FTP.Failed => { deleteOK _ FALSE; ftpHandle.GenerateNo[code: IF code = protocolError THEN transientError ELSE code, text: text, sendEOC: TRUE]; GOTO return; }; ]; }; listener.serverProcs.delete[h: ftpHandle, confirm: confirm, complete: comp ! FTP.Failed => { ftpHandle.PutCommand[mark: no, code: IF code = protocolError THEN transientError ELSE code, text: text, sendEOC: ~resumable]; IF resumable THEN RESUME ELSE GOTO return; }; ]; ftpHandle.PutEOC[]; EXITS return => RETURN; }; Rename: PROC [ftpHandle: Handle, listener: Listener] = { ftpHandle.pList[remote] _ ftpHandle.GetPList[gobbleEOC: FALSE]; ftpHandle.pList[local] _ ftpHandle.GetPList[gobbleEOC: TRUE]; IF listener.serverProcs.checkCredentials # NIL THEN { listener.serverProcs.checkCredentials[ftpHandle ! FTP.Failed => { ftpHandle.GenerateNo[code: IF code = protocolError THEN transientError ELSE code, text: text, sendEOC: TRUE]; GOTO return; }; ]; }; listener.serverProcs.rename[h: ftpHandle ! FTP.Failed => { ftpHandle.PutCommand[mark: no, code: IF code = protocolError THEN transientError ELSE code, text: text, sendEOC: ~resumable]; IF resumable THEN RESUME ELSE GOTO return; }; ]; ftpHandle.PutCommand[mark: yes, text: "Rename OK", sendEOC: TRUE]; EXITS return => RETURN; }; bumpConnections: ENTRY PROC = { Connections _ Connections + 1; }; debumpConnections: ENTRY PROC = { Connections _ Connections - 1; }; FileInfoServer: PROC [him: Listener, socket: PupDefs.PupSocket] = TRUSTED { b: PupDefs.PupBuffer; UNTIL him.stop DO b _ socket.get[]; IF b # NIL THEN { SELECT b.pupType FROM LookupFileType => { fileNameChars: CARDINAL = PupDefs.GetPupContentsBytes[b]; file: REF TEXT _ RefText.New[fileNameChars]; create: BasicTime.GMT; ok, return: BOOL; bytes: CARD; version: CARDINAL; FOR index: CARDINAL IN [0..fileNameChars) DO file _ RefText.InlineAppendChar[file, b.pupChars[index]]; ENDLOOP; [ok, return, version, create, bytes] _ him.fileInfo[Rope.FromRefText[file], b.source]; IF ok THEN { ResultRec: TYPE = MACHINE DEPENDENT RECORD[v: CARDINAL, c, l: ReverseCardinal]; rPtr: LONG POINTER TO ResultRec = LOOPHOLE[@b.pupBody]; altoTime: LONG CARDINAL = BasicTime.ToPupTime[create]; b.pupType _ LookupFileReplyType; rPtr.v _ version; rPtr.c _ ReverseCardinalFromLongCard[altoTime]; rPtr.l _ ReverseCardinalFromLongCard[bytes]; PupDefs.SetPupContentsWords[b, SIZE[ResultRec]]; } ELSE { b.pupType _ LookupFileErrorType; PupDefs.SetPupContentsBytes[b, 0]; }; PupDefs.SwapPupSourceAndDest[b]; IF return THEN PupDefs.PupRouterSendThis[b] ELSE PupDefs.ReturnFreePupBuffer[b]; }; ENDCASE => PupDefs.SendErrorPup[b, LOOPHOLE[100B], "File Lookup expected"]; }; ENDLOOP; }; ReverseCardinalFromLongCard: PROC [r: CARD] RETURNS [ReverseCardinal] = TRUSTED MACHINE CODE {PrincOps.zEXCH}; MyName _ PupDefs.GetMyName[]; END. ΒFTPServer.mesa Last edited by: Bob Hagmann April 7, 1986 9:55:37 am PST Carl Hauser, October 10, 1985 3:03:51 pm PDT FTP. Internal Procedures check for duplicate not a duplicate, make a new connection For every stream accepted by filterProc and created in CreatePupByteStreamListener via CreateListener, this procedure is forked. ignore comments Single Packet Protocol for File Info Perform the "Single Packet Protocol for File Info". Forked as many times as needed. Init Bob Hagmann April 7, 1986 9:54:30 am PST mapped protocolError to transientError while catching FTP.Failed. protocolError is local and cannot be sent via the protocol. changes to: Retrieve, Store, Enumeration, Delete, Rename Κ8– "cedar" style˜Jšœ™™Icode™(K™,—unitšΟk ˜ K˜ K˜Kšœ˜Kšœ ˜ Kšœ˜K˜ K˜K˜K˜Kšœ ˜ Kšœ ˜ K˜K˜Kšœ ˜ K˜—šœ œ˜KšœœZ˜yKšœœ˜ Kšœ˜Kšœœœ˜Kšœœœœ˜K˜Kš œœœ œœ œ˜GK˜Kšœ#œ˜2Kšœ(œ˜7Kšœ(œ˜7K˜K˜Kšœ œΟcB˜YKšœ œœ˜Kšœ œAœ˜ZKšœ œ$˜5K˜Kšœœœ˜)Kšœœ˜"K˜Kšœœœ˜K˜—J˜™šœœœ˜!Jšœ œ˜Jšœ$˜$J˜—Lšœ œœ˜$šœœœœ˜&J˜J˜Jšœœ˜ Jšœœ ˜Jšœ œ˜Jšœœ˜Kšœ œ ˜Kšœ œ ˜Kš œœœœœ˜8Kšœ œ˜ Kšœ˜—šΟnœœœ:œœœœœJœ œ˜ Kšœœ˜$Kšœ˜Kšœ\˜\Kšœ5˜5Kšœ œ˜Kšœ˜Kšœ˜Kšœœ˜Kšœ œ˜$šœ œœ˜šœ œœ˜4Kšœœ˜ Kšœ ˜ Kšœœ˜Kšœh˜hKšœœ#˜+Kšœœ)˜3Kšœœ˜šœ)œœ˜5šœ2œ ˜BJš œœœœœ˜mJšœ˜ J˜—J˜J˜—šœOœ ˜^Jšœ%œœœ(˜}Jš œ œœœœ˜*Jšœ˜—Jšœ˜Jšœ˜š˜Jšœ œ˜—Jšœ˜J˜—š‘œœ,˜7Jšœœ˜ šœœ˜ Jšœ'œ˜-JšœœFœ˜[Jšœ ˜J˜—šœ œ˜$J˜Kšœœ ˜JšœXœ˜^Jšœ&˜&šœ˜šœ˜Jšœ˜J˜—šœ˜Kšœ"œ˜(Jšœœ˜ K˜—Jšœ,˜3—J˜—Jšœ8œ˜>šœ)œœ˜5šœ2œ ˜BJš œœœœœ˜mJšœ˜ J˜—J˜J˜—šœLœ ˜[Jšœ%œœœ(˜}Jš œ œœœœ˜*Jšœ˜—Jšœ˜Jšœ˜š˜Jšœ œ˜—Jšœ˜J˜—š‘ œœQ˜bJšœ œœ˜šœœ˜!Jšœ œœ œ!˜IJšœ œ˜Jšœ"˜"J˜—Jšœ8œ˜>šœ)œœ˜5šœ2œ ˜BJš œœœœœ˜mJšœ˜ J˜—J˜J˜—šœFœ ˜UJšœ%œœœ(˜}Jš œ œœœœ˜*Jšœ˜—Jšœ˜šœ œ˜JšœKœ˜QJ˜—Jšœœ˜š˜Jšœ œ˜—Jšœ˜J˜—š‘œœ,˜8Jšœ œ˜šœœ˜ Jšœ œ=œ˜TJšœ ˜J˜—šœ œ˜J˜Kšœœ ˜JšœXœ˜^Jšœ&˜&Jšœ œ˜šœ˜šœ˜Jšœ"œ˜(Jšœœ˜J˜—šœ˜Kšœ"œ˜(Kšœœ˜K˜—Jšœ,˜3—J˜—Jšœ8œ˜>šœ)œœ˜5šœ2œ ˜BJšœ œ˜Jš œœœœœ˜mJšœ˜ J˜—J˜J˜—šœMœ ˜\Jšœ%œœœ(˜}Jš œ œœœœ˜*Jšœ˜—Jšœ˜Jšœ˜š˜Jšœ œ˜—Jšœ˜—š‘œœ,˜8Jšœ8œ˜?Jšœ7œ˜=šœ)œœ˜5šœ2œ ˜BJš œœœœœ˜mJšœ˜ J˜—J˜J˜—šœ+œ ˜:Jšœ%œœœ(˜}Jš œ œœœœ˜*Jšœ˜—Jšœ˜Jšœ<œ˜Bš˜Jšœ œ˜—Jšœ˜—J˜š œœœ˜Mšœ˜M˜—š œœœ˜!Mšœ˜M˜—M˜—™$šŸœœ.œ˜KK™TKšœ˜šœ ˜Kšœ˜šœœœ˜šœ ˜šœ˜Kšœœ"˜9Kšœœœ˜,Kšœœ˜Kšœ œ˜Kšœœ˜ Kšœ œ˜šœœœ˜,Kšœ9˜9Kšœ˜—KšœV˜Všœœ˜ Kš œ œœ œœœ˜OKš œœœœ œ ˜7Kšœ œœ˜6Kšœ ˜ Kšœ˜Kšœ/˜/Kšœ,˜,Kšœœ ˜0K˜—šœœ˜Kšœ ˜ K˜"K˜—Kšœ ˜ Kšœœœ ˜PKšœ˜—Kšœœ ˜K—Kšœ˜—Kšœ˜—Kšœ˜—K˜š Ÿœœœœ˜OIcode2šœœ˜—J˜—™Jšœ˜Lšœ˜—J˜J˜™(Kšœ~™~Kšœ Οr,™8—K™—…—7άLΦ