Monday, November 08, 2010 at 12:05 AM.

system.verbs.builtins.radio.webServer.responder

on responder (pta) {
	<<Changes:
		<<8/23/02; 12:16:58 AM by JES
			<<Respect flRender directives in #prefs files. Fixes a bug when RCS is running in Radio, where initial resources files would be erroneously rendered.
		<<5/16/02; 10:18:54 PM by JES
			<<Redirect to the setup page if user.radio.prefs.usernum is not defined.
		<<1/22/02; 9:31:51 AM by DW
			<<If system.temp.radio.misc.flProfileBuildPage is defined and true it builds the page with profiling.
			<<http://radio.userland.com/profiling
		<<12/18/01; 9:18:51 AM by DW
			<<Handle redirect of legacy URLs here. At first I thought the code didn't need to be here (it's already handled by webserver.responders.websiteFramework.methods.any), but then I realized that this url: http://127.0.0.1:5335/UserLand/, won't flow through the WSF responder because it doesn't end in .wsf. Sure enough it was flowing through here. Ipso facto, we must handle the redirects here as well (but only if they're enabled).
		<<12/9/01; 6:53:28 AM by DW
			<<Set it up better for debugging, now we can just uncomment the scratchpad.params assignment, make the request in the browser, switch over to the script and run the request in the debugger. A tried-and-true procedure elsewhere.
			<<Added comment on "if true" bundle for parsing searchArgs no matter what the method. (Note to Jake, changes to a core routine like this deserve better notes. But there *was* a comment in the Changes list, so I wasn't completely without a clue about the if true code.)
			<<BTW, to people who are watching, we're starting to use these notes as a weblog for each code bit. Why not? We have an outliner for editing script code, so they can all be collapsed to one line. 
		<<12/8/01; 7:11:41 PM by DW
			<<We were unconditionally converting the path to lower case. 
			<<We're not doing that anymore.
		<<12/2/01; 1:05:32 PM by DW
			<<If it's an expired trial version, only allow accesses to the serial number page, and gifs. (Otherwise the page would display with broken images, not good.) Redirect to the serial number page for all other acesses.
		<<11/24/01; 8:11:09 PM by JES
			<<If this is a folderView page, return the text of the file on disk, without rendering it to HTML.
		<<10/15/01; 11:07:22 PM by JES
			<<Backed out the last change. There's a bug in IE for Windows which causes an infinite loop, redirecting over and over again. This will have to be done a different way.
		<<10/15/01; 5:24:20 PM by JES
			<<When changing the menu, redirect to the first page in the sub-menu. Now clicking Reading takes you to the News page, and clicking Writing takes you to the Weblog page, etc. A corresponding change has been made to the Back link: it now links to the referring page as well as changing the menu. (See radio.macros.editorsOnlyMenu for that code.)
		<<10/9/01; 10:46:38 AM by DW
			<<If we're serving a folder, but user.radio.prefs.flDirectoryListings is false, return an accurate error message. 
			<<It was saying "Can't serve / because the file wasn't found." It took me about fifteen minutes to figure out what was going on. A clear error message would shorten that significantly.
		<<8/14/01; 6:05:22 PM by JES
			<<Re-worked the Accept header logic:
				<<If the file's MIME-type is present in the Accept header, but has a lower priority than text/html (it's q value is less, or it appears later in the list), then flRender is true.
				<<If text/html is not present, or has a lower priority than the file's MIME-type, then flRender is false.
		<<8/14/01; 2:47:35 AM by JES
			<<Removed try...else code which handled redirects. Instead, we just check to see if pta^.redirectUrl is defined before rendering the page -- if so, then we do the redirect immediately.
				<<Formerly, the page was rendered before checking for pta^.redirectUrl, which meant that the page had to be rendered even when a 302 was returned to the client.
		<<8/11/01; 4:16:41 PM by JES
			<<Always parse searchArgs into the radioResponder.getArgs table. This preserves the Editors Only menu selection (menu=# searchArg) after submitting a prefs form.
		<<8/3/01; 3:16:10 AM by JES
			<<Set folderView to true if folderView is present in POST args. Needed for upstreaming prefs form to work.
		<<8/2/01; 3:00:36 AM by JES
			<<When redirecting to a folder, include the searchArgs in the redirect URL.
		<<8/2/01; 2:24:42 AM by JES
			<<Don't serve any files outside the www folder.
		<<7/28/01; 1:22:32 AM by JES
			<<Added priority-based parsing of the HTTP Accept header, per a report from Mark Paschal.
				<<If the mime type is defined and true at user.radio.prefs.typesToRender, then render it.
				<<Otherwise, find the highest-priority matching mime type in the HTTP Accept header. If found, don't render unless the found type is text/html.
	<<scratchpad.params = pta^
	local (now = clock.now ());
	user.radio.stats.dateLastHit = now;
	local (fldynamicpage = false, flFolderView = false);
	on errorPage (message) {
		pta^.code = 404;
		pta^.responseHeaders.["Content-Type"] = "text/html";
		pta^.responseBody = webserver.util.buildErrorPage ("File Not Found", message);
		return (true)};
	on fileNotFoundError () {
		return (errorPage ("Can't serve " + pta^.path + " because the file wasn't found."))};
	on redirect (url) {
		<<if sizeOf (pta^.searchArgs) > 0
			<<url = url + "?" + pta^.searchArgs
		pta^.code = 302; //non-permanent redirect
		pta^.responseBody = webserver.util.buildErrorPage ("302 FOUND", "Found the page.");
		pta^.responseHeaders.location = url;
		pta^.responseHeaders.URI = url;
		try {delete (@pta^.responseHeaders.["Content-Type"])};
		return (true)};
	on servefile (f) {
		local (adrfiletable);
		radio.file.getFileAttributes (f, @adrfiletable);
		local (mimetype = pta^.radioResponder.adrFileTable^.mimetype, flrender = false, whenmodified);
		if defined (user.radio.prefs.typesToRender.[mimetype]) { //these types are always rendered
			if user.radio.prefs.typesToRender.[mimetype] {
				flrender = true}};
		if flrender { //turn off rendering if the client specifically supports the type, with precidence over text/html
			<<Radio-as-a-client wants to get the OPML, not the HTML rendering
			if defined (pta^.requestHeaders.accept) {
				local (adrTypes = @pta^.radioResponder.acceptMimeTypes);
				adrTypes^ = radio.webserver.parseAcceptHeader (pta);
				local (ctTypes = sizeOf (adrTypes^));
				local (flTextSlashHtmlFound = false);
				for i = ctTypes downTo 1 {
					local (ixtype);
					for ixtype = 1 to sizeOf (adrTypes^[i]) {
						if adrTypes^[i][ixtype] contains "text/html" {
							flTextSlashHtmlFound = true};
						if adrTypes^[i][ixtype] == mimetype {
							flrender = false;
							if flTextSlashHtmlFound {
								flrender = true};
							break}}}}};
		if flrender { //turn off rendering if this is a folderView page
			if flFolderView { //set flrender to false, and possibly adjust mimetype
				flrender = false;
				case mimetype { //adjust mimetype for viewing in a browser
					"text/x-opml" { //change to text/xml
						mimetype = "text/xml"};
					"text/html" { //change to text/plain
						"text/plain"}}}};
		if flrender { //turn off rendering based on an flRender directive in a #prefs file
			try {
				if not file.isFolder (f) {
					local (nomad = file.folderFromPath (f));
					loop {
						local (prefsfile);
						if radio.file.locateFileIgnoringExtension (nomad + "#prefs", @prefsfile) {
							local (adrprefs = @user.radio.settings.files.[prefsfile]);
							if defined (adrprefs^) {
								if defined (adrprefs^.flrender) {
									if not adrprefs^.flrender {
										flrender = false;
										break}}}};
						if nomad == user.radio.prefs.wwwFolder {
							break};
						if nomad == "" { //reality check
							break};
						nomad = file.folderFromPath (nomad)}}}};
		if flrender { //render it
			pta^.radioResponder.fileMimeType = mimetype;
			bundle { //set pta^.radioResponder.flHomePage
				if not pta^.radioResponder.flHomePage { //the home page can be accessed through different urls
					if file.folderfrompath (f) == user.radio.prefs.wwwfolder {
						local (lowerfname = string.lower (file.filefrompath (f)), adr);
						for adr in @user.radio.prefs.indexFileNames {
							if adr^ == lowerfname {
								pta^.radioResponder.flHomePage = true;
								break}}}}};
			if defined (pta^.radioResponder.redirectUrl) { //it's a redirect
				redirect (pta^.radioResponder.redirectUrl);
				return};
			
			bundle { //build the page, with support for profiling
				local (flprofile = false);
				if defined (system.temp.radio.misc.flProfileBuildPage) {
					flprofile = boolean (system.temp.radio.misc.flProfileBuildPage)};
				if flprofile {
					local (t);
					new (tabletype, @t);
					script.startprofile (true);
					pta^.responseBody = radio.webserver.buildpage (f, pta);
					script.stopprofile (@t);
					
					local (oldtarget = target.set (@t));
					table.sortby ("Value");
					target.set (oldtarget);
					
					local (adroutline = @system.temp.radio.profiles);
					if not defined (adroutline^) {
						new (outlinetype, adroutline)};
					
					local (dir, i, adr);
					oldtarget = target.set (adroutline);
					op.firstsummit ();
					op.insert (clock.now () + "; " + path, up); dir = right;
					for i = sizeof (t) downto 1 {
						adr = @t [i];
						if adr^ == 0 {
							break};
						op.insert (string.padwithzeros (adr^, 4) + ": " + nameof (adr^), dir); dir = down};
					op.firstsummit ();
					target.set (oldtarget);
					edit (adroutline)}
				else {
					pta^.responseBody = radio.webserver.buildpage (f, pta)}};
			
			if defined (pta^.radioResponder.redirectUrl) {
				redirect (pta^.radioResponder.redirectUrl);
				return};
			whenmodified = clock.now ();
			mimetype = "text/html"}
		else { //don't render
			local (filetext = string (file.readwholefile (f)));
			bundle { //workaround for displaying #template and #homeTemplate in MSIE & OmniWeb
				if defined (pta^.requestHeaders.["User-Agent"]) {
					local (loweragent = string.lower (pta^.requestHeaders.["User-Agent"]));
					if (loweragent contains "msie") or (loweragent contains "omniweb") {
						if mimetype == "text/plain" {
							filetext = string.replaceall (filetext, "&", "&"); //PBS 06/15/01
							filetext = string.replaceall (filetext, "<", "<");
							filetext = string.replaceall (filetext, "\\", "\");
							filetext = "<html><pre>" + filetext + "</pre></html>";
							pta^.responseBody = filetext}}}};
			pta^.responseBody = filetext;
			whenmodified = file.modified (f)};
		pta^.responseHeaders.["Content-Type"] = mimetype;
		pta^.responseHeaders.["Last-Modified"] = date.netStandardString (whenmodified)};
	bundle { //set up radioResponder table
		new (tabletype, @pta^.radioResponder);
		local (flSameMachine = false);
		bundle { //set flSameMachine
			local (client = pta^.client);
			if string.lower (client) == "localhost" {
				flSameMachine = true}
			else {
				if client == "127.0.0.1" {
					flSameMachine = true}
				else {
					try {flSameMachine = tcp.equalNames (client, tcp.myDottedID ())}}};
			pta^.radioResponder.flSameMachine = flSameMachine};
		bundle { //get postArgs
			if pta^.method == "POST" {
				if string.lower (pta^.requestHeaders.["Content-type"]) beginsWith "multipart/form-data" {
					radio.webserver.parseMultipart (pta)} //parse multipart form into pta^.radioResponder.postArgs
				else {
					new (tabletype, @pta^.radioResponder.postArgs);
					webserver.parseArgs (pta^.requestBody, @pta^.radioResponder.postArgs)};
				if defined (pta^.radioResponder.postArgs.folderView) {
					if pta^.radioResponder.postArgs.folderView != "0" {
						flFolderView = true}}}};
		bundle { //side-effects of GET
			if true { //pta^.method == "GET" //8/11/01; 4:16:41 PM by JES
				new (tabletype, @pta^.radioResponder.getArgs);
				webserver.parseArgs (pta^.searchArgs, @pta^.radioResponder.getArgs);
				if defined (pta^.radioResponder.getArgs.folderView) {
					if pta^.radioResponder.getArgs.folderView != "0" {
						flFolderView = true}}}};
		pta^.radioResponder.flHomePage = false;
		pta^.radioResponder.flStaticRendering = false;
		pta^.radioResponder.flImagePatching = false};
	bundle { //call the firewall
		if not radio.webserver.firewall (pta) {
			return (true)}};
	local (path = string.urlDecode (pta^.path));
	bundle { //redirect to the setup page if user.radio.prefs.usernum is not defined
		if not defined (user.radio.prefs.usernum) {
			if string.lower (path) != string.lower (radio.data.systemUrls.setupRadio) {
				if not ( (path endswith ".gif") or (path endswith ".jpg") ) { //reject
					return (redirect (radio.data.systemUrls.setupRadio))}}}};
	bundle { //respect user.radio.settings.flExpired
		if user.radio.settings.flExpired {
			if string.lower (path) != string.lower (radio.data.systemurls.serialnumber) {
				if not ( (path endswith ".gif") or (path endswith ".jpg") ) { //reject
					return (redirect (radio.data.systemurls.serialnumber))}}}};
	bundle { //redirect URLs that worked in 7.0.1
		if user.radio.prefs.flLegacyUrlRedirects {
			local (adrlegacy = @radio.data.legacyRedirects.[path]);
			if defined (adrlegacy^) { //let's make it work!
				return (redirect (radio.data.systemUrls.[adrlegacy^]))}}};
	if radio.webserver.walk (path, @f) {
		if not (string.lower (f) beginsWith (string.lower (user.radio.prefs.wwwfolder)) ) {
			scriptError ("Permission denied.")}; //08/02/01 JES: don't serve any file outside the www folder
		on setfiletableaddress (f) { //set adrfiletable, bump hitcounter 
			local (adrfiletable);
			radio.file.getFileAttributes (f, @adrfiletable);
			adrfiletable^.hits++;
			pta^.radioResponder.adrFileTable = adrfiletable};
		if file.isfolder (f) {
			if not (pta^.path endswith "/") {
				local (searchArgsString = "");
				if defined (pta^.searchArgs) {
					searchArgsString = "?" + pta^.searchArgs};
				return (redirect (pta^.path + "/" + searchArgsString))};
			if flFolderView and (not user.radio.prefs.flDirectoryListings) { //10/9/01; 10:54:11 AM by DW
				return (errorPage ("Can't serve the folder because user.radio.prefs.flDirectoryListings is false."))};
			if not flFolderView {
				local (adr);
				for adr in @user.radio.prefs.indexFileNames {
					findex = f + adr^;
					if file.exists (findex) {
						pta^.radioResponder.flHomePage = (f == user.radio.prefs.wwwfolder);
						setfiletableaddress (findex);
						servefile (findex);
						return (true)}}};
			if user.radio.prefs.flDirectoryListings {
				pta^.responseHeaders.["Content-Type"] = "text/html";
				setfiletableaddress (f);
				pta^.responseBody = radio.webserver.buildpage (f, pta);
				return (true)}
			else {
				return (fileNotFoundError ())}}
		else {
			setfiletableaddress (f);
			servefile (f)}}
	else {
		return (fileNotFoundError ())};
	return (true)}
<<bundle //test code
	<<responder (@scratchpad.params)



This listing is for code that runs in the OPML Editor environment. I created these listings because I wanted the search engines to index it, so that when I want to look up something in my codebase I don't have to use the much slower search functionality in my object database. Dave Winer.