Object subclass: #Authorizer instanceVariableNames: 'users realm ' classVariableNames: '' poolDictionaries: '' category: 'PluggableWebServer'! Authorizer comment: 'The Authorizer does user authorization checking. Each instance of authorizer keeps track of the realm that it is authorizing for and the users table of authorized users. An authorizer can be asked to return the user name/symbol associated with a userID (which concatenates the username and password from the HTTP request) with the user: method. '! !Authorizer methodsFor: 'realms' stamp: 'mjg 11/3/97 12:33'! realm ^realm! ! !Authorizer methodsFor: 'realms' stamp: 'mjg 11/3/97 12:33'! realm: aString realm := aString ! ! !Authorizer methodsFor: 'authentication' stamp: 'mjg 11/3/97 13:01'! encode: nameString password: pwdString "Encode per RFC1421 of the username:password combination." | clear code clearSize idx map | clear := (nameString, ':', pwdString) asByteArray. clearSize := clear size. [ clear size \\ 3 ~= 0 ] whileTrue: [ clear := clear, #(0) ]. idx := 1. map := 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'. code := WriteStream on: ''. [ idx < clear size ] whileTrue: [ code nextPut: (map at: (clear at: idx) // 4 + 1); nextPut: (map at: (clear at: idx) \\ 4 * 16 + ((clear at: idx + 1) // 16) + 1); nextPut: (map at: (clear at: idx + 1) \\ 16 * 4 + ((clear at: idx + 2) // 64) + 1); nextPut: (map at: (clear at: idx + 2) \\ 64 + 1). idx := idx + 3 ]. code := code contents. idx := code size. clear size - clearSize timesRepeat: [ code at: idx put: $=. idx := idx - 1]. ^code! ! !Authorizer methodsFor: 'authentication' stamp: 'mjg 11/3/97 12:31'! mapFrom: aKey to: aPerson "Establish a mapping from a RFC 1421 key to a user." users isNil ifTrue: [ users := Dictionary new ]. aPerson isNil ifTrue: [ users removeKey: aKey ] ifFalse: [ users removeKey: (users keyAtValue: aPerson ifAbsent: []) ifAbsent: []. users at: aKey put: aPerson ] ! ! !Authorizer methodsFor: 'authentication' stamp: 'mjg 11/3/97 13:02'! mapName: nameString password: pwdString to: aPerson "Insert/remove the encoding per RFC1421 of the username:password combination into/from the UserMap." self mapFrom: (self encode: nameString password: pwdString) to: aPerson ! ! !Authorizer methodsFor: 'authentication' stamp: 'mjg 11/17/97 13:07'! user: userId "Return the requesting user." ^users at: userId ifAbsent: [ self error: (PWS unauthorizedFor: realm) ]! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Authorizer class instanceVariableNames: ''! Object subclass: #Comment instanceVariableNames: '' classVariableNames: 'CommentsTable ' poolDictionaries: '' category: 'PluggableWebServer'! Comment comment: 'A Comment space for a Web page.'! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Comment class instanceVariableNames: ''! !Comment class methodsFor: 'initialization' stamp: 'mjg 11/10/97 10:44'! initialize CommentsTable := Dictionary new.! ! !Comment class methodsFor: 'initialization' stamp: 'mjg 11/24/97 15:20'! readIn: filename |f| f _ ReferenceStream fileNamed: filename. CommentsTable _ f next. f close.! ! !Comment class methodsFor: 'initialization' stamp: 'mjg 11/24/97 15:20'! saveTo: filename |f| f _ ReferenceStream fileNamed: filename. f nextPut: CommentsTable. f close.! ! !Comment class methodsFor: 'initialization' stamp: 'mjg 11/17/97 14:52'! setUpExample | newDiscussion | newDiscussion _ Discussion new. newDiscussion title: 'pws'. newDiscussion description: 'Here is a space for talking about the Pluggable Web Server.'. CommentsTable at: 'pws' put: newDiscussion. ! ! !Comment class methodsFor: 'URL processing' stamp: 'mjg 11/10/97 11:18'! comments ^CommentsTable! ! !Comment class methodsFor: 'URL processing' stamp: 'mjg 1/12/98 13:05'! createComment: request "Create a new comment from a Web request" | newNote newMap | request fields isNil ifTrue: [self error: 'No request to create a comment from!!']. newNote _ Note new. newMap _ URLmap new. newNote author: (request fields at: 'author' ifAbsent: ['Anonymous']). newNote title: (request fields at: 'title' ifAbsent: ['Untitled']). newNote text: (HTMLformatter swikify: (request fields at: 'text' ifAbsent: ['Nothing much to say']) linkhandler: [:phrase | newMap linkFor: phrase from: request peerName storingTo: OrderedCollection new]). newNote timestamp: Date today printString , ' ' , Time now printString. newNote children: OrderedCollection new. "For later addition of threaded comments" ^ newNote! ! !Comment class methodsFor: 'URL processing' stamp: 'mjg 12/8/97 11:38'! process: request "URLs are of the form Comment.commentKey or Comment.commentKey.note of Comment.commentKey.gif. If commentKey is accessed but not created, create an empty one. If note is accessed, display it." | commentKey noteIndex newNote | commentKey _ request message at: 2. (CommentsTable includesKey: commentKey) ifFalse: [CommentsTable at: commentKey put: Discussion new. (CommentsTable at: commentKey) title: commentKey. (CommentsTable at: commentKey) description: 'Discussion on ' , commentKey]. request fields isNil ifFalse: ["Are there input fields?" newNote _ self createComment: request. newNote parent: commentKey. (CommentsTable at: commentKey) addNote: newNote. newNote url: ('Comment.',commentKey,'.', (CommentsTable at: commentKey) notes size printString)]. request message size > 2 ifTrue: ["There's a note reference or a request for a status image" noteIndex _ request message at: 3. noteIndex asUppercase = 'GIF' ifTrue: [ request reply: (PWS success),(PWS content: 'image/gif'). request reply: (HTMLformatter textToGIF: (CommentsTable at: commentKey) status)] ifFalse: [request reply: (self showNote: ((CommentsTable at: commentKey) at: noteIndex asNumber))]] ifFalse: [request reply: (self showComment: (CommentsTable at: commentKey))]! ! !Comment class methodsFor: 'URL processing' stamp: 'mjg 11/18/97 10:28'! showComment: aComment | fileName | fileName := (ServerAction serverDirectory) , 'ShowComment.html'. ^HTMLformatter evalEmbedded: (FileStream fileNamed: fileName) contentsOfEntireFile with: aComment. ! ! !Comment class methodsFor: 'URL processing' stamp: 'mjg 11/18/97 10:28'! showNote: aNote | fileName | fileName := (ServerAction serverDirectory) , 'ShowNote.html'. ^HTMLformatter evalEmbedded: (FileStream fileNamed: fileName) contentsOfEntireFile with: aNote. ! ! Comment initialize! Object subclass: #Discussion instanceVariableNames: 'notes title description relatedURL ' classVariableNames: '' poolDictionaries: '' category: 'PluggableWebServer'! Discussion comment: 'A Discussion has some header information and a collection of related notes.'! !Discussion methodsFor: 'access' stamp: 'mjg 11/17/97 14:25'! addNote: aNote notes isNil ifTrue: [notes _ OrderedCollection new.]. notes add: aNote. ^notes size ! ! !Discussion methodsFor: 'access' stamp: 'mjg 11/17/97 14:29'! at: aKey ^notes at: aKey! ! !Discussion methodsFor: 'access' stamp: 'mjg 11/17/97 14:24'! at: aKey addNote: aNote notes isNil ifTrue: [notes _ Dictionary new.]. notes at: aKey put: aNote. ! ! !Discussion methodsFor: 'access' stamp: 'mjg 11/17/97 14:21'! description ^description! ! !Discussion methodsFor: 'access' stamp: 'mjg 11/17/97 14:21'! description: aString description _ aString! ! !Discussion methodsFor: 'access' stamp: 'mjg 11/17/97 14:20'! notes ^notes ! ! !Discussion methodsFor: 'access' stamp: 'mjg 11/17/97 14:21'! relatedURL ^relatedURL! ! !Discussion methodsFor: 'access' stamp: 'mjg 11/17/97 14:21'! relatedURL: aString relatedURL _ aString! ! !Discussion methodsFor: 'access' stamp: 'mjg 12/8/97 11:11'! status | reply | reply _ WriteStream on: String new. reply nextPutAll: 'Number of notes: ', (notes size printString). notes size > 0 ifTrue: [reply nextPutAll: '. Last note: ',(notes last timestamp).]. ^reply contents! ! !Discussion methodsFor: 'access' stamp: 'mjg 11/17/97 14:20'! title ^title! ! !Discussion methodsFor: 'access' stamp: 'mjg 11/17/97 14:21'! title: aString title _ aString! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! Discussion class instanceVariableNames: ''! Object subclass: #HTMLformatter instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'PluggableWebServer'! HTMLformatter comment: 'HTMLformatter class (instances don''t know anything) knows alot about HTML formatting: Creating forms, pages, different widgets, etc. It also knows how to process Smalltalk embedded within an HTML document. '! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! HTMLformatter class instanceVariableNames: ''! !HTMLformatter class methodsFor: 'pages and forms' stamp: 'mjg 10/31/97 16:00'! endForm ^ '' ! ! !HTMLformatter class methodsFor: 'pages and forms' stamp: 'mjg 10/31/97 15:58'! endPage | stream | stream _ WriteStream on: ''. stream nextPutAll: '';cr; nextPutAll: '';cr. ^stream contents! ! !HTMLformatter class methodsFor: 'pages and forms' stamp: 'mjg 10/31/97 15:59'! startForm: action | stream | stream _ WriteStream on: ''. stream nextPutAll: '
'; cr. ^ stream contents! ! !HTMLformatter class methodsFor: 'pages and forms' stamp: 'mjg 10/31/97 15:57'! startPage: title | stream | stream _ WriteStream on: ''. stream nextPutAll: ''; cr; nextPutAll: '';cr; nextPutAll: '';cr; nextPutAll: ''; nextPutAll: title; nextPutAll: '';cr; nextPutAll: ''; cr. ^ stream contents! ! !HTMLformatter class methodsFor: 'translating' stamp: 'mjg 11/4/97 16:23'! evalEmbedded: stringOrStream with: request | sourceStream targetStream evalStream currentStream evalValue peekValue ch | (stringOrStream isKindOf: Stream) ifTrue: [sourceStream := stringOrStream] ifFalse: [sourceStream := ReadStream on: stringOrStream]. targetStream := WriteStream on: String new. currentStream := targetStream. [sourceStream atEnd] whileFalse: [ch := sourceStream next. ch = $< ifTrue: [ peekValue := sourceStream peek. (peekValue = $?) ifTrue: [evalStream := WriteStream on: String new. currentStream := evalStream. sourceStream next. "Eat the ?" ch := sourceStream next.]]. ((currentStream = evalStream) and: [ch = $?]) ifTrue: [ peekValue := sourceStream peek. (peekValue = $>) ifTrue: [sourceStream next. "Eat the >" currentStream := targetStream. evalValue := (Compiler new evaluate: (evalStream contents) in: thisContext to: self notifying: nil ifFail: [^nil]). (evalValue isKindOf: String) ifFalse: [evalValue := evalValue printString]. currentStream nextPutAll: evalValue.]] ifFalse: [currentStream nextPut: ch].]. ^targetStream contents ! ! !HTMLformatter class methodsFor: 'translating' stamp: 'mjg 11/25/97 11:50'! fixEndings: aStringOrStream | sourceStream targetStream aLine | (aStringOrStream isKindOf: Stream) ifTrue: [sourceStream := aStringOrStream] ifFalse: [sourceStream := ReadStream on: aStringOrStream]. targetStream := ReadWriteStream on: String new. [sourceStream atEnd] whileFalse: [aLine := sourceStream upTo: (Character linefeed). targetStream nextPutAll: aLine. targetStream nextPut: Character cr.]. ^targetStream ! ! !HTMLformatter class methodsFor: 'translating' stamp: 'mjg 11/10/97 12:38'! simpleProcess: aStringOrStream | sourceStream targetStream ch | (aStringOrStream isKindOf: Stream) ifTrue: [sourceStream := aStringOrStream] ifFalse: [sourceStream := ReadStream on: aStringOrStream]. targetStream := WriteStream on: String new. [sourceStream atEnd] whileFalse: [ch := sourceStream next. (ch = Character linefeed) ifTrue: [(sourceStream peek) = (Character linefeed) ifTrue: [sourceStream next. targetStream nextPutAll: '

'] ifFalse: [targetStream nextPutAll: '
']]. targetStream nextPut: ch]. ^targetStream contents. ! ! !HTMLformatter class methodsFor: 'translating' stamp: 'mjg 1/5/98 12:53'! swikify: aStringOrStream linkhandler: aBlock | sourceStream aLine targetStream start end | (aStringOrStream isKindOf: Stream) ifTrue: [sourceStream _ aStringOrStream] ifFalse: [sourceStream _ ReadStream on: aStringOrStream]. targetStream _ WriteStream on: String new. [sourceStream atEnd] whileFalse: [aLine _ sourceStream upTo: Character linefeed. " Now, look for links" start _ 1. [(start _ aLine indexOfSubCollection: '*' startingAt: start ifAbsent: [0]) ~= 0 and: [start < aLine size]] whileTrue: [(aLine at: start + 1) = $* ifTrue: [aLine _ aLine copyReplaceFrom: start to: start + 1 with: '*'. start _ start + 1] ifFalse: [(end _ aLine indexOfSubCollection: '*' startingAt: start + 1 ifAbsent: [0]) ~= 0 ifTrue: [aLine _ aLine copyReplaceFrom: start to: end with: (aBlock value: (aLine copyFrom: start + 1 to: end - 1))] ifFalse: [start _ start + 1]]]. "If it's at least 4 dashes, make it a horizontal rule" (aLine indexOfSubCollection: '----' startingAt: 1) = 1 ifTrue: [targetStream nextPutAll: '


'] ifFalse: [targetStream nextPutAll: aLine]. sourceStream peek = $< ifFalse: ["If just before a tag, ignore the newline" sourceStream peek = Character linefeed ifTrue: [sourceStream next. targetStream nextPutAll: '

'] ifFalse: [targetStream nextPutAll: '
']]]. ^ targetStream contents! ! !HTMLformatter class methodsFor: 'translating' stamp: 'mjg 12/8/97 11:37'! textToGIF: oneLineString | form filename | form _ (Form extent: 400@20 depth: Display depth) fillWhite. oneLineString displayOn: form at: 2@0. "form display." filename _ 'f',(SmallInteger maxVal atRandom) printString,'.gif'. GIFReadWriter putForm: form onFileNamed: filename. ^(FileStream fileNamed: filename) contentsOfEntireFile ! ! !HTMLformatter class methodsFor: 'formatting' stamp: 'mjg 10/31/97 15:48'! checkbox: buttonname value: b ^ ''! ! !HTMLformatter class methodsFor: 'formatting' stamp: 'mjg 10/24/97 16:56'! formFooter "Write the standard footer for a form." self reply: '


' ! ! !HTMLformatter class methodsFor: 'formatting' stamp: 'mjg 10/24/97 16:58'! formHeader: title For: aReference "Write the standard header for a page and form for editing anObject." self title: title; reply: '
' ! ! !HTMLformatter class methodsFor: 'formatting' stamp: 'mjg 10/31/97 15:54'! graphic: f ^self graphic: f standIn: 'Picture' alignment: 'right'! ! !HTMLformatter class methodsFor: 'formatting' stamp: 'mjg 10/31/97 15:53'! graphic: f standIn: s alignment: a ^ '' , s , ''! ! !HTMLformatter class methodsFor: 'formatting' stamp: 'mjg 10/31/97 15:55'! hiddenName: n value: v ^ ''! ! !HTMLformatter class methodsFor: 'formatting' stamp: 'mjg 10/31/97 16:02'! linkTo: url label: label ^'',label,''.! ! !HTMLformatter class methodsFor: 'formatting' stamp: 'mjg 10/31/97 15:50'! select: n values: values selection: selection size: size ^ self select: n values: values selections: (Array with: selection) size: size multiple: false! ! !HTMLformatter class methodsFor: 'formatting' stamp: 'mjg 10/31/97 15:49'! select: buttonname values: values selections: selections size: size multiple: multiple | stream | stream _ WriteStream on: ''. stream nextPutAll: ''. ^ stream contents! ! !HTMLformatter class methodsFor: 'formatting' stamp: 'mjg 10/31/97 15:53'! submit: label ^ self submit: 'submit' label: label! ! !HTMLformatter class methodsFor: 'formatting' stamp: 'mjg 10/31/97 15:52'! submit: buttonName label: v ^ ''! ! !HTMLformatter class methodsFor: 'formatting' stamp: 'mjg 10/31/97 15:51'! text: fieldName ^ self text: fieldName value: '' length: 80. ! ! !HTMLformatter class methodsFor: 'formatting' stamp: 'mjg 10/31/97 15:51'! text: fieldName value: v length: l ^ ''! ! !HTMLformatter class methodsFor: 'formatting' stamp: 'mjg 11/4/97 14:30'! textArea: fieldName ^ (self textAreaStart: fieldName rows: 15 cols: 70), self textAreaEnd! ! !HTMLformatter class methodsFor: 'formatting' stamp: 'mjg 11/4/97 14:30'! textArea: fieldName value: value ^ (self textAreaStart: fieldName rows: 15 cols: 70), value, self textAreaEnd! ! !HTMLformatter class methodsFor: 'formatting' stamp: 'mjg 11/4/97 14:28'! textAreaEnd ^ '' ! ! !HTMLformatter class methodsFor: 'formatting' stamp: 'mjg 11/4/97 14:28'! textAreaStart: fieldName rows: rows cols: cols ^ '