代码拉取完成,页面将自动刷新
同步操作将从 tupelo-shen/mysnapd 强制同步,此操作会覆盖自 Fork 仓库以来所做的任何修改,且无法恢复!!!
确定后同步将在后台操作,完成时将刷新页面,请耐心等待。
// -*- Mode: Go; indent-tabs-mode: t -*-
/*
* Copyright (C) 2019 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package daemon
import (
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"net/http"
"path/filepath"
"regexp"
"strconv"
"time"
"gitee.com/mysnapcore/mysnapd/logger"
"gitee.com/mysnapcore/mysnapd/overlord/auth"
"gitee.com/mysnapcore/mysnapd/overlord/state"
"gitee.com/mysnapcore/mysnapd/randutil"
"gitee.com/mysnapcore/mysnapd/snap"
"gitee.com/mysnapcore/mysnapd/store"
)
var snapDownloadCmd = &Command{
Path: "/v2/download",
POST: postSnapDownload,
WriteAccess: authenticatedAccess{Polkit: polkitActionManage},
}
var validRangeRegexp = regexp.MustCompile(`^\s*bytes=(\d+)-\s*$`)
// SnapDownloadAction is used to request a snap download
type snapDownloadAction struct {
SnapName string `json:"snap-name"`
snapRevisionOptions
// HeaderPeek if set requests a peek at the header without the
// body being returned.
HeaderPeek bool `json:"header-peek"`
ResumeToken string `json:"resume-token"`
resumePosition int64
}
var (
errDownloadNameRequired = errors.New("download operation requires one snap name")
errDownloadHeaderPeekResume = errors.New("cannot request header-only peek when resuming")
errDownloadResumeNoToken = errors.New("cannot resume without a token")
)
func (action *snapDownloadAction) validate() error {
if action.SnapName == "" {
return errDownloadNameRequired
}
if action.HeaderPeek && (action.resumePosition > 0 || action.ResumeToken != "") {
return errDownloadHeaderPeekResume
}
if action.resumePosition > 0 && action.ResumeToken == "" {
return errDownloadResumeNoToken
}
return action.snapRevisionOptions.validate()
}
func postSnapDownload(c *Command, r *http.Request, user *auth.UserState) Response {
var action snapDownloadAction
decoder := json.NewDecoder(r.Body)
if err := decoder.Decode(&action); err != nil {
return BadRequest("cannot decode request body into download operation: %v", err)
}
if decoder.More() {
return BadRequest("extra content found after download operation")
}
if rangestr := r.Header.Get("Range"); rangestr != "" {
// "An origin server MUST ignore a Range header field
// that contains a range unit it does not understand."
subs := validRangeRegexp.FindStringSubmatch(rangestr)
if len(subs) == 2 {
n, err := strconv.ParseInt(subs[1], 10, 64)
if err == nil {
action.resumePosition = n
}
}
}
if err := action.validate(); err != nil {
return BadRequest(err.Error())
}
return streamOneSnap(c, action, user)
}
func streamOneSnap(c *Command, action snapDownloadAction, user *auth.UserState) Response {
secret, err := downloadTokensSecret(c.d)
if err != nil {
return InternalError(err.Error())
}
theStore := storeFrom(c.d)
var ss *snapStream
if action.ResumeToken == "" {
var info *snap.Info
actions := []*store.SnapAction{{
Action: "download",
InstanceName: action.SnapName,
Revision: action.Revision,
CohortKey: action.CohortKey,
Channel: action.Channel,
}}
results, _, err := theStore.SnapAction(context.TODO(), nil, actions, nil, user, nil)
if err != nil {
return errToResponse(err, []string{action.SnapName}, InternalError, "cannot download snap: %v")
}
if len(results) != 1 {
return InternalError("internal error: unexpected number %v of results for a single download", len(results))
}
info = results[0].Info
ss, err = newSnapStream(action.SnapName, info, secret)
if err != nil {
return InternalError(err.Error())
}
} else {
var err error
ss, err = newResumingSnapStream(action.SnapName, action.ResumeToken, secret)
if err != nil {
return BadRequest(err.Error())
}
ss.resume = action.resumePosition
}
if !action.HeaderPeek {
stream, status, err := theStore.DownloadStream(context.TODO(), action.SnapName, ss.Info, action.resumePosition, user)
if err != nil {
return InternalError(err.Error())
}
ss.stream = stream
if status != 206 {
// store/cdn has no partial content (valid
// reply per RFC)
logger.Debugf("store refused our range request")
ss.resume = 0
}
}
return ss
}
func newSnapStream(snapName string, info *snap.Info, secret []byte) (*snapStream, error) {
dlInfo := &info.DownloadInfo
fname := filepath.Base(info.MountFile())
tokenJSON := downloadTokenJSON{
SnapName: snapName,
Filename: fname,
Info: dlInfo,
}
tokStr, err := sealDownloadToken(&tokenJSON, secret)
if err != nil {
return nil, err
}
return &snapStream{
SnapName: snapName,
Filename: fname,
Info: dlInfo,
Token: tokStr,
}, nil
}
func newResumingSnapStream(snapName string, tokStr string, secret []byte) (*snapStream, error) {
d, err := unsealDownloadToken(tokStr, secret)
if err != nil {
return nil, err
}
if d.SnapName != snapName {
return nil, fmt.Errorf("resume snap name does not match original snap name")
}
return &snapStream{
SnapName: snapName,
Filename: d.Filename,
Info: d.Info,
Token: tokStr,
}, nil
}
type downloadTokenJSON struct {
SnapName string `json:"snap-name"`
Filename string `json:"filename"`
Info *snap.DownloadInfo `json:"dl-info"`
}
func sealDownloadToken(d *downloadTokenJSON, secret []byte) (string, error) {
b, err := json.Marshal(d)
if err != nil {
return "", err
}
mac := hmac.New(sha256.New, secret)
mac.Write(b)
// append the HMAC hash to b to build the full raw token tok
tok := mac.Sum(b)
return base64.RawURLEncoding.EncodeToString(tok), nil
}
var errInvalidDownloadToken = errors.New("download token is invalid")
func unsealDownloadToken(tokStr string, secret []byte) (*downloadTokenJSON, error) {
tok, err := base64.RawURLEncoding.DecodeString(tokStr)
if err != nil {
return nil, errInvalidDownloadToken
}
sz := len(tok)
if sz < sha256.Size {
return nil, errInvalidDownloadToken
}
h := tok[sz-sha256.Size:]
b := tok[:sz-sha256.Size]
mac := hmac.New(sha256.New, secret)
mac.Write(b)
if !hmac.Equal(h, mac.Sum(nil)) {
return nil, errInvalidDownloadToken
}
var d downloadTokenJSON
if err := json.Unmarshal(b, &d); err != nil {
return nil, err
}
return &d, nil
}
func downloadTokensSecret(d *Daemon) (secret []byte, err error) {
st := d.overlord.State()
st.Lock()
defer st.Unlock()
const k = "api-download-tokens-secret"
err = st.Get(k, &secret)
if err == nil {
return secret, nil
}
if err != nil && !errors.Is(err, state.ErrNoState) {
return nil, err
}
secret, err = randutil.CryptoTokenBytes(32)
if err != nil {
return nil, err
}
st.Set(k, secret)
st.Set(k+"-time", time.Now().UTC())
return secret, nil
}
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。