github.com/pwn-term/docker@v0.0.0-20210616085119-6e977cce2565/moby/api/server/router/container/container_routes.go (about)

     1  package container // import "github.com/docker/docker/api/server/router/container"
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"net/http"
     9  	"strconv"
    10  	"syscall"
    11  
    12  	"github.com/containerd/containerd/platforms"
    13  	"github.com/docker/docker/api/server/httputils"
    14  	"github.com/docker/docker/api/types"
    15  	"github.com/docker/docker/api/types/backend"
    16  	"github.com/docker/docker/api/types/container"
    17  	"github.com/docker/docker/api/types/filters"
    18  	"github.com/docker/docker/api/types/versions"
    19  	containerpkg "github.com/docker/docker/container"
    20  	"github.com/docker/docker/errdefs"
    21  	"github.com/docker/docker/pkg/ioutils"
    22  	"github.com/docker/docker/pkg/signal"
    23  	specs "github.com/opencontainers/image-spec/specs-go/v1"
    24  	"github.com/pkg/errors"
    25  	"github.com/sirupsen/logrus"
    26  	"golang.org/x/net/websocket"
    27  )
    28  
    29  func (s *containerRouter) postCommit(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
    30  	if err := httputils.ParseForm(r); err != nil {
    31  		return err
    32  	}
    33  
    34  	if err := httputils.CheckForJSON(r); err != nil {
    35  		return err
    36  	}
    37  
    38  	// TODO: remove pause arg, and always pause in backend
    39  	pause := httputils.BoolValue(r, "pause")
    40  	version := httputils.VersionFromContext(ctx)
    41  	if r.FormValue("pause") == "" && versions.GreaterThanOrEqualTo(version, "1.13") {
    42  		pause = true
    43  	}
    44  
    45  	config, _, _, err := s.decoder.DecodeConfig(r.Body)
    46  	if err != nil && err != io.EOF { // Do not fail if body is empty.
    47  		return err
    48  	}
    49  
    50  	commitCfg := &backend.CreateImageConfig{
    51  		Pause:   pause,
    52  		Repo:    r.Form.Get("repo"),
    53  		Tag:     r.Form.Get("tag"),
    54  		Author:  r.Form.Get("author"),
    55  		Comment: r.Form.Get("comment"),
    56  		Config:  config,
    57  		Changes: r.Form["changes"],
    58  	}
    59  
    60  	imgID, err := s.backend.CreateImageFromContainer(r.Form.Get("container"), commitCfg)
    61  	if err != nil {
    62  		return err
    63  	}
    64  
    65  	return httputils.WriteJSON(w, http.StatusCreated, &types.IDResponse{ID: imgID})
    66  }
    67  
    68  func (s *containerRouter) getContainersJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
    69  	if err := httputils.ParseForm(r); err != nil {
    70  		return err
    71  	}
    72  	filter, err := filters.FromJSON(r.Form.Get("filters"))
    73  	if err != nil {
    74  		return err
    75  	}
    76  
    77  	config := &types.ContainerListOptions{
    78  		All:     httputils.BoolValue(r, "all"),
    79  		Size:    httputils.BoolValue(r, "size"),
    80  		Since:   r.Form.Get("since"),
    81  		Before:  r.Form.Get("before"),
    82  		Filters: filter,
    83  	}
    84  
    85  	if tmpLimit := r.Form.Get("limit"); tmpLimit != "" {
    86  		limit, err := strconv.Atoi(tmpLimit)
    87  		if err != nil {
    88  			return err
    89  		}
    90  		config.Limit = limit
    91  	}
    92  
    93  	containers, err := s.backend.Containers(config)
    94  	if err != nil {
    95  		return err
    96  	}
    97  
    98  	return httputils.WriteJSON(w, http.StatusOK, containers)
    99  }
   100  
   101  func (s *containerRouter) getContainersStats(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   102  	if err := httputils.ParseForm(r); err != nil {
   103  		return err
   104  	}
   105  
   106  	stream := httputils.BoolValueOrDefault(r, "stream", true)
   107  	if !stream {
   108  		w.Header().Set("Content-Type", "application/json")
   109  	}
   110  	var oneShot bool
   111  	if versions.GreaterThanOrEqualTo(httputils.VersionFromContext(ctx), "1.41") {
   112  		oneShot = httputils.BoolValueOrDefault(r, "one-shot", false)
   113  	}
   114  
   115  	config := &backend.ContainerStatsConfig{
   116  		Stream:    stream,
   117  		OneShot:   oneShot,
   118  		OutStream: w,
   119  		Version:   httputils.VersionFromContext(ctx),
   120  	}
   121  
   122  	return s.backend.ContainerStats(ctx, vars["name"], config)
   123  }
   124  
   125  func (s *containerRouter) getContainersLogs(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   126  	if err := httputils.ParseForm(r); err != nil {
   127  		return err
   128  	}
   129  
   130  	// Args are validated before the stream starts because when it starts we're
   131  	// sending HTTP 200 by writing an empty chunk of data to tell the client that
   132  	// daemon is going to stream. By sending this initial HTTP 200 we can't report
   133  	// any error after the stream starts (i.e. container not found, wrong parameters)
   134  	// with the appropriate status code.
   135  	stdout, stderr := httputils.BoolValue(r, "stdout"), httputils.BoolValue(r, "stderr")
   136  	if !(stdout || stderr) {
   137  		return errdefs.InvalidParameter(errors.New("Bad parameters: you must choose at least one stream"))
   138  	}
   139  
   140  	containerName := vars["name"]
   141  	logsConfig := &types.ContainerLogsOptions{
   142  		Follow:     httputils.BoolValue(r, "follow"),
   143  		Timestamps: httputils.BoolValue(r, "timestamps"),
   144  		Since:      r.Form.Get("since"),
   145  		Until:      r.Form.Get("until"),
   146  		Tail:       r.Form.Get("tail"),
   147  		ShowStdout: stdout,
   148  		ShowStderr: stderr,
   149  		Details:    httputils.BoolValue(r, "details"),
   150  	}
   151  
   152  	msgs, tty, err := s.backend.ContainerLogs(ctx, containerName, logsConfig)
   153  	if err != nil {
   154  		return err
   155  	}
   156  
   157  	// if has a tty, we're not muxing streams. if it doesn't, we are. simple.
   158  	// this is the point of no return for writing a response. once we call
   159  	// WriteLogStream, the response has been started and errors will be
   160  	// returned in band by WriteLogStream
   161  	httputils.WriteLogStream(ctx, w, msgs, logsConfig, !tty)
   162  	return nil
   163  }
   164  
   165  func (s *containerRouter) getContainersExport(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   166  	return s.backend.ContainerExport(vars["name"], w)
   167  }
   168  
   169  type bodyOnStartError struct{}
   170  
   171  func (bodyOnStartError) Error() string {
   172  	return "starting container with non-empty request body was deprecated since API v1.22 and removed in v1.24"
   173  }
   174  
   175  func (bodyOnStartError) InvalidParameter() {}
   176  
   177  func (s *containerRouter) postContainersStart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   178  	// If contentLength is -1, we can assumed chunked encoding
   179  	// or more technically that the length is unknown
   180  	// https://golang.org/src/pkg/net/http/request.go#L139
   181  	// net/http otherwise seems to swallow any headers related to chunked encoding
   182  	// including r.TransferEncoding
   183  	// allow a nil body for backwards compatibility
   184  
   185  	version := httputils.VersionFromContext(ctx)
   186  	var hostConfig *container.HostConfig
   187  	// A non-nil json object is at least 7 characters.
   188  	if r.ContentLength > 7 || r.ContentLength == -1 {
   189  		if versions.GreaterThanOrEqualTo(version, "1.24") {
   190  			return bodyOnStartError{}
   191  		}
   192  
   193  		if err := httputils.CheckForJSON(r); err != nil {
   194  			return err
   195  		}
   196  
   197  		c, err := s.decoder.DecodeHostConfig(r.Body)
   198  		if err != nil {
   199  			return err
   200  		}
   201  		hostConfig = c
   202  	}
   203  
   204  	if err := httputils.ParseForm(r); err != nil {
   205  		return err
   206  	}
   207  
   208  	checkpoint := r.Form.Get("checkpoint")
   209  	checkpointDir := r.Form.Get("checkpoint-dir")
   210  	if err := s.backend.ContainerStart(vars["name"], hostConfig, checkpoint, checkpointDir); err != nil {
   211  		return err
   212  	}
   213  
   214  	w.WriteHeader(http.StatusNoContent)
   215  	return nil
   216  }
   217  
   218  func (s *containerRouter) postContainersStop(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   219  	if err := httputils.ParseForm(r); err != nil {
   220  		return err
   221  	}
   222  
   223  	var seconds *int
   224  	if tmpSeconds := r.Form.Get("t"); tmpSeconds != "" {
   225  		valSeconds, err := strconv.Atoi(tmpSeconds)
   226  		if err != nil {
   227  			return err
   228  		}
   229  		seconds = &valSeconds
   230  	}
   231  
   232  	if err := s.backend.ContainerStop(vars["name"], seconds); err != nil {
   233  		return err
   234  	}
   235  	w.WriteHeader(http.StatusNoContent)
   236  
   237  	return nil
   238  }
   239  
   240  func (s *containerRouter) postContainersKill(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   241  	if err := httputils.ParseForm(r); err != nil {
   242  		return err
   243  	}
   244  
   245  	var sig syscall.Signal
   246  	name := vars["name"]
   247  
   248  	// If we have a signal, look at it. Otherwise, do nothing
   249  	if sigStr := r.Form.Get("signal"); sigStr != "" {
   250  		var err error
   251  		if sig, err = signal.ParseSignal(sigStr); err != nil {
   252  			return errdefs.InvalidParameter(err)
   253  		}
   254  	}
   255  
   256  	if err := s.backend.ContainerKill(name, uint64(sig)); err != nil {
   257  		var isStopped bool
   258  		if errdefs.IsConflict(err) {
   259  			isStopped = true
   260  		}
   261  
   262  		// Return error that's not caused because the container is stopped.
   263  		// Return error if the container is not running and the api is >= 1.20
   264  		// to keep backwards compatibility.
   265  		version := httputils.VersionFromContext(ctx)
   266  		if versions.GreaterThanOrEqualTo(version, "1.20") || !isStopped {
   267  			return errors.Wrapf(err, "Cannot kill container: %s", name)
   268  		}
   269  	}
   270  
   271  	w.WriteHeader(http.StatusNoContent)
   272  	return nil
   273  }
   274  
   275  func (s *containerRouter) postContainersRestart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   276  	if err := httputils.ParseForm(r); err != nil {
   277  		return err
   278  	}
   279  
   280  	var seconds *int
   281  	if tmpSeconds := r.Form.Get("t"); tmpSeconds != "" {
   282  		valSeconds, err := strconv.Atoi(tmpSeconds)
   283  		if err != nil {
   284  			return err
   285  		}
   286  		seconds = &valSeconds
   287  	}
   288  
   289  	if err := s.backend.ContainerRestart(vars["name"], seconds); err != nil {
   290  		return err
   291  	}
   292  
   293  	w.WriteHeader(http.StatusNoContent)
   294  
   295  	return nil
   296  }
   297  
   298  func (s *containerRouter) postContainersPause(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   299  	if err := httputils.ParseForm(r); err != nil {
   300  		return err
   301  	}
   302  
   303  	if err := s.backend.ContainerPause(vars["name"]); err != nil {
   304  		return err
   305  	}
   306  
   307  	w.WriteHeader(http.StatusNoContent)
   308  
   309  	return nil
   310  }
   311  
   312  func (s *containerRouter) postContainersUnpause(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   313  	if err := httputils.ParseForm(r); err != nil {
   314  		return err
   315  	}
   316  
   317  	if err := s.backend.ContainerUnpause(vars["name"]); err != nil {
   318  		return err
   319  	}
   320  
   321  	w.WriteHeader(http.StatusNoContent)
   322  
   323  	return nil
   324  }
   325  
   326  func (s *containerRouter) postContainersWait(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   327  	// Behavior changed in version 1.30 to handle wait condition and to
   328  	// return headers immediately.
   329  	version := httputils.VersionFromContext(ctx)
   330  	legacyBehaviorPre130 := versions.LessThan(version, "1.30")
   331  	legacyRemovalWaitPre134 := false
   332  
   333  	// The wait condition defaults to "not-running".
   334  	waitCondition := containerpkg.WaitConditionNotRunning
   335  	if !legacyBehaviorPre130 {
   336  		if err := httputils.ParseForm(r); err != nil {
   337  			return err
   338  		}
   339  		switch container.WaitCondition(r.Form.Get("condition")) {
   340  		case container.WaitConditionNextExit:
   341  			waitCondition = containerpkg.WaitConditionNextExit
   342  		case container.WaitConditionRemoved:
   343  			waitCondition = containerpkg.WaitConditionRemoved
   344  			legacyRemovalWaitPre134 = versions.LessThan(version, "1.34")
   345  		}
   346  	}
   347  
   348  	waitC, err := s.backend.ContainerWait(ctx, vars["name"], waitCondition)
   349  	if err != nil {
   350  		return err
   351  	}
   352  
   353  	w.Header().Set("Content-Type", "application/json")
   354  
   355  	if !legacyBehaviorPre130 {
   356  		// Write response header immediately.
   357  		w.WriteHeader(http.StatusOK)
   358  		if flusher, ok := w.(http.Flusher); ok {
   359  			flusher.Flush()
   360  		}
   361  	}
   362  
   363  	// Block on the result of the wait operation.
   364  	status := <-waitC
   365  
   366  	// With API < 1.34, wait on WaitConditionRemoved did not return
   367  	// in case container removal failed. The only way to report an
   368  	// error back to the client is to not write anything (i.e. send
   369  	// an empty response which will be treated as an error).
   370  	if legacyRemovalWaitPre134 && status.Err() != nil {
   371  		return nil
   372  	}
   373  
   374  	var waitError *container.ContainerWaitOKBodyError
   375  	if status.Err() != nil {
   376  		waitError = &container.ContainerWaitOKBodyError{Message: status.Err().Error()}
   377  	}
   378  
   379  	return json.NewEncoder(w).Encode(&container.ContainerWaitOKBody{
   380  		StatusCode: int64(status.ExitCode()),
   381  		Error:      waitError,
   382  	})
   383  }
   384  
   385  func (s *containerRouter) getContainersChanges(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   386  	changes, err := s.backend.ContainerChanges(vars["name"])
   387  	if err != nil {
   388  		return err
   389  	}
   390  
   391  	return httputils.WriteJSON(w, http.StatusOK, changes)
   392  }
   393  
   394  func (s *containerRouter) getContainersTop(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   395  	if err := httputils.ParseForm(r); err != nil {
   396  		return err
   397  	}
   398  
   399  	procList, err := s.backend.ContainerTop(vars["name"], r.Form.Get("ps_args"))
   400  	if err != nil {
   401  		return err
   402  	}
   403  
   404  	return httputils.WriteJSON(w, http.StatusOK, procList)
   405  }
   406  
   407  func (s *containerRouter) postContainerRename(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   408  	if err := httputils.ParseForm(r); err != nil {
   409  		return err
   410  	}
   411  
   412  	name := vars["name"]
   413  	newName := r.Form.Get("name")
   414  	if err := s.backend.ContainerRename(name, newName); err != nil {
   415  		return err
   416  	}
   417  	w.WriteHeader(http.StatusNoContent)
   418  	return nil
   419  }
   420  
   421  func (s *containerRouter) postContainerUpdate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   422  	if err := httputils.ParseForm(r); err != nil {
   423  		return err
   424  	}
   425  	if err := httputils.CheckForJSON(r); err != nil {
   426  		return err
   427  	}
   428  
   429  	var updateConfig container.UpdateConfig
   430  
   431  	decoder := json.NewDecoder(r.Body)
   432  	if err := decoder.Decode(&updateConfig); err != nil {
   433  		return err
   434  	}
   435  	if versions.LessThan(httputils.VersionFromContext(ctx), "1.40") {
   436  		updateConfig.PidsLimit = nil
   437  	}
   438  	if updateConfig.PidsLimit != nil && *updateConfig.PidsLimit <= 0 {
   439  		// Both `0` and `-1` are accepted to set "unlimited" when updating.
   440  		// Historically, any negative value was accepted, so treat them as
   441  		// "unlimited" as well.
   442  		var unlimited int64
   443  		updateConfig.PidsLimit = &unlimited
   444  	}
   445  
   446  	hostConfig := &container.HostConfig{
   447  		Resources:     updateConfig.Resources,
   448  		RestartPolicy: updateConfig.RestartPolicy,
   449  	}
   450  
   451  	name := vars["name"]
   452  	resp, err := s.backend.ContainerUpdate(name, hostConfig)
   453  	if err != nil {
   454  		return err
   455  	}
   456  
   457  	return httputils.WriteJSON(w, http.StatusOK, resp)
   458  }
   459  
   460  func (s *containerRouter) postContainersCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   461  	if err := httputils.ParseForm(r); err != nil {
   462  		return err
   463  	}
   464  	if err := httputils.CheckForJSON(r); err != nil {
   465  		return err
   466  	}
   467  
   468  	name := r.Form.Get("name")
   469  
   470  	config, hostConfig, networkingConfig, err := s.decoder.DecodeConfig(r.Body)
   471  	if err != nil {
   472  		return err
   473  	}
   474  	version := httputils.VersionFromContext(ctx)
   475  	adjustCPUShares := versions.LessThan(version, "1.19")
   476  
   477  	// When using API 1.24 and under, the client is responsible for removing the container
   478  	if hostConfig != nil && versions.LessThan(version, "1.25") {
   479  		hostConfig.AutoRemove = false
   480  	}
   481  
   482  	if hostConfig != nil && versions.LessThan(version, "1.40") {
   483  		// Ignore BindOptions.NonRecursive because it was added in API 1.40.
   484  		for _, m := range hostConfig.Mounts {
   485  			if bo := m.BindOptions; bo != nil {
   486  				bo.NonRecursive = false
   487  			}
   488  		}
   489  		// Ignore KernelMemoryTCP because it was added in API 1.40.
   490  		hostConfig.KernelMemoryTCP = 0
   491  
   492  		// Older clients (API < 1.40) expects the default to be shareable, make them happy
   493  		if hostConfig.IpcMode.IsEmpty() {
   494  			hostConfig.IpcMode = container.IpcMode("shareable")
   495  		}
   496  	}
   497  	if hostConfig != nil && versions.LessThan(version, "1.41") && !s.cgroup2 {
   498  		// Older clients expect the default to be "host" on cgroup v1 hosts
   499  		if hostConfig.CgroupnsMode.IsEmpty() {
   500  			hostConfig.CgroupnsMode = container.CgroupnsMode("host")
   501  		}
   502  	}
   503  
   504  	var platform *specs.Platform
   505  	if versions.GreaterThanOrEqualTo(version, "1.41") {
   506  		if v := r.Form.Get("platform"); v != "" {
   507  			p, err := platforms.Parse(v)
   508  			if err != nil {
   509  				return errdefs.InvalidParameter(err)
   510  			}
   511  			platform = &p
   512  		}
   513  	}
   514  
   515  	if hostConfig != nil && hostConfig.PidsLimit != nil && *hostConfig.PidsLimit <= 0 {
   516  		// Don't set a limit if either no limit was specified, or "unlimited" was
   517  		// explicitly set.
   518  		// Both `0` and `-1` are accepted as "unlimited", and historically any
   519  		// negative value was accepted, so treat those as "unlimited" as well.
   520  		hostConfig.PidsLimit = nil
   521  	}
   522  
   523  	ccr, err := s.backend.ContainerCreate(types.ContainerCreateConfig{
   524  		Name:             name,
   525  		Config:           config,
   526  		HostConfig:       hostConfig,
   527  		NetworkingConfig: networkingConfig,
   528  		AdjustCPUShares:  adjustCPUShares,
   529  		Platform:         platform,
   530  	})
   531  	if err != nil {
   532  		return err
   533  	}
   534  
   535  	return httputils.WriteJSON(w, http.StatusCreated, ccr)
   536  }
   537  
   538  func (s *containerRouter) deleteContainers(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   539  	if err := httputils.ParseForm(r); err != nil {
   540  		return err
   541  	}
   542  
   543  	name := vars["name"]
   544  	config := &types.ContainerRmConfig{
   545  		ForceRemove:  httputils.BoolValue(r, "force"),
   546  		RemoveVolume: httputils.BoolValue(r, "v"),
   547  		RemoveLink:   httputils.BoolValue(r, "link"),
   548  	}
   549  
   550  	if err := s.backend.ContainerRm(name, config); err != nil {
   551  		return err
   552  	}
   553  
   554  	w.WriteHeader(http.StatusNoContent)
   555  
   556  	return nil
   557  }
   558  
   559  func (s *containerRouter) postContainersResize(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   560  	if err := httputils.ParseForm(r); err != nil {
   561  		return err
   562  	}
   563  
   564  	height, err := strconv.Atoi(r.Form.Get("h"))
   565  	if err != nil {
   566  		return errdefs.InvalidParameter(err)
   567  	}
   568  	width, err := strconv.Atoi(r.Form.Get("w"))
   569  	if err != nil {
   570  		return errdefs.InvalidParameter(err)
   571  	}
   572  
   573  	return s.backend.ContainerResize(vars["name"], height, width)
   574  }
   575  
   576  func (s *containerRouter) postContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   577  	err := httputils.ParseForm(r)
   578  	if err != nil {
   579  		return err
   580  	}
   581  	containerName := vars["name"]
   582  
   583  	_, upgrade := r.Header["Upgrade"]
   584  	detachKeys := r.FormValue("detachKeys")
   585  
   586  	hijacker, ok := w.(http.Hijacker)
   587  	if !ok {
   588  		return errdefs.InvalidParameter(errors.Errorf("error attaching to container %s, hijack connection missing", containerName))
   589  	}
   590  
   591  	setupStreams := func() (io.ReadCloser, io.Writer, io.Writer, error) {
   592  		conn, _, err := hijacker.Hijack()
   593  		if err != nil {
   594  			return nil, nil, nil, err
   595  		}
   596  
   597  		// set raw mode
   598  		conn.Write([]byte{})
   599  
   600  		if upgrade {
   601  			fmt.Fprintf(conn, "HTTP/1.1 101 UPGRADED\r\nContent-Type: application/vnd.docker.raw-stream\r\nConnection: Upgrade\r\nUpgrade: tcp\r\n\r\n")
   602  		} else {
   603  			fmt.Fprintf(conn, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
   604  		}
   605  
   606  		closer := func() error {
   607  			httputils.CloseStreams(conn)
   608  			return nil
   609  		}
   610  		return ioutils.NewReadCloserWrapper(conn, closer), conn, conn, nil
   611  	}
   612  
   613  	attachConfig := &backend.ContainerAttachConfig{
   614  		GetStreams: setupStreams,
   615  		UseStdin:   httputils.BoolValue(r, "stdin"),
   616  		UseStdout:  httputils.BoolValue(r, "stdout"),
   617  		UseStderr:  httputils.BoolValue(r, "stderr"),
   618  		Logs:       httputils.BoolValue(r, "logs"),
   619  		Stream:     httputils.BoolValue(r, "stream"),
   620  		DetachKeys: detachKeys,
   621  		MuxStreams: true,
   622  	}
   623  
   624  	if err = s.backend.ContainerAttach(containerName, attachConfig); err != nil {
   625  		logrus.Errorf("Handler for %s %s returned error: %v", r.Method, r.URL.Path, err)
   626  		// Remember to close stream if error happens
   627  		conn, _, errHijack := hijacker.Hijack()
   628  		if errHijack == nil {
   629  			statusCode := errdefs.GetHTTPErrorStatusCode(err)
   630  			statusText := http.StatusText(statusCode)
   631  			fmt.Fprintf(conn, "HTTP/1.1 %d %s\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n%s\r\n", statusCode, statusText, err.Error())
   632  			httputils.CloseStreams(conn)
   633  		} else {
   634  			logrus.Errorf("Error Hijacking: %v", err)
   635  		}
   636  	}
   637  	return nil
   638  }
   639  
   640  func (s *containerRouter) wsContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   641  	if err := httputils.ParseForm(r); err != nil {
   642  		return err
   643  	}
   644  	containerName := vars["name"]
   645  
   646  	var err error
   647  	detachKeys := r.FormValue("detachKeys")
   648  
   649  	done := make(chan struct{})
   650  	started := make(chan struct{})
   651  
   652  	version := httputils.VersionFromContext(ctx)
   653  
   654  	setupStreams := func() (io.ReadCloser, io.Writer, io.Writer, error) {
   655  		wsChan := make(chan *websocket.Conn)
   656  		h := func(conn *websocket.Conn) {
   657  			wsChan <- conn
   658  			<-done
   659  		}
   660  
   661  		srv := websocket.Server{Handler: h, Handshake: nil}
   662  		go func() {
   663  			close(started)
   664  			srv.ServeHTTP(w, r)
   665  		}()
   666  
   667  		conn := <-wsChan
   668  		// In case version 1.28 and above, a binary frame will be sent.
   669  		// See 28176 for details.
   670  		if versions.GreaterThanOrEqualTo(version, "1.28") {
   671  			conn.PayloadType = websocket.BinaryFrame
   672  		}
   673  		return conn, conn, conn, nil
   674  	}
   675  
   676  	attachConfig := &backend.ContainerAttachConfig{
   677  		GetStreams: setupStreams,
   678  		Logs:       httputils.BoolValue(r, "logs"),
   679  		Stream:     httputils.BoolValue(r, "stream"),
   680  		DetachKeys: detachKeys,
   681  		UseStdin:   true,
   682  		UseStdout:  true,
   683  		UseStderr:  true,
   684  		MuxStreams: false, // TODO: this should be true since it's a single stream for both stdout and stderr
   685  	}
   686  
   687  	err = s.backend.ContainerAttach(containerName, attachConfig)
   688  	close(done)
   689  	select {
   690  	case <-started:
   691  		if err != nil {
   692  			logrus.Errorf("Error attaching websocket: %s", err)
   693  		} else {
   694  			logrus.Debug("websocket connection was closed by client")
   695  		}
   696  		return nil
   697  	default:
   698  	}
   699  	return err
   700  }
   701  
   702  func (s *containerRouter) postContainersPrune(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
   703  	if err := httputils.ParseForm(r); err != nil {
   704  		return err
   705  	}
   706  
   707  	pruneFilters, err := filters.FromJSON(r.Form.Get("filters"))
   708  	if err != nil {
   709  		return errdefs.InvalidParameter(err)
   710  	}
   711  
   712  	pruneReport, err := s.backend.ContainersPrune(ctx, pruneFilters)
   713  	if err != nil {
   714  		return err
   715  	}
   716  	return httputils.WriteJSON(w, http.StatusOK, pruneReport)
   717  }