client.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800
  1. /*
  2. Copyright (c) 2014-2018 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/tls"
  20. "crypto/x509"
  21. "encoding/json"
  22. "errors"
  23. "fmt"
  24. "io"
  25. "io/ioutil"
  26. "log"
  27. "net"
  28. "net/http"
  29. "net/http/cookiejar"
  30. "net/url"
  31. "os"
  32. "path/filepath"
  33. "regexp"
  34. "strings"
  35. "sync"
  36. "time"
  37. "github.com/vmware/govmomi/vim25/progress"
  38. "github.com/vmware/govmomi/vim25/types"
  39. "github.com/vmware/govmomi/vim25/xml"
  40. )
  41. type HasFault interface {
  42. Fault() *Fault
  43. }
  44. type RoundTripper interface {
  45. RoundTrip(ctx context.Context, req, res HasFault) error
  46. }
  47. const (
  48. SessionCookieName = "vmware_soap_session"
  49. )
  50. type Client struct {
  51. http.Client
  52. u *url.URL
  53. k bool // Named after curl's -k flag
  54. d *debugContainer
  55. t *http.Transport
  56. hostsMu sync.Mutex
  57. hosts map[string]string
  58. Namespace string // Vim namespace
  59. Version string // Vim version
  60. UserAgent string
  61. cookie string
  62. }
  63. var schemeMatch = regexp.MustCompile(`^\w+://`)
  64. type errInvalidCACertificate struct {
  65. File string
  66. }
  67. func (e errInvalidCACertificate) Error() string {
  68. return fmt.Sprintf(
  69. "invalid certificate '%s', cannot be used as a trusted CA certificate",
  70. e.File,
  71. )
  72. }
  73. // ParseURL is wrapper around url.Parse, where Scheme defaults to "https" and Path defaults to "/sdk"
  74. func ParseURL(s string) (*url.URL, error) {
  75. var err error
  76. var u *url.URL
  77. if s != "" {
  78. // Default the scheme to https
  79. if !schemeMatch.MatchString(s) {
  80. s = "https://" + s
  81. }
  82. u, err = url.Parse(s)
  83. if err != nil {
  84. return nil, err
  85. }
  86. // Default the path to /sdk
  87. if u.Path == "" {
  88. u.Path = "/sdk"
  89. }
  90. if u.User == nil {
  91. u.User = url.UserPassword("", "")
  92. }
  93. }
  94. return u, nil
  95. }
  96. func NewClient(u *url.URL, insecure bool) *Client {
  97. c := Client{
  98. u: u,
  99. k: insecure,
  100. d: newDebug(),
  101. }
  102. // Initialize http.RoundTripper on client, so we can customize it below
  103. if t, ok := http.DefaultTransport.(*http.Transport); ok {
  104. c.t = &http.Transport{
  105. Proxy: t.Proxy,
  106. DialContext: t.DialContext,
  107. MaxIdleConns: t.MaxIdleConns,
  108. IdleConnTimeout: t.IdleConnTimeout,
  109. TLSHandshakeTimeout: t.TLSHandshakeTimeout,
  110. ExpectContinueTimeout: t.ExpectContinueTimeout,
  111. }
  112. } else {
  113. c.t = new(http.Transport)
  114. }
  115. c.hosts = make(map[string]string)
  116. c.t.TLSClientConfig = &tls.Config{InsecureSkipVerify: c.k}
  117. // Don't bother setting DialTLS if InsecureSkipVerify=true
  118. if !c.k {
  119. c.t.DialTLS = c.dialTLS
  120. }
  121. c.Client.Transport = c.t
  122. c.Client.Jar, _ = cookiejar.New(nil)
  123. // Remove user information from a copy of the URL
  124. c.u = c.URL()
  125. c.u.User = nil
  126. return &c
  127. }
  128. // NewServiceClient creates a NewClient with the given URL.Path and namespace.
  129. func (c *Client) NewServiceClient(path string, namespace string) *Client {
  130. vc := c.URL()
  131. u, err := url.Parse(path)
  132. if err != nil {
  133. log.Panicf("url.Parse(%q): %s", path, err)
  134. }
  135. if u.Host == "" {
  136. u.Scheme = vc.Scheme
  137. u.Host = vc.Host
  138. }
  139. client := NewClient(u, c.k)
  140. client.Namespace = "urn:" + namespace
  141. client.Transport.(*http.Transport).TLSClientConfig = c.Transport.(*http.Transport).TLSClientConfig
  142. if cert := c.Certificate(); cert != nil {
  143. client.SetCertificate(*cert)
  144. }
  145. // Copy the trusted thumbprints
  146. c.hostsMu.Lock()
  147. for k, v := range c.hosts {
  148. client.hosts[k] = v
  149. }
  150. c.hostsMu.Unlock()
  151. // Copy the cookies
  152. client.Client.Jar.SetCookies(u, c.Client.Jar.Cookies(u))
  153. // Set SOAP Header cookie
  154. for _, cookie := range client.Jar.Cookies(u) {
  155. if cookie.Name == SessionCookieName {
  156. client.cookie = cookie.Value
  157. break
  158. }
  159. }
  160. // Copy any query params (e.g. GOVMOMI_TUNNEL_PROXY_PORT used in testing)
  161. client.u.RawQuery = vc.RawQuery
  162. return client
  163. }
  164. // SetRootCAs defines the set of root certificate authorities
  165. // that clients use when verifying server certificates.
  166. // By default TLS uses the host's root CA set.
  167. //
  168. // See: http.Client.Transport.TLSClientConfig.RootCAs
  169. func (c *Client) SetRootCAs(file string) error {
  170. pool := x509.NewCertPool()
  171. for _, name := range filepath.SplitList(file) {
  172. pem, err := ioutil.ReadFile(name)
  173. if err != nil {
  174. return err
  175. }
  176. if ok := pool.AppendCertsFromPEM(pem); !ok {
  177. return errInvalidCACertificate{
  178. File: name,
  179. }
  180. }
  181. }
  182. c.t.TLSClientConfig.RootCAs = pool
  183. return nil
  184. }
  185. // Add default https port if missing
  186. func hostAddr(addr string) string {
  187. _, port := splitHostPort(addr)
  188. if port == "" {
  189. return addr + ":443"
  190. }
  191. return addr
  192. }
  193. // SetThumbprint sets the known certificate thumbprint for the given host.
  194. // A custom DialTLS function is used to support thumbprint based verification.
  195. // We first try tls.Dial with the default tls.Config, only falling back to thumbprint verification
  196. // if it fails with an x509.UnknownAuthorityError or x509.HostnameError
  197. //
  198. // See: http.Client.Transport.DialTLS
  199. func (c *Client) SetThumbprint(host string, thumbprint string) {
  200. host = hostAddr(host)
  201. c.hostsMu.Lock()
  202. if thumbprint == "" {
  203. delete(c.hosts, host)
  204. } else {
  205. c.hosts[host] = thumbprint
  206. }
  207. c.hostsMu.Unlock()
  208. }
  209. // Thumbprint returns the certificate thumbprint for the given host if known to this client.
  210. func (c *Client) Thumbprint(host string) string {
  211. host = hostAddr(host)
  212. c.hostsMu.Lock()
  213. defer c.hostsMu.Unlock()
  214. return c.hosts[host]
  215. }
  216. // LoadThumbprints from file with the give name.
  217. // If name is empty or name does not exist this function will return nil.
  218. func (c *Client) LoadThumbprints(file string) error {
  219. if file == "" {
  220. return nil
  221. }
  222. for _, name := range filepath.SplitList(file) {
  223. err := c.loadThumbprints(name)
  224. if err != nil {
  225. return err
  226. }
  227. }
  228. return nil
  229. }
  230. func (c *Client) loadThumbprints(name string) error {
  231. f, err := os.Open(name)
  232. if err != nil {
  233. if os.IsNotExist(err) {
  234. return nil
  235. }
  236. return err
  237. }
  238. scanner := bufio.NewScanner(f)
  239. for scanner.Scan() {
  240. e := strings.SplitN(scanner.Text(), " ", 2)
  241. if len(e) != 2 {
  242. continue
  243. }
  244. c.SetThumbprint(e[0], e[1])
  245. }
  246. _ = f.Close()
  247. return scanner.Err()
  248. }
  249. // ThumbprintSHA1 returns the thumbprint of the given cert in the same format used by the SDK and Client.SetThumbprint.
  250. //
  251. // See: SSLVerifyFault.Thumbprint, SessionManagerGenericServiceTicket.Thumbprint, HostConnectSpec.SslThumbprint
  252. func ThumbprintSHA1(cert *x509.Certificate) string {
  253. sum := sha1.Sum(cert.Raw)
  254. hex := make([]string, len(sum))
  255. for i, b := range sum {
  256. hex[i] = fmt.Sprintf("%02X", b)
  257. }
  258. return strings.Join(hex, ":")
  259. }
  260. func (c *Client) dialTLS(network string, addr string) (net.Conn, error) {
  261. // Would be nice if there was a tls.Config.Verify func,
  262. // see tls.clientHandshakeState.doFullHandshake
  263. conn, err := tls.Dial(network, addr, c.t.TLSClientConfig)
  264. if err == nil {
  265. return conn, nil
  266. }
  267. switch err.(type) {
  268. case x509.UnknownAuthorityError:
  269. case x509.HostnameError:
  270. default:
  271. return nil, err
  272. }
  273. thumbprint := c.Thumbprint(addr)
  274. if thumbprint == "" {
  275. return nil, err
  276. }
  277. config := &tls.Config{InsecureSkipVerify: true}
  278. conn, err = tls.Dial(network, addr, config)
  279. if err != nil {
  280. return nil, err
  281. }
  282. cert := conn.ConnectionState().PeerCertificates[0]
  283. peer := ThumbprintSHA1(cert)
  284. if thumbprint != peer {
  285. _ = conn.Close()
  286. return nil, fmt.Errorf("Host %q thumbprint does not match %q", addr, thumbprint)
  287. }
  288. return conn, nil
  289. }
  290. // splitHostPort is similar to net.SplitHostPort,
  291. // but rather than return error if there isn't a ':port',
  292. // return an empty string for the port.
  293. func splitHostPort(host string) (string, string) {
  294. ix := strings.LastIndex(host, ":")
  295. if ix <= strings.LastIndex(host, "]") {
  296. return host, ""
  297. }
  298. name := host[:ix]
  299. port := host[ix+1:]
  300. return name, port
  301. }
  302. const sdkTunnel = "sdkTunnel:8089"
  303. func (c *Client) Certificate() *tls.Certificate {
  304. certs := c.t.TLSClientConfig.Certificates
  305. if len(certs) == 0 {
  306. return nil
  307. }
  308. return &certs[0]
  309. }
  310. func (c *Client) SetCertificate(cert tls.Certificate) {
  311. t := c.Client.Transport.(*http.Transport)
  312. // Extension or HoK certificate
  313. t.TLSClientConfig.Certificates = []tls.Certificate{cert}
  314. }
  315. // Tunnel returns a Client configured to proxy requests through vCenter's http port 80,
  316. // to the SDK tunnel virtual host. Use of the SDK tunnel is required by LoginExtensionByCertificate()
  317. // and optional for other methods.
  318. func (c *Client) Tunnel() *Client {
  319. tunnel := c.NewServiceClient(c.u.Path, c.Namespace)
  320. t := tunnel.Client.Transport.(*http.Transport)
  321. // Proxy to vCenter host on port 80
  322. host := tunnel.u.Hostname()
  323. // Should be no reason to change the default port other than testing
  324. key := "GOVMOMI_TUNNEL_PROXY_PORT"
  325. port := tunnel.URL().Query().Get(key)
  326. if port == "" {
  327. port = os.Getenv(key)
  328. }
  329. if port != "" {
  330. host += ":" + port
  331. }
  332. t.Proxy = http.ProxyURL(&url.URL{
  333. Scheme: "http",
  334. Host: host,
  335. })
  336. // Rewrite url Host to use the sdk tunnel, required for a certificate request.
  337. tunnel.u.Host = sdkTunnel
  338. return tunnel
  339. }
  340. func (c *Client) URL() *url.URL {
  341. urlCopy := *c.u
  342. return &urlCopy
  343. }
  344. type marshaledClient struct {
  345. Cookies []*http.Cookie
  346. URL *url.URL
  347. Insecure bool
  348. }
  349. func (c *Client) MarshalJSON() ([]byte, error) {
  350. m := marshaledClient{
  351. Cookies: c.Jar.Cookies(c.u),
  352. URL: c.u,
  353. Insecure: c.k,
  354. }
  355. return json.Marshal(m)
  356. }
  357. func (c *Client) UnmarshalJSON(b []byte) error {
  358. var m marshaledClient
  359. err := json.Unmarshal(b, &m)
  360. if err != nil {
  361. return err
  362. }
  363. *c = *NewClient(m.URL, m.Insecure)
  364. c.Jar.SetCookies(m.URL, m.Cookies)
  365. return nil
  366. }
  367. type kindContext struct{}
  368. func (c *Client) Do(ctx context.Context, req *http.Request, f func(*http.Response) error) error {
  369. if ctx == nil {
  370. ctx = context.Background()
  371. }
  372. // Create debugging context for this round trip
  373. d := c.d.newRoundTrip()
  374. if d.enabled() {
  375. defer d.done()
  376. }
  377. if c.UserAgent != "" {
  378. req.Header.Set(`User-Agent`, c.UserAgent)
  379. }
  380. if d.enabled() {
  381. d.debugRequest(req)
  382. }
  383. tstart := time.Now()
  384. res, err := c.Client.Do(req.WithContext(ctx))
  385. tstop := time.Now()
  386. if d.enabled() {
  387. var name string
  388. if kind, ok := ctx.Value(kindContext{}).(HasFault); ok {
  389. name = fmt.Sprintf("%T", kind)
  390. } else {
  391. name = fmt.Sprintf("%s %s", req.Method, req.URL)
  392. }
  393. d.logf("%6dms (%s)", tstop.Sub(tstart)/time.Millisecond, name)
  394. }
  395. if err != nil {
  396. return err
  397. }
  398. defer res.Body.Close()
  399. if d.enabled() {
  400. d.debugResponse(res)
  401. }
  402. return f(res)
  403. }
  404. // Signer can be implemented by soap.Header.Security to sign requests.
  405. // If the soap.Header.Security field is set to an implementation of Signer via WithHeader(),
  406. // then Client.RoundTrip will call Sign() to marshal the SOAP request.
  407. type Signer interface {
  408. Sign(Envelope) ([]byte, error)
  409. }
  410. type headerContext struct{}
  411. // WithHeader can be used to modify the outgoing request soap.Header fields.
  412. func (c *Client) WithHeader(ctx context.Context, header Header) context.Context {
  413. return context.WithValue(ctx, headerContext{}, header)
  414. }
  415. func (c *Client) RoundTrip(ctx context.Context, reqBody, resBody HasFault) error {
  416. var err error
  417. var b []byte
  418. reqEnv := Envelope{Body: reqBody}
  419. resEnv := Envelope{Body: resBody}
  420. h, ok := ctx.Value(headerContext{}).(Header)
  421. if !ok {
  422. h = Header{}
  423. }
  424. // We added support for OperationID before soap.Header was exported.
  425. if id, ok := ctx.Value(types.ID{}).(string); ok {
  426. h.ID = id
  427. }
  428. h.Cookie = c.cookie
  429. if h.Cookie != "" || h.ID != "" || h.Security != nil {
  430. reqEnv.Header = &h // XML marshal header only if a field is set
  431. }
  432. if signer, ok := h.Security.(Signer); ok {
  433. b, err = signer.Sign(reqEnv)
  434. if err != nil {
  435. return err
  436. }
  437. } else {
  438. b, err = xml.Marshal(reqEnv)
  439. if err != nil {
  440. panic(err)
  441. }
  442. }
  443. rawReqBody := io.MultiReader(strings.NewReader(xml.Header), bytes.NewReader(b))
  444. req, err := http.NewRequest("POST", c.u.String(), rawReqBody)
  445. if err != nil {
  446. panic(err)
  447. }
  448. req.Header.Set(`Content-Type`, `text/xml; charset="utf-8"`)
  449. action := h.Action
  450. if action == "" {
  451. action = fmt.Sprintf("%s/%s", c.Namespace, c.Version)
  452. }
  453. req.Header.Set(`SOAPAction`, action)
  454. return c.Do(context.WithValue(ctx, kindContext{}, resBody), req, func(res *http.Response) error {
  455. switch res.StatusCode {
  456. case http.StatusOK:
  457. // OK
  458. case http.StatusInternalServerError:
  459. // Error, but typically includes a body explaining the error
  460. default:
  461. return errors.New(res.Status)
  462. }
  463. dec := xml.NewDecoder(res.Body)
  464. dec.TypeFunc = types.TypeFunc()
  465. err = dec.Decode(&resEnv)
  466. if err != nil {
  467. return err
  468. }
  469. if f := resBody.Fault(); f != nil {
  470. return WrapSoapFault(f)
  471. }
  472. return err
  473. })
  474. }
  475. func (c *Client) CloseIdleConnections() {
  476. c.t.CloseIdleConnections()
  477. }
  478. // ParseURL wraps url.Parse to rewrite the URL.Host field
  479. // In the case of VM guest uploads or NFC lease URLs, a Host
  480. // field with a value of "*" is rewritten to the Client's URL.Host.
  481. func (c *Client) ParseURL(urlStr string) (*url.URL, error) {
  482. u, err := url.Parse(urlStr)
  483. if err != nil {
  484. return nil, err
  485. }
  486. host, _ := splitHostPort(u.Host)
  487. if host == "*" {
  488. // Also use Client's port, to support port forwarding
  489. u.Host = c.URL().Host
  490. }
  491. return u, nil
  492. }
  493. type Upload struct {
  494. Type string
  495. Method string
  496. ContentLength int64
  497. Headers map[string]string
  498. Ticket *http.Cookie
  499. Progress progress.Sinker
  500. }
  501. var DefaultUpload = Upload{
  502. Type: "application/octet-stream",
  503. Method: "PUT",
  504. }
  505. // Upload PUTs the local file to the given URL
  506. func (c *Client) Upload(ctx context.Context, f io.Reader, u *url.URL, param *Upload) error {
  507. var err error
  508. if param.Progress != nil {
  509. pr := progress.NewReader(ctx, param.Progress, f, param.ContentLength)
  510. f = pr
  511. // Mark progress reader as done when returning from this function.
  512. defer func() {
  513. pr.Done(err)
  514. }()
  515. }
  516. req, err := http.NewRequest(param.Method, u.String(), f)
  517. if err != nil {
  518. return err
  519. }
  520. req = req.WithContext(ctx)
  521. req.ContentLength = param.ContentLength
  522. req.Header.Set("Content-Type", param.Type)
  523. for k, v := range param.Headers {
  524. req.Header.Add(k, v)
  525. }
  526. if param.Ticket != nil {
  527. req.AddCookie(param.Ticket)
  528. }
  529. res, err := c.Client.Do(req)
  530. if err != nil {
  531. return err
  532. }
  533. defer res.Body.Close()
  534. switch res.StatusCode {
  535. case http.StatusOK:
  536. case http.StatusCreated:
  537. default:
  538. err = errors.New(res.Status)
  539. }
  540. return err
  541. }
  542. // UploadFile PUTs the local file to the given URL
  543. func (c *Client) UploadFile(ctx context.Context, file string, u *url.URL, param *Upload) error {
  544. if param == nil {
  545. p := DefaultUpload // Copy since we set ContentLength
  546. param = &p
  547. }
  548. s, err := os.Stat(file)
  549. if err != nil {
  550. return err
  551. }
  552. f, err := os.Open(file)
  553. if err != nil {
  554. return err
  555. }
  556. defer f.Close()
  557. param.ContentLength = s.Size()
  558. return c.Upload(ctx, f, u, param)
  559. }
  560. type Download struct {
  561. Method string
  562. Headers map[string]string
  563. Ticket *http.Cookie
  564. Progress progress.Sinker
  565. Writer io.Writer
  566. }
  567. var DefaultDownload = Download{
  568. Method: "GET",
  569. }
  570. // DownloadRequest wraps http.Client.Do, returning the http.Response without checking its StatusCode
  571. func (c *Client) DownloadRequest(ctx context.Context, u *url.URL, param *Download) (*http.Response, error) {
  572. req, err := http.NewRequest(param.Method, u.String(), nil)
  573. if err != nil {
  574. return nil, err
  575. }
  576. req = req.WithContext(ctx)
  577. for k, v := range param.Headers {
  578. req.Header.Add(k, v)
  579. }
  580. if param.Ticket != nil {
  581. req.AddCookie(param.Ticket)
  582. }
  583. return c.Client.Do(req)
  584. }
  585. // Download GETs the remote file from the given URL
  586. func (c *Client) Download(ctx context.Context, u *url.URL, param *Download) (io.ReadCloser, int64, error) {
  587. res, err := c.DownloadRequest(ctx, u, param)
  588. if err != nil {
  589. return nil, 0, err
  590. }
  591. switch res.StatusCode {
  592. case http.StatusOK:
  593. default:
  594. err = errors.New(res.Status)
  595. }
  596. if err != nil {
  597. return nil, 0, err
  598. }
  599. r := res.Body
  600. return r, res.ContentLength, nil
  601. }
  602. func (c *Client) WriteFile(ctx context.Context, file string, src io.Reader, size int64, s progress.Sinker, w io.Writer) error {
  603. var err error
  604. r := src
  605. fh, err := os.Create(file)
  606. if err != nil {
  607. return err
  608. }
  609. if s != nil {
  610. pr := progress.NewReader(ctx, s, src, size)
  611. src = pr
  612. // Mark progress reader as done when returning from this function.
  613. defer func() {
  614. pr.Done(err)
  615. }()
  616. }
  617. if w == nil {
  618. w = fh
  619. } else {
  620. w = io.MultiWriter(w, fh)
  621. }
  622. _, err = io.Copy(w, r)
  623. cerr := fh.Close()
  624. if err == nil {
  625. err = cerr
  626. }
  627. return err
  628. }
  629. // DownloadFile GETs the given URL to a local file
  630. func (c *Client) DownloadFile(ctx context.Context, file string, u *url.URL, param *Download) error {
  631. var err error
  632. if param == nil {
  633. param = &DefaultDownload
  634. }
  635. rc, contentLength, err := c.Download(ctx, u, param)
  636. if err != nil {
  637. return err
  638. }
  639. return c.WriteFile(ctx, file, rc, contentLength, param.Progress, param.Writer)
  640. }