github.com/bugraaydogar/snapd@v0.0.0-20210315170335-8c70bb858939/daemon/response.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2015-2020 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package daemon
    21  
    22  import (
    23  	"bufio"
    24  	"encoding/json"
    25  	"fmt"
    26  	"io"
    27  	"mime"
    28  	"net"
    29  	"net/http"
    30  	"path/filepath"
    31  	"strconv"
    32  	"time"
    33  
    34  	"github.com/snapcore/snapd/arch"
    35  	"github.com/snapcore/snapd/asserts"
    36  	"github.com/snapcore/snapd/client"
    37  	"github.com/snapcore/snapd/logger"
    38  	"github.com/snapcore/snapd/overlord/snapshotstate"
    39  	"github.com/snapcore/snapd/overlord/snapstate"
    40  	"github.com/snapcore/snapd/overlord/state"
    41  	"github.com/snapcore/snapd/snap"
    42  	"github.com/snapcore/snapd/store"
    43  	"github.com/snapcore/snapd/systemd"
    44  )
    45  
    46  // ResponseType is the response type
    47  type ResponseType string
    48  
    49  // "there are three standard return types: Standard return value,
    50  // Background operation, Error", each returning a JSON object with the
    51  // following "type" field:
    52  const (
    53  	ResponseTypeSync  ResponseType = "sync"
    54  	ResponseTypeAsync ResponseType = "async"
    55  	ResponseTypeError ResponseType = "error"
    56  )
    57  
    58  // Response knows how to serve itself, and how to find itself
    59  type Response interface {
    60  	ServeHTTP(w http.ResponseWriter, r *http.Request)
    61  }
    62  
    63  type resp struct {
    64  	Status int          `json:"status-code"`
    65  	Type   ResponseType `json:"type"`
    66  	Result interface{}  `json:"result,omitempty"`
    67  	*Meta
    68  	Maintenance *errorResult `json:"maintenance,omitempty"`
    69  }
    70  
    71  func maintenanceForRestartType(rst state.RestartType) *errorResult {
    72  	e := &errorResult{}
    73  	switch rst {
    74  	case state.RestartSystem, state.RestartSystemNow:
    75  		e.Kind = client.ErrorKindSystemRestart
    76  		e.Message = daemonRestartMsg
    77  	case state.RestartDaemon:
    78  		e.Kind = client.ErrorKindDaemonRestart
    79  		e.Message = systemRestartMsg
    80  	case state.RestartSocket:
    81  		e.Kind = client.ErrorKindDaemonRestart
    82  		e.Message = socketRestartMsg
    83  	case state.RestartUnset:
    84  		// shouldn't happen, maintenance for unset type should just be nil
    85  		panic("internal error: cannot marshal maintenance for RestartUnset")
    86  	}
    87  	return e
    88  }
    89  
    90  func (r *resp) addWarningsToMeta(count int, stamp time.Time) {
    91  	if r.Meta != nil && r.Meta.WarningCount != 0 {
    92  		return
    93  	}
    94  	if count == 0 {
    95  		return
    96  	}
    97  	if r.Meta == nil {
    98  		r.Meta = &Meta{}
    99  	}
   100  	r.Meta.WarningCount = count
   101  	r.Meta.WarningTimestamp = &stamp
   102  }
   103  
   104  // TODO This is being done in a rush to get the proper external
   105  //      JSON representation in the API in time for the release.
   106  //      The right code style takes a bit more work and unifies
   107  //      these fields inside resp.
   108  // Increment the counter if you read this: 43
   109  type Meta struct {
   110  	Sources           []string   `json:"sources,omitempty"`
   111  	SuggestedCurrency string     `json:"suggested-currency,omitempty"`
   112  	Change            string     `json:"change,omitempty"`
   113  	WarningTimestamp  *time.Time `json:"warning-timestamp,omitempty"`
   114  	WarningCount      int        `json:"warning-count,omitempty"`
   115  }
   116  
   117  type respJSON struct {
   118  	Type       ResponseType `json:"type"`
   119  	Status     int          `json:"status-code"`
   120  	StatusText string       `json:"status"`
   121  	Result     interface{}  `json:"result"`
   122  	*Meta
   123  	Maintenance *errorResult `json:"maintenance,omitempty"`
   124  }
   125  
   126  func (r *resp) MarshalJSON() ([]byte, error) {
   127  	return json.Marshal(respJSON{
   128  		Type:        r.Type,
   129  		Status:      r.Status,
   130  		StatusText:  http.StatusText(r.Status),
   131  		Result:      r.Result,
   132  		Meta:        r.Meta,
   133  		Maintenance: r.Maintenance,
   134  	})
   135  }
   136  
   137  func (r *resp) ServeHTTP(w http.ResponseWriter, _ *http.Request) {
   138  	status := r.Status
   139  	bs, err := r.MarshalJSON()
   140  	if err != nil {
   141  		logger.Noticef("cannot marshal %#v to JSON: %v", *r, err)
   142  		bs = nil
   143  		status = 500
   144  	}
   145  
   146  	hdr := w.Header()
   147  	if r.Status == 202 || r.Status == 201 {
   148  		if m, ok := r.Result.(map[string]interface{}); ok {
   149  			if location, ok := m["resource"]; ok {
   150  				if location, ok := location.(string); ok && location != "" {
   151  					hdr.Set("Location", location)
   152  				}
   153  			}
   154  		}
   155  	}
   156  
   157  	hdr.Set("Content-Type", "application/json")
   158  	w.WriteHeader(status)
   159  	w.Write(bs)
   160  }
   161  
   162  type errorValue interface{}
   163  
   164  type errorResult struct {
   165  	Message string `json:"message"` // note no omitempty
   166  	// Kind is the error kind. See client/errors.go
   167  	Kind  client.ErrorKind `json:"kind,omitempty"`
   168  	Value errorValue       `json:"value,omitempty"`
   169  }
   170  
   171  // SyncResponse builds a "sync" response from the given result.
   172  func SyncResponse(result interface{}, meta *Meta) Response {
   173  	if err, ok := result.(error); ok {
   174  		return InternalError("internal error: %v", err)
   175  	}
   176  
   177  	if rsp, ok := result.(Response); ok {
   178  		return rsp
   179  	}
   180  
   181  	return &resp{
   182  		Type:   ResponseTypeSync,
   183  		Status: 200,
   184  		Result: result,
   185  		Meta:   meta,
   186  	}
   187  }
   188  
   189  // AsyncResponse builds an "async" response from the given *Task
   190  func AsyncResponse(result map[string]interface{}, meta *Meta) Response {
   191  	return &resp{
   192  		Type:   ResponseTypeAsync,
   193  		Status: 202,
   194  		Result: result,
   195  		Meta:   meta,
   196  	}
   197  }
   198  
   199  // makeErrorResponder builds an errorResponder from the given error status.
   200  func makeErrorResponder(status int) errorResponder {
   201  	return func(format string, v ...interface{}) Response {
   202  		res := &errorResult{}
   203  		if len(v) == 0 {
   204  			res.Message = format
   205  		} else {
   206  			res.Message = fmt.Sprintf(format, v...)
   207  		}
   208  		if status == 401 {
   209  			res.Kind = client.ErrorKindLoginRequired
   210  		}
   211  		return &resp{
   212  			Type:   ResponseTypeError,
   213  			Result: res,
   214  			Status: status,
   215  		}
   216  	}
   217  }
   218  
   219  // A snapStream ServeHTTP method streams a snap
   220  type snapStream struct {
   221  	SnapName string
   222  	Filename string
   223  	Info     *snap.DownloadInfo
   224  	Token    string
   225  	stream   io.ReadCloser
   226  	resume   int64
   227  }
   228  
   229  // ServeHTTP from the Response interface
   230  func (s *snapStream) ServeHTTP(w http.ResponseWriter, _ *http.Request) {
   231  	hdr := w.Header()
   232  	hdr.Set("Content-Type", "application/octet-stream")
   233  	snapname := fmt.Sprintf("attachment; filename=%s", s.Filename)
   234  	hdr.Set("Content-Disposition", snapname)
   235  
   236  	hdr.Set("Snap-Sha3-384", s.Info.Sha3_384)
   237  	// can't set Content-Length when stream is nil as it breaks http clients
   238  	// setting it also when there is a stream, for consistency
   239  	hdr.Set("Snap-Length", strconv.FormatInt(s.Info.Size, 10))
   240  	if s.Token != "" {
   241  		hdr.Set("Snap-Download-Token", s.Token)
   242  	}
   243  
   244  	if s.stream == nil {
   245  		// nothing to actually stream
   246  		return
   247  	}
   248  	hdr.Set("Content-Length", strconv.FormatInt(s.Info.Size-s.resume, 10))
   249  
   250  	if s.resume > 0 {
   251  		hdr.Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", s.resume, s.Info.Size-1, s.Info.Size))
   252  		w.WriteHeader(206)
   253  	}
   254  
   255  	defer s.stream.Close()
   256  	bytesCopied, err := io.Copy(w, s.stream)
   257  	if err != nil {
   258  		logger.Noticef("cannot copy snap %s (%#v) to the stream: %v", s.SnapName, s.Info, err)
   259  		http.Error(w, err.Error(), 500)
   260  	}
   261  	if bytesCopied != s.Info.Size-s.resume {
   262  		logger.Noticef("cannot copy snap %s (%#v) to the stream: bytes copied=%d, expected=%d", s.SnapName, s.Info, bytesCopied, s.Info.Size)
   263  		http.Error(w, io.EOF.Error(), 502)
   264  	}
   265  }
   266  
   267  // A snapshotExportResponse 's ServeHTTP method serves a specific snapshot ID
   268  type snapshotExportResponse struct {
   269  	*snapshotstate.SnapshotExport
   270  	setID uint64
   271  	st    *state.State
   272  }
   273  
   274  // ServeHTTP from the Response interface
   275  func (s snapshotExportResponse) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   276  	w.Header().Add("Content-Length", strconv.FormatInt(s.Size(), 10))
   277  	w.Header().Add("Content-Type", client.SnapshotExportMediaType)
   278  	if err := s.StreamTo(w); err != nil {
   279  		logger.Debugf("cannot export snapshot: %v", err)
   280  	}
   281  	s.Close()
   282  	s.st.Lock()
   283  	defer s.st.Unlock()
   284  	snapshotstate.UnsetSnapshotOpInProgress(s.st, s.setID)
   285  }
   286  
   287  // A fileResponse 's ServeHTTP method serves the file
   288  type fileResponse string
   289  
   290  // ServeHTTP from the Response interface
   291  func (f fileResponse) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   292  	filename := fmt.Sprintf("attachment; filename=%s", filepath.Base(string(f)))
   293  	w.Header().Add("Content-Disposition", filename)
   294  	http.ServeFile(w, r, string(f))
   295  }
   296  
   297  // A journalLineReaderSeqResponse's ServeHTTP method reads lines (presumed to
   298  // be, each one on its own, a JSON dump of a systemd.Log, as output by
   299  // journalctl -o json) from an io.ReadCloser, loads that into a client.Log, and
   300  // outputs the json dump of that, padded with RS and LF to make it a valid
   301  // json-seq response.
   302  //
   303  // The reader is always closed when done (this is important for
   304  // osutil.WatingStdoutPipe).
   305  //
   306  // Tip: “jq” knows how to read this; “jq --seq” both reads and writes this.
   307  type journalLineReaderSeqResponse struct {
   308  	io.ReadCloser
   309  	follow bool
   310  }
   311  
   312  func (rr *journalLineReaderSeqResponse) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   313  	w.Header().Set("Content-Type", "application/json-seq")
   314  
   315  	flusher, hasFlusher := w.(http.Flusher)
   316  
   317  	var err error
   318  	dec := json.NewDecoder(rr)
   319  	writer := bufio.NewWriter(w)
   320  	enc := json.NewEncoder(writer)
   321  	for {
   322  		var log systemd.Log
   323  		if err = dec.Decode(&log); err != nil {
   324  			break
   325  		}
   326  
   327  		writer.WriteByte(0x1E) // RS -- see ascii(7), and RFC7464
   328  
   329  		// ignore the error...
   330  		t, _ := log.Time()
   331  		if err = enc.Encode(client.Log{
   332  			Timestamp: t,
   333  			Message:   log.Message(),
   334  			SID:       log.SID(),
   335  			PID:       log.PID(),
   336  		}); err != nil {
   337  			break
   338  		}
   339  
   340  		if rr.follow {
   341  			if e := writer.Flush(); e != nil {
   342  				break
   343  			}
   344  			if hasFlusher {
   345  				flusher.Flush()
   346  			}
   347  		}
   348  	}
   349  	if err != nil && err != io.EOF {
   350  		fmt.Fprintf(writer, `\x1E{"error": %q}\n`, err)
   351  		logger.Noticef("cannot stream response; problem reading: %v", err)
   352  	}
   353  	if err := writer.Flush(); err != nil {
   354  		logger.Noticef("cannot stream response; problem writing: %v", err)
   355  	}
   356  	rr.Close()
   357  }
   358  
   359  type assertResponse struct {
   360  	assertions []asserts.Assertion
   361  	bundle     bool
   362  }
   363  
   364  // AssertResponse builds a response whose ServerHTTP method serves one or a bundle of assertions.
   365  func AssertResponse(asserts []asserts.Assertion, bundle bool) Response {
   366  	if len(asserts) > 1 {
   367  		bundle = true
   368  	}
   369  	return &assertResponse{assertions: asserts, bundle: bundle}
   370  }
   371  
   372  func (ar assertResponse) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   373  	t := asserts.MediaType
   374  	if ar.bundle {
   375  		t = mime.FormatMediaType(t, map[string]string{"bundle": "y"})
   376  	}
   377  	w.Header().Set("Content-Type", t)
   378  	w.Header().Set("X-Ubuntu-Assertions-Count", strconv.Itoa(len(ar.assertions)))
   379  	w.WriteHeader(200)
   380  	enc := asserts.NewEncoder(w)
   381  	for _, a := range ar.assertions {
   382  		err := enc.Encode(a)
   383  		if err != nil {
   384  			logger.Noticef("cannot write encoded assertion into response: %v", err)
   385  			break
   386  
   387  		}
   388  	}
   389  }
   390  
   391  // errorResponder is a callable that produces an error Response.
   392  // e.g., InternalError("something broke: %v", err), etc.
   393  type errorResponder func(string, ...interface{}) Response
   394  
   395  // standard error responses
   396  var (
   397  	Unauthorized     = makeErrorResponder(401)
   398  	NotFound         = makeErrorResponder(404)
   399  	BadRequest       = makeErrorResponder(400)
   400  	MethodNotAllowed = makeErrorResponder(405)
   401  	InternalError    = makeErrorResponder(500)
   402  	NotImplemented   = makeErrorResponder(501)
   403  	Forbidden        = makeErrorResponder(403)
   404  	Conflict         = makeErrorResponder(409)
   405  )
   406  
   407  // SnapNotFound is an error responder used when an operation is
   408  // requested on a snap that doesn't exist.
   409  func SnapNotFound(snapName string, err error) Response {
   410  	return &resp{
   411  		Type: ResponseTypeError,
   412  		Result: &errorResult{
   413  			Message: err.Error(),
   414  			Kind:    client.ErrorKindSnapNotFound,
   415  			Value:   snapName,
   416  		},
   417  		Status: 404,
   418  	}
   419  }
   420  
   421  // SnapRevisionNotAvailable is an error responder used when an
   422  // operation is requested for which no revivision can be found
   423  // in the given context (e.g. request an install from a stable
   424  // channel when this channel is empty).
   425  func SnapRevisionNotAvailable(snapName string, rnaErr *store.RevisionNotAvailableError) Response {
   426  	var value interface{} = snapName
   427  	kind := client.ErrorKindSnapRevisionNotAvailable
   428  	msg := rnaErr.Error()
   429  	if len(rnaErr.Releases) != 0 && rnaErr.Channel != "" {
   430  		thisArch := arch.DpkgArchitecture()
   431  		values := map[string]interface{}{
   432  			"snap-name":    snapName,
   433  			"action":       rnaErr.Action,
   434  			"channel":      rnaErr.Channel,
   435  			"architecture": thisArch,
   436  		}
   437  		archOK := false
   438  		releases := make([]map[string]interface{}, 0, len(rnaErr.Releases))
   439  		for _, c := range rnaErr.Releases {
   440  			if c.Architecture == thisArch {
   441  				archOK = true
   442  			}
   443  			releases = append(releases, map[string]interface{}{
   444  				"architecture": c.Architecture,
   445  				"channel":      c.Name,
   446  			})
   447  		}
   448  		// we return all available releases (arch x channel)
   449  		// as reported in the store error, but we hint with
   450  		// the error kind whether there was anything at all
   451  		// available for this architecture
   452  		if archOK {
   453  			kind = client.ErrorKindSnapChannelNotAvailable
   454  			msg = "no snap revision on specified channel"
   455  		} else {
   456  			kind = client.ErrorKindSnapArchitectureNotAvailable
   457  			msg = "no snap revision on specified architecture"
   458  		}
   459  		values["releases"] = releases
   460  		value = values
   461  	}
   462  	return &resp{
   463  		Type: ResponseTypeError,
   464  		Result: &errorResult{
   465  			Message: msg,
   466  			Kind:    kind,
   467  			Value:   value,
   468  		},
   469  		Status: 404,
   470  	}
   471  }
   472  
   473  // SnapChangeConflict is an error responder used when an operation is
   474  // conflicts with another change.
   475  func SnapChangeConflict(cce *snapstate.ChangeConflictError) Response {
   476  	value := map[string]interface{}{}
   477  	if cce.Snap != "" {
   478  		value["snap-name"] = cce.Snap
   479  	}
   480  	if cce.ChangeKind != "" {
   481  		value["change-kind"] = cce.ChangeKind
   482  	}
   483  
   484  	return &resp{
   485  		Type: ResponseTypeError,
   486  		Result: &errorResult{
   487  			Message: cce.Error(),
   488  			Kind:    client.ErrorKindSnapChangeConflict,
   489  			Value:   value,
   490  		},
   491  		Status: 409,
   492  	}
   493  }
   494  
   495  // InsufficientSpace is an error responder used when an operation cannot
   496  // be performed due to low disk space.
   497  func InsufficientSpace(dserr *snapstate.InsufficientSpaceError) Response {
   498  	value := map[string]interface{}{}
   499  	if len(dserr.Snaps) > 0 {
   500  		value["snap-names"] = dserr.Snaps
   501  	}
   502  	if dserr.ChangeKind != "" {
   503  		value["change-kind"] = dserr.ChangeKind
   504  	}
   505  	return &resp{
   506  		Type: ResponseTypeError,
   507  		Result: &errorResult{
   508  			Message: dserr.Error(),
   509  			Kind:    client.ErrorKindInsufficientDiskSpace,
   510  			Value:   value,
   511  		},
   512  		Status: 507,
   513  	}
   514  }
   515  
   516  // AppNotFound is an error responder used when an operation is
   517  // requested on a app that doesn't exist.
   518  func AppNotFound(format string, v ...interface{}) Response {
   519  	res := &errorResult{
   520  		Message: fmt.Sprintf(format, v...),
   521  		Kind:    client.ErrorKindAppNotFound,
   522  	}
   523  	return &resp{
   524  		Type:   ResponseTypeError,
   525  		Result: res,
   526  		Status: 404,
   527  	}
   528  }
   529  
   530  // AuthCancelled is an error responder used when a user cancelled
   531  // the auth process.
   532  func AuthCancelled(format string, v ...interface{}) Response {
   533  	res := &errorResult{
   534  		Message: fmt.Sprintf(format, v...),
   535  		Kind:    client.ErrorKindAuthCancelled,
   536  	}
   537  	return &resp{
   538  		Type:   ResponseTypeError,
   539  		Result: res,
   540  		Status: 403,
   541  	}
   542  }
   543  
   544  // InterfacesUnchanged is an error responder used when an operation
   545  // that would normally change interfaces finds it has nothing to do
   546  func InterfacesUnchanged(format string, v ...interface{}) Response {
   547  	res := &errorResult{
   548  		Message: fmt.Sprintf(format, v...),
   549  		Kind:    client.ErrorKindInterfacesUnchanged,
   550  	}
   551  	return &resp{
   552  		Type:   ResponseTypeError,
   553  		Result: res,
   554  		Status: 400,
   555  	}
   556  }
   557  
   558  func errToResponse(err error, snaps []string, fallback func(format string, v ...interface{}) Response, format string, v ...interface{}) Response {
   559  	var kind client.ErrorKind
   560  	var snapName string
   561  
   562  	switch err {
   563  	case store.ErrSnapNotFound:
   564  		switch len(snaps) {
   565  		case 1:
   566  			return SnapNotFound(snaps[0], err)
   567  		// store.ErrSnapNotFound should only be returned for individual
   568  		// snap queries; in all other cases something's wrong
   569  		case 0:
   570  			return InternalError("store.SnapNotFound with no snap given")
   571  		default:
   572  			return InternalError("store.SnapNotFound with %d snaps", len(snaps))
   573  		}
   574  	case store.ErrNoUpdateAvailable:
   575  		kind = client.ErrorKindSnapNoUpdateAvailable
   576  	case store.ErrLocalSnap:
   577  		kind = client.ErrorKindSnapLocal
   578  	default:
   579  		handled := true
   580  		switch err := err.(type) {
   581  		case *store.RevisionNotAvailableError:
   582  			// store.ErrRevisionNotAvailable should only be returned for
   583  			// individual snap queries; in all other cases something's wrong
   584  			switch len(snaps) {
   585  			case 1:
   586  				return SnapRevisionNotAvailable(snaps[0], err)
   587  			case 0:
   588  				return InternalError("store.RevisionNotAvailable with no snap given")
   589  			default:
   590  				return InternalError("store.RevisionNotAvailable with %d snaps", len(snaps))
   591  			}
   592  		case *snap.AlreadyInstalledError:
   593  			kind = client.ErrorKindSnapAlreadyInstalled
   594  			snapName = err.Snap
   595  		case *snap.NotInstalledError:
   596  			kind = client.ErrorKindSnapNotInstalled
   597  			snapName = err.Snap
   598  		case *snapstate.ChangeConflictError:
   599  			return SnapChangeConflict(err)
   600  		case *snapstate.SnapNeedsDevModeError:
   601  			kind = client.ErrorKindSnapNeedsDevMode
   602  			snapName = err.Snap
   603  		case *snapstate.SnapNeedsClassicError:
   604  			kind = client.ErrorKindSnapNeedsClassic
   605  			snapName = err.Snap
   606  		case *snapstate.SnapNeedsClassicSystemError:
   607  			kind = client.ErrorKindSnapNeedsClassicSystem
   608  			snapName = err.Snap
   609  		case *snapstate.SnapNotClassicError:
   610  			kind = client.ErrorKindSnapNotClassic
   611  			snapName = err.Snap
   612  		case *snapstate.InsufficientSpaceError:
   613  			return InsufficientSpace(err)
   614  		case net.Error:
   615  			if err.Timeout() {
   616  				kind = client.ErrorKindNetworkTimeout
   617  			} else {
   618  				handled = false
   619  			}
   620  		case *store.SnapActionError:
   621  			// we only handle a few specific cases
   622  			_, _, e := err.SingleOpError()
   623  			if e != nil {
   624  				// 👉😎👉
   625  				return errToResponse(e, snaps, fallback, format)
   626  			}
   627  			handled = false
   628  		default:
   629  			handled = false
   630  		}
   631  
   632  		if !handled {
   633  			v = append(v, err)
   634  			return fallback(format, v...)
   635  		}
   636  	}
   637  
   638  	return SyncResponse(&resp{
   639  		Type:   ResponseTypeError,
   640  		Result: &errorResult{Message: err.Error(), Kind: kind, Value: snapName},
   641  		Status: 400,
   642  	}, nil)
   643  }