diff --git a/Config/Server.xml b/Config/Server.xml index 6c02a4ea..c5a8fa65 100644 --- a/Config/Server.xml +++ b/Config/Server.xml @@ -8,7 +8,6 @@ 0 0 0 - 0 0 diff --git a/Core/Boot.dyalog b/Core/Boot.dyalog index e8d3b3f4..d18ceeb9 100644 --- a/Core/Boot.dyalog +++ b/Core/Boot.dyalog @@ -4,663 +4,663 @@ :section Startup/Shutdown - ∇ {msroot}Run root - :If 0≠⎕NC'msroot' ⋄ MSRoot←msroot ⋄ :EndIf - AppRoot←folderize root ⍝ application (website) root - Load AppRoot ⍝ load essential objects - ms←Init ConfigureServer AppRoot ⍝ read configuration and create server instance - Configure ms - ms.Run - ∇ - - ∇ {msroot}RunWC2 root - :If 0≠⎕NC'msroot' ⋄ MSRoot←msroot ⋄ :EndIf - AppRoot←folderize root ⍝ application (website) root - Load AppRoot ⍝ load essential objects - !!!ms←1 Init ConfigureServer AppRoot ⍝ read configuration and create server instance - Configure ms - {ms.Run} - ∇ - - ∇ Load AppRoot;filterOut;files;HTML;f;failed;dir;name;file;folder;callingEnv + ∇ {msroot}Run root + :If 0≠⎕NC'msroot' ⋄ MSRoot←msroot ⋄ :EndIf + AppRoot←folderize root ⍝ application (website) root + Load AppRoot ⍝ load essential objects + ms←Init ConfigureServer AppRoot ⍝ read configuration and create server instance + Configure ms + ms.Run + ∇ + + ∇ {msroot}RunWC2 root + :If 0≠⎕NC'msroot' ⋄ MSRoot←msroot ⋄ :EndIf + AppRoot←folderize root ⍝ application (website) root + Load AppRoot ⍝ load essential objects + !!!ms←1 Init ConfigureServer AppRoot ⍝ read configuration and create server instance + Configure ms + {ms.Run} + ∇ + + ∇ Load AppRoot;filterOut;files;HTML;f;failed;dir;name;file;folder;callingEnv ⍝ Load required objects for MiServer - - HtmlRenderer←{0::⍵ ⋄ HtmlRenderer}0 - - :If 0=#.⎕NC'Files' ⋄ ⎕SE.SALT.Load MSRoot,'Utils/Files -target=#' ⋄ :EndIf - - filterOut←{⍺←'' ⋄ ⍺{0∊⍴⍺:⍵ ⋄ ⍺{∊¨↓⍵⌿⍨~⍵[;2]∊eis ⍺}↑⎕NPARTS¨⍵}⊃#.Files.Dir ⍵,'/*.dyalog'} - - files←'Boot'filterOut MSRoot,'Core' - files,←'Files'filterOut MSRoot,'Utils' ⍝ find utility libraries - files,←filterOut MSRoot,'Extensions' - - - failed←'' - :For f :In files - {326=⎕DR ⍵: ⋄ '***'≡3↑⍵:failed,←⊂(('<.+>'⎕S{1↓¯1↓⍵.Match})⍵)}⎕SE.SALT.Load f,' -target=#' ⍝ do not reload already loaded spaces - :EndFor - - :For file :In failed - disperror ⎕SE.SALT.Load∊'"',file,'" -target=#' - :EndFor - - - HTML←'_JQ' '_JS'{⍵[⍋⍺⍳(↑⎕NPARTS¨⍵)[;2]]}filterOut MSRoot,'HTML' ⍝ prioritize loading of _JQ and _JS - - #.SupportedHtml5Elements.Build_html_namespace - + + HtmlRenderer←{0::⍵ ⋄ HtmlRenderer}0 + + :If 0=#.⎕NC'Files' ⋄ ⎕SE.SALT.Load MSRoot,'Utils/Files -target=#' ⋄ :EndIf + + filterOut←{⍺←'' ⋄ ⍺{0∊⍴⍺:⍵ ⋄ ⍺{∊¨↓⍵⌿⍨~⍵[;2]∊eis ⍺}↑⎕NPARTS¨⍵}⊃#.Files.Dir ⍵,'/*.dyalog'} + + files←'Boot'filterOut MSRoot,'Core' + files,←'Files'filterOut MSRoot,'Utils' ⍝ find utility libraries + files,←filterOut MSRoot,'Extensions' + + + failed←'' + :For f :In files + {326=⎕DR ⍵: ⋄ '***'≡3↑⍵:failed,←⊂(('<.+>'⎕S{1↓¯1↓⍵.Match})⍵)}⎕SE.SALT.Load f,' -target=#' ⍝ do not reload already loaded spaces + :EndFor + + :For file :In failed + disperror ⎕SE.SALT.Load∊'"',file,'" -target=#' + :EndFor + + + HTML←'_JQ' '_JS'{⍵[⍋⍺⍳(↑⎕NPARTS¨⍵)[;2]]}filterOut MSRoot,'HTML' ⍝ prioritize loading of _JQ and _JS + + #.SupportedHtml5Elements.Build_html_namespace + ⍝↓↓↓ Some controls may require controls in other folders. ⍝ So we attempt to load everything, and keep track of what failed ⍝ and then go back and try to load the failed controls again their - failed←'' - :For f :In HTML - (folder name)←2↑⎕NPARTS f - disperror ⎕SE.SALT.Load f,' -target=#' - :If #.Files.DirExists dir←folder,name,'/' - dir∘{326=⎕DR ⍵: ⋄ '***'≡3↑⍵:failed,←⊂⍺(('<.+>'⎕S{1↓¯1↓⍵.Match})⍵)}¨⎕SE.SALT.Load dir,'* -target=#.',name - :EndIf - :EndFor - - :For (f file) :In failed - disperror ⎕SE.SALT.Load∊'"',file,'" -target=#.',f - :EndFor - - LoadFromFolder MSRoot,'Loadable' - - 'Pages'#.⎕NS'' ⍝ Container Space for loaded classes - #.Pages.(MiPage RESTfulPage)←#.(MiPage RESTfulPage) - - 'CachedPages'#.⎕NS'' ⍝ Container for cached pages - - BuildEAWC ⍝ build the Easy As ⎕WC namespace - + failed←'' + :For f :In HTML + (folder name)←2↑⎕NPARTS f + disperror ⎕SE.SALT.Load f,' -target=#' + :If #.Files.DirExists dir←folder,name,'/' + dir∘{326=⎕DR ⍵: ⋄ '***'≡3↑⍵:failed,←⊂⍺(('<.+>'⎕S{1↓¯1↓⍵.Match})⍵)}¨⎕SE.SALT.Load dir,'* -target=#.',name + :EndIf + :EndFor + + :For (f file) :In failed + disperror ⎕SE.SALT.Load∊'"',file,'" -target=#.',f + :EndFor + + LoadFromFolder MSRoot,'Loadable' + + 'Pages'#.⎕NS'' ⍝ Container Space for loaded classes + #.Pages.(MiPage RESTfulPage)←#.(MiPage RESTfulPage) + + 'CachedPages'#.⎕NS'' ⍝ Container for cached pages + + BuildEAWC ⍝ build the Easy As ⎕WC namespace + ⍝ Now load any code from the MiSite - - :If ~0∊⍴AppRoot - :Trap 22 - :For class :In filterOut AppRoot,'Code' ⍝ Classes in application folder - disperror ⎕SE.SALT.Load class,' -target=#' - :EndFor - - :If #.Files.DirExists AppRoot,'/Code/Templates/' - disperror ⎕SE.SALT.Load AppRoot,'/Code/Templates/* -target=#.Pages' - :EndIf - :EndTrap - :EndIf - ∇ - - ∇ Cleanup - #.⎕EX¨classes - #.⎕EX¨utils - #.⎕EX'Pages' - #.⎕EX'CachedPages' - #.⎕EX¨'MiServer' 'HTTPRequest' - ∇ - - ∇ ms←{HtmlRenderer}Init Config;path;class;classes;e;res;mask + + :If ~0∊⍴AppRoot + :Trap 22 + :For class :In filterOut AppRoot,'Code' ⍝ Classes in application folder + disperror ⎕SE.SALT.Load class,' -target=#' + :EndFor + + :If #.Files.DirExists AppRoot,'/Code/Templates/' + disperror ⎕SE.SALT.Load AppRoot,'/Code/Templates/* -target=#.Pages' + :EndIf + :EndTrap + :EndIf + ∇ + + ∇ Cleanup + #.⎕EX¨classes + #.⎕EX¨utils + #.⎕EX'Pages' + #.⎕EX'CachedPages' + #.⎕EX¨'MiServer' 'HTTPRequest' + ∇ + + ∇ ms←{HtmlRenderer}Init Config;path;class;classes;e;res;mask ⍝ Create instances of MiServer, Session and Authentication Handlers - - HtmlRenderer←{0::⍵ ⋄ HtmlRenderer}0 ⍝ using HTMLRenderer? - - :If 0≠HtmlRenderer - ms←⎕NS'' - ms.Config←Config - ms.Log←{⎕←⍵} - :Else - ms←⎕NEW(#⍎Config.ClassName)Config - path←MSRoot,'Extensions/' - - :If 0≠⍴Config.SessionHandler - class←⎕SE.SALT.Load path,Config.SessionHandler - ms.SessionHandler←⎕NEW class ms - :EndIf - - :If 0≠⍴Config.Authentication - class←⎕SE.SALT.Load path,Config.Authentication - ms.Authentication←⎕NEW class ms - :EndIf - - :If 0≠⍴Config.SupportedEncodings - {}⎕SE.SALT.Load path,'ContentEncoder' - :For e :In Config.SupportedEncodings - class←⎕SE.SALT.Load path,e - ms.Encoders,←⎕NEW class - :EndFor - :If ∨/mask←0≠1⊃¨res←ms.Encoders.Init - 2 ms.Log'Content Encoding Initialization failed for:',∊' ',¨mask/ms.Encoders.Encoding - ms.Encoders←(~mask)/ms.Encoders - :EndIf - :EndIf - Config.UseContentEncoding∧←0≠⍴ms.Encoders - - :If 0≠⍴Config.Logger - class←⎕SE.SALT.Load path,Config.Logger - ms.Logger←⎕NEW class ms - :EndIf - :EndIf - ∇ - - ∇ End;classes;z;m + + HtmlRenderer←{0::⍵ ⋄ HtmlRenderer}0 ⍝ using HTMLRenderer? + + :If 0≠HtmlRenderer + ms←⎕NS'' + ms.Config←Config + ms.Log←{⎕←⍵} + :Else + ms←⎕NEW(#⍎Config.ClassName)Config + path←MSRoot,'Extensions/' + + :If 0≠⍴Config.SessionHandler + class←⎕SE.SALT.Load path,Config.SessionHandler + ms.SessionHandler←⎕NEW class ms + :EndIf + + :If 0≠⍴Config.Authentication + class←⎕SE.SALT.Load path,Config.Authentication + ms.Authentication←⎕NEW class ms + :EndIf + + :If 0≠⍴Config.SupportedEncodings + {}⎕SE.SALT.Load path,'ContentEncoder' + :For e :In Config.SupportedEncodings + class←⎕SE.SALT.Load path,e + ms.Encoders,←⎕NEW class + :EndFor + :If ∨/mask←0≠1⊃¨res←ms.Encoders.Init + 2 ms.Log'Content Encoding Initialization failed for:',∊' ',¨mask/ms.Encoders.Encoding + ms.Encoders←(~mask)/ms.Encoders + :EndIf + :EndIf + Config.UseContentEncoding∧←0≠⍴ms.Encoders + + :If 0≠⍴Config.Logger + class←⎕SE.SALT.Load path,Config.Logger + ms.Logger←⎕NEW class ms + :EndIf + :EndIf + ∇ + + ∇ End;classes;z;m ⍝ Clean up the workspace - - :If 9=⎕NC'ms' - :Trap 0 - ms.End - :EndTrap - {}try'⎕EX⍕⊃⊃⎕CLASS ms.SessionHandler' - {}try'⎕EX⍕⊃⊃⎕CLASS ms.Authentication' - {}try'⎕EX⍕⊃⊃⎕CLASS ms.Logger' - {}try'⎕EX⍕¨∪∊ ⎕CLASS¨ms.Encoders' - ⎕EX⍕⊃⊃⎕CLASS ms - ⎕EX'ms' - :EndIf - - :If 9=⎕NC'SQA' - {}try'SQA.Close''.''' - :EndIf - - :If 0≠⍴classes←↓#.⎕NL 9.4 - :AndIf 0≠⍴classes←(m←2=⊃∘⍴¨z←⎕CLASS¨#⍎¨classes)/classes - :AndIf 0≠⎕NC'#.MiPage' - classes←(#.MiPage≡¨2 1∘⊃¨m/z)/classes - #.⎕EX↑⍕¨classes ⍝ Erase loaded classes - :EndIf - - ⎕EX'#.MiPage' - ⎕EX'AppRoot' - {}try'#.DRC.Close ''.''' - ⎕EX'#.DRC' - #.Conga.UnloadSharedLib - {}⎕WA - ∇ - - ∇ BuildEAWC;src;sources;fields;source;list;mask;refs;target + + :If 9=⎕NC'ms' + :Trap 0 + ms.End + :EndTrap + {}try'⎕EX⍕⊃⊃⎕CLASS ms.SessionHandler' + {}try'⎕EX⍕⊃⊃⎕CLASS ms.Authentication' + {}try'⎕EX⍕⊃⊃⎕CLASS ms.Logger' + {}try'⎕EX⍕¨∪∊ ⎕CLASS¨ms.Encoders' + ⎕EX⍕⊃⊃⎕CLASS ms + ⎕EX'ms' + :EndIf + + :If 9=⎕NC'SQA' + {}try'SQA.Close''.''' + :EndIf + + :If 0≠⍴classes←↓#.⎕NL 9.4 + :AndIf 0≠⍴classes←(m←2=⊃∘⍴¨z←⎕CLASS¨#⍎¨classes)/classes + :AndIf 0≠⎕NC'#.MiPage' + classes←(#.MiPage≡¨2 1∘⊃¨m/z)/classes + #.⎕EX↑⍕¨classes ⍝ Erase loaded classes + :EndIf + + ⎕EX'#.MiPage' + ⎕EX'AppRoot' + {}try'#.DRC.Close ''.''' + ⎕EX'#.DRC' + #.Conga.UnloadSharedLib + {}⎕WA + ∇ + + ∇ BuildEAWC;src;sources;fields;source;list;mask;refs;target ⍝ Build the Easy As ⎕WC namespace from core classes and its own source ⍝ Also build the #._ namespace with shortcuts - sources←#._html #._SF #._JQ #._DC #._JS + sources←#._html #._SF #._JQ #._DC #._JS ⍝ fields←'' - '_'#.⎕NS'' - :For source :In sources - list←source.⎕NL ¯9.4 - list←list/⍨'_'≠⊃¨list - :If ∨/mask←0≠refs←source{6::0 ⋄ (⍕⍺){('.'∊1↓s↓r)<⍺≡(s←⍴⍺)↑r←⍕⍵}t←⍺⍎⍵:t ⋄ 0}¨list - #._⍎∊(mask/list){'⋄',⍺,'←',⍕⍵}¨mask/refs - :EndIf - :EndFor - ∇ - - ∇ {root}LoadFromFolder path;type;name;nsName;parts;ns + '_'#.⎕NS'' + :For source :In sources + list←source.⎕NL ¯9.4 + list←list/⍨'_'≠⊃¨list + :If ∨/mask←0≠refs←source{6::0 ⋄ (⍕⍺){('.'∊1↓s↓r)<⍺≡(s←⍴⍺)↑r←⍕⍵}t←⍺⍎⍵:t ⋄ 0}¨list + #._⍎∊(mask/list){'⋄',⍺,'←',⍕⍵}¨mask/refs + :EndIf + :EndFor + ∇ + + ∇ {root}LoadFromFolder path;type;name;nsName;parts;ns ⍝ Loads an APL "project" folder - root←{6::⍵ ⋄ root}# - :For name type :In ↓{⍵[⍒⍵[;2];]}⍉↑0 1 #.Files.Dir path,'/*' - nsName←∊1↓parts←1 ⎕NPARTS name - :If 1=type ⍝ directory? - :Select ⊃root.⎕NC nsName - :Case 9 ⋄ ns←⍕root⍎nsName - :Case 0 ⋄ ns←nsName root.⎕NS'' - :Else ⋄ Log'"',name,'" is not a namespace' - :EndSelect - ⎕SE.SALT.Load name,'/* -target=',⍕ns - :Else - :If ~∨/∊(⎕NSI,¨'.')⍷⍨¨⊂'.',(2⊃1 ⎕NPARTS name),'.' ⍝ don't load it if we're being called from it - ⎕SE.SALT.Load name,' -target=',⍕root - :EndIf - :EndIf - :EndFor - ∇ + root←{6::⍵ ⋄ root}# + :For name type :In ↓{⍵[⍒⍵[;2];]}⍉↑0 1 #.Files.Dir path,'/*' + nsName←∊1↓parts←1 ⎕NPARTS name + :If 1=type ⍝ directory? + :Select ⊃root.⎕NC nsName + :Case 9 ⋄ ns←⍕root⍎nsName + :Case 0 ⋄ ns←nsName root.⎕NS'' + :Else ⋄ Log'"',name,'" is not a namespace' + :EndSelect + ⎕SE.SALT.Load name,'/* -target=',⍕ns + :Else + :If ~∨/∊(⎕NSI,¨'.')⍷⍨¨⊂'.',(2⊃1 ⎕NPARTS name),'.' ⍝ don't load it if we're being called from it + ⎕SE.SALT.Load name,' -target=',⍕root + :EndIf + :EndIf + :EndFor + ∇ :endsection :section Configuration - ∇ Configure ms - ConfigureDatasources ms - ConfigureVirtual ms - ConfigureResources ms - ConfigureContentTypes ms - ConfigureLogger ms - ms AddConfiguration'MappingHandlers' - ∇ - - ∇ ms AddConfiguration name;conf - conf←ReadConfiguration name - {ms.Config⍎name,'←⍵'}conf - ∇ - - ∇ r←ns Setting pars;name;num;default;mask + ∇ Configure ms + ConfigureDatasources ms + ConfigureVirtual ms + ConfigureResources ms + ConfigureContentTypes ms + ConfigureLogger ms + ms AddConfiguration'MappingHandlers' + ∇ + + ∇ ms AddConfiguration name;conf + conf←ReadConfiguration name + {ms.Config⍎name,'←⍵'}conf + ∇ + + ∇ r←ns Setting pars;name;num;default;mask ⍝ returns setting from a config style namespace or provides a default if it doesn't exist ⍝ pars - name [num] [default] ⍝ ns - namespace reference ⍝ name - name of the setting ⍝ num - 1 if setting is numeric scalar, (,1) if numeric vector is allowed, 0 otherwise ⍝ default - default value if not found - pars←eis pars - (name num)←2↑pars,(⍴pars)↓'' 0 '' - :If 2<⍴pars ⋄ default←3⊃pars - :Else ⋄ default←(1+num)⊃''⍬ - :EndIf - r←(⍴ns)⍴⊂default - :If ∨/mask←0≠⊃¨ns.⎕NC⊂name - (mask/r)←(((⍴⍴num)∘tonum)⍣(⊃num))¨(mask/ns).⍎⊂name - :EndIf - :If 0=⍴⍴r ⋄ r←⊃r ⋄ :EndIf - ∇ - - ∇ config←{element}ReadConfiguration type;serverconfig;file;siteconfig;thing;ind;mask + pars←eis pars + (name num)←2↑pars,(⍴pars)↓'' 0 '' + :If 2<⍴pars ⋄ default←3⊃pars + :Else ⋄ default←(1+num)⊃''⍬ + :EndIf + r←(⍴ns)⍴⊂default + :If ∨/mask←0≠⊃¨ns.⎕NC⊂name + (mask/r)←(((⍴⍴num)∘tonum)⍣(⊃num))¨(mask/ns).⍎⊂name + :EndIf + :If 0=⍴⍴r ⋄ r←⊃r ⋄ :EndIf + ∇ + + ∇ config←{element}ReadConfiguration type;serverconfig;file;siteconfig;thing;ind;mask ⍝ Attempt to read configuration file ⍝ 1) from server root MSRoot ⍝ 2) from site root AppRoot ⍝ merging the two if they both exist - site settings overrule server settings - config←'' - :If #.Files.Exists file←MSRoot,'Config/',type,'.xml' - config←serverconfig←(#.XML.ToNS #.Files.ReadText file)⍎type - :EndIf - :If #.Files.Exists file←AppRoot,'Config/',type,'.xml' - siteconfig←(#.XML.ToNS #.Files.ReadText file)⍎type - :If 0∊⍴config - config←siteconfig - :ElseIf 0=⎕NC'element' - {}{try'serverconfig.',⍵,'←siteconfig.',⍵}¨siteconfig.⎕NL ¯2 - :Else ⍝ element specifies the element(s) to search on - :For thing :In siteconfig - :If 0≠thing.⎕NC element - :If ∨/mask←0≠∊serverconfig.⎕NC⊂element - :If (+/mask)|≡⍵),⍵} ⍝ Enclose if simple - - tonum←{⍺←0 - 1∊⍺:tonumvec ⍵ - w←⍵ ⋄ ((w='-')/w)←'¯' - ⊃⊃{~∧/⍺:⎕SIGNAL 11 ⋄ ⍵}/⎕VFI w} - - ∇ r←tonumvec v;to;minus;digits;c;mask + pars←eis pars + (name num)←2↑pars,(⍴pars)↓'' 0 '' + :If 2<⍴pars ⋄ default←3⊃pars + :Else ⋄ default←(1+num)⊃''⍬ + :EndIf + r←(⍴ns)⍴⊂default + :If ∨/mask←0≠⊃¨ns.⎕NC⊂name + (mask/r)←(((⍴⍴num)∘tonum)⍣(⊃num))¨(mask/ns).⍎⊂name + :EndIf + :If 0=⍴⍴r ⋄ r←⊃r ⋄ :EndIf + ∇ + + eis←{(,∘⊂)⍣((326∊⎕DR ⍵)<2>|≡⍵),⍵} ⍝ Enclose if simple + + tonum←{⍺←0 + 1∊⍺:tonumvec ⍵ + w←⍵ ⋄ ((w='-')/w)←'¯' + ⊃⊃{~∧/⍺:⎕SIGNAL 11 ⋄ ⍵}/⎕VFI w} + + ∇ r←tonumvec v;to;minus;digits;c;mask ⍝ tonum vector version ⍝ allows for specific of ranges and comma or space delimited numbers ⍝ tonumvec '8080-8090' or '5,7-9,11-15' - r←⍬ - ⎕SIGNAL 11/⍨~∧/v∊⎕D,'., -¯' - to←{⍺←⍵ ⋄ ⍺,⍺+(¯1*⍺>⍵)×⍳|⍺-⍵} - v←('^\s*|\s*$'⎕R'')('\s+'⎕R' ')('\s*-\s*'⎕R'-')v - minus←'-'=v - digits←v∊⎕D,'.' - ((minus>(minus∨{1↓⍵,0}digits)∧{¯1↓0,⍵}digits)/v)←⊂'¯' - ((' '=v)/v)←',' - (('-'=v)/v)←⊂' to ' - :Trap 0 - :For c :In {⎕ML←3 ⋄ ⍵⊂⍨⍵≠','}∊v - r,←⍎∊c - :EndFor - :Else - ⎕SIGNAL 11 - :EndTrap - ∇ - - :Endclass + r←⍬ + ⎕SIGNAL 11/⍨~∧/v∊⎕D,'., -¯' + to←{⍺←⍵ ⋄ ⍺,⍺+(¯1*⍺>⍵)×⍳|⍺-⍵} + v←('^\s*|\s*$'⎕R'')('\s+'⎕R' ')('\s*-\s*'⎕R'-')v + minus←'-'=v + digits←v∊⎕D,'.' + ((minus>(minus∨{1↓⍵,0}digits)∧{¯1↓0,⍵}digits)/v)←⊂'¯' + ((' '=v)/v)←',' + (('-'=v)/v)←⊂' to ' + :Trap 0 + :For c :In {⎕ML←3 ⋄ ⍵⊂⍨⍵≠','}∊v + r,←⍎∊c + :EndFor + :Else + ⎕SIGNAL 11 + :EndTrap + ∇ + + :Endclass :endsection :section Utilities - disperror←{326=⎕DR ⍵: ⋄ '***'≡3↑⍵:⎕←⍵} - isWin←'Win'≡3↑1⊃#.⎕WG'APLVersion' - fileSep←'/\'[1+isWin] - isRelPath←{{~'/\'∊⍨(⎕IO+2×isWin∧':'∊⍵)⊃⍵}3↑⍵} + disperror←{326=⎕DR ⍵: ⋄ '***'≡3↑⍵:⎕←⍵} + isWin←'Win'≡3↑1⊃#.⎕WG'APLVersion' + fileSep←'/\'[1+isWin] + isRelPath←{{~'/\'∊⍨(⎕IO+2×isWin∧':'∊⍵)⊃⍵}3↑⍵} MSRoot←{('.'=⊃⍵)∨isRelPath ⍵:'.',fileSep,⍵ ⋄ ⍵}{(1-⌊/'/\'⍳⍨⌽⍵)↓⍵}⎕WSID - tonum←{⍺←0 - 1∊⍺:tonumvec ⍵ - w←⍵ ⋄ ((w='-')/w)←'¯' - ⊃⊃{~∧/⍺:⎕SIGNAL 11 ⋄ ⍵}/⎕VFI w} - try←{0::'' ⋄⍎⍵} - empty←{0∊⍴⍵} - notEmpty←~∘empty - eis←{(,∘⊂)⍣((326∊⎕DR ⍵)<2>|≡⍵),⍵} ⍝ Enclose if simple - isRef←{(0∊⍴⍴⍵)∧326=⎕DR ⍵} - folderize←{19 22::⍵,'/'↓⍨'/\'∊⍨¯1↑⍵ ⋄ ∊1 ⎕NPARTS⊃{⍺,(⍵=1)/'/'}/0 1 ⎕NINFO ⍵} ⍝ append trailing file separator unless empty and left arg←1 - makeSitePath←{folderize ⍺{((isRelPath ⍵)/⍺),⍵},(2×'./'≡2↑⍵)↓⍵} - subdirs←{⊃{(⍵=1)/⍺}/0 1(⎕NINFO⍠1)⍵,'/*'} - Log←{⎕←⍵} - - ∇ {r}←AutoStatus setting + tonum←{⍺←0 + 1∊⍺:tonumvec ⍵ + w←⍵ ⋄ ((w='-')/w)←'¯' + ⊃⊃{~∧/⍺:⎕SIGNAL 11 ⋄ ⍵}/⎕VFI w} + try←{0::'' ⋄⍎⍵} + empty←{0∊⍴⍵} + notEmpty←~∘empty + eis←{(,∘⊂)⍣((326∊⎕DR ⍵)<2>|≡⍵),⍵} ⍝ Enclose if simple + isRef←{(0∊⍴⍴⍵)∧326=⎕DR ⍵} + folderize←{19 22::⍵,'/'↓⍨'/\'∊⍨¯1↑⍵ ⋄ ∊1 ⎕NPARTS⊃{⍺,(⍵=1)/'/'}/0 1 ⎕NINFO ⍵} ⍝ append trailing file separator unless empty and left arg←1 + makeSitePath←{folderize ⍺{((isRelPath ⍵)/⍺),⍵},(2×'./'≡2↑⍵)↓⍵} + subdirs←{⊃{(⍵=1)/⍺}/0 1(⎕NINFO⍠1)⍵,'/*'} + Log←{⎕←⍵} + + ∇ {r}←AutoStatus setting ⍝ Set Dyalog/Windows AutoStatus setting - :If r←isWin - :Trap 0 - :If setting≠r←⎕SE.mb.tools.status_error.Checked - 1 ⎕NQ ⎕SE.mb.tools.status_error'Select' - :EndIf - :EndTrap - :EndIf - ∇ - - ∇ r←tonumvec v;to;minus;digits;c;mask + :If r←isWin + :Trap 0 + :If setting≠r←⎕SE.mb.tools.status_error.Checked + 1 ⎕NQ ⎕SE.mb.tools.status_error'Select' + :EndIf + :EndTrap + :EndIf + ∇ + + ∇ r←tonumvec v;to;minus;digits;c;mask ⍝ tonum vector version ⍝ allows for specific of ranges and comma or space delimited numbers ⍝ tonumvec '8080-8090' or '5,7-9,11-15' - r←⍬ - ⎕SIGNAL 11/⍨~∧/v∊⎕D,'., -¯' - to←{⍺←⍵ ⋄ ⍺,⍺+(¯1*⍺>⍵)×⍳|⍺-⍵} - v←('^\s*|\s*$'⎕R'')('\s+'⎕R' ')('\s*-\s*'⎕R'-')v - minus←'-'=v - digits←v∊⎕D,'.' - ((minus>(minus∨{1↓⍵,0}digits)∧{¯1↓0,⍵}digits)/v)←⊂'¯' - ((' '=v)/v)←',' - (('-'=v)/v)←⊂' to ' - :Trap 0 - :For c :In {⎕ML←3 ⋄ ⍵⊂⍨⍵≠','}∊v - r,←⍎∊c - :EndFor - :Else - ⎕SIGNAL 11 - :EndTrap - ∇ - - ∇ r←SubstPath r - r←(#.Strings.subst∘('%ServerRoot%'(¯1↓MSRoot)))r - r←(#.Strings.subst∘('%SiteRoot%'(¯1↓AppRoot)))r - ∇ - - ∇ r←isRunning - :Trap r←0 - r←ms.TID∊⎕TNUMS - :EndTrap - ∇ - - ∇ r←Oops;dmx;ends;xsi + r←⍬ + ⎕SIGNAL 11/⍨~∧/v∊⎕D,'., -¯' + to←{⍺←⍵ ⋄ ⍺,⍺+(¯1*⍺>⍵)×⍳|⍺-⍵} + v←('^\s*|\s*$'⎕R'')('\s+'⎕R' ')('\s*-\s*'⎕R'-')v + minus←'-'=v + digits←v∊⎕D,'.' + ((minus>(minus∨{1↓⍵,0}digits)∧{¯1↓0,⍵}digits)/v)←⊂'¯' + ((' '=v)/v)←',' + (('-'=v)/v)←⊂' to ' + :Trap 0 + :For c :In {⎕ML←3 ⋄ ⍵⊂⍨⍵≠','}∊v + r,←⍎∊c + :EndFor + :Else + ⎕SIGNAL 11 + :EndTrap + ∇ + + ∇ r←SubstPath r + r←(#.Strings.subst∘('%ServerRoot%'(¯1↓MSRoot)))r + r←(#.Strings.subst∘('%SiteRoot%'(¯1↓AppRoot)))r + ∇ + + ∇ r←isRunning + :Trap r←0 + r←ms.TID∊⎕TNUMS + :EndTrap + ∇ + + ∇ r←Oops;dmx;ends;xsi ⍝ debugging framework to bubble up to user's code when rendering fails - r←'⎕SIGNAL 811' - ends←{(,⍺)≡(-⍴,⍺)↑⍵} - :If {0::0 ⋄ #.HtmlPage∊∊⎕CLASS ⍵}⊃⊃⎕RSI - r←'⎕TRAP←(800 ''C'' ''→FAIL'')(811 ''E'' ''⎕SIGNAL 801'')(813 ''E'' ''⎕SIGNAL 803'')(812 ''S'')(85 ''N'')(0 ''S'')' - ⎕←'' - ⎕←'*** MiServer Debug ***' - ⎕←↑⎕DMX.DM - ⎕←'' - ⎕←' ⎕SIGNAL 800 ⍝ to ignore this error and carry on' - ⎕←' or Press Ctrl-Enter to invoke debugger' - :Else - :Select ⎕DMX.EN - :Case 801 - xsi←⎕XSI - :If '.HandleMSP'ends 2⊃xsi - r←'→FAIL' - :ElseIf '.HandleMSP'ends 4⊃xsi - :If '.Render'ends 3⊃xsi - r←'⎕SIGNAL 813' - :EndIf - :ElseIf '.HandleMSP'ends 3⊃xsi - :If '.Wrap'ends 2⊃xsi - r←'⎕SIGNAL 813' - :EndIf - :Else - r←'⎕SIGNAL 811' - :EndIf - :Case 803 - r←'⎕SIGNAL 812' - ⎕←'Press Ctrl-Enter to invoke debugger' - :Else - :Trap 0 - dmx←⎕DMX - ⎕←'*** MiServer Debug ***' - ⎕←'' 'occurred at:',⍪dmx.(EM(2⊃DM)) - ⎕←'' 'SI Stack is ',(⍕¯1+⍴⎕XSI),' levels deep' - ⎕←'' - :EndTrap - ⎕←' ⎕SIGNAL 800 ⍝ to ignore this error and carry on' - ⎕←' ⎕SIGNAL 801 ⍝ to cut back and debug' - r←'' - :EndSelect - :EndIf - ∇ + r←'⎕SIGNAL 811' + ends←{(,⍺)≡(-⍴,⍺)↑⍵} + :If {0::0 ⋄ #.HtmlPage∊∊⎕CLASS ⍵}⊃⊃⎕RSI + r←'⎕TRAP←(800 ''C'' ''→FAIL'')(811 ''E'' ''⎕SIGNAL 801'')(813 ''E'' ''⎕SIGNAL 803'')(812 ''S'')(85 ''N'')(0 ''S'')' + ⎕←'' + ⎕←'*** MiServer Debug ***' + ⎕←↑⎕DMX.DM + ⎕←'' + ⎕←' ⎕SIGNAL 800 ⍝ to ignore this error and carry on' + ⎕←' or Press Ctrl-Enter to invoke debugger' + :Else + :Select ⎕DMX.EN + :Case 801 + xsi←⎕XSI + :If '.HandleMSP'ends 2⊃xsi + r←'→FAIL' + :ElseIf '.HandleMSP'ends 4⊃xsi + :If '.Render'ends 3⊃xsi + r←'⎕SIGNAL 813' + :EndIf + :ElseIf '.HandleMSP'ends 3⊃xsi + :If '.Wrap'ends 2⊃xsi + r←'⎕SIGNAL 813' + :EndIf + :Else + r←'⎕SIGNAL 811' + :EndIf + :Case 803 + r←'⎕SIGNAL 812' + ⎕←'Press Ctrl-Enter to invoke debugger' + :Else + :Trap 0 + dmx←⎕DMX + ⎕←'*** MiServer Debug ***' + ⎕←'' 'occurred at:',⍪dmx.(EM(2⊃DM)) + ⎕←'' 'SI Stack is ',(⍕¯1+⍴⎕XSI),' levels deep' + ⎕←'' + :EndTrap + ⎕←' ⎕SIGNAL 800 ⍝ to ignore this error and carry on' + ⎕←' ⎕SIGNAL 801 ⍝ to cut back and debug' + r←'' + :EndSelect + :EndIf + ∇ :endsection :EndNamespace diff --git a/Core/HTTPRequest.dyalog b/Core/HTTPRequest.dyalog index fdf2b3b5..da3fcd53 100644 --- a/Core/HTTPRequest.dyalog +++ b/Core/HTTPRequest.dyalog @@ -10,10 +10,15 @@ SC,←(500 'Internal Server Error')(501 'Not Implemented')(503 'Service Unavailable') SC←↑SC +⍝ Shared Fields + + :Field Public Shared DecodeBuffers←1 ⍝ have Conga decode HTTP message buffers by default (MiServer sets this on initialization) + :Field Public Shared Server ⍝ reference back to the server + ⍝ Fields related to the Request :Field Public Instance Complete←0 ⍝ do we have a complete request? - :Field Public Instance Input←'' + :Field Public Instance URI←'' ⍝ the complete URI (page + query string) :Field Public Instance Headers←0 2⍴⊂'' ⍝ HTTPRequest header fields (plus any supplied from HTTPTrailer event) :Field Public Instance Method←'' ⍝ HTTP method (GET, POST, PUT, etc) :Field Public Instance Page←'' ⍝ Requested URI @@ -28,7 +33,6 @@ :Field Public Instance HTTPVersion←'' :Field Public Instance Cookies←0 2⍴⊂'' :Field Public Instance Session←'' - :Field Public Instance Server←⎕NS '' :Field Public Instance CloseConnection←0 ⍝ Fields related to the Response @@ -42,7 +46,7 @@ ine←{0∊⍴⍺:'' ⋄ ⍵} ⍝ if not empty inf←{∨/⍵⍷⍺:'' ⋄ ⍵} ⍝ if not found begins←{⍺≡(⍴⍺)↑⍵} - split←{p←(⍺⍷⍵)⍳1 ⋄ ((p-1)↑⍵)(p↓⍵)} ⍝ Split ⍵ on first occurrence of ⍺ + split←{p←(⍺⍷⍵)⍳1 ⋄ ((p-≢⍺)↑⍵)(p↓⍵)} ⍝ Split ⍵ on first occurrence of ⍺ ∇ r←eis w :Access public shared @@ -52,17 +56,34 @@ ∇ Make args;query;cookies :Access Public Instance :Implements Constructor - ⍝ args [1] HTTP method [2] URI [3] HTTP version [4] 2-column headers + ⍝ args is either: + ⍝ [1] HTTP method [2] URI [3] HTTP version [4] 2-column headers + ⍝ character vector if Conga could not parse the HTTP header or if DecodeBuffers is turned off + + args←eis args + :If 1=≢args ⍝ single arg means Conga did not or could not decode + :Trap 999 + :If DecodeBuffers ⍝ this is a header that Conga failed to parse + 1 Server.Log'Bad HTTP header received:',⊃args ⍝ may want to improve this if bad header is very long + :EndIf + (Method URI HTTPVersion Headers)←ParseHead⊃args + :Else + 1 Fail 400 + →0 + :EndTrap + :Else + (Method URI HTTPVersion Headers)←args + :EndIf - (Method Input HTTPVersion Headers)←args Headers[;1]←#.Strings.lc Headers[;1] ⍝ header names are case insensitive + Headers←CombineHeaders Headers ⍝ combine any multiple header entries Method←#.Strings.lc Method Response←⎕NS'' Response.(Status StatusText Headers File HTML HTMLHead PeerAddr NoWrap Bytes MSec)←200 'OK'(0 2⍴⊂'')0 '' '' '' 0(0 0)(⎕AI[3]) Host←GetHeader'host' - Page query←'?'split Input + Page query←'?'split URI Page←PercentDecode Page :If '/'≠⊃Page ⍝!!! need to update this to deal with absolute URI's, see https://tools.ietf.org/html/rfc7230#section-5.3.2 @@ -75,7 +96,7 @@ :If ~0∊⍴cookies←GetHeader'cookie' cookies←CookieSplit cookies :If ~0∊⍴cookies←(2=⊃∘⍴¨cookies)/cookies - Cookies←↑{(' '~⍨1⊃⍵)(DeCode 2⊃⍵)}¨cookies + Cookies←↑{(' '~⍨1⊃⍵)(PercentDecode 2⊃⍵)}¨cookies :EndIf :EndIf :If 'get'≡Method @@ -84,24 +105,69 @@ :EndIf ∇ + ∇ (method uri version headers)←ParseHead head;start;ind;header;t + ⍝ manually parse HTTP head + (start header)←1(↑{⍺ ⍵}↓)(⊂'')~⍨{2↓¨⍵⊂⍨NL⍷⍵}NL,head ⍝ split start line from headers + ⎕SIGNAL 999/⍨3≠≢t←start⊆⍨' '≠start←∊start ⍝ start line should be method uri httpversion + (method uri version)←t + ⎕SIGNAL 999/⍨~∧/':'∊¨header ⍝ a valid header must have a ':' + headers←↑{dltb¨':'split ⍵}¨header + ∇ + ∇ ProcessBody args :Access public Body←args FinalizeRequest ∇ - ∇ ProcessChunk args + ∇ ProcessChunk args;len;chunk :Access public - ⍝ args is [1] chunk content [2] chunk-extension name/value pairs (which we don't expect and won't process) - Body,←1⊃args + ⍝ args is either + ⍝ [1] chunk content [2] chunk-extension name/value pairs (which we don't expect and won't process) + ⍝ or a character vector if either DecodeBuffers=0 or Conga failed to parse the chunk + :If DecodeBuffers=1=≡,args ⍝ if we're Decoding buffers, we expect a nested arg, if not, we expect a simple vector + 1 Fail 400 + →0 + :EndIf + + :If DecodeBuffers + Body,←1⊃args + :Else + :Trap 0 + (len chunk)←NL split args + Body,←(hex len)↑chunk + :Else + 1 Fail 400 + :EndTrap + :EndIf ∇ - ∇ ProcessTrailer args;inds;mask + ∇ ProcessTrailer args;inds;mask;len;trailer;forbidden :Access public - args[;1]←#.Strings.lc args[;1] - mask←(≢Headers)≥inds←Headers[;1]⍳args[;1] - Headers[mask/inds;2]←mask/args[;2] - Headers⍪←(~mask)⌿args + ⍝ args is either + ⍝ 2-column matrix of + ⍝ or a character vector if either DecodeBuffers=0 or Conga failed to parse the chunk + :If 1=≢⍴args + (len trailer)←NL split args + :If len≢,'0' + 1 Fail 400 + →0 + :EndIf + args←0 2⍴⊂'' + :If ~0∊⍴trailer + args←↑{dltb¨':'split ⍵}¨(⊂'')~⍨{2↓¨⍵⊂⍨NL⍷⍵}NL,trailer + :EndIf + :EndIf + :If ~0∊⍴args + args[;1]←#.Strings.lc args[;1] + ⍝ The following is an attempt to comply with https://tools.ietf.org/html/rfc7230#section-4.1.2 + ⍝ However, there doesn't seem to be a definitive list of the forbidden trailer fields, so, we do our best... + forbidden←'age' 'authorization' 'cache-control' 'content-encoding' 'content-length' 'content-range' 'content-type' 'date' 'expect' 'expires' 'host' 'location' 'max-forwards' 'pragma' 'proxy-authenticate' 'proxy-authorization' 'range' 'retry-after' 'set-cookie' 'te' 'trailer' 'transfer-encoding' 'vary' 'warning' 'www-authenticate' + args⌿←~args[;1]∊forbidden + mask←(≢Headers)≥inds←Headers[;1]⍳args[;1] + Headers[mask/inds;2]←mask/args[;2] + Headers⍪←(~mask)⌿args + :EndIf FinalizeRequest ∇ @@ -132,10 +198,22 @@ CloseConnection←'close'≡GetHeader'connection' ∇ + ∇ headers←CombineHeaders headers;inds;hdrs;hdr;i + :Access public shared + ⍝ combines any headers that may occur more than once + :If ~0∊⍴inds←⍸1<≢¨2⌷[2]hdrs←{⍺ ⍵}⌸headers[;1] + :For (hdr i) :In ↓hdrs[inds;] + headers[⊃i;2]←⊂¯1↓∊headers[i;2],¨',;'[1+'cookie'≡∊hdr] + :EndFor + headers←headers[⊃¨hdrs[;2];] + :EndIf + ∇ + + ∇ Wipe :Access public ⍝ clear out all request data - Input←'' + URI←'' Headers←'' Method←'' Page←'' @@ -147,7 +225,6 @@ Data←⍬ Cookies←'' Session←'' - Server←⎕NS'' Response←⎕NS'' ∇ @@ -174,13 +251,11 @@ :EndIf ∇ - ∇ r←GetArgument name :Access Public Instance r←name GetFromTable Arguments ∇ - ∇ r←GetData name :Access Public Instance r←name GetFromTableCS Data @@ -342,6 +417,7 @@ :EndIf :EndFor :EndIf + Complete←CloseConnection←1 ∇ ∇ {code}Redirect location @@ -439,7 +515,7 @@ ∇ ∇ SetContentType x;ct - :Access public instance + :Access public instance ⍝ Sets response content-type header ⍝ x is either a file name or extension (if it contains a period), in which case we attempt to look up the appropriate content-type ⍝ or the actual setting for content-type @@ -543,36 +619,27 @@ :endsection - Split←{(⎕IO ⎕ML)←0 3 ⍝ Split Http syntax. - ⍺←'?&=' ⋄ (,⍺){ ⍝ Default separator chars. - 0=⍴⍺:⍵ ⍝ No separator chars left: finished. - sepr←↑⍺ ⍝ First char is separator char. - (1↓⍺)∘∇∘{ ⍝ Recursively split at each sub level. - ⍵~sepr ⍝ Separator removed. - }¨(1+⍵=sepr)⊂⍵ ⍝ String partitioned at separator char. - }⍵ ⍝ Http string. + dltb←{ ⍝ delete leading/trailing blanks + (⌽∘{⍵/⍨∨\⍵≠' '}⍣2)⍵ } - CookieSplit←{(⎕IO ⎕ML)←0 3 ⍝ Split cookies - {{db←{⍵/⍨∨\⍵≠' '} ⋄ ⌽db⌽db ⍵}¨⍵⊂⍨~<\'='=⍵}¨⍵⊂⍨⍵≠';'} - - - DeCode←{(⎕IO ⎕ML)←0 3 ⍝ Decode Special chars in HTML string. - hex←'0123456789ABCDEF' ⍝ Hex chars. - { ⍝ Convert numbers. - v f←⎕VFI ⍵ ⍝ Check for numbers. - ~∧/v:⍵ ⍝ Not all numbers: char vec. - 1=⍴f:↑f ⋄ f ⍝ Numeric scalar or vector. - }∊{ ⍝ Enlist of segments. - '%'≠↑⍵:⍵ ⍝ 1st seg may not contain special char. - (⎕UCS 16⊥hex⍳1↓3↑⍵),3↓⍵ ⍝ Hex code replaced with corresp. ⎕AV char. - }¨(1+⍵='%')⊂,⍵ ⍝ Segments split at '%' char. - } + ∇ r←CookieSplit w + :Access public shared + r←{ ⍝ Split cookies + {dltb¨'='split ⍵}¨⍵⊆⍨⍵≠';' + }w + ∇ + ∇ r←hex w;i;⎕IO + :Access public shared + ⎕IO←0 + ⎕SIGNAL 11/⍨31∨.APLVersion)/⊂('EN' 11)('Message' 'Dyalog v16.0 or later is required to use HTMLRenderer-based features') + →0↓⍨0∊⍴r←(17>APLVersion)/⊂('EN' 11)('Message' 'Dyalog v17.0 or later is required to use HTMLRenderer-based features') Props←⎕NS'' _Config←#.Boot.ms.Config _PageName←3⊃⎕SI,⊂'WC2Page' @@ -66,7 +66,7 @@ ∇ run arg :Access public - _Renderer←⎕NEW'HTMLRenderer'(('Coord'Coord)('Size' Size)('Event'('onHTTPRequest' '__CallbackFn'))('URL'_PageName)) + _Renderer←⎕NEW'HTMLRenderer'(('Coord'Coord)('Size' Size)('Event'('onHTTPRequest' '__CallbackFn'))('URL'_PageName)('InterceptedURLs' (1 2⍴'*' 1))) :If ~0∊⍴props←_Renderer.PropList∩Props.⎕NL ¯2 {_Renderer⍎⍺,'←⍵'}/¨{⍵(Props⍎⍵)}¨props :EndIf @@ -92,7 +92,7 @@ ∇ r←__CallbackFn args;ext;mimeType;filename;url;mask;cbdata;request;int;handler;content - :Access public + :Access public ∘∘∘ r←args →0⍴⍨0∊⍴8⊃args diff --git a/Utils/HtmlUtils.dyalog b/Utils/HtmlUtils.dyalog index 99b6d27a..c4949630 100644 --- a/Utils/HtmlUtils.dyalog +++ b/Utils/HtmlUtils.dyalog @@ -161,13 +161,13 @@ ∇ r←HtmlSafeText txt;i;m;u;ucs;s ⍝ make text HTML "safe" r←,⎕FMT txt - i←'&<>"#'⍳r - i-←(i=1)∧1↓(i=5),0 ⍝ mark & that aren't &# - m←i∊⍳4 + i←'&<>"''#'⍳r + i-←(i=1)∧1↓(i=6),0 ⍝ mark & that aren't &# + m←i∊⍳5 u←127'⎕FMT u/ucs r←∊r ∇ diff --git a/miserver.dws b/miserver.dws index dcd9885e..3b79d7a5 100644 Binary files a/miserver.dws and b/miserver.dws differ