home *** CD-ROM | disk | FTP | other *** search
/ PC World 2002 May / PCWorld_2002-05_cd.bin / Software / TemaCD / activetcltk / ActiveTcl8.3.4.1-8.win32-ix86.exe / ActiveTcl8.3.4.1-win32-ix86 / lib / tclsoap1.6.1 / SOAP.tcl < prev    next >
Encoding:
Text File  |  2001-10-22  |  41.8 KB  |  1,204 lines

  1. # SOAP.tcl - Copyright (C) 2001 Pat Thoyts <Pat.Thoyts@bigfoot.com>
  2. #
  3. # Provide Tcl access to SOAP 1.1 methods.
  4. #
  5. # See http://tclsoap.sourceforge.net/ or doc/TclSOAP.html for usage details.
  6. #
  7. # -------------------------------------------------------------------------
  8. # This software is distributed in the hope that it will be useful, but
  9. # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  10. # or FITNESS FOR A PARTICULAR PURPOSE.  See the accompanying file `LICENSE'
  11. # for more details.
  12. # -------------------------------------------------------------------------
  13.  
  14. package provide SOAP 1.6
  15.  
  16. # -------------------------------------------------------------------------
  17.  
  18. package require http 2.0
  19. package require SOAP::Utils
  20. package require rpcvar
  21.  
  22. if {[catch {
  23.     package require tdom
  24.     package require SOAP::dom
  25.     puts "using tDOM and SOAP::dom"
  26. }]} {
  27.     if { [catch {package require dom 2.0} domVer]} {
  28.         if { [catch {package require dom 1.6} domVer]} {
  29.             error "require dom package greater than 1.6"
  30.         }
  31.         package require SOAP::xpath
  32.     }
  33. }
  34.  
  35.  
  36. namespace eval SOAP {
  37.     variable version 1.6
  38.     variable domVersion $domVer
  39.     variable rcs_version { $Id: SOAP.tcl,v 1.36 2001/10/11 22:38:38 patthoyts Exp $ }
  40.  
  41.     namespace export create cget dump configure proxyconfig export
  42.     catch {namespace import -force Utils::*} ;# catch to allow pkg_mkIndex.
  43.     catch {namespace import -force [uplevel {namespace current}]::rpcvar::*}
  44. }
  45.  
  46. unset domVer
  47.  
  48. # -------------------------------------------------------------------------
  49.  
  50. # Description:
  51. #   Called from SOAP package methods, shift up to the callers level and
  52. #   get the fully namespace qualified name for the given proc / var
  53. # Parameters:
  54. #   name - the name of a Tcl entity, or list of command and arguments
  55. # Result:
  56. #   Fully qualified namespace path for the named entity. If the name 
  57. #   parameter is a list the the first element is namespace qualified
  58. #   and the remainder of the list is unchanged.
  59. #
  60. proc SOAP::qualifyNamespace {name} {
  61.     if {$name != {}} {
  62.         set name [lreplace $name 0 0 \
  63.                 [uplevel 2 namespace origin [lindex $name 0]]]
  64.     }
  65.     return $name
  66. }
  67.  
  68. # -------------------------------------------------------------------------
  69.  
  70. proc SOAP::methodVarName {methodName} {
  71.     set name [uplevel 2 namespace origin $methodName]
  72.     regsub -all {::+} $name {_} name
  73.     return [namespace current]::$name
  74. }
  75.  
  76. # -------------------------------------------------------------------------
  77.  
  78. # Retrieve configuration variables
  79.  
  80. proc SOAP::cget { args } {
  81.  
  82.     if { [llength $args] != 2 } {
  83.         error "wrong # args: should be \"cget methodName optionName\""
  84.     }
  85.  
  86.     set methodName [lindex $args 0]
  87.     set optionName [lindex $args 1]
  88.     set configVarName [methodVarName $methodName]
  89.  
  90.     if {[catch {set [subst $configVarName]([string trimleft $optionName "-"])} result]} {
  91.         # kenstir@synchonicity.com: Fixed typo.
  92.         error "unknown option \"$optionName\""
  93.     }
  94.     return $result
  95. }
  96.  
  97. # -------------------------------------------------------------------------
  98.  
  99. # Dump the HTTP data from the last request performed.
  100. # Options to dump the HTTP meta data the reply data or the XML of the
  101. # SOAP request that was posted to the server
  102. #
  103. proc SOAP::dump {args} {
  104.     if {[llength $args] == 1} {
  105.         set type -reply
  106.         set methodName [lindex $args 0]
  107.     } elseif { [llength $args] == 2 } {
  108.         set type [lindex $args 0]
  109.         set methodName [lindex $args 1]
  110.     } else {
  111.         error "wrong # args: should be \"dump ?option? methodName\""
  112.     }
  113.  
  114.     # Check that methodName exists and has a http variable.
  115.     if { [catch {cget $methodName http} token] } {
  116.         error "invalid method name: \"$methodName\" is not a SOAP command"
  117.     }
  118.     if { $token == {} } {
  119.         error "no information HTTP information available for SOAP method \"$methodName\""
  120.     }
  121.  
  122.     set result {}
  123.     switch -glob -- $type {
  124.         -meta   {set result [lindex [array get $token meta] 1]}
  125.         -qu*    -
  126.         -req*   {set result [lindex [array get $token -query] 1]}
  127.         -rep*   {set result [::http::data $token]}
  128.         default {
  129.             error "unrecognised option: must be one of \
  130.                     \"-meta\", \"-request\" or \"-reply\""
  131.         }
  132.     }
  133.  
  134.     return $result
  135. }
  136.  
  137. # -------------------------------------------------------------------------
  138.  
  139. # Description:
  140. #   Configure or display a SOAP method options.
  141. # Parameters:
  142. #   procName - the SOAP method Tcl procedure name
  143. #   args     - list of option name / option pairs
  144. # Result:
  145. #   Sets up a configuration array for the SOAP method.
  146.  
  147. proc SOAP::configure { procName args } {
  148.     # The list of valid options, used in the error messsage
  149.     set options { uri proxy params name transport action \
  150.                   wrapProc replyProc parseProc postProc \
  151.                   command errorCommand schemas version \
  152.                   encoding }
  153.  
  154.     if { $procName == "-transport" } {
  155.         return [eval "transport_configure $args"]
  156.     }
  157.  
  158.     # construct the name of the options array from the procName.
  159.     set procVarName "[uplevel namespace current]::$procName"
  160.     regsub -all {::+} $procVarName {_} procVarName
  161.     set procVarName [namespace current]::$procVarName
  162.  
  163.     # Check that the named method has actually been defined
  164.     if {! [array exists $procVarName]} {
  165.         error "invalid command: \"$procName\" not defined"
  166.     }
  167.  
  168.     # if no args - print out the current settings.
  169.     if { [llength $args] == 0 } {
  170.         set r {}
  171.         foreach {opt value} [array get $procVarName] {
  172.             lappend r -$opt $value
  173.         }
  174.         return $r
  175.     }
  176.  
  177.     foreach {opt value} $args {
  178.         switch -- $opt {
  179.             -uri       { set [subst $procVarName](uri) $value }
  180.             -proxy     { set [subst $procVarName](proxy) $value }
  181.             -params    { set [subst $procVarName](params) $value }
  182.             -transport { set [subst $procVarName](transport) $value }
  183.             -name      { set [subst $procVarName](name) $value }
  184.             -action    { set [subst $procVarName](action) $value }
  185.             -schemas   { set [subst $procVarName](schemas) $value }
  186.             -version   { set [subst $procVarName](version) $value }
  187.             -encoding  { set [subst $procVarName](encoding) $value }
  188.             -wrapProc  { set [subst $procVarName](wrapProc) \
  189.                     [qualifyNamespace $value] }
  190.             -replyProc { set [subst $procVarName](replyProc) \
  191.                     [qualifyNamespace $value] }
  192.             -parseProc { set [subst $procVarName](parseProc) \
  193.                     [qualifyNamespace $value] }
  194.             -postProc  { set [subst $procVarName](postProc) \
  195.                     [qualifyNamespace $value] }
  196.             -command   { set [subst $procVarName](command) \
  197.                     [qualifyNamespace $value] }
  198.             -errorCommand { set [subst $procVarName](errorCommand) \
  199.                     [qualifyNamespace $value] }
  200.             default {
  201.                 error "unknown option \"$opt\": must be one of ${options}"
  202.             }
  203.         }
  204.     }
  205.  
  206.     if { [set [subst $procVarName](name)] == {} } { 
  207.         set [subst $procVarName](name) $procName
  208.     }
  209.  
  210.     if { [set [subst $procVarName](transport)] == {} } {
  211.         set [subst $procVarName](transport) \
  212.                 [namespace current]::Transport::http::xfer
  213.     } 
  214.     
  215.     # The default version is SOAP 1.1
  216.     set soapver [set [subst $procVarName](version)]
  217.     if { $soapver == {} } {
  218.         set soapver SOAP1.1
  219.     }
  220.     # Canonicalize the SOAP version URI
  221.     switch -glob -- $soapver {
  222.         SOAP1.1 - 1.1 { set soapver "http://schemas.xmlsoap.org/soap/envelope/" }
  223.         SOAP1.2 - 1.2 { set soapver "http://www.w3.org/2001/06/soap-envelope" }
  224.     }
  225.     set [subst $procVarName](version) $soapver
  226.  
  227.     # Default SOAP encoding is SOAP 1.1
  228.     set soapenc [set [subst $procVarName](encoding)]
  229.     if { $soapenc == {} } {
  230.         set soapenc SOAP1.1
  231.     }
  232.     switch -glob -- $soapenc {
  233.         SOAP1.1 - 1.1 { set soapenc "http://schemas.xmlsoap.org/soap/encoding/" }
  234.         SOAP1.2 - 1.2 { set soapenc "http://www.w3.org/2001/06/soap-encoding" }
  235.     }
  236.     set [subst $procVarName](encoding) $soapenc
  237.  
  238.     # Select the default parser unless one is specified
  239.     if { [set [subst $procVarName](parseProc)] == {} } {
  240.         set [subst $procVarName](parseProc) \
  241.                 [namespace current]::parse_soap_response
  242.     } 
  243.  
  244.     # If no request wrapper is set, use the default SOAP wrap proc.
  245.     if { [set [subst $procVarName](wrapProc)] == {} } {
  246.         set [subst $procVarName](wrapProc) \
  247.                 [namespace current]::soap_request
  248.     }
  249.  
  250.     # Create the Tcl procedure that maps to this RPC method.
  251.     uplevel 1 "proc $procName { args } {eval [namespace current]::invoke $procVarName \$args}"
  252.  
  253.     # return the fully qualified command name created.
  254.     return [uplevel 1 "namespace which $procName"]
  255. }
  256.  
  257. # -------------------------------------------------------------------------
  258.  
  259. proc SOAP::create { args } {
  260.     if { [llength $args] < 1 } {
  261.         error "wrong # args: should be \"create procName ?options?\""
  262.     } else {
  263.         set procName [lindex $args 0]
  264.         set args [lreplace $args 0 0]
  265.     }
  266.  
  267.     set ns "[uplevel namespace current]::$procName"
  268.     regsub -all {::+} $ns {_} varName
  269.     set varName [namespace current]::$varName
  270.     array set $varName {}
  271.     array set $varName {uri       {}} ;# the XML namespace URI for this method 
  272.     array set $varName {proxy     {}} ;# URL for the location of a provider
  273.     array set $varName {params    {}} ;# name/type pairs for the parameters
  274.     array set $varName {transport {}} ;# transport procedure for this method
  275.     array set $varName {name      {}} ;# SOAP method name
  276.     array set $varName {action    {}} ;# Contents of the SOAPAction header
  277.     array set $varName {http      {}} ;# the http data variable (if used)
  278.     array set $varName {wrapProc  {}} ;# encode request into XML for sending
  279.     array set $varName {replyProc {}} ;# post process the raw XML result
  280.     array set $varName {parseProc {}} ;# parse raw XML and extract the values
  281.     array set $varName {postProc  {}} ;# post process the parsed result
  282.     array set $varName {command   {}} ;# asynchronous reply handler
  283.     array set $varName {errorCommand {}} ;# asynchronous error handler
  284.     array set $varName {headers   {}} ;# SOAP Header information of last call
  285.     array set $varName {schemas   {}} ;# List of SOAP Schemas in force
  286.     array set $varName {version   {}} ;# SOAP Version in force (URI)
  287.     array set $varName {encoding  {}} ;# SOAP Encoding (URI)
  288.  
  289.     # call configure from the callers level so it can get the namespace.
  290.     return [uplevel 1 "[namespace current]::configure $procName $args"]
  291. }
  292.  
  293. # -------------------------------------------------------------------------
  294.  
  295. proc SOAP::export {args} {
  296.     foreach item $args {
  297.         uplevel "set \[namespace current\]::__soap_exports($item)\
  298.                 \[namespace code $item\]"
  299.     }
  300.     return
  301. }
  302.  
  303. # -------------------------------------------------------------------------
  304.  
  305. # Description:
  306. #   Make a SOAP method call using the configured transport.
  307. # Parameters:
  308. #   procName  - the SOAP method configuration variable path
  309. #   args      - the parameter list for the SOAP method call
  310. # Returns:
  311. #   Returns the parsed and processed result of the method call
  312. #
  313. proc SOAP::invoke { procVarName args } {
  314.     set procName [lindex [split $procVarName {_}] end]
  315.     if {![array exists $procVarName]} {
  316.         error "invalid command: \"$procName\" not defined"
  317.     }
  318.  
  319.     # Get the URL
  320.     set url [set [subst $procVarName](proxy)]
  321.  
  322.     # Get the XML data containing our request by calling the -wrapProc 
  323.     # procedure
  324.     set req [eval "[set [subst $procVarName](wrapProc)] $procVarName $args"]
  325.  
  326.     # Send the SOAP packet (req) using the configured transport procedure
  327.     set transport [set [subst $procVarName](transport)]
  328.     set reply [$transport $procVarName $url $req]
  329.  
  330.     # Check for an async command handler. If async then return now,
  331.     # otherwise call the invoke second stage immediately.
  332.     if { [set [subst $procVarName](command)] != {} } {
  333.         return $reply
  334.     }
  335.     return [invoke2 $procVarName $reply]
  336. }
  337.  
  338. # -------------------------------------------------------------------------
  339.  
  340. # Description:
  341. #   The second stage of the method invocation deals with unwrapping the
  342. #   reply packet that has been received from the remote service.
  343. # Parameters:
  344. #   procVarName - the SOAP method configuration variable path
  345. #   reply       - the raw data returned from the remote service
  346. # Notes:
  347. #   This has been separated from `invoke' to support asynchronous
  348. #   transports. It calls the various unwrapping hooks in turn.
  349. #
  350. proc SOAP::invoke2 {procVarName reply} {
  351.     set ::lastReply $reply
  352.  
  353.     set procName [lindex [split $procVarName {_}] end]
  354.  
  355.     # Post-process the raw XML using -replyProc
  356.     set replyProc [set [subst $procVarName](replyProc)]
  357.     if { $replyProc != {} } {
  358.         set reply [$replyProc $procVarName $reply]
  359.     }
  360.  
  361.     # Call the relevant parser to extract the returned values
  362.     set parseProc [set [subst $procVarName](parseProc)]
  363.     if { $parseProc == {} } {
  364.         set parseProc parse_soap_response
  365.     }
  366.     set r [$parseProc $procVarName $reply]
  367.  
  368.     # Post process the parsed reply using -postProc
  369.     set postProc [set [subst $procVarName](postProc)]
  370.     if { $postProc != {} } {
  371.         set r [$postProc $procVarName $r]
  372.     }
  373.  
  374.     return $r
  375. }
  376.  
  377. # -------------------------------------------------------------------------
  378.  
  379. # Description:
  380. #   Handle a proxy server.
  381. # Notes:
  382. #   Needs expansion to use a list of non-proxied sites or a list of
  383. #   {regexp proxy} or something.
  384. #   The proxy variable in this namespace is set up by 
  385. #   configure -transport http.
  386. #
  387. namespace eval SOAP::Transport::http {
  388.     variable options
  389.  
  390.     proc filter {host} {
  391.         variable options
  392.         if { [string match "localhost*" $host] \
  393.                 || [string match "127.*" $host] } {
  394.             return {}
  395.         }
  396.         return [lrange [split $options(proxy) {:}] 0 1]
  397.     }
  398.  
  399.     # Provide missing code for http < 2.3
  400.     if {[info proc ::http::ncode] == {}} {
  401.         namespace eval ::http {
  402.             proc ncode {token} {
  403.                 return [lindex [split [code $token]] 1]
  404.             }
  405.         }
  406.     }
  407. }
  408.  
  409. # -------------------------------------------------------------------------
  410.  
  411. # Description:
  412. #   Perform a remote procedure call using HTTP as the transport protocol.
  413. #   This uses the Tcl http package to do the work. If the SOAP method has
  414. #   the -command option set to something then the call is made 
  415. #   asynchronously and the result data passed to the users callback
  416. #   procedure.
  417. #   If you have an HTTP proxy to deal with then you should set up the 
  418. #   SOAP::Transport::http::filter procedure and proxy variable to suit.
  419. #   This can be done using SOAP::proxyconfig.
  420. # Parameters:
  421. #   procVarName - 
  422. #   url         -
  423. #   request     -
  424. # Result:
  425. #   The request data is POSTed to the SOAP provider via HTTP using any
  426. #   configured proxy host. If the HTTP returns an error code then an error
  427. #   is raised otherwise the reply data is returned. If the method has
  428. #   been configured to be asynchronous then the async handler is called
  429. #   once the http request completes.
  430. #
  431. proc SOAP::Transport::http::xfer { procVarName url request } {
  432.     variable options
  433.  
  434.     # Get the SOAP package version
  435.     set version [set [namespace parent [namespace parent]]::version]
  436.  
  437.     # setup the HTTP POST request
  438.     ::http::config -useragent "TclSOAP/$version ($::tcl_platform(os))"
  439.  
  440.     # If a proxy was configured, use it.
  441.     if { [info exists options(proxy)] && $options(proxy) != {} } {
  442.         ::http::config -proxyfilter [namespace origin filter]
  443.     }
  444.  
  445.     # Check for an HTTP progress callback.
  446.     set local_progress {}
  447.     if { [info exists options(progress)] && $options(progress) != {} } {
  448.         set local_progress "-progress [list $options(progress)]"
  449.     }
  450.     
  451.     # There may be http headers configured. eg: for proxy servers
  452.     # eg: SOAP::configure -transport http -headers 
  453.     #    [list "Proxy-Authorization" [basic_authorization]]
  454.     set local_headers {}
  455.     if {[info exists options(headers)]} {
  456.         set local_headers $options(headers)
  457.     }
  458.  
  459.     # Add mandatory SOAPAction header (SOAP 1.1). This may be empty otherwise
  460.     # must be in quotes.
  461.     set action [set [subst $procVarName](action)]
  462.     if { $action != {} } { 
  463.         set action [string trim $action "\""]
  464.         set action "\"$action\""
  465.         lappend local_headers "SOAPAction" $action
  466.     }
  467.  
  468.     # cleanup the last http request
  469.     if { [set [subst $procVarName](http)] != {} } {
  470.         catch { eval "::http::cleanup [set [subst $procVarName](http)]" }
  471.     }
  472.  
  473.     # Check for an asynchronous handler and perform the transfer.
  474.     # If async - return immediately.
  475.     set command {}
  476.     if {[set [subst $procVarName](command)] != {}} {
  477.         set command "-command {[namespace current]::asynchronous $procVarName}"
  478.     }
  479.  
  480.     set token [eval ::http::geturl [list $url] \
  481.             -headers [list $local_headers] \
  482.             -type text/xml -query [list $request] \
  483.             $local_progress $command]
  484.     set [subst $procVarName](http) $token
  485.     if { $command != {}} { return {} }
  486.     
  487.  
  488.     # store the http structure reference for possible access later.
  489.     set [subst $procVarName](http) $token
  490.  
  491.     # Some other sort of error ...
  492.     if {[::http::status $token] != "ok"} {
  493.          error "SOAP transport error: \"[::http::code $token]\""
  494.     }
  495.  
  496.     return [::http::data $token]
  497. }
  498.  
  499. # -------------------------------------------------------------------------
  500.  
  501. # Description:
  502. #    Asynchronous http handler command.
  503. proc SOAP::Transport::http::asynchronous {procVarName token} {
  504.     if {[catch {asynchronous2 $procVarName $token} msg]} {
  505.         if {[set [subst $procVarName](errorCommand)] != {}} {
  506.             set errorCommand [set [subst $procVarName](errorCommand)]
  507.             if {[catch {eval $errorCommand [list $msg]} result]} {
  508.                 bgerror $result
  509.             }
  510.         } else {
  511.             bgerror $msg
  512.         }
  513.     }
  514.     return $msg
  515. }
  516.  
  517. proc SOAP::Transport::http::asynchronous2 {procVarName token} {
  518.     set procName [lindex [split $procVarName {_}] end]
  519.  
  520.     # Some other sort of error ...
  521.     if {[::http::status $token] != "ok"} {
  522.          error "SOAP transport error: \"[::http::code $token]\""
  523.     }
  524.  
  525.     set reply [::http::data $token]
  526.  
  527.     # Call the second part of invoke to unwrap the packet data.
  528.     set reply [SOAP::invoke2 $procVarName $reply]
  529.  
  530.     # Call the users handler.
  531.     set command [set [subst $procVarName](command)]
  532.     return [eval $command [list $reply]]
  533. }
  534.  
  535. # -------------------------------------------------------------------------
  536.  
  537. # Description:
  538. #   A dummy SOAP transport procedure to examine the SOAP requests generated.
  539. # Parameters:
  540. #   procVarName  - SOAP method name configuration variable
  541. #   url          - URL of the remote server method implementation
  542. #   soap         - the XML payload for this SOAP method call
  543. #
  544. namespace eval SOAP::Transport::print {
  545.     proc print { procVarName url soap } {
  546.         puts "$soap"
  547.     }
  548. }
  549.  
  550. namespace eval SOAP::Transport::reflect {
  551.     proc reflect {procVarName url soap} {
  552.         return $soap
  553.     }
  554. }
  555.  
  556. # -------------------------------------------------------------------------
  557.  
  558. # Description:
  559. #   Helper procedure called from configure used to setup the SOAP transport
  560. #   options. Calling `invoke' for a method will call the configured 
  561. #   transport procedure.
  562. # Parameters:
  563. #   transport - the name of the transport mechanism (smtp, http, etc)
  564. #   args      - list of options for the named transport mechanism
  565. #
  566. proc SOAP::transport_configure { transport args } {
  567.     switch -- $transport {
  568.         http {
  569.             # If no args then print out the current settings
  570.             if { $args == {} } {
  571.                 set r {}
  572.                 foreach {opt value} [array get Transport::http::options] {
  573.                     lappend r "-$opt" $value
  574.                 }
  575.                 return $r
  576.             }
  577.             
  578.             foreach {opt value} $args {
  579.                 switch -- $opt {
  580.                     -proxy   {
  581.                         set Transport::http::options(proxy) $value
  582.                     }
  583.                     -headers {
  584.                         if {[catch {
  585.                             set h $Transport::http::options(headers)}]
  586.                         } {
  587.                             set h {}
  588.                         }
  589.                         set Transport::http::options(headers) \
  590.                                 [concat $h $value]
  591.                     }
  592.                     -progress {
  593.                         set Transport::http::options(progress) $value
  594.                     }
  595.                     default {
  596.                         error [concat "invalid option \"$opt\":" \
  597.                                 "must be \"-proxy host:port\" "\
  598.                                 "or \"-headers list\""]
  599.                     }
  600.                 }
  601.             }
  602.         }
  603.         print {
  604.             return "no configuration required"
  605.         }
  606.         default {
  607.             error "SOAP transport \"$transport\" is undefined: \
  608.                     must be one of \"http\" or \"print\"."
  609.         }
  610.     }
  611. }
  612.  
  613. # -------------------------------------------------------------------------
  614.  
  615. # Description:
  616. #   Setup SOAP HTTP transport for an authenticating proxy HTTP server.
  617. #   At present the SOAP package only supports Basic authentication and this
  618. #   dialog is used to configure the proxy information.
  619. # Parameters:
  620. #   none
  621.  
  622. proc SOAP::proxyconfig {} {
  623.     package require Tk
  624.     if { [catch {package require base64}] } {
  625.         if { [catch {package require Trf}] } {
  626.             error "proxyconfig requires either tcllib or Trf packages."
  627.         } else {
  628.             set local64 "base64 -mode enc"
  629.         }
  630.     } else {
  631.         set local64 "base64::encode"
  632.     }
  633.  
  634.     toplevel .tx
  635.     wm title .tx "Proxy Configuration"
  636.     set m [message .tx.m1 -relief groove -justify left -width 6c -aspect 200 \
  637.             -text "Enter details of your proxy server (if any) and your username and password if it is needed by the proxy."]
  638.     set f1 [frame .tx.f1]
  639.     set f2 [frame .tx.f2]
  640.     button $f2.b -text "OK" -command {destroy .tx}
  641.     pack $f2.b -side right
  642.     label $f1.l1 -text "Proxy (host:port)"
  643.     label $f1.l2 -text "Username"
  644.     label $f1.l3 -text "Password"
  645.     entry $f1.e1 -textvariable SOAP::conf_proxy
  646.     entry $f1.e2 -textvariable SOAP::conf_userid
  647.     entry $f1.e3 -textvariable SOAP::conf_passwd -show {*}
  648.     grid $f1.l1 -column 0 -row 0 -sticky e
  649.     grid $f1.l2 -column 0 -row 1 -sticky e
  650.     grid $f1.l3 -column 0 -row 2 -sticky e
  651.     grid $f1.e1 -column 1 -row 0 -sticky news
  652.     grid $f1.e2 -column 1 -row 1 -sticky news
  653.     grid $f1.e3 -column 1 -row 2 -sticky news
  654.     grid columnconfigure $f1 1 -weight 1
  655.     pack $f2 -side bottom -fill x
  656.     pack $m  -side top -fill x -expand 1
  657.     pack $f1 -side top -anchor n -fill both -expand 1
  658.     tkwait window .tx
  659.     SOAP::configure -transport http -proxy $SOAP::conf_proxy
  660.     if { [info exists SOAP::conf_userid] } {
  661.         SOAP::configure -transport http \
  662.             -headers [list "Proxy-Authorization" \
  663.             "Basic [lindex [$local64 ${SOAP::conf_userid}:${SOAP::conf_passwd}] 0]" ]
  664.     }
  665.     unset SOAP::conf_passwd
  666. }
  667.  
  668. # -------------------------------------------------------------------------
  669.  
  670. # Description:
  671. #   Prepare a SOAP fault message
  672. # Parameters:
  673. #   faultcode   - the SOAP faultcode e.g: SOAP-ENV:Client
  674. #   faultstring - summary of the fault
  675. #   detail      - list of {detailName detailInfo}
  676. # Result:
  677. #   returns the XML text of the SOAP Fault packet.
  678. proc SOAP::fault {faultcode faultstring {detail {}}} {
  679.     set doc [dom::DOMImplementation create]
  680.     set bod [reply_envelope $doc]
  681.     set flt [dom::document createElement $bod "SOAP-ENV:Fault"]
  682.     set fcd [dom::document createElement $flt "faultcode"]
  683.     dom::document createTextNode $fcd $faultcode
  684.     set fst [dom::document createElement $flt "faultstring"]
  685.     dom::document createTextNode $fst $faultstring
  686.  
  687.     if { $detail != {} } {
  688.         set dtl0 [dom::document createElement $flt "detail"]
  689.         set dtl  [dom::document createElement $dtl0 "e:errorInfo"]
  690.         dom::element setAttribute $dtl "xmlns:e" "urn:TclSOAP-ErrorInfo"
  691.         
  692.         foreach {detailName detailInfo} $detail {
  693.             set err [dom::document createElement $dtl $detailName]
  694.             dom::document createTextNode $err $detailInfo
  695.         }
  696.     }
  697.     
  698.     # serialize the DOM document and return the XML text
  699.     regsub "<!DOCTYPE\[^>\]*>\n" [dom::DOMImplementation serialize $doc] {} r
  700.     dom::DOMImplementation destroy $doc
  701.     return $r
  702. }
  703.  
  704. # -------------------------------------------------------------------------
  705.  
  706. # Description:
  707. #   Generate the common portion of a SOAP replay packet
  708. # Parameters:
  709. #   doc   - the document element of a DOM document
  710. # Result:
  711. #   returns the body node
  712. #
  713. proc SOAP::reply_envelope { doc } {
  714.     set env [dom::document createElement $doc "SOAP-ENV:Envelope"]
  715.     dom::element setAttribute $env \
  716.             "xmlns:SOAP-ENV" "http://schemas.xmlsoap.org/soap/envelope/"
  717.     dom::element setAttribute $env \
  718.             "xmlns:xsi"      "http://www.w3.org/1999/XMLSchema-instance"
  719.     dom::element setAttribute $env \
  720.             "xmlns:xsd"      "http://www.w3.org/1999/XMLSchema"
  721.     dom::element setAttribute $env \
  722.             "xmlns:SOAP-ENC" "http://schemas.xmlsoap.org/soap/encoding/"
  723.     set bod [dom::document createElement $env "SOAP-ENV:Body"]
  724.     return $bod
  725. }
  726.  
  727. # -------------------------------------------------------------------------
  728.  
  729. # Description:
  730. #   Generate a SOAP reply packet. Uses 'rpcvar' variable type information to
  731. #   manage complex data structures and arrays.
  732. # Parameters:
  733. #   doc         empty DOM document element
  734. #   uri         URI of the SOAP method
  735. #   methodName  the SOAP method name
  736. #   result      the reply data
  737. # Result:
  738. #   returns the DOM document root
  739. #
  740. proc SOAP::reply { doc uri methodName result } {
  741.     set bod [reply_envelope $doc]
  742.     set cmd [dom::document createElement $bod "ns:$methodName"]
  743.     dom::element setAttribute $cmd "xmlns:ns" $uri
  744.     dom::element setAttribute $cmd \
  745.             "SOAP-ENV:encodingStyle" \
  746.             "http://schemas.xmlsoap.org/soap/encoding/"
  747.  
  748.     # insert the results into the DOM tree (unless it's a void result)
  749.     if {$result != {}} {
  750.         set retnode [dom::document createElement $cmd "return"]
  751.         SOAP::insert_value $retnode $result
  752.     }
  753.  
  754.     return $doc
  755. }
  756.  
  757. # -------------------------------------------------------------------------
  758.  
  759. # Description:
  760. #   Procedure to generate the XML data for a configured SOAP procedure.
  761. #   This is the default SOAP -wrapProc procedure
  762. # Parameters:
  763. #   procVarName - the path of the SOAP method configuration variable
  764. #   args        - the arguments for this SOAP method
  765. # Result:
  766. #   XML data containing the SOAP method call.
  767. #
  768. proc SOAP::soap_request {procVarName args} {
  769.  
  770.     set procName [lindex [split $procVarName {_}] end]
  771.     set params [set [subst $procVarName](params)]
  772.     set name [set [subst $procVarName](name)]
  773.     set uri [set [subst $procVarName](uri)]
  774.     set soapenv [set [subst $procVarName](version)]
  775.     set soapenc [set [subst $procVarName](encoding)]
  776.  
  777.     # Check for options (ie: -header)
  778.     array set opts {-headers {} -attributes {}}
  779.     while {[string match -* [lindex $args 0]]} {
  780.         switch -glob -- [lindex $args 0] {
  781.             -header* {
  782.                 set opts(-headers) [concat $opts(-headers) [lindex $args 1]]
  783.                 set args [lreplace $args 0 0]
  784.             }
  785.             -attr* {
  786.                 set opts(-attributes) [concat $opts(-attributes) [lindex $args 1]]
  787.                 set args [lreplace $args 0 0]
  788.             }
  789.             -- {
  790.                 set args [lreplace $args 0 0]
  791.                 break
  792.             }
  793.             default {
  794.                 # stop option processing at the first invalid option.
  795.                 break
  796.             }
  797.         }
  798.         set args [lreplace $args 0 0]
  799.     }
  800.  
  801.     # check for variable number of params and set the num required.
  802.     if {[lindex $params end] == "args"} {
  803.         set n_params [expr ( [llength $params] - 1 ) / 2]
  804.     } else {
  805.         set n_params [expr [llength $params] / 2]
  806.     }
  807.  
  808.     # check we have the correct number of parameters supplied.
  809.     if {[llength $args] < $n_params} {
  810.         set msg "wrong # args: should be \"$procName"
  811.         foreach { id type } $params {
  812.             append msg " " $id
  813.         }
  814.         append msg "\""
  815.         error $msg
  816.     }
  817.  
  818.     set doc [dom::DOMImplementation create]
  819.     set envx [dom::document createElement $doc "SOAP-ENV:Envelope"]
  820.  
  821.     dom::element setAttribute $envx "xmlns:SOAP-ENV" $soapenv
  822.     dom::element setAttribute $envx "xmlns:SOAP-ENC" $soapenc
  823.     dom::element setAttribute $envx "SOAP-ENV:encodingStyle" $soapenc
  824.  
  825.     # The set of namespaces depends upon the SOAP encoding as specified by
  826.     # the encoding option and the user specified set of relevant schemas.
  827.     foreach {nsname url} [rpcvar::default_schemas $soapenc] {
  828.         dom::element setAttribute $envx $nsname $url
  829.     }
  830.  
  831.     # Insert the Header elements (if any)
  832.     if {$opts(-headers) != {}} {
  833.         set headelt [dom::document createElement $envx "SOAP-ENV:Header"]
  834.         foreach {hname hvalue} $opts(-headers) {
  835.             set hnode [dom::document createElement $headelt $hname]
  836.             insert_value $hnode $hvalue
  837.         }
  838.     }
  839.  
  840.     # Insert the body element and atributes.
  841.     set bod [dom::document createElement $envx "SOAP-ENV:Body"]
  842.     if {$uri == ""} {
  843.         # don't use a namespace prefix if we don't have a namespace.
  844.         set cmd [dom::document createElement $bod "$name" ]
  845.     } else {
  846.         set cmd [dom::document createElement $bod "ns:$name" ]
  847.         dom::element setAttribute $cmd "xmlns:ns" $uri
  848.     }
  849.  
  850.     # Insert any method attributes
  851.     if {$opts(-attributes) != {}} {
  852.         foreach {atname atvalue} $opts(-attributes) {
  853.             dom::element setAttribute $cmd $atname $atvalue
  854.         }
  855.     }
  856.  
  857.     # insert the parameters.
  858.     set param_no 0
  859.     foreach {key type} $params {
  860.         set val [lindex $args $param_no]
  861.         set d_param [dom::document createElement $cmd $key]
  862.         insert_value $d_param [rpcvar $type $val]
  863.         incr param_no
  864.     }
  865.  
  866.     # We have to strip out the DOCTYPE element though. It would be better to
  867.     # remove the DOM node for this, but that didn't work.
  868.     set prereq [dom::DOMImplementation serialize $doc]
  869.     set req {}
  870.     dom::DOMImplementation destroy $doc              ;# clean up
  871.     regsub "<!DOCTYPE\[^>\]*>\r?\n?" $prereq {} req  ;# hack
  872.  
  873.     set req [encoding convertto utf-8 $req]          ;# make it UTF-8
  874.     return $req                                      ;# return the XML data
  875. }
  876.  
  877. # -------------------------------------------------------------------------
  878.  
  879. # Description:
  880. #   Procedure to generate the XML data for a configured XML-RPC procedure.
  881. # Parameters:
  882. #   procVarName - the name of the XML-RPC method variable
  883. #   args        - the arguments for this RPC method
  884. # Result:
  885. #   XML data containing the XML-RPC method call.
  886. #
  887. proc SOAP::xmlrpc_request {procVarName args} {
  888.  
  889.     set procName [lindex [split $procVarName {_}] end]
  890.     set params [set [subst $procVarName](params)]
  891.     set name   [set [subst $procVarName](name)]
  892.     
  893.     if { [llength $args] != [expr [llength $params] / 2]} {
  894.         set msg "wrong # args: should be \"$procName"
  895.         foreach { id type } $params {
  896.             append msg " " $id
  897.         }
  898.         append msg "\""
  899.         error $msg
  900.     }
  901.     
  902.     set doc [dom::DOMImplementation create]
  903.     set d_root [dom::document createElement $doc "methodCall"]
  904.     set d_meth [dom::document createElement $d_root "methodName"]
  905.     dom::document createTextNode $d_meth $name
  906.     
  907.     if { [llength $params] != 0 } {
  908.         set d_params [dom::document createElement $d_root "params"]
  909.     }
  910.     
  911.     set param_no 0
  912.     foreach {key type} $params {
  913.         set val [lindex $args $param_no]
  914.         set d_param [dom::document createElement $d_params "param"]
  915.         XMLRPC::insert_value $d_param [rpcvar $type $val]
  916.         incr param_no
  917.     }
  918.  
  919.     # We have to strip out the DOCTYPE element though. It would be better to
  920.     # remove the DOM element, but that didn't work.
  921.     set prereq [dom::DOMImplementation serialize $doc]
  922.     set req {}
  923.     dom::DOMImplementation destroy $doc          ;# clean up
  924.     regsub "<!DOCTYPE\[^>\]*>\n" $prereq {} req  ;# hack
  925.  
  926.     return $req                                  ;# return the XML data
  927. }
  928.  
  929. # -------------------------------------------------------------------------
  930.  
  931. # Description:
  932. #   Parse a SOAP response payload. Check for Fault response otherwise 
  933. #   extract the value data.
  934. # Parameters:
  935. #   procVarName  - the name of the SOAP method configuration variable
  936. #   xml          - the XML payload of the response
  937. # Result:
  938. #   The returned value data.
  939. # Notes:
  940. #   Needs work to cope with struct or array types.
  941. #
  942. proc SOAP::parse_soap_response { procVarName xml } {
  943.     # Sometimes Fault packets come back with HTTP code 200
  944.     #
  945.     # kenstir@synchronicity.com: Catch xml parse errors and present a
  946.     #   friendlier message.  The parse method throws awful messages like
  947.     #   "{invalid attribute list} around line 16".
  948.     if {[catch {set doc [dom::DOMImplementation parse $xml]}]} {
  949.         error "Server response is not well-formed XML.\nresponse was $xml" \
  950.                 $::errorInfo Server
  951.     }
  952.  
  953.     set faultNode [selectNode $doc "/Envelope/Body/Fault"]
  954.     if {$faultNode != {}} {
  955.         array set fault [decomposeSoap $faultNode]
  956.         dom::DOMImplementation destroy $doc
  957.         if {![info exists fault(detail)]} { set fault(detail) {}}
  958.         error [list $fault(faultcode) $fault(faultstring)] $fault(detail)
  959.     }
  960.  
  961.     # If there is a header element then make it available via SOAP::getHeader
  962.     set headerNode [selectNode $doc "/Envelope/Header"]
  963.     if {$headerNode != {} \
  964.             && [string match \
  965.                     "http://schemas.xmlsoap.org/soap/envelope/" \
  966.                     [namespaceURI $headerNode]]} {
  967.         set [subst $procVarName](headers) [decomposeSoap $headerNode]
  968.     } else {
  969.         set [subst $procVarName](headers) {}
  970.     }
  971.     
  972.     set result {}
  973.  
  974.     if {[info exists [subst $procVarName](name)]} {
  975.         set responseName "[set [subst $procVarName](name)]Response"
  976.     } else {
  977.         set responseName "*"
  978.     }
  979.     set responseNode [selectNode $doc "/Envelope/Body/$responseName"]
  980.     if {$responseNode == {}} {
  981.         set responseNode [lindex [selectNode $doc "/Envelope/Body/*"] 0]
  982.     }
  983.  
  984.     set nodes [getElements $responseNode]
  985.     foreach node $nodes {
  986.         set r [decomposeSoap $node]
  987.         if {$result == {}} { set result $r } else { lappend result $r }
  988.     }
  989.  
  990.     dom::DOMImplementation destroy $doc
  991.     return $result
  992. }
  993.  
  994. # -------------------------------------------------------------------------
  995.  
  996. # Description:
  997. #   Parse an XML-RPC response payload. Check for fault response otherwise 
  998. #   extract the value data.
  999. # Parameters:
  1000. #   procVarName  - the name of the XML-RPC method configuration variable
  1001. #   xml          - the XML payload of the response
  1002. # Result:
  1003. #   The extracted value(s). Array types are converted into lists and struct
  1004. #   types are turned into lists of name/value pairs suitable for array set
  1005. # Notes:
  1006. #   The XML-RPC fault response doesn't allow us to add in extra values
  1007. #   to the fault struct. So where to put the servers errorInfo?
  1008. #
  1009. proc SOAP::parse_xmlrpc_response { procVarName xml } {
  1010.     set result {}
  1011.     if {[catch {set doc [dom::DOMImplementation parse $xml]}]} {
  1012.         error "Server response is not well-formed XML.\nresponse was $xml" \
  1013.                 $::errorInfo Server
  1014.     }
  1015.  
  1016.     set faultNode [selectNode $doc "/methodResponse/fault"]
  1017.     if {$faultNode != {}} {
  1018.         array set err [lindex [decomposeXMLRPC \
  1019.                 [selectNode $doc /methodResponse]] 0]
  1020.         dom::DOMImplementation destroy $doc
  1021.         error $err(faultString) {Received XML-RPC Error} $err(faultCode)
  1022.     }
  1023.     
  1024.     # Recurse over each params/param/value
  1025.     set n_params 0
  1026.     foreach valueNode [selectNode $doc \
  1027.             "/methodResponse/params/param/value"] {
  1028.         lappend result [xmlrpc_value_from_node $valueNode]
  1029.         incr n_params
  1030.     }
  1031.     dom::DOMImplementation destroy $doc
  1032.  
  1033.     # If (as is usual) there is only one param, simplify things for the user
  1034.     # ie: sort {one two three} should return a 3 element list, not a single
  1035.     # element list whose first element has 3 elements!
  1036.     if {$n_params == 1} {set result [lindex $result 0]}
  1037.     return $result
  1038. }
  1039.  
  1040. # -------------------------------------------------------------------------
  1041.  
  1042. ### NB: this procedure needs to be moved into XMLRPC namespace
  1043.  
  1044. # Description:
  1045. #   Retrieve the value under the given <value> node.
  1046. # Parameters:
  1047. #   valueNode - reference to a <value> element in the response dom tree
  1048. # Result:
  1049. #   Either a single value or a list of values. Arrays expand into a list
  1050. #   of values, structs to a list of name/value pairs.
  1051. # Notes:
  1052. #   Called recursively when processing arrays and structs.
  1053. #
  1054. proc SOAP::xmlrpc_value_from_node {valueNode} {
  1055.     set value {}
  1056.     set elts [getElements $valueNode]
  1057.  
  1058.     if {[llength $elts] != 1} {
  1059.         return [getElementValue $valueNode]
  1060.     }
  1061.     set typeElement [lindex $elts 0]
  1062.     set type [dom::node cget $typeElement -nodeName]
  1063.  
  1064.     if {$type == "array"} {
  1065.         set dataElement [lindex [getElements $typeElement] 0]
  1066.         foreach valueElement [getElements $dataElement] {
  1067.             lappend value [xmlrpc_value_from_node $valueElement]
  1068.         }
  1069.     } elseif {$type == "struct"} {
  1070.         # struct type has 1+ members which have a name and a value elt.
  1071.         foreach memberElement [getElements $typeElement] {
  1072.             set params [getElements $memberElement]
  1073.             foreach param $params {
  1074.                 set nodeName [dom::node cget $param -nodeName]
  1075.                 if { $nodeName == "name"} {
  1076.                     set pname [getElementValue $param]
  1077.                 } elseif { $nodeName == "value" } {
  1078.                     set pvalue [xmlrpc_value_from_node $param]
  1079.                 }
  1080.             }
  1081.             lappend value $pname $pvalue
  1082.         }
  1083.     } else {
  1084.         set value [getElementValue $typeElement]
  1085.     }
  1086.     return $value
  1087. }
  1088.  
  1089. # -------------------------------------------------------------------------
  1090.  
  1091. proc SOAP::insert_headers {node headers} {
  1092.     set doc [SOAP::Utils::getDocumentElement $node]
  1093.     if {[set h [selectNode $doc /Envelope/Header]] == {}} {
  1094.         set e [dom::document cget $doc -documentElement]
  1095.         set h [dom::document createElement $e "SOAP-ENV:Header"]
  1096.     }
  1097.     foreach {name value} $headers {
  1098.         if {$name != {}} {
  1099.             set elt [dom::document createElement $h $name]
  1100.             insert_value $elt $value
  1101.         }
  1102.     }
  1103. }
  1104.  
  1105. # -------------------------------------------------------------------------
  1106.  
  1107. proc SOAP::insert_value {node value} {
  1108.  
  1109.     set type     [rpctype $value]
  1110.     set subtype  [rpcsubtype $value]
  1111.     set attrs    [rpcattributes $value]
  1112.     set headers  [rpcheaders $value]
  1113.     set value    [rpcvalue $value]
  1114.     set typeinfo [typedef -info $type]
  1115.     set typexmlns [typedef -namespace $type]
  1116.  
  1117.     # Handle any header elements
  1118.     if {$headers != {}} {
  1119.         insert_headers $node $headers
  1120.     }
  1121.     
  1122.     # If the rpcvar namespace is a URI then assign it a tag and ensure we
  1123.     # have our colon only when required.
  1124.     if {$typexmlns != {} && [regexp : $typexmlns]} {
  1125.         dom::element setAttribute $node "xmlns:t" $typexmlns
  1126.         set typexmlns t
  1127.     }
  1128.     if {$typexmlns != {}} { append typexmlns : }
  1129.  
  1130.     # If there are any attributes assigned, apply them.
  1131.     if {$attrs != {}} {
  1132.         foreach {aname avalue} $attrs {
  1133.             dom::element setAttribute $node $aname $avalue
  1134.         }
  1135.     }
  1136.  
  1137.     if {[string match {*()} $type] || [string match array $type]} {
  1138.         # array type: arrays are indicated by a () suffix or the word 'array'
  1139.         set itemtype [string trimright $type ()]
  1140.         if {$itemtype == "array"} {
  1141.             set itemtype ur-type
  1142.         }
  1143.         
  1144.         # Look up the typedef info of the item type
  1145.         set itemxmlns [typedef -namespace $itemtype]
  1146.         if {$itemxmlns != {} && [regexp : $itemxmlns]} {
  1147.             dom::element setAttribute $node "xmlns:i" $itemxmlns
  1148.             set itemxmlns i
  1149.         }
  1150.         
  1151.         dom::element setAttribute $node \
  1152.                 "xmlns:SOAP-ENC" "http://schemas.xmlsoap.org/soap/encoding/"
  1153.         dom::element setAttribute $node "xsi:type" "SOAP-ENC:Array"
  1154.         dom::element setAttribute $node \
  1155.                 "SOAP-ENC:arrayType" "$itemxmlns:$itemtype\[[llength $value]\]"
  1156.  
  1157.         foreach elt $value {
  1158.             set d_elt [dom::document createElement $node "item"]
  1159.             if {[string match "ur-type" $itemtype]} {
  1160.                 insert_value $d_elt $elt
  1161.             } else {
  1162.                 insert_value $d_elt [rpcvar $itemtype $elt]
  1163.             }
  1164.         }
  1165.     } elseif {[llength $typeinfo] > 1} {
  1166.         # a typedef'd struct.
  1167.         if {$typexmlns != {}} {
  1168.             dom::element setAttribute $node "xsi:type" "${typexmlns}${type}"
  1169.         }
  1170.         array set ti $typeinfo
  1171.         # Bounds checking - <simon@e-ppraisal.com>
  1172.         if {[llength $typeinfo] != [llength $value]} {
  1173.             error "wrong # args: type $type contains \"$typeinfo\""
  1174.         }
  1175.         foreach {eltname eltvalue} $value {
  1176.             set d_elt [dom::document createElement $node $eltname]
  1177.             if {![info exists ti($eltname)]} {
  1178.                 error "invalid member name: \"$eltname\" is not a member of\
  1179.                         the $type type."
  1180.             }
  1181.             insert_value $d_elt [rpcvar $ti($eltname) $eltvalue]
  1182.         }
  1183.     } elseif {$type == "struct"} {
  1184.         # an unspecified struct
  1185.         foreach {eltname eltvalue} $value {
  1186.             set d_elt [dom::document createElement $node $eltname]
  1187.             insert_value $d_elt $eltvalue
  1188.         }
  1189.     } else {
  1190.         # simple type or typedef'd enumeration
  1191.         if {$typexmlns != {}} {
  1192.             dom::element setAttribute $node "xsi:type" "${typexmlns}${type}"
  1193.         }
  1194.         dom::document createTextNode $node $value
  1195.     }
  1196. }
  1197.  
  1198. # -------------------------------------------------------------------------
  1199.  
  1200. # Local variables:
  1201. #    indent-tabs-mode: nil
  1202. # End:
  1203.