Patch title: Release 90 bulk changes
Abstract:
File: /protocol/http/server.pli
Key:
    Removed line
    Added line
   
abstract
  [Pliant HTTP server implementation, according to RFC2616] 
  [This implementation also contains a very powerfull mecani



public
  type HtmlPage
    field Link:Stream http_stream
    field Pointer:HttpRequest http_request
    # styling
abstract
  [Pliant HTTP server implementation, according to RFC2616] 
  [This implementation also contains a very powerfull mecani



public
  type HtmlPage
    field Link:Stream http_stream
    field Pointer:HttpRequest http_request
    # styling
    field HtmlStack html_stack
    field TagStack tag_stack
    field Dictionary environment
    field Link:Function html_hook text_hook begin_hook end_h
    # informations from the HTTP request
    field Str file_name
    field Str virtual_path
    field Str options                 # decoded options



    field Dictionary environment
    field Link:Function html_hook text_hook begin_hook end_h
    # informations from the HTTP request
    field Str file_name
    field Str virtual_path
    field Str options                 # decoded options



method request assign_rights uname ip rights
  arg_rw HttpRequest request ; arg Str uname ip ; arg_rw Str rights
  var Data:User u :> user uname
  if u:style_options<>""
    request style_options += "[lf]"+u:style_options
  each r u:right
    if (string request:user_auth_level)>=r:auth and (ip is_inside_ip_domain r:ip) and (r:server="" or (" "+r:server+" " search " "+computer_fullname+" " -1)<>(-1))
      request:rights kmap r:right CBool := true
      if r:right="administrator"
        request user_is_admin := true
      rights += " "+r:right

method request assign_user
  arg_rw HttpRequest request
  var Str ruser := request:stream safe_query "remote_user"
  var Str ip := request:stream query "remote_ip_address"
  if ruser<>""
    request user_name := ruser
    request user_auth_level := 3
    if compression and (request:stream safe_query "encoding"
      request supported_encoding := ""
  request rights := var Dictionary empty_dictionary
  var Str rights
  if request:user_is_admin
    rights := " administrator"
  else
    rights := ""

method request assign_user
  arg_rw HttpRequest request
  var Str ruser := request:stream safe_query "remote_user"
  var Str ip := request:stream query "remote_ip_address"
  if ruser<>""
    request user_name := ruser
    request user_auth_level := 3
    if compression and (request:stream safe_query "encoding"
      request supported_encoding := ""
  request rights := var Dictionary empty_dictionary
  var Str rights
  if request:user_is_admin
    rights := " administrator"
  else
    rights := ""
    for (var Int lap) (shunt request:user_name<>"" 1 0) 0 st
      var Data:User u :> user (shunt lap=0 "anonymous" reque
      if u:style_options<>""
        request style_options += "[lf]"+u:style_options
      each r u:right
        if (string request:user_auth_level)>=r:auth and (ip 
          request:rights kmap r:right CBool := true
          if r:right="administrator"
            request user_is_admin := true
          rights += " "+r:right
    request assign_rights "anonymous" ip rights
    each t (user request:user_name):template
      request assign_rights t ip rights
    if request:user_name<>""
      request assign_rights request:user_name ip rights
  request:log trace "user " request:user_name " " request:us
  if request:user_name<>""
    if not (login_record request:user_name ip (shunt request
      request user_name := ""
      request user_auth_level := 0
      request rights := var Dictionary empty_dictionary  
  plugin assign_user


method p execute_style_setup name -> status
  arg_rw HtmlPage p ; arg Str name ; arg Status status
  html_styles_sem rd_request
  var Pointer:Arrow c :> html_styles first name
  if c=null
    # console "no " name " style" eol
    html_styles_sem rd_release
    return failure
  # console "execute " name " style" eol
  var Link:Function f :> c map Function
  html_styles_sem rd_release
  request:log trace "user " request:user_name " " request:us
  if request:user_name<>""
    if not (login_record request:user_name ip (shunt request
      request user_name := ""
      request user_auth_level := 0
      request rights := var Dictionary empty_dictionary  
  plugin assign_user


method p execute_style_setup name -> status
  arg_rw HtmlPage p ; arg Str name ; arg Status status
  html_styles_sem rd_request
  var Pointer:Arrow c :> html_styles first name
  if c=null
    # console "no " name " style" eol
    html_styles_sem rd_release
    return failure
  # console "execute " name " style" eol
  var Link:Function f :> c map Function
  html_styles_sem rd_release
  p:html_stack initialize
  p:tag_stack initialize
  p execute_style_setup_prototype f
  status := success


method p bind r
  arg_rw HtmlPage p ; arg_rw HttpRequest r
  p http_request :> r
  p http_stream :> r answer_stream
  p options := r options
  p html_hook :> the_function '. default_html_hook' HtmlPage
  p text_hook :> the_function '. default_text_hook' HtmlPage
  p begin_hook :> the_function '. default_begin_end_hook' Ht
  p end_hook :> the_function '. default_begin_end_hook' Html
  p execute_style_setup_prototype f
  status := success


method p bind r
  arg_rw HtmlPage p ; arg_rw HttpRequest r
  p http_request :> r
  p http_stream :> r answer_stream
  p options := r options
  p html_hook :> the_function '. default_html_hook' HtmlPage
  p text_hook :> the_function '. default_text_hook' HtmlPage
  p begin_hook :> the_function '. default_begin_end_hook' Ht
  p end_hook :> the_function '. default_begin_end_hook' Html
  p:html_stack mark
  p:tag_stack mark
  p execute_style_setup "/pliant/protocol/http/style/default
  p execute_style_setup r:style_name
  p execute_style_setup "/pliant/protocol/http/style/default
  p execute_style_setup r:style_name
  p:html_stack initialize
  p:tag_stack initialize

method p unbind
  arg_rw HtmlPage p

method p unbind
  arg_rw HtmlPage p
  p:html_stack rewind
  p:tag_stack rewind



method request browser_walkaround
  arg_rw HttpRequest request



method request browser_walkaround
  arg_rw HttpRequest request
  if request:browser_model="opera" or request:browser_model=
    if request:supported_encoding="deflate"
      request supported_encoding := "gzip"
  plugin browser_walkaround
  
method request forward target extra
  arg_rw HttpRequest request ; arg Str target extra
  plugin forward_begin
  var Link:Stream s :> request stream
  s stream_flags := s:stream_flags .or. noautopost
  var Link:Stream d :> new Stream
  d open target in+out+cr+lf+safe+noautopost
  if d=failure
    return
  d writeline request:query_first_line
  var Pointer:Arrow c :> request:query_log first
  while c<>null
    d writeline (c map Str)
    c :> request:query_log next c
  if extra:len>0
    d writeline extra
  d writeline ""
  d flush anytime
  var Sem sem ; sem request
  thread
    while { d read_available (var Address adr2) (var Int siz
      s raw_write adr2 size2
      s flush anytime
    s safe_configure "shutdown"
    share:sem release
  while { s read_available (var Address adr1) (var Int size1
    d raw_write adr1 size1
    d flush anytime
  d safe_configure "shutdown"
  sem request ; sem release
  plugin forward_end
     


method request parse_then_answer
  arg_rw HttpRequest request
  implicit request
    user_name := ""
    user_auth_level := 0
    user_shaker := ""
    user_is_admin := false
    site_name := ""
    form := ""
    context := ""
    context_type :> null map Type
    keep_alive_applied := false
    query_log := var List empty_list
    answer_header_sent := false
    answer_footer_sent := false
    answer_extra := var List empty_list
    answer_status := ""
    answer_mime_type := ""
    answer_datetime := undefined
    answer_is_dynamic := true
    answer_size := undefined
  plugin browser_walkaround
  
method request forward target extra
  arg_rw HttpRequest request ; arg Str target extra
  plugin forward_begin
  var Link:Stream s :> request stream
  s stream_flags := s:stream_flags .or. noautopost
  var Link:Stream d :> new Stream
  d open target in+out+cr+lf+safe+noautopost
  if d=failure
    return
  d writeline request:query_first_line
  var Pointer:Arrow c :> request:query_log first
  while c<>null
    d writeline (c map Str)
    c :> request:query_log next c
  if extra:len>0
    d writeline extra
  d writeline ""
  d flush anytime
  var Sem sem ; sem request
  thread
    while { d read_available (var Address adr2) (var Int siz
      s raw_write adr2 size2
      s flush anytime
    s safe_configure "shutdown"
    share:sem release
  while { s read_available (var Address adr1) (var Int size1
    d raw_write adr1 size1
    d flush anytime
  d safe_configure "shutdown"
  sem request ; sem release
  plugin forward_end
     


method request parse_then_answer
  arg_rw HttpRequest request
  implicit request
    user_name := ""
    user_auth_level := 0
    user_shaker := ""
    user_is_admin := false
    site_name := ""
    form := ""
    context := ""
    context_type :> null map Type
    keep_alive_applied := false
    query_log := var List empty_list
    answer_header_sent := false
    answer_footer_sent := false
    answer_extra := var List empty_list
    answer_status := ""
    answer_mime_type := ""
    answer_datetime := undefined
    answer_is_dynamic := true
    answer_size := undefined
    answer_encoding := ""
    answer_chunked := false
    answer_stream :> request stream
    log_mark := request:log mark
  var Pointer:Stream http :> request stream
  part parse "parse HTTP request"
    var Str cmd := http readline ## section "parse_then_answ
    request query_first_line := cmd
    request:log trace "query " cmd
    var Str command protocol
    if (cmd eparse any:command _ (any request:encoded_path) 
      if (request:encoded_path eparse "http://" any "/" any:
        request encoded_path := "/"+remain
      if not (protocol parse word:"HTTP" "/" request:protoco
        request send_empty_answer "400 Bad Request"
        return
      request protocol_level := min request:protocol_level r
    eif (cmd eparse any:command "_" (any request:encoded_pat
      request protocol_level := 0.9
    else
      request send_empty_answer "400 Bad Request"
      return
    request keep_alive_requested := request:protocol_level>=
    if (request:encoded_path parse any:(var Str base) "?" (a
      request encoded_path := base
    else
      request encoded_options := ""
    request command := command
    request path := http_decode request:encoded_path
    request options := http_decode request:encoded_options
    var Int length := undefined
    var CBool chunked := false
    var CBool multipart := false
    var CBool continue := false
    var Int header_length := 0
    var Str tag value
    if request:protocol_level>=1
      while { var Str param := http readline ; param<>"" }
        header_length += param:len+16
        if header_length<request:server:maximal_header_lengt
          request:query_log append addressof:(new Str param)
          request:log trace "option " param
        if (param parse any:tag ":" any:value)
          tag := lower tag
          if tag="host"
            request site_name := value 0 (value search ":" v
          eif tag="content-length"
            if not (value parse length)
              request send_empty_answer "400 Bad Request" ; 
          eif tag="transfer-encoding" and (value parse acwor
            chunked := true
          eif tag="content-type"
            if (value parse acword:"multipart" any acword:"b
              multipart := true ; boundary := "[cr][lf]--"+b
          eif tag="expect"
            if (value parse acword:"100-continue" any)
              continue := true
          eif tag="connection"
            if (value parse any acword:"keep-alive" any)
              if request:server:keep_alive_connections
                request keep_alive_requested := true
            eif (value parse any acword:"close" any)
              request keep_alive_requested := false
          eif tag="user-agent"
            request browser := value
            find_browser_identity request:browser request:br
          eif tag="accept-language"
            request lang := value
          eif compression and tag="accept-encoding"
            request supported_encoding := shunt (value parse
          eif tag="authorization"
            if (value parse acword:"basic" any:(var Str enco
              var Str auth := base64_decode encoded
              if (auth parse any "\" any:(var Str user) ":" 
                var Data:UserSecret u :> user_secret_databas
                if u:password_md5=string_md5_hexa_signature:
                  request user_name := user
                  request user_auth_level := 1
                  request user_shaker := u shaker
                else
                  sleep 1
            eif false # (value parse acword:"digest" any)
              var Str user := value option "username=" Str
              console "user is: " user eol
              var Str password := "b"
              var Str realm := value option "realm=" Str
              console "realm is: " realm eol
              var Str nonce := value option "nonce=" Str
              console "nonce is: " nonce eol
              var Str uri := value option "uri=" Str
              var Str A1 := user+":"+realm+":"+password
              var Str A2 := "GET:"+uri
              var Str answer := digest digest:A1+":"+nonce+"
              console "signature for "+user+" is: " answer e
              var Str response := value option "response=" S
              if response=answer
                console "YES !" eol
  request browser_walkaround
  request assign_user ## section "parse_then_answer user" ; 
  request assign_site ## how "" section "method assign_site"
  if request:forward<>""
    request forward request:forward "Origin-IP: "+(request:s
    return
  if continue
    request send_empty_answer "100 Continue"
  request:query_stream :> new Stream
  if length<>undefined
    request:query_stream open "count:" "size "+string:length
  eif chunked
    request:query_stream open "chunked:" "" in+safe pliant_d
  else
    request:query_stream open "count:" "size 0" in+safe plia
  plugin answer_begin
  part answer "site '"+request:site_name+"' user '"+request:
    if not (request allowed "read")
      if request:user_name=""
        request send_authentification_request
      else
        request send_misc_answer "not_allowed"
        request:log trace "requested a not allowed page " re
      leave answer
    if request:style_name<>""
      compile_style request:style_name
    if command="GET"
      if query_mime_type:(request:path (request:path search_
        if (request send_static_file (request what_file requ
          leave answer
      if (request send_dynamic_answer request:path)=success
        leave answer
      if (request:path request:path:len-1)<>"/"
        var FileInfo info := file_query (request what_file r
        if info=defined and info:is_directory and request:pr
          request send_redirect_answer "http://"+(shunt requ
          leave answer
      var Str filename := request site_default
      filename := (filename 0 (filename search_last "." file
      if (request send_dynamic_file filename request:path)=f
        request:log trace "requested an unknown page " reque
        request send_simple_page "Not found" "" "The request
    eif command="POST" ## section "parse_then_answer POST"
      if not multipart
        part read_form "read HTTP form"
          var Address buffer := null ; var Int done := 0
          while not request:query_stream:atend and done<requ
            request:query_stream read_available (var Address
            buffer := memory_resize buffer done+size null
            memory_copy adr (buffer translate Byte done) siz
            done += size
          request:form set buffer done true
      else
        part parse_multipart_form "parse HTTP multipart form
          request form := ""
          var Int avail := request:server:maximal_form_lengt
          var Int avail2 := request:server:maximal_file_leng
          part multi
            while not request:query_stream:atend
              var Str value
              var Str label := "" ; var Str filename := ""
              while { var Str line := request:query_stream r
                if not (line parse any word:"name" "=" "[dq]
                  line parse any word:"name" "=" "[dq]" any:
                  line parse any word:"filename" "=" "[dq]" 
              if (label parse word:"file" _ word:"upload" _ 
                label := label2
                var Str temp := file_temporary
                var Str name := replace filename "\" "/"
                name := name (name search_last "/" -1)+1 nam
                value := string:temp+" remote_path "+string:
                request:temp_files append addressof:(new Str
                (var Stream data) open value out+safe
                var Str cache := "[cr][lf]" ; var Int drop :
                while cache:len<boundary:len and not request
                  if cache:len=0
                    var Address a := memory_search request:q
                    if a=null
                      a := request:query_stream stream_read_
                    var Int step := (cast a Int).-.(cast req
                    data raw_write request:query_stream:stre
                    request:query_stream stream_read_cur := 
                  request:query_stream raw_read addressof:(v
                  while cache<>(boundary 0 cache:len)
                    if drop=0
                      data raw_write cache:characters 1 ; av
                    else
                      drop -= 1
                    cache := cache 1 cache:len
                  if avail2<0
                    request send_empty_answer "413 Request E
                data close
                request:log trace "file upload " value " -> 
              else
                value := ""
                var Str cache := ""
                while not request:query_stream:atend and cac
                  if cache:len=0
                    var Address a := memory_search request:q
                    if a=null
                      a := request:query_stream stream_read_
                    var Int step := (cast a Int).-.(cast req
                    (var Str temp) set request:query_stream:
                    value += temp ; avail -= step
                    request:query_stream stream_read_cur := 
                  request:query_stream raw_read addressof:(v
                  while cache<>(boundary 0 cache:len)
                    (var Str temp) set cache:characters 1 fa
                    value += temp ; avail -= 1
                    cache := cache 1 cache:len
                  if avail<0
                    request send_empty_answer "413 Request E
              request form += "&"+http_encode:label+"="+http
              request:query_stream raw_read addressof:(var C
              request:query_stream readline
              if ch="-"
                leave multi
      request:log trace "form " request:form
      request send_dynamic_answer request:path
    else
      var Str virtualpage := reverse request:path
      var Str virtualpath := ""
      while (virtualpage parse any:(var Str extra) "/" any:(
        virtualpath := "/"+reverse:extra+virtualpath
        var Str virtualfile := request what_file reverse:bas
        if (request send_dynamic_file virtualfile virtualpat
          leave answer
        virtualpage := base
      request send_empty_answer "501 Not Implemented"
  plugin answer_end



export '. temporary_cleanup'
export '. send_static_file' '. execute_dynamic_page' '. do_c
export '. find_dynamic_page'
export '. forward'
    answer_chunked := false
    answer_stream :> request stream
    log_mark := request:log mark
  var Pointer:Stream http :> request stream
  part parse "parse HTTP request"
    var Str cmd := http readline ## section "parse_then_answ
    request query_first_line := cmd
    request:log trace "query " cmd
    var Str command protocol
    if (cmd eparse any:command _ (any request:encoded_path) 
      if (request:encoded_path eparse "http://" any "/" any:
        request encoded_path := "/"+remain
      if not (protocol parse word:"HTTP" "/" request:protoco
        request send_empty_answer "400 Bad Request"
        return
      request protocol_level := min request:protocol_level r
    eif (cmd eparse any:command "_" (any request:encoded_pat
      request protocol_level := 0.9
    else
      request send_empty_answer "400 Bad Request"
      return
    request keep_alive_requested := request:protocol_level>=
    if (request:encoded_path parse any:(var Str base) "?" (a
      request encoded_path := base
    else
      request encoded_options := ""
    request command := command
    request path := http_decode request:encoded_path
    request options := http_decode request:encoded_options
    var Int length := undefined
    var CBool chunked := false
    var CBool multipart := false
    var CBool continue := false
    var Int header_length := 0
    var Str tag value
    if request:protocol_level>=1
      while { var Str param := http readline ; param<>"" }
        header_length += param:len+16
        if header_length<request:server:maximal_header_lengt
          request:query_log append addressof:(new Str param)
          request:log trace "option " param
        if (param parse any:tag ":" any:value)
          tag := lower tag
          if tag="host"
            request site_name := value 0 (value search ":" v
          eif tag="content-length"
            if not (value parse length)
              request send_empty_answer "400 Bad Request" ; 
          eif tag="transfer-encoding" and (value parse acwor
            chunked := true
          eif tag="content-type"
            if (value parse acword:"multipart" any acword:"b
              multipart := true ; boundary := "[cr][lf]--"+b
          eif tag="expect"
            if (value parse acword:"100-continue" any)
              continue := true
          eif tag="connection"
            if (value parse any acword:"keep-alive" any)
              if request:server:keep_alive_connections
                request keep_alive_requested := true
            eif (value parse any acword:"close" any)
              request keep_alive_requested := false
          eif tag="user-agent"
            request browser := value
            find_browser_identity request:browser request:br
          eif tag="accept-language"
            request lang := value
          eif compression and tag="accept-encoding"
            request supported_encoding := shunt (value parse
          eif tag="authorization"
            if (value parse acword:"basic" any:(var Str enco
              var Str auth := base64_decode encoded
              if (auth parse any "\" any:(var Str user) ":" 
                var Data:UserSecret u :> user_secret_databas
                if u:password_md5=string_md5_hexa_signature:
                  request user_name := user
                  request user_auth_level := 1
                  request user_shaker := u shaker
                else
                  sleep 1
            eif false # (value parse acword:"digest" any)
              var Str user := value option "username=" Str
              console "user is: " user eol
              var Str password := "b"
              var Str realm := value option "realm=" Str
              console "realm is: " realm eol
              var Str nonce := value option "nonce=" Str
              console "nonce is: " nonce eol
              var Str uri := value option "uri=" Str
              var Str A1 := user+":"+realm+":"+password
              var Str A2 := "GET:"+uri
              var Str answer := digest digest:A1+":"+nonce+"
              console "signature for "+user+" is: " answer e
              var Str response := value option "response=" S
              if response=answer
                console "YES !" eol
  request browser_walkaround
  request assign_user ## section "parse_then_answer user" ; 
  request assign_site ## how "" section "method assign_site"
  if request:forward<>""
    request forward request:forward "Origin-IP: "+(request:s
    return
  if continue
    request send_empty_answer "100 Continue"
  request:query_stream :> new Stream
  if length<>undefined
    request:query_stream open "count:" "size "+string:length
  eif chunked
    request:query_stream open "chunked:" "" in+safe pliant_d
  else
    request:query_stream open "count:" "size 0" in+safe plia
  plugin answer_begin
  part answer "site '"+request:site_name+"' user '"+request:
    if not (request allowed "read")
      if request:user_name=""
        request send_authentification_request
      else
        request send_misc_answer "not_allowed"
        request:log trace "requested a not allowed page " re
      leave answer
    if request:style_name<>""
      compile_style request:style_name
    if command="GET"
      if query_mime_type:(request:path (request:path search_
        if (request send_static_file (request what_file requ
          leave answer
      if (request send_dynamic_answer request:path)=success
        leave answer
      if (request:path request:path:len-1)<>"/"
        var FileInfo info := file_query (request what_file r
        if info=defined and info:is_directory and request:pr
          request send_redirect_answer "http://"+(shunt requ
          leave answer
      var Str filename := request site_default
      filename := (filename 0 (filename search_last "." file
      if (request send_dynamic_file filename request:path)=f
        request:log trace "requested an unknown page " reque
        request send_simple_page "Not found" "" "The request
    eif command="POST" ## section "parse_then_answer POST"
      if not multipart
        part read_form "read HTTP form"
          var Address buffer := null ; var Int done := 0
          while not request:query_stream:atend and done<requ
            request:query_stream read_available (var Address
            buffer := memory_resize buffer done+size null
            memory_copy adr (buffer translate Byte done) siz
            done += size
          request:form set buffer done true
      else
        part parse_multipart_form "parse HTTP multipart form
          request form := ""
          var Int avail := request:server:maximal_form_lengt
          var Int avail2 := request:server:maximal_file_leng
          part multi
            while not request:query_stream:atend
              var Str value
              var Str label := "" ; var Str filename := ""
              while { var Str line := request:query_stream r
                if not (line parse any word:"name" "=" "[dq]
                  line parse any word:"name" "=" "[dq]" any:
                  line parse any word:"filename" "=" "[dq]" 
              if (label parse word:"file" _ word:"upload" _ 
                label := label2
                var Str temp := file_temporary
                var Str name := replace filename "\" "/"
                name := name (name search_last "/" -1)+1 nam
                value := string:temp+" remote_path "+string:
                request:temp_files append addressof:(new Str
                (var Stream data) open value out+safe
                var Str cache := "[cr][lf]" ; var Int drop :
                while cache:len<boundary:len and not request
                  if cache:len=0
                    var Address a := memory_search request:q
                    if a=null
                      a := request:query_stream stream_read_
                    var Int step := (cast a Int).-.(cast req
                    data raw_write request:query_stream:stre
                    request:query_stream stream_read_cur := 
                  request:query_stream raw_read addressof:(v
                  while cache<>(boundary 0 cache:len)
                    if drop=0
                      data raw_write cache:characters 1 ; av
                    else
                      drop -= 1
                    cache := cache 1 cache:len
                  if avail2<0
                    request send_empty_answer "413 Request E
                data close
                request:log trace "file upload " value " -> 
              else
                value := ""
                var Str cache := ""
                while not request:query_stream:atend and cac
                  if cache:len=0
                    var Address a := memory_search request:q
                    if a=null
                      a := request:query_stream stream_read_
                    var Int step := (cast a Int).-.(cast req
                    (var Str temp) set request:query_stream:
                    value += temp ; avail -= step
                    request:query_stream stream_read_cur := 
                  request:query_stream raw_read addressof:(v
                  while cache<>(boundary 0 cache:len)
                    (var Str temp) set cache:characters 1 fa
                    value += temp ; avail -= 1
                    cache := cache 1 cache:len
                  if avail<0
                    request send_empty_answer "413 Request E
              request form += "&"+http_encode:label+"="+http
              request:query_stream raw_read addressof:(var C
              request:query_stream readline
              if ch="-"
                leave multi
      request:log trace "form " request:form
      request send_dynamic_answer request:path
    else
      var Str virtualpage := reverse request:path
      var Str virtualpath := ""
      while (virtualpage parse any:(var Str extra) "/" any:(
        virtualpath := "/"+reverse:extra+virtualpath
        var Str virtualfile := request what_file reverse:bas
        if (request send_dynamic_file virtualfile virtualpat
          leave answer
        virtualpage := base
      request send_empty_answer "501 Not Implemented"
  plugin answer_end



export '. temporary_cleanup'
export '. send_static_file' '. execute_dynamic_page' '. do_c
export '. find_dynamic_page'
export '. forward'