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.