Patch title: Release 90 bulk changes
Abstract:
File: /protocol/http/client.pli
Key:
    Removed line
    Added line
# Copyright  Hubert Tonneau  hubert.tonneau@pliant.cx
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 2
# as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# version 2 along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

module "/pliant/language/unsafe.pli"
module "/pliant/language/context.pli"
module "/pliant/language/stream.pli"
module "/pliant/language/stream/multi.pli"
module "/pliant/language/stream/filesystembase.pli"
module "/pliant/language/stream/openmode.pli"
module "/pliant/language/stream/flushmode.pli"
module "/pliant/language/stream/listmode.pli"
module "/pliant/util/encoding/html.pli"
module "/pliant/util/encoding/date.pli"
module "/pliant/util/encoding/http.pli"
module "/pliant/util/encoding/base64.pli"
module "/pliant/admin/file.pli"

constant potencial_timeout 45
constant debug false


(gvar TraceSlot http_trace) configure "HTTP client"

method tcp readline log -> l
  arg_rw Stream tcp ; arg_rw TraceSession log ; arg Str l
  l := tcp readline
  if l<>""
    log trace "answer " l

method tcp writeline l log
  arg_rw Stream tcp ; arg Str l ; arg_rw TraceSession log
  tcp writeline l
  log trace "query " l

function write_http_options server options tcp log
  arg Str server options ; arg_rw Stream tcp ; arg_rw TraceSession log
  if server<>""
    tcp writeline "Host: "+server log
  var Str browser := options option "browser" Str "Pliant/"+string:pliant_release_number
  tcp writeline "User-Agent: "+browser log
  if (options option "user" Str)<>""
    tcp writeline "Authorization: Basic "+(base64_encode (options option "user" Str)+":"+(options option "password" Str)) log
  if (options option "form")
    tcp writeline "Content-Length: "+(string (options option "form" Str):len)
  tcp writeline ""


type HttpFileSystem
  void
FileSystem maybe HttpFileSystem

type HttpStreamDriver
  field Link:Stream tcp
  field CBool direct
  field Intn remain
  field Str filename
  field Link:Stream temp
  field CBool initial_ack
  field Str server path ; field DateTime datetime ; field Str options
  field DateTime opened_on
  field TraceSession log
  field Str mime
StreamDriver maybe HttpStreamDriver

method http read buf mini maxi -> red
  arg_rw HttpStreamDriver http ; arg Address buf ; arg Int mini maxi red
  if (exists http:temp)
    red := 0
  eif http:tcp:stream_read_cur<>http:tcp:stream_read_stop
    red := min (cast http:tcp:stream_read_stop Int).-.(cast http:tcp:stream_read_cur Int) maxi
    memory_copy http:tcp:stream_read_cur buf red
    http:tcp:stream_read_cur := http:tcp:stream_read_cur translate Byte red
  else
    red := http:tcp:stream_driver read buf mini maxi

method http write buf mini maxi -> written
  arg_rw HttpStreamDriver http ; arg Address buf ; arg Int mini maxi written
  if http:direct
    if http:remain>=maxi
      written := maxi
    eif http:remain>=mini
      written := http remain
    else
      written := 0
    if debug
      console "try to write " written "/" maxi " remaining " http:remain
    http:tcp raw_write buf written
    if http:tcp=failure
      written := 0
    http remain -= written
    if debug
      console " -> " written eol
  eif (exists http:temp)
    http:temp raw_write buf maxi
    written := shunt http:temp=success maxi 0
  else
    written := 0

method http flush level -> status
  arg_rw HttpStreamDriver http ; arg Int level ; arg Status status
  http:tcp flush level
  status := success

method http write_header size tcp log -> status
  arg_rw HttpStreamDriver http ; arg Intn size ; arg_rw Stream tcp ; arg_rw TraceSession log ; arg ExtendedStatus status
  var Str option := http:options option "option" Str
  tcp writeline "PUT /"+http:path+(shunt option<>"" "?"+option "")+" HTTP/1.0" log
  tcp writeline "Host: "+http:server log
  tcp writeline "Content-Length: "+string:size log
  if http:datetime=defined
    tcp writeline "Last-Modified: "+(rfc1123_date http:datetime) log
  var Str p_opt := ""
  if (http:options option "mode")
    p_opt += "mode "+string:(http:options option "mode" Int)
  if (http:options option "uid")
    p_opt += " uid "+string:(http:options option "uid" Int)
  if (http:options option "gid")
    p_opt += " gid "+string:(http:options option "gid" Int)
  if p_opt<>""
    tcp writeline "Pliant-Options: "+http_encode:p_opt log
  write_http_options "" http:options tcp log
  if http:initial_ack
    if debug
      console "wait for initial ack" eol
    var Str answer := tcp readline log
    if not (answer parse "HTTP/" (var Float protocol_level) _ (var Int retcode) _ any) or retcode>=300
      return (failure "HTTP server returned initial error "+string:answer)
    if debug
      console "wait for initial options" eol
    while { var Str l := tcp readline log ; l<>"" }
      void
    if debug
      console "got initial answer" eol
  status := success

method http close -> status
  arg_rw HttpStreamDriver http ; arg ExtendedStatus status
  var Pointer:Stream tcp :> http tcp
  if http:direct
    if debug
      console "close with " http:remain " remaining bytes" eol
    if http:remain<>0
      return failure:"Too fiew bytes written"
  eif (exists http:temp)
    http:temp close
    var FileInfo info := file_query http:filename standard
    if datetime:seconds-http:opened_on:seconds>=potencial_timeout
      tcp open tcp:name in+out+safe+cr+lf
    status := http write_header info:size tcp http:log
    if status=failure
      file_delete http:filename
      return
    (var Stream s) open http:filename in
    while (raw_copy s tcp 1 2^24)>0
      void
    s close
    file_delete http:filename
  else
    tcp flush end
  if http:direct or (exists http:temp)
    if debug
      console "wait for final ack" eol
    var Str answer := tcp readline http:log
    if not (answer parse "HTTP/" (var Float protocol_level) _ (var Int retcode) _ any) or retcode>=300
      return (failure "HTTP server returned final error "+string:answer)
    if debug 
      console "final answer is " answer eol
    if debug
      console "wait for final options" eol
    while { var Str l := tcp readline http:log ; l<>"" }
      void
    if debug
      console "got final answer" eol
  status := tcp close
  if debug
    console "http client done: " (shunt status=success "ok" "failed") eol

method http query command stream answer -> status
  arg_rw HttpStreamDriver http ; arg Str command ; arg_rw Stream stream ; arg_w Str answer ; arg ExtendedStatus status 
  status := http:tcp:stream_driver query command http:tcp answer
  if command="mime"
    answer := http mime ; status := success
  else
    status := http:tcp:stream_driver query command http:tcp answer

method http configure command stream -> status
  arg_rw HttpStreamDriver http ; arg Str command ; arg_rw Stream stream ; arg ExtendedStatus status
  status := http:tcp:stream_driver configure command http:tcp

method fs query name options flags info -> status 
  arg_rw HttpFileSystem fs ; arg Str name options ; arg Int flags ; arg_rw FileInfo info ; arg ExtendedStatus status
  if (name eparse "//" any:(var Str server) "/" any:(var Str path))
    void
  else
    return failure
  var Int port
  if (server eparse any:(var Str server1) ":" port)
    server := server1
  else
    port := 80
  var Str channel := options option "channel" Str
  if channel=""
    channel := "tcp://"+server+"/client/"+string:port
  var Link:Stream tcp :> new Stream
  tcp open channel in+out+safe+cr+lf
  if tcp=failure
    http_trace trace "Failed to connect to " channel
    return failure
  (var TraceSession log) bind http_trace
  tcp writeline "HEAD /"+path+" HTTP/1.0" log
  write_http_options server options tcp log
  if not ((tcp readline log) parse "HTTP/" (var Float protocol_level) _ (var Int retcode) _ any) or retcode>=300
    tcp open "tcp://"+server+"/client/"+string:port in+out+safe+cr+lf
    tcp writeline "GET /"+path+" HTTP/1.0" log
    write_http_options server options tcp log
    if not ((tcp readline log) parse "HTTP/" (var Float protocol_level) _ (var Int retcode) _ any) or retcode>=300
      return failure
  while { var Str l := tcp readline log ; l<>"" }
    if (l parse "Content-Length" ":" (var Intn length))
      info size := length
    eif (l parse "Last-Modified" ":" any "," (var Int day) _ any:(var Str ascii_month) _ (var Int year) _ (var Int hour) ":" (var Int minute) ":" (var Int second) any)
      var Int month := ("XXX Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec" search ascii_month 0)\4
      if month>0
        info datetime := datetime year month day hour minute second 0
  status := success


method fs list epath options flags files -> supported_flags
  oarg_rw HttpFileSystem fs ; arg Str epath options ; arg Int flags supported_flags ; arg_rw List files
  supported_flags := extended
  if (epath eparse "//" any:(var Str server) "/" any:(var Str path))
    void
  else
    return
  if not (options option "no_http_encode")
    path := http_encode path
  var Int port
  if (server eparse any:(var Str server1) ":" port)
    server := server1
  else
    port := 80
  var Str channel := options option "channel" Str
  if channel=""
    channel := "tcp://"+server+"/client/"+string:port
  var Link:Stream tcp :> new Stream
  tcp open channel in+out+safe+cr+lf
  if tcp=failure
    http_trace trace "Failed to connect to " channel
    return failure
  (var TraceSession log) bind http_trace
  tcp writeline "GET /"+path+"?list HTTP/1.0" log
  write_http_options server options tcp log
  if not ((tcp readline log) parse "HTTP/" (var Float protocol_level) _ (var Int retcode) _ any) or retcode>=300
    return
  while { var Str l := tcp readline log ; l<>"" }
    void
  while not tcp:atend
    var Str l := tcp readline
    if (l eparse "<pfile name=[dq]" any:(var Str name) "[dq] size=[dq]" (var Intn size) "[dq] date=[dq]" (var DateTime dt) "[dq] options=[dq]" any:(var Str opt) "[dq] />")
      var Link:FileInfo info :> new FileInfo 
      info name := epath+html_decode:name
      info size := size
      info datetime := dt
      info options := html_decode:opt
      files append addressof:info
      log trace "list" string:name " " size " " datetime " " string:options


method fs open name options flags stream support -> status
  arg_rw HttpFileSystem fs ; arg Str name options ; arg Int flags ; arg_rw Stream stream support ; arg ExtendedStatus status
  if (name eparse "//" any:(var Str server) "/" any:(var Str path))
    void
  else
    return failure
  if not (options option "no_http_encode")
    path := http_encode path
  var Int port
  if (server eparse any:(var Str server1) ":" port)
    server := server1
  else
    port := 80
  var Str channel := options option "channel" Str
  if channel=""
    channel := "tcp://"+server+"/client/"+string:port
  var Link:Stream tcp :> new Stream
  tcp open channel in+out+safe+cr+lf
  if tcp=failure
    http_trace trace "Failed to connect to " channel
    return (failure "Failed to connect to "+string:server+" TCP port "+string:port)
  var Link:HttpStreamDriver http :> new HttpStreamDriver
  http tcp :> tcp
  http direct := false
  http initial_ack := options option "http_initial_ack"
  http server := server+(shunt port=80 "" ":"+string:port)
  http path := path
  http datetime := options option "datetime" DateTime
  http options := options
  http opened_on := datetime
  http:log bind http_trace
  var Str msg := ""
  if (flags .and. in_out)=in
    var CBool post := options option "form"
    var Str option := options option "option" Str
    tcp writeline (shunt post "POST" "GET")+" /"+path+(shunt option<>"" "?"+option "")+" HTTP/1.0" http:log
    write_http_options server options tcp http:log
    if post
      tcp writechars (options option "form" Str)
    var Str answer := tcp readline http:log
    if not (answer parse "HTTP/" (var Float protocol_level) _ (var Int retcode) _ any)
      return (failure "Unexpected answer "+string:answer+" from "+string:server)
    msg := "http_return_code "+string:retcode
    while { var Str l := tcp readline http:log ; l<>"" }
      msg += " http_answer_option "+string:l
      void
      if (l parse "Content-Type" ":" any:(var Str value))
        http mime := value
    if retcode>=300
      return failure:msg
  eif (flags .and. in_out)=out
    var Intn size := options option "file_size" Intn
    if size<>undefined
      if debug
        console "upload on the fly "+options eol
      http direct := true
      http remain := size
      status := http write_header size tcp http:log
      if status=failure
        return
    else
      if debug
        console "store then upload" eol
      http filename := file_temporary
      http temp :> new Stream
      http:temp open http:filename out+(flags .and. safe)
  else
    return failure
  stream stream_driver :> http
  status := success ; status message := msg
    
method fs configure name options command -> status
  arg_rw HttpFileSystem fs ; arg Str name options command ; arg ExtendedStatus status
  if command="delete"
    if (name eparse "//" any:(var Str server) "/" any:(var Str path))
      void
    else
      return failure
    var Int port
    if (server eparse any:(var Str server1) ":" port)
      server := server1
    else
      port := 80
    var Str channel := options option "channel" Str
    if channel=""
      channel := "tcp://"+server+"/client/"+string:port
    (var Stream tcp) open channel in+out+safe+cr+lf
    if tcp=failure
      http_trace trace "Failed to connect to " channel
      return failure
    (var TraceSession log) bind http_trace
    tcp writeline "DELETE /"+path+" HTTP/1.0" log
    write_http_options server options tcp log
    var Str answer := tcp readline log
    status := shunt (answer parse "HTTP/" (var Float protocol_level) _ (var Int retcode) _ any) and retcode<300 success (failure "HTTP server rejected delete command "+string:answer)
  eif false # (command parse word:"datetime" (var DateTime dt))
    console "set date and time to " dt eol
    if (name eparse "//" any:(var Str server) "/" any:(var Str path))
      void
    else
      return failure
    var Int port
    if (server eparse any:(var Str server1) ":" port)
      server := server1
    else
      port := 80
    var Str channel := options option "channel" Str
    if channel=""
      channel := "tcp://"+server+"/client/"+string:port
    (var Stream tcp) open channel in+out+safe+cr+lf
    if tcp=failure
      http_trace trace "Failed to connect to " channel
      return failure

    (var TraceSession log) bind http_trace
    tcp writeline "PROPFIND /"+path+" HTTP/1.0" log
    var Str form := ""
    form += "<?xml version=[dq]1.0[dq] encoding=[dq]utf-8[dq] ?>[lf]"
    form += "<propfind xmlns=[dq]DAV:[dq]>[lf]"
    form += "  <allprop/>[lf]"
    form += "</propfind>[lf]"
    write_http_options server "form "+string:form+" "+options tcp log
    tcp writechars form
    console form
    var Str answer := tcp readline log
    console "answer is " answer eol
    status := shunt (answer parse "HTTP/" (var Float protocol_level) _ (var Int retcode) _ any) and retcode<300 success (failure "HTTP server rejected delete command "+string:answer)
    while not tcp:atend
      console tcp:readline eol

    (var TraceSession log) bind http_trace
    console "PROPPATCH /"+path+" HTTP/1.0" eol
    tcp writeline "PROPPATCH /"+path+" HTTP/1.0" log
    var Str form := ""
    form += "<?xml version=[dq]1.0[dq] encoding=[dq]utf-8[dq] ?>[lf]"
    form += "<D:propertyupdate xmlns:D=[dq]DAV:[dq]>[lf]"
    form += "<D:set>[lf]"
    form += "<D:prop>[lf]"
    form += "<D:getlastmodified>"+rfc1123_date:dt+"</D:getlastmodified>[lf]"
    form += "</D:prop>[lf]"
    form += "</D:set>[lf]"
    form += "</D:propertyupdate>[lf]"
    write_http_options server "form "+string:form+" "+options tcp log
    tcp writechars form
    console form
    var Str answer := tcp readline log
    console "answer is " answer eol
    status := shunt (answer parse "HTTP/" (var Float protocol_level) _ (var Int retcode) _ any) and retcode<300 success (failure "HTTP server rejected delete command "+string:answer)
    while not tcp:atend
      console tcp:readline eol
  else
    status := failure

gvar HttpFileSystem http_file_system
pliant_multi_file_system mount "http:" "" http_file_system