client.go 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990
  1. /*
  2. Copyright (c) 2014-2023 VMware, Inc. All Rights Reserved.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package soap
  14. import (
  15. "bufio"
  16. "bytes"
  17. "context"
  18. "crypto/sha1"
  19. "crypto/sha256"
  20. "crypto/tls"
  21. "crypto/x509"
  22. "encoding/json"
  23. "errors"
  24. "fmt"
  25. "io"
  26. "log"
  27. "net"
  28. "net/http"
  29. "net/http/cookiejar"
  30. "net/url"
  31. "os"
  32. "path"
  33. "path/filepath"
  34. "reflect"
  35. "regexp"
  36. "runtime"
  37. "strings"
  38. "sync"
  39. "github.com/vmware/govmomi/internal/version"
  40. "github.com/vmware/govmomi/vim25/progress"
  41. "github.com/vmware/govmomi/vim25/types"
  42. "github.com/vmware/govmomi/vim25/xml"
  43. )
  44. type HasFault interface {
  45. Fault() *Fault
  46. }
  47. type RoundTripper interface {
  48. RoundTrip(ctx context.Context, req, res HasFault) error
  49. }
  50. const (
  51. SessionCookieName = "vmware_soap_session"
  52. )
  53. // defaultUserAgent is the default user agent string, e.g.
  54. // "govc govmomi/0.28.0 (go1.18.3;linux;amd64)"
  55. var defaultUserAgent = fmt.Sprintf(
  56. "%s %s/%s (%s)",
  57. execName(),
  58. version.ClientName,
  59. version.ClientVersion,
  60. strings.Join([]string{runtime.Version(), runtime.GOOS, runtime.GOARCH}, ";"),
  61. )
  62. type Client struct {
  63. http.Client
  64. u *url.URL
  65. k bool // Named after curl's -k flag
  66. d *debugContainer
  67. t *http.Transport
  68. hostsMu sync.Mutex
  69. hosts map[string]string
  70. Namespace string `json:"namespace"` // Vim namespace
  71. Version string `json:"version"` // Vim version
  72. Types types.Func `json:"types"`
  73. UserAgent string `json:"userAgent"`
  74. cookie string
  75. insecureCookies bool
  76. useJSON bool
  77. }
  78. var schemeMatch = regexp.MustCompile(`^\w+://`)
  79. type errInvalidCACertificate struct {
  80. File string
  81. }
  82. func (e errInvalidCACertificate) Error() string {
  83. return fmt.Sprintf(
  84. "invalid certificate '%s', cannot be used as a trusted CA certificate",
  85. e.File,
  86. )
  87. }
  88. // ParseURL is wrapper around url.Parse, where Scheme defaults to "https" and Path defaults to "/sdk"
  89. func ParseURL(s string) (*url.URL, error) {
  90. var err error
  91. var u *url.URL
  92. if s != "" {
  93. // Default the scheme to https
  94. if !schemeMatch.MatchString(s) {
  95. s = "https://" + s
  96. }
  97. s := strings.TrimSuffix(s, "/")
  98. u, err = url.Parse(s)
  99. if err != nil {
  100. return nil, err
  101. }
  102. // Default the path to /sdk
  103. if u.Path == "" {
  104. u.Path = "/sdk"
  105. }
  106. if u.User == nil {
  107. u.User = url.UserPassword("", "")
  108. }
  109. }
  110. return u, nil
  111. }
  112. func NewClient(u *url.URL, insecure bool) *Client {
  113. var t *http.Transport
  114. if d, ok := http.DefaultTransport.(*http.Transport); ok {
  115. t = d.Clone()
  116. } else {
  117. t = new(http.Transport)
  118. }
  119. if insecure {
  120. if t.TLSClientConfig == nil {
  121. t.TLSClientConfig = new(tls.Config)
  122. }
  123. t.TLSClientConfig.InsecureSkipVerify = insecure
  124. }
  125. c := newClientWithTransport(u, insecure, t)
  126. // Always set DialTLS and DialTLSContext, even if InsecureSkipVerify=true,
  127. // because of how certificate verification has been delegated to the host's
  128. // PKI framework in Go 1.18. Please see the following links for more info:
  129. //
  130. // * https://tip.golang.org/doc/go1.18 (search for "Certificate.Verify")
  131. // * https://github.com/square/certigo/issues/264
  132. t.DialTLSContext = c.dialTLSContext
  133. return c
  134. }
  135. func newClientWithTransport(u *url.URL, insecure bool, t *http.Transport) *Client {
  136. c := Client{
  137. u: u,
  138. k: insecure,
  139. d: newDebug(),
  140. t: t,
  141. Types: types.TypeFunc(),
  142. }
  143. c.hosts = make(map[string]string)
  144. c.Client.Transport = c.t
  145. c.Client.Jar, _ = cookiejar.New(nil)
  146. // Remove user information from a copy of the URL
  147. c.u = c.URL()
  148. c.u.User = nil
  149. if c.u.Scheme == "http" {
  150. c.insecureCookies = os.Getenv("GOVMOMI_INSECURE_COOKIES") == "true"
  151. }
  152. return &c
  153. }
  154. func (c *Client) DefaultTransport() *http.Transport {
  155. return c.t
  156. }
  157. // NewServiceClient creates a NewClient with the given URL.Path and namespace.
  158. func (c *Client) NewServiceClient(path string, namespace string) *Client {
  159. return c.newServiceClientWithTransport(path, namespace, c.t)
  160. }
  161. func (c *Client) newServiceClientWithTransport(path string, namespace string, t *http.Transport) *Client {
  162. vc := c.URL()
  163. u, err := url.Parse(path)
  164. if err != nil {
  165. log.Panicf("url.Parse(%q): %s", path, err)
  166. }
  167. if u.Host == "" {
  168. u.Scheme = vc.Scheme
  169. u.Host = vc.Host
  170. }
  171. client := newClientWithTransport(u, c.k, t)
  172. client.Namespace = "urn:" + namespace
  173. // Copy the trusted thumbprints
  174. c.hostsMu.Lock()
  175. for k, v := range c.hosts {
  176. client.hosts[k] = v
  177. }
  178. c.hostsMu.Unlock()
  179. // Copy the cookies
  180. client.Client.Jar.SetCookies(u, c.Client.Jar.Cookies(u))
  181. // Set SOAP Header cookie
  182. for _, cookie := range client.Jar.Cookies(u) {
  183. if cookie.Name == SessionCookieName {
  184. client.cookie = cookie.Value
  185. break
  186. }
  187. }
  188. // Copy any query params (e.g. GOVMOMI_TUNNEL_PROXY_PORT used in testing)
  189. client.u.RawQuery = vc.RawQuery
  190. client.UserAgent = c.UserAgent
  191. vimTypes := c.Types
  192. client.Types = func(name string) (reflect.Type, bool) {
  193. kind, ok := vimTypes(name)
  194. if ok {
  195. return kind, ok
  196. }
  197. // vim25/xml typeToString() does not have an option to include namespace prefix.
  198. // Workaround this by re-trying the lookup with the namespace prefix.
  199. return vimTypes(namespace + ":" + name)
  200. }
  201. return client
  202. }
  203. // UseJSON changes the protocol between SOAP and JSON. Starting with vCenter
  204. // 8.0.1 JSON over HTTP can be used. Note this method has no locking and clients
  205. // should be careful to not interfere with concurrent use of the client
  206. // instance.
  207. func (c *Client) UseJSON(useJSON bool) {
  208. c.useJSON = useJSON
  209. }
  210. // SetRootCAs defines the set of PEM-encoded file locations of root certificate
  211. // authorities the client uses when verifying server certificates instead of the
  212. // TLS defaults which uses the host's root CA set. Multiple PEM file locations
  213. // can be specified using the OS-specific PathListSeparator.
  214. //
  215. // See: http.Client.Transport.TLSClientConfig.RootCAs and
  216. // https://pkg.go.dev/os#PathListSeparator
  217. func (c *Client) SetRootCAs(pemPaths string) error {
  218. pool := x509.NewCertPool()
  219. for _, name := range filepath.SplitList(pemPaths) {
  220. pem, err := os.ReadFile(filepath.Clean(name))
  221. if err != nil {
  222. return err
  223. }
  224. if ok := pool.AppendCertsFromPEM(pem); !ok {
  225. return errInvalidCACertificate{
  226. File: name,
  227. }
  228. }
  229. }
  230. c.t.TLSClientConfig.RootCAs = pool
  231. return nil
  232. }
  233. // Add default https port if missing
  234. func hostAddr(addr string) string {
  235. _, port := splitHostPort(addr)
  236. if port == "" {
  237. return addr + ":443"
  238. }
  239. return addr
  240. }
  241. // SetThumbprint sets the known certificate thumbprint for the given host.
  242. // A custom DialTLS function is used to support thumbprint based verification.
  243. // We first try tls.Dial with the default tls.Config, only falling back to thumbprint verification
  244. // if it fails with an x509.UnknownAuthorityError or x509.HostnameError
  245. //
  246. // See: http.Client.Transport.DialTLS
  247. func (c *Client) SetThumbprint(host string, thumbprint string) {
  248. host = hostAddr(host)
  249. c.hostsMu.Lock()
  250. if thumbprint == "" {
  251. delete(c.hosts, host)
  252. } else {
  253. c.hosts[host] = thumbprint
  254. }
  255. c.hostsMu.Unlock()
  256. }
  257. // Thumbprint returns the certificate thumbprint for the given host if known to this client.
  258. func (c *Client) Thumbprint(host string) string {
  259. host = hostAddr(host)
  260. c.hostsMu.Lock()
  261. defer c.hostsMu.Unlock()
  262. return c.hosts[host]
  263. }
  264. // KnownThumbprint checks whether the provided thumbprint is known to this client.
  265. func (c *Client) KnownThumbprint(tp string) bool {
  266. c.hostsMu.Lock()
  267. defer c.hostsMu.Unlock()
  268. for _, v := range c.hosts {
  269. if v == tp {
  270. return true
  271. }
  272. }
  273. return false
  274. }
  275. // LoadThumbprints from file with the give name.
  276. // If name is empty or name does not exist this function will return nil.
  277. func (c *Client) LoadThumbprints(file string) error {
  278. if file == "" {
  279. return nil
  280. }
  281. for _, name := range filepath.SplitList(file) {
  282. err := c.loadThumbprints(name)
  283. if err != nil {
  284. return err
  285. }
  286. }
  287. return nil
  288. }
  289. func (c *Client) loadThumbprints(name string) error {
  290. f, err := os.Open(filepath.Clean(name))
  291. if err != nil {
  292. if os.IsNotExist(err) {
  293. return nil
  294. }
  295. return err
  296. }
  297. scanner := bufio.NewScanner(f)
  298. for scanner.Scan() {
  299. e := strings.SplitN(scanner.Text(), " ", 2)
  300. if len(e) != 2 {
  301. continue
  302. }
  303. c.SetThumbprint(e[0], e[1])
  304. }
  305. _ = f.Close()
  306. return scanner.Err()
  307. }
  308. // ThumbprintSHA1 returns the thumbprint of the given cert in the same format used by the SDK and Client.SetThumbprint.
  309. //
  310. // See: SSLVerifyFault.Thumbprint, SessionManagerGenericServiceTicket.Thumbprint, HostConnectSpec.SslThumbprint
  311. func ThumbprintSHA1(cert *x509.Certificate) string {
  312. sum := sha1.Sum(cert.Raw)
  313. hex := make([]string, len(sum))
  314. for i, b := range sum {
  315. hex[i] = fmt.Sprintf("%02X", b)
  316. }
  317. return strings.Join(hex, ":")
  318. }
  319. // ThumbprintSHA256 returns the sha256 thumbprint of the given cert.
  320. func ThumbprintSHA256(cert *x509.Certificate) string {
  321. sum := sha256.Sum256(cert.Raw)
  322. hex := make([]string, len(sum))
  323. for i, b := range sum {
  324. hex[i] = fmt.Sprintf("%02X", b)
  325. }
  326. return strings.Join(hex, ":")
  327. }
  328. func thumbprintMatches(thumbprint string, cert *x509.Certificate) bool {
  329. return thumbprint == ThumbprintSHA256(cert) || thumbprint == ThumbprintSHA1(cert)
  330. }
  331. func (c *Client) dialTLSContext(
  332. ctx context.Context,
  333. network, addr string) (net.Conn, error) {
  334. // Would be nice if there was a tls.Config.Verify func,
  335. // see tls.clientHandshakeState.doFullHandshake
  336. conn, err := tls.Dial(network, addr, c.t.TLSClientConfig)
  337. if err == nil {
  338. return conn, nil
  339. }
  340. // Allow a thumbprint verification attempt if the error indicates
  341. // the failure was due to lack of trust.
  342. if !IsCertificateUntrusted(err) {
  343. return nil, err
  344. }
  345. thumbprint := c.Thumbprint(addr)
  346. if thumbprint == "" {
  347. return nil, err
  348. }
  349. config := &tls.Config{InsecureSkipVerify: true}
  350. conn, err = tls.Dial(network, addr, config)
  351. if err != nil {
  352. return nil, err
  353. }
  354. cert := conn.ConnectionState().PeerCertificates[0]
  355. if thumbprintMatches(thumbprint, cert) {
  356. return conn, nil
  357. }
  358. _ = conn.Close()
  359. return nil, fmt.Errorf("host %q thumbprint does not match %q", addr, thumbprint)
  360. }
  361. // splitHostPort is similar to net.SplitHostPort,
  362. // but rather than return error if there isn't a ':port',
  363. // return an empty string for the port.
  364. func splitHostPort(host string) (string, string) {
  365. ix := strings.LastIndex(host, ":")
  366. if ix <= strings.LastIndex(host, "]") {
  367. return host, ""
  368. }
  369. name := host[:ix]
  370. port := host[ix+1:]
  371. return name, port
  372. }
  373. const sdkTunnel = "sdkTunnel:8089"
  374. // Certificate returns the current TLS certificate.
  375. func (c *Client) Certificate() *tls.Certificate {
  376. certs := c.t.TLSClientConfig.Certificates
  377. if len(certs) == 0 {
  378. return nil
  379. }
  380. return &certs[0]
  381. }
  382. // SetCertificate st a certificate for TLS use.
  383. func (c *Client) SetCertificate(cert tls.Certificate) {
  384. t := c.Client.Transport.(*http.Transport)
  385. // Extension or HoK certificate
  386. t.TLSClientConfig.Certificates = []tls.Certificate{cert}
  387. }
  388. // UseServiceVersion sets Client.Version to the current version of the service endpoint via /sdk/vimServiceVersions.xml
  389. func (c *Client) UseServiceVersion(kind ...string) error {
  390. ns := "vim"
  391. if len(kind) != 0 {
  392. ns = kind[0]
  393. }
  394. u := c.URL()
  395. u.Path = path.Join("/sdk", ns+"ServiceVersions.xml")
  396. res, err := c.Get(u.String())
  397. if err != nil {
  398. return err
  399. }
  400. if res.StatusCode != http.StatusOK {
  401. return fmt.Errorf("http.Get(%s): %s", u.Path, res.Status)
  402. }
  403. v := struct {
  404. Namespace *string `xml:"namespace>name"`
  405. Version *string `xml:"namespace>version"`
  406. }{
  407. &c.Namespace,
  408. &c.Version,
  409. }
  410. err = xml.NewDecoder(res.Body).Decode(&v)
  411. _ = res.Body.Close()
  412. if err != nil {
  413. return fmt.Errorf("xml.Decode(%s): %s", u.Path, err)
  414. }
  415. return nil
  416. }
  417. // Tunnel returns a Client configured to proxy requests through vCenter's http port 80,
  418. // to the SDK tunnel virtual host. Use of the SDK tunnel is required by LoginExtensionByCertificate()
  419. // and optional for other methods.
  420. func (c *Client) Tunnel() *Client {
  421. tunnel := c.newServiceClientWithTransport(c.u.Path, c.Namespace, c.DefaultTransport().Clone())
  422. t := tunnel.Client.Transport.(*http.Transport)
  423. // Proxy to vCenter host on port 80
  424. host := tunnel.u.Hostname()
  425. // Should be no reason to change the default port other than testing
  426. key := "GOVMOMI_TUNNEL_PROXY_PORT"
  427. port := tunnel.URL().Query().Get(key)
  428. if port == "" {
  429. port = os.Getenv(key)
  430. }
  431. if port != "" {
  432. host += ":" + port
  433. }
  434. t.Proxy = http.ProxyURL(&url.URL{
  435. Scheme: "http",
  436. Host: host,
  437. })
  438. // Rewrite url Host to use the sdk tunnel, required for a certificate request.
  439. tunnel.u.Host = sdkTunnel
  440. return tunnel
  441. }
  442. // URL returns the URL to which the client is configured
  443. func (c *Client) URL() *url.URL {
  444. urlCopy := *c.u
  445. return &urlCopy
  446. }
  447. type marshaledClient struct {
  448. Cookies []*http.Cookie `json:"cookies"`
  449. URL *url.URL `json:"url"`
  450. Insecure bool `json:"insecure"`
  451. Version string `json:"version"`
  452. UseJSON bool `json:"useJSON"`
  453. }
  454. // MarshalJSON writes the Client configuration to JSON.
  455. func (c *Client) MarshalJSON() ([]byte, error) {
  456. m := marshaledClient{
  457. Cookies: c.Jar.Cookies(c.u),
  458. URL: c.u,
  459. Insecure: c.k,
  460. Version: c.Version,
  461. UseJSON: c.useJSON,
  462. }
  463. return json.Marshal(m)
  464. }
  465. // UnmarshalJSON rads Client configuration from JSON.
  466. func (c *Client) UnmarshalJSON(b []byte) error {
  467. var m marshaledClient
  468. err := json.Unmarshal(b, &m)
  469. if err != nil {
  470. return err
  471. }
  472. *c = *NewClient(m.URL, m.Insecure)
  473. c.Version = m.Version
  474. c.Jar.SetCookies(m.URL, m.Cookies)
  475. c.useJSON = m.UseJSON
  476. return nil
  477. }
  478. func (c *Client) setInsecureCookies(res *http.Response) {
  479. cookies := res.Cookies()
  480. if len(cookies) != 0 {
  481. for _, cookie := range cookies {
  482. cookie.Secure = false
  483. }
  484. c.Jar.SetCookies(c.u, cookies)
  485. }
  486. }
  487. // Do is equivalent to http.Client.Do and takes care of API specifics including
  488. // logging, user-agent header, handling cookies, measuring responsiveness of the
  489. // API
  490. func (c *Client) Do(ctx context.Context, req *http.Request, f func(*http.Response) error) error {
  491. if ctx == nil {
  492. ctx = context.Background()
  493. }
  494. // Create debugging context for this round trip
  495. d := c.d.newRoundTrip()
  496. if d.enabled() {
  497. defer d.done()
  498. }
  499. // use default
  500. if c.UserAgent == "" {
  501. c.UserAgent = defaultUserAgent
  502. }
  503. req.Header.Set(`User-Agent`, c.UserAgent)
  504. ext := ""
  505. if d.enabled() {
  506. ext = d.debugRequest(req)
  507. }
  508. res, err := c.Client.Do(req.WithContext(ctx))
  509. if err != nil {
  510. return err
  511. }
  512. if d.enabled() {
  513. d.debugResponse(res, ext)
  514. }
  515. if c.insecureCookies {
  516. c.setInsecureCookies(res)
  517. }
  518. defer res.Body.Close()
  519. return f(res)
  520. }
  521. // Signer can be implemented by soap.Header.Security to sign requests.
  522. // If the soap.Header.Security field is set to an implementation of Signer via WithHeader(),
  523. // then Client.RoundTrip will call Sign() to marshal the SOAP request.
  524. type Signer interface {
  525. Sign(Envelope) ([]byte, error)
  526. }
  527. type headerContext struct{}
  528. // WithHeader can be used to modify the outgoing request soap.Header fields.
  529. func (c *Client) WithHeader(ctx context.Context, header Header) context.Context {
  530. return context.WithValue(ctx, headerContext{}, header)
  531. }
  532. type statusError struct {
  533. res *http.Response
  534. }
  535. // Temporary returns true for HTTP response codes that can be retried
  536. // See vim25.IsTemporaryNetworkError
  537. func (e *statusError) Temporary() bool {
  538. switch e.res.StatusCode {
  539. case http.StatusBadGateway:
  540. return true
  541. }
  542. return false
  543. }
  544. func (e *statusError) Error() string {
  545. return e.res.Status
  546. }
  547. func newStatusError(res *http.Response) error {
  548. return &url.Error{
  549. Op: res.Request.Method,
  550. URL: res.Request.URL.Path,
  551. Err: &statusError{res},
  552. }
  553. }
  554. // RoundTrip executes an API request to VMOMI server.
  555. func (c *Client) RoundTrip(ctx context.Context, reqBody, resBody HasFault) error {
  556. if !c.useJSON {
  557. return c.soapRoundTrip(ctx, reqBody, resBody)
  558. }
  559. return c.jsonRoundTrip(ctx, reqBody, resBody)
  560. }
  561. func (c *Client) soapRoundTrip(ctx context.Context, reqBody, resBody HasFault) error {
  562. var err error
  563. var b []byte
  564. reqEnv := Envelope{Body: reqBody}
  565. resEnv := Envelope{Body: resBody}
  566. h, ok := ctx.Value(headerContext{}).(Header)
  567. if !ok {
  568. h = Header{}
  569. }
  570. // We added support for OperationID before soap.Header was exported.
  571. if id, ok := ctx.Value(types.ID{}).(string); ok {
  572. h.ID = id
  573. }
  574. h.Cookie = c.cookie
  575. if h.Cookie != "" || h.ID != "" || h.Security != nil {
  576. reqEnv.Header = &h // XML marshal header only if a field is set
  577. }
  578. if signer, ok := h.Security.(Signer); ok {
  579. b, err = signer.Sign(reqEnv)
  580. if err != nil {
  581. return err
  582. }
  583. } else {
  584. b, err = xml.Marshal(reqEnv)
  585. if err != nil {
  586. panic(err)
  587. }
  588. }
  589. rawReqBody := io.MultiReader(strings.NewReader(xml.Header), bytes.NewReader(b))
  590. req, err := http.NewRequest("POST", c.u.String(), rawReqBody)
  591. if err != nil {
  592. panic(err)
  593. }
  594. req.Header.Set(`Content-Type`, `text/xml; charset="utf-8"`)
  595. action := h.Action
  596. if action == "" {
  597. action = fmt.Sprintf("%s/%s", c.Namespace, c.Version)
  598. }
  599. req.Header.Set(`SOAPAction`, action)
  600. return c.Do(ctx, req, func(res *http.Response) error {
  601. switch res.StatusCode {
  602. case http.StatusOK:
  603. // OK
  604. case http.StatusInternalServerError:
  605. // Error, but typically includes a body explaining the error
  606. default:
  607. return newStatusError(res)
  608. }
  609. dec := xml.NewDecoder(res.Body)
  610. dec.TypeFunc = c.Types
  611. err = dec.Decode(&resEnv)
  612. if err != nil {
  613. return err
  614. }
  615. if f := resBody.Fault(); f != nil {
  616. return WrapSoapFault(f)
  617. }
  618. return err
  619. })
  620. }
  621. func (c *Client) CloseIdleConnections() {
  622. c.t.CloseIdleConnections()
  623. }
  624. // ParseURL wraps url.Parse to rewrite the URL.Host field
  625. // In the case of VM guest uploads or NFC lease URLs, a Host
  626. // field with a value of "*" is rewritten to the Client's URL.Host.
  627. func (c *Client) ParseURL(urlStr string) (*url.URL, error) {
  628. u, err := url.Parse(urlStr)
  629. if err != nil {
  630. return nil, err
  631. }
  632. host, _ := splitHostPort(u.Host)
  633. if host == "*" {
  634. // Also use Client's port, to support port forwarding
  635. u.Host = c.URL().Host
  636. }
  637. return u, nil
  638. }
  639. type Upload struct {
  640. Type string
  641. Method string
  642. ContentLength int64
  643. Headers map[string]string
  644. Ticket *http.Cookie
  645. Progress progress.Sinker
  646. Close bool
  647. }
  648. var DefaultUpload = Upload{
  649. Type: "application/octet-stream",
  650. Method: "PUT",
  651. }
  652. // Upload PUTs the local file to the given URL
  653. func (c *Client) Upload(ctx context.Context, f io.Reader, u *url.URL, param *Upload) error {
  654. var err error
  655. if param.Progress != nil {
  656. pr := progress.NewReader(ctx, param.Progress, f, param.ContentLength)
  657. f = pr
  658. // Mark progress reader as done when returning from this function.
  659. defer func() {
  660. pr.Done(err)
  661. }()
  662. }
  663. req, err := http.NewRequest(param.Method, u.String(), f)
  664. if err != nil {
  665. return err
  666. }
  667. req = req.WithContext(ctx)
  668. req.Close = param.Close
  669. req.ContentLength = param.ContentLength
  670. req.Header.Set("Content-Type", param.Type)
  671. for k, v := range param.Headers {
  672. req.Header.Add(k, v)
  673. }
  674. if param.Ticket != nil {
  675. req.AddCookie(param.Ticket)
  676. }
  677. res, err := c.Client.Do(req)
  678. if err != nil {
  679. return err
  680. }
  681. defer res.Body.Close()
  682. switch res.StatusCode {
  683. case http.StatusOK:
  684. case http.StatusCreated:
  685. default:
  686. err = errors.New(res.Status)
  687. }
  688. return err
  689. }
  690. // UploadFile PUTs the local file to the given URL
  691. func (c *Client) UploadFile(ctx context.Context, file string, u *url.URL, param *Upload) error {
  692. if param == nil {
  693. p := DefaultUpload // Copy since we set ContentLength
  694. param = &p
  695. }
  696. s, err := os.Stat(file)
  697. if err != nil {
  698. return err
  699. }
  700. f, err := os.Open(filepath.Clean(file))
  701. if err != nil {
  702. return err
  703. }
  704. defer f.Close()
  705. param.ContentLength = s.Size()
  706. return c.Upload(ctx, f, u, param)
  707. }
  708. type Download struct {
  709. Method string
  710. Headers map[string]string
  711. Ticket *http.Cookie
  712. Progress progress.Sinker
  713. Writer io.Writer
  714. Close bool
  715. }
  716. var DefaultDownload = Download{
  717. Method: "GET",
  718. }
  719. // DownloadRequest wraps http.Client.Do, returning the http.Response without checking its StatusCode
  720. func (c *Client) DownloadRequest(ctx context.Context, u *url.URL, param *Download) (*http.Response, error) {
  721. req, err := http.NewRequest(param.Method, u.String(), nil)
  722. if err != nil {
  723. return nil, err
  724. }
  725. req = req.WithContext(ctx)
  726. req.Close = param.Close
  727. for k, v := range param.Headers {
  728. req.Header.Add(k, v)
  729. }
  730. if param.Ticket != nil {
  731. req.AddCookie(param.Ticket)
  732. }
  733. return c.Client.Do(req)
  734. }
  735. // Download GETs the remote file from the given URL
  736. func (c *Client) Download(ctx context.Context, u *url.URL, param *Download) (io.ReadCloser, int64, error) {
  737. res, err := c.DownloadRequest(ctx, u, param)
  738. if err != nil {
  739. return nil, 0, err
  740. }
  741. switch res.StatusCode {
  742. case http.StatusOK:
  743. default:
  744. err = fmt.Errorf("download(%s): %s", u, res.Status)
  745. }
  746. if err != nil {
  747. return nil, 0, err
  748. }
  749. r := res.Body
  750. return r, res.ContentLength, nil
  751. }
  752. func (c *Client) WriteFile(ctx context.Context, file string, src io.Reader, size int64, s progress.Sinker, w io.Writer) error {
  753. var err error
  754. r := src
  755. fh, err := os.Create(file)
  756. if err != nil {
  757. return err
  758. }
  759. if s != nil {
  760. pr := progress.NewReader(ctx, s, src, size)
  761. r = pr
  762. // Mark progress reader as done when returning from this function.
  763. defer func() {
  764. pr.Done(err)
  765. }()
  766. }
  767. if w == nil {
  768. w = fh
  769. } else {
  770. w = io.MultiWriter(w, fh)
  771. }
  772. _, err = io.Copy(w, r)
  773. cerr := fh.Close()
  774. if err == nil {
  775. err = cerr
  776. }
  777. return err
  778. }
  779. // DownloadFile GETs the given URL to a local file
  780. func (c *Client) DownloadFile(ctx context.Context, file string, u *url.URL, param *Download) error {
  781. var err error
  782. if param == nil {
  783. param = &DefaultDownload
  784. }
  785. rc, contentLength, err := c.Download(ctx, u, param)
  786. if err != nil {
  787. return err
  788. }
  789. return c.WriteFile(ctx, file, rc, contentLength, param.Progress, param.Writer)
  790. }
  791. // execName gets the name of the executable for the current process
  792. func execName() string {
  793. name, err := os.Executable()
  794. if err != nil {
  795. return "N/A"
  796. }
  797. return strings.TrimSuffix(filepath.Base(name), ".exe")
  798. }