2019-05-22 18:26:53 +02:00
|
|
|
// -*- 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"
|
2020-02-14 13:02:54 +01:00
|
|
|
"crypto/hmac"
|
|
|
|
|
"crypto/sha256"
|
|
|
|
|
"encoding/base64"
|
2019-05-22 18:26:53 +02:00
|
|
|
"encoding/json"
|
2019-11-01 19:53:53 +00:00
|
|
|
"errors"
|
2020-02-14 13:02:54 +01:00
|
|
|
"fmt"
|
2019-05-22 18:26:53 +02:00
|
|
|
"net/http"
|
2019-09-26 09:22:29 +02:00
|
|
|
"path/filepath"
|
daemon, store: support resuming downloads
This implements a first pass at the snapd side of resumable-via-snapd
downloads.
There's a little dance the client needs to do to find out what it's
going to download: right now snapd replies with the sha3, and the
client should pass that to snapd when wanting to resume. It's not
prefixed but probably should be, so we can switch to using hmac
without having to add another blob field in the api.
It goes like this: first, get the hash and other info,
$ sudo curl -D- --unix-socket /run/snapd.socket -d '{"snap-name":"http","no-body":true}' http://localhost/v2/download
HTTP/1.1 200 OK
Content-Disposition: attachment; filename=http_29.snap
Content-Type: application/octet-stream
Snap-Length: 2572288
Snap-Sha3-384: bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea
Date: Sat, 01 Feb 2020 02:07:30 GMT
Content-Length: 0
then you ask for the whole thing,
$ sudo curl -s -D- -o http_29.snap --unix-socket /run/snapd.socket -d '{"snap-name":"http","resume-stamp":"bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea"}' http://localhost/v2/download
HTTP/1.1 200 OK
Content-Disposition: attachment; filename=http_29.snap
Content-Length: 2572288
Content-Type: application/octet-stream
Snap-Length: 2572288
Snap-Sha3-384: bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea
Date: Sat, 01 Feb 2020 02:10:41 GMT
then you truncate the file
$ sudo truncate -s 1MB http_29.snap
and ask to continue (note in 16.04's curl at least the -C option
doesn't work well with POSTs -- it tries to continue the POST? I have
no idea),
$ sudo curl -s -D- -H Range:bytes=1000000- -o http_29.snap --unix-socket /run/snapd.socket -d '{"snap-name":"http","resume-stamp":"bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea"}' http://localhost/v2/download
HTTP/1.1 206 Partial Content
Content-Disposition: attachment; filename=http_29.snap
Content-Length: 1572288
Content-Range: bytes 1000000-2572287/2572288
Content-Type: application/octet-stream
Snap-Length: 2572288
Snap-Sha3-384: bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea
Date: Sat, 01 Feb 2020 02:16:31 GMT
(also note this change only has snapd supporting ranges like this one,
ie "bytes=<start>-"; everything else results in a 200).
I haven't had time to write unit tests for this, other than for some
of the store bits which are better covered than before (but need
more). Notably the 200 vs 206 thing I describe below is NOT covered.
I've tested it "live" using curl and httpie, and it's sensible and
works, and can resume downloading a snap that's in the cache. Right
now the store isn't ever replying with a 206 when I ask for a range,
so I haven't been able to end-to-end that path, but it's not anything
I'm doing wrong AFAICT; the store also doesn't 206 when curl or httpie
talk to it directly. I think this is a bug in the store, but it's also
correct behaviour AFAIK (from reading the RFC).
The client will need to check whether a ranged request comes back with
a 206 or a 200 to know whether it's actually resuming or not.
NOTE the store itself (`downloadImpl`) doesn't support this behaviour;
this needs fixing as currently it'll be downloading the whole thing
twice as soon as it tries to resume once and gets the full content
instead (the sha3 won't match). I tried to dive into fixing this but
it's waaay too late.
2020-02-01 01:31:15 +00:00
|
|
|
"regexp"
|
|
|
|
|
"strconv"
|
2020-02-17 13:02:50 +01:00
|
|
|
"time"
|
2019-05-22 18:26:53 +02:00
|
|
|
|
daemon, store: support resuming downloads
This implements a first pass at the snapd side of resumable-via-snapd
downloads.
There's a little dance the client needs to do to find out what it's
going to download: right now snapd replies with the sha3, and the
client should pass that to snapd when wanting to resume. It's not
prefixed but probably should be, so we can switch to using hmac
without having to add another blob field in the api.
It goes like this: first, get the hash and other info,
$ sudo curl -D- --unix-socket /run/snapd.socket -d '{"snap-name":"http","no-body":true}' http://localhost/v2/download
HTTP/1.1 200 OK
Content-Disposition: attachment; filename=http_29.snap
Content-Type: application/octet-stream
Snap-Length: 2572288
Snap-Sha3-384: bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea
Date: Sat, 01 Feb 2020 02:07:30 GMT
Content-Length: 0
then you ask for the whole thing,
$ sudo curl -s -D- -o http_29.snap --unix-socket /run/snapd.socket -d '{"snap-name":"http","resume-stamp":"bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea"}' http://localhost/v2/download
HTTP/1.1 200 OK
Content-Disposition: attachment; filename=http_29.snap
Content-Length: 2572288
Content-Type: application/octet-stream
Snap-Length: 2572288
Snap-Sha3-384: bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea
Date: Sat, 01 Feb 2020 02:10:41 GMT
then you truncate the file
$ sudo truncate -s 1MB http_29.snap
and ask to continue (note in 16.04's curl at least the -C option
doesn't work well with POSTs -- it tries to continue the POST? I have
no idea),
$ sudo curl -s -D- -H Range:bytes=1000000- -o http_29.snap --unix-socket /run/snapd.socket -d '{"snap-name":"http","resume-stamp":"bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea"}' http://localhost/v2/download
HTTP/1.1 206 Partial Content
Content-Disposition: attachment; filename=http_29.snap
Content-Length: 1572288
Content-Range: bytes 1000000-2572287/2572288
Content-Type: application/octet-stream
Snap-Length: 2572288
Snap-Sha3-384: bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea
Date: Sat, 01 Feb 2020 02:16:31 GMT
(also note this change only has snapd supporting ranges like this one,
ie "bytes=<start>-"; everything else results in a 200).
I haven't had time to write unit tests for this, other than for some
of the store bits which are better covered than before (but need
more). Notably the 200 vs 206 thing I describe below is NOT covered.
I've tested it "live" using curl and httpie, and it's sensible and
works, and can resume downloading a snap that's in the cache. Right
now the store isn't ever replying with a 206 when I ask for a range,
so I haven't been able to end-to-end that path, but it's not anything
I'm doing wrong AFAICT; the store also doesn't 206 when curl or httpie
talk to it directly. I think this is a bug in the store, but it's also
correct behaviour AFAIK (from reading the RFC).
The client will need to check whether a ranged request comes back with
a 206 or a 200 to know whether it's actually resuming or not.
NOTE the store itself (`downloadImpl`) doesn't support this behaviour;
this needs fixing as currently it'll be downloading the whole thing
twice as soon as it tries to resume once and gets the full content
instead (the sha3 won't match). I tried to dive into fixing this but
it's waaay too late.
2020-02-01 01:31:15 +00:00
|
|
|
"github.com/snapcore/snapd/logger"
|
2019-05-22 18:26:53 +02:00
|
|
|
"github.com/snapcore/snapd/overlord/auth"
|
2020-02-14 13:02:54 +01:00
|
|
|
"github.com/snapcore/snapd/overlord/state"
|
|
|
|
|
"github.com/snapcore/snapd/randutil"
|
daemon, store: support resuming downloads
This implements a first pass at the snapd side of resumable-via-snapd
downloads.
There's a little dance the client needs to do to find out what it's
going to download: right now snapd replies with the sha3, and the
client should pass that to snapd when wanting to resume. It's not
prefixed but probably should be, so we can switch to using hmac
without having to add another blob field in the api.
It goes like this: first, get the hash and other info,
$ sudo curl -D- --unix-socket /run/snapd.socket -d '{"snap-name":"http","no-body":true}' http://localhost/v2/download
HTTP/1.1 200 OK
Content-Disposition: attachment; filename=http_29.snap
Content-Type: application/octet-stream
Snap-Length: 2572288
Snap-Sha3-384: bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea
Date: Sat, 01 Feb 2020 02:07:30 GMT
Content-Length: 0
then you ask for the whole thing,
$ sudo curl -s -D- -o http_29.snap --unix-socket /run/snapd.socket -d '{"snap-name":"http","resume-stamp":"bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea"}' http://localhost/v2/download
HTTP/1.1 200 OK
Content-Disposition: attachment; filename=http_29.snap
Content-Length: 2572288
Content-Type: application/octet-stream
Snap-Length: 2572288
Snap-Sha3-384: bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea
Date: Sat, 01 Feb 2020 02:10:41 GMT
then you truncate the file
$ sudo truncate -s 1MB http_29.snap
and ask to continue (note in 16.04's curl at least the -C option
doesn't work well with POSTs -- it tries to continue the POST? I have
no idea),
$ sudo curl -s -D- -H Range:bytes=1000000- -o http_29.snap --unix-socket /run/snapd.socket -d '{"snap-name":"http","resume-stamp":"bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea"}' http://localhost/v2/download
HTTP/1.1 206 Partial Content
Content-Disposition: attachment; filename=http_29.snap
Content-Length: 1572288
Content-Range: bytes 1000000-2572287/2572288
Content-Type: application/octet-stream
Snap-Length: 2572288
Snap-Sha3-384: bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea
Date: Sat, 01 Feb 2020 02:16:31 GMT
(also note this change only has snapd supporting ranges like this one,
ie "bytes=<start>-"; everything else results in a 200).
I haven't had time to write unit tests for this, other than for some
of the store bits which are better covered than before (but need
more). Notably the 200 vs 206 thing I describe below is NOT covered.
I've tested it "live" using curl and httpie, and it's sensible and
works, and can resume downloading a snap that's in the cache. Right
now the store isn't ever replying with a 206 when I ask for a range,
so I haven't been able to end-to-end that path, but it's not anything
I'm doing wrong AFAICT; the store also doesn't 206 when curl or httpie
talk to it directly. I think this is a bug in the store, but it's also
correct behaviour AFAIK (from reading the RFC).
The client will need to check whether a ranged request comes back with
a 206 or a 200 to know whether it's actually resuming or not.
NOTE the store itself (`downloadImpl`) doesn't support this behaviour;
this needs fixing as currently it'll be downloading the whole thing
twice as soon as it tries to resume once and gets the full content
instead (the sha3 won't match). I tried to dive into fixing this but
it's waaay too late.
2020-02-01 01:31:15 +00:00
|
|
|
"github.com/snapcore/snapd/snap"
|
2019-05-22 18:26:53 +02:00
|
|
|
"github.com/snapcore/snapd/store"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var snapDownloadCmd = &Command{
|
2020-08-07 08:32:56 +08:00
|
|
|
Path: "/v2/download",
|
|
|
|
|
POST: postSnapDownload,
|
2020-08-27 13:48:57 +08:00
|
|
|
WriteAccess: authenticatedAccess{Polkit: polkitActionManage},
|
2019-05-22 18:26:53 +02:00
|
|
|
}
|
|
|
|
|
|
2020-02-17 12:03:17 +01:00
|
|
|
var validRangeRegexp = regexp.MustCompile(`^\s*bytes=(\d+)-\s*$`)
|
daemon, store: support resuming downloads
This implements a first pass at the snapd side of resumable-via-snapd
downloads.
There's a little dance the client needs to do to find out what it's
going to download: right now snapd replies with the sha3, and the
client should pass that to snapd when wanting to resume. It's not
prefixed but probably should be, so we can switch to using hmac
without having to add another blob field in the api.
It goes like this: first, get the hash and other info,
$ sudo curl -D- --unix-socket /run/snapd.socket -d '{"snap-name":"http","no-body":true}' http://localhost/v2/download
HTTP/1.1 200 OK
Content-Disposition: attachment; filename=http_29.snap
Content-Type: application/octet-stream
Snap-Length: 2572288
Snap-Sha3-384: bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea
Date: Sat, 01 Feb 2020 02:07:30 GMT
Content-Length: 0
then you ask for the whole thing,
$ sudo curl -s -D- -o http_29.snap --unix-socket /run/snapd.socket -d '{"snap-name":"http","resume-stamp":"bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea"}' http://localhost/v2/download
HTTP/1.1 200 OK
Content-Disposition: attachment; filename=http_29.snap
Content-Length: 2572288
Content-Type: application/octet-stream
Snap-Length: 2572288
Snap-Sha3-384: bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea
Date: Sat, 01 Feb 2020 02:10:41 GMT
then you truncate the file
$ sudo truncate -s 1MB http_29.snap
and ask to continue (note in 16.04's curl at least the -C option
doesn't work well with POSTs -- it tries to continue the POST? I have
no idea),
$ sudo curl -s -D- -H Range:bytes=1000000- -o http_29.snap --unix-socket /run/snapd.socket -d '{"snap-name":"http","resume-stamp":"bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea"}' http://localhost/v2/download
HTTP/1.1 206 Partial Content
Content-Disposition: attachment; filename=http_29.snap
Content-Length: 1572288
Content-Range: bytes 1000000-2572287/2572288
Content-Type: application/octet-stream
Snap-Length: 2572288
Snap-Sha3-384: bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea
Date: Sat, 01 Feb 2020 02:16:31 GMT
(also note this change only has snapd supporting ranges like this one,
ie "bytes=<start>-"; everything else results in a 200).
I haven't had time to write unit tests for this, other than for some
of the store bits which are better covered than before (but need
more). Notably the 200 vs 206 thing I describe below is NOT covered.
I've tested it "live" using curl and httpie, and it's sensible and
works, and can resume downloading a snap that's in the cache. Right
now the store isn't ever replying with a 206 when I ask for a range,
so I haven't been able to end-to-end that path, but it's not anything
I'm doing wrong AFAICT; the store also doesn't 206 when curl or httpie
talk to it directly. I think this is a bug in the store, but it's also
correct behaviour AFAIK (from reading the RFC).
The client will need to check whether a ranged request comes back with
a 206 or a 200 to know whether it's actually resuming or not.
NOTE the store itself (`downloadImpl`) doesn't support this behaviour;
this needs fixing as currently it'll be downloading the whole thing
twice as soon as it tries to resume once and gets the full content
instead (the sha3 won't match). I tried to dive into fixing this but
it's waaay too late.
2020-02-01 01:31:15 +00:00
|
|
|
|
2019-05-22 18:26:53 +02:00
|
|
|
// SnapDownloadAction is used to request a snap download
|
|
|
|
|
type snapDownloadAction struct {
|
daemon, store: support resuming downloads
This implements a first pass at the snapd side of resumable-via-snapd
downloads.
There's a little dance the client needs to do to find out what it's
going to download: right now snapd replies with the sha3, and the
client should pass that to snapd when wanting to resume. It's not
prefixed but probably should be, so we can switch to using hmac
without having to add another blob field in the api.
It goes like this: first, get the hash and other info,
$ sudo curl -D- --unix-socket /run/snapd.socket -d '{"snap-name":"http","no-body":true}' http://localhost/v2/download
HTTP/1.1 200 OK
Content-Disposition: attachment; filename=http_29.snap
Content-Type: application/octet-stream
Snap-Length: 2572288
Snap-Sha3-384: bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea
Date: Sat, 01 Feb 2020 02:07:30 GMT
Content-Length: 0
then you ask for the whole thing,
$ sudo curl -s -D- -o http_29.snap --unix-socket /run/snapd.socket -d '{"snap-name":"http","resume-stamp":"bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea"}' http://localhost/v2/download
HTTP/1.1 200 OK
Content-Disposition: attachment; filename=http_29.snap
Content-Length: 2572288
Content-Type: application/octet-stream
Snap-Length: 2572288
Snap-Sha3-384: bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea
Date: Sat, 01 Feb 2020 02:10:41 GMT
then you truncate the file
$ sudo truncate -s 1MB http_29.snap
and ask to continue (note in 16.04's curl at least the -C option
doesn't work well with POSTs -- it tries to continue the POST? I have
no idea),
$ sudo curl -s -D- -H Range:bytes=1000000- -o http_29.snap --unix-socket /run/snapd.socket -d '{"snap-name":"http","resume-stamp":"bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea"}' http://localhost/v2/download
HTTP/1.1 206 Partial Content
Content-Disposition: attachment; filename=http_29.snap
Content-Length: 1572288
Content-Range: bytes 1000000-2572287/2572288
Content-Type: application/octet-stream
Snap-Length: 2572288
Snap-Sha3-384: bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea
Date: Sat, 01 Feb 2020 02:16:31 GMT
(also note this change only has snapd supporting ranges like this one,
ie "bytes=<start>-"; everything else results in a 200).
I haven't had time to write unit tests for this, other than for some
of the store bits which are better covered than before (but need
more). Notably the 200 vs 206 thing I describe below is NOT covered.
I've tested it "live" using curl and httpie, and it's sensible and
works, and can resume downloading a snap that's in the cache. Right
now the store isn't ever replying with a 206 when I ask for a range,
so I haven't been able to end-to-end that path, but it's not anything
I'm doing wrong AFAICT; the store also doesn't 206 when curl or httpie
talk to it directly. I think this is a bug in the store, but it's also
correct behaviour AFAIK (from reading the RFC).
The client will need to check whether a ranged request comes back with
a 206 or a 200 to know whether it's actually resuming or not.
NOTE the store itself (`downloadImpl`) doesn't support this behaviour;
this needs fixing as currently it'll be downloading the whole thing
twice as soon as it tries to resume once and gets the full content
instead (the sha3 won't match). I tried to dive into fixing this but
it's waaay too late.
2020-02-01 01:31:15 +00:00
|
|
|
SnapName string `json:"snap-name"`
|
2019-10-08 08:53:39 +02:00
|
|
|
snapRevisionOptions
|
2020-02-14 10:22:35 +01:00
|
|
|
|
|
|
|
|
// HeaderPeek if set requests a peek at the header without the
|
|
|
|
|
// body being returned.
|
|
|
|
|
HeaderPeek bool `json:"header-peek"`
|
|
|
|
|
|
2020-02-14 13:02:54 +01:00
|
|
|
ResumeToken string `json:"resume-token"`
|
daemon, store: support resuming downloads
This implements a first pass at the snapd side of resumable-via-snapd
downloads.
There's a little dance the client needs to do to find out what it's
going to download: right now snapd replies with the sha3, and the
client should pass that to snapd when wanting to resume. It's not
prefixed but probably should be, so we can switch to using hmac
without having to add another blob field in the api.
It goes like this: first, get the hash and other info,
$ sudo curl -D- --unix-socket /run/snapd.socket -d '{"snap-name":"http","no-body":true}' http://localhost/v2/download
HTTP/1.1 200 OK
Content-Disposition: attachment; filename=http_29.snap
Content-Type: application/octet-stream
Snap-Length: 2572288
Snap-Sha3-384: bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea
Date: Sat, 01 Feb 2020 02:07:30 GMT
Content-Length: 0
then you ask for the whole thing,
$ sudo curl -s -D- -o http_29.snap --unix-socket /run/snapd.socket -d '{"snap-name":"http","resume-stamp":"bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea"}' http://localhost/v2/download
HTTP/1.1 200 OK
Content-Disposition: attachment; filename=http_29.snap
Content-Length: 2572288
Content-Type: application/octet-stream
Snap-Length: 2572288
Snap-Sha3-384: bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea
Date: Sat, 01 Feb 2020 02:10:41 GMT
then you truncate the file
$ sudo truncate -s 1MB http_29.snap
and ask to continue (note in 16.04's curl at least the -C option
doesn't work well with POSTs -- it tries to continue the POST? I have
no idea),
$ sudo curl -s -D- -H Range:bytes=1000000- -o http_29.snap --unix-socket /run/snapd.socket -d '{"snap-name":"http","resume-stamp":"bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea"}' http://localhost/v2/download
HTTP/1.1 206 Partial Content
Content-Disposition: attachment; filename=http_29.snap
Content-Length: 1572288
Content-Range: bytes 1000000-2572287/2572288
Content-Type: application/octet-stream
Snap-Length: 2572288
Snap-Sha3-384: bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea
Date: Sat, 01 Feb 2020 02:16:31 GMT
(also note this change only has snapd supporting ranges like this one,
ie "bytes=<start>-"; everything else results in a 200).
I haven't had time to write unit tests for this, other than for some
of the store bits which are better covered than before (but need
more). Notably the 200 vs 206 thing I describe below is NOT covered.
I've tested it "live" using curl and httpie, and it's sensible and
works, and can resume downloading a snap that's in the cache. Right
now the store isn't ever replying with a 206 when I ask for a range,
so I haven't been able to end-to-end that path, but it's not anything
I'm doing wrong AFAICT; the store also doesn't 206 when curl or httpie
talk to it directly. I think this is a bug in the store, but it's also
correct behaviour AFAIK (from reading the RFC).
The client will need to check whether a ranged request comes back with
a 206 or a 200 to know whether it's actually resuming or not.
NOTE the store itself (`downloadImpl`) doesn't support this behaviour;
this needs fixing as currently it'll be downloading the whole thing
twice as soon as it tries to resume once and gets the full content
instead (the sha3 won't match). I tried to dive into fixing this but
it's waaay too late.
2020-02-01 01:31:15 +00:00
|
|
|
resumePosition int64
|
2019-05-22 18:26:53 +02:00
|
|
|
}
|
|
|
|
|
|
daemon, store: support resuming downloads
This implements a first pass at the snapd side of resumable-via-snapd
downloads.
There's a little dance the client needs to do to find out what it's
going to download: right now snapd replies with the sha3, and the
client should pass that to snapd when wanting to resume. It's not
prefixed but probably should be, so we can switch to using hmac
without having to add another blob field in the api.
It goes like this: first, get the hash and other info,
$ sudo curl -D- --unix-socket /run/snapd.socket -d '{"snap-name":"http","no-body":true}' http://localhost/v2/download
HTTP/1.1 200 OK
Content-Disposition: attachment; filename=http_29.snap
Content-Type: application/octet-stream
Snap-Length: 2572288
Snap-Sha3-384: bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea
Date: Sat, 01 Feb 2020 02:07:30 GMT
Content-Length: 0
then you ask for the whole thing,
$ sudo curl -s -D- -o http_29.snap --unix-socket /run/snapd.socket -d '{"snap-name":"http","resume-stamp":"bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea"}' http://localhost/v2/download
HTTP/1.1 200 OK
Content-Disposition: attachment; filename=http_29.snap
Content-Length: 2572288
Content-Type: application/octet-stream
Snap-Length: 2572288
Snap-Sha3-384: bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea
Date: Sat, 01 Feb 2020 02:10:41 GMT
then you truncate the file
$ sudo truncate -s 1MB http_29.snap
and ask to continue (note in 16.04's curl at least the -C option
doesn't work well with POSTs -- it tries to continue the POST? I have
no idea),
$ sudo curl -s -D- -H Range:bytes=1000000- -o http_29.snap --unix-socket /run/snapd.socket -d '{"snap-name":"http","resume-stamp":"bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea"}' http://localhost/v2/download
HTTP/1.1 206 Partial Content
Content-Disposition: attachment; filename=http_29.snap
Content-Length: 1572288
Content-Range: bytes 1000000-2572287/2572288
Content-Type: application/octet-stream
Snap-Length: 2572288
Snap-Sha3-384: bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea
Date: Sat, 01 Feb 2020 02:16:31 GMT
(also note this change only has snapd supporting ranges like this one,
ie "bytes=<start>-"; everything else results in a 200).
I haven't had time to write unit tests for this, other than for some
of the store bits which are better covered than before (but need
more). Notably the 200 vs 206 thing I describe below is NOT covered.
I've tested it "live" using curl and httpie, and it's sensible and
works, and can resume downloading a snap that's in the cache. Right
now the store isn't ever replying with a 206 when I ask for a range,
so I haven't been able to end-to-end that path, but it's not anything
I'm doing wrong AFAICT; the store also doesn't 206 when curl or httpie
talk to it directly. I think this is a bug in the store, but it's also
correct behaviour AFAIK (from reading the RFC).
The client will need to check whether a ranged request comes back with
a 206 or a 200 to know whether it's actually resuming or not.
NOTE the store itself (`downloadImpl`) doesn't support this behaviour;
this needs fixing as currently it'll be downloading the whole thing
twice as soon as it tries to resume once and gets the full content
instead (the sha3 won't match). I tried to dive into fixing this but
it's waaay too late.
2020-02-01 01:31:15 +00:00
|
|
|
var (
|
2020-02-14 10:22:35 +01:00
|
|
|
errDownloadNameRequired = errors.New("download operation requires one snap name")
|
|
|
|
|
errDownloadHeaderPeekResume = errors.New("cannot request header-only peek when resuming")
|
2020-02-14 13:02:54 +01:00
|
|
|
errDownloadResumeNoToken = errors.New("cannot resume without a token")
|
daemon, store: support resuming downloads
This implements a first pass at the snapd side of resumable-via-snapd
downloads.
There's a little dance the client needs to do to find out what it's
going to download: right now snapd replies with the sha3, and the
client should pass that to snapd when wanting to resume. It's not
prefixed but probably should be, so we can switch to using hmac
without having to add another blob field in the api.
It goes like this: first, get the hash and other info,
$ sudo curl -D- --unix-socket /run/snapd.socket -d '{"snap-name":"http","no-body":true}' http://localhost/v2/download
HTTP/1.1 200 OK
Content-Disposition: attachment; filename=http_29.snap
Content-Type: application/octet-stream
Snap-Length: 2572288
Snap-Sha3-384: bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea
Date: Sat, 01 Feb 2020 02:07:30 GMT
Content-Length: 0
then you ask for the whole thing,
$ sudo curl -s -D- -o http_29.snap --unix-socket /run/snapd.socket -d '{"snap-name":"http","resume-stamp":"bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea"}' http://localhost/v2/download
HTTP/1.1 200 OK
Content-Disposition: attachment; filename=http_29.snap
Content-Length: 2572288
Content-Type: application/octet-stream
Snap-Length: 2572288
Snap-Sha3-384: bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea
Date: Sat, 01 Feb 2020 02:10:41 GMT
then you truncate the file
$ sudo truncate -s 1MB http_29.snap
and ask to continue (note in 16.04's curl at least the -C option
doesn't work well with POSTs -- it tries to continue the POST? I have
no idea),
$ sudo curl -s -D- -H Range:bytes=1000000- -o http_29.snap --unix-socket /run/snapd.socket -d '{"snap-name":"http","resume-stamp":"bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea"}' http://localhost/v2/download
HTTP/1.1 206 Partial Content
Content-Disposition: attachment; filename=http_29.snap
Content-Length: 1572288
Content-Range: bytes 1000000-2572287/2572288
Content-Type: application/octet-stream
Snap-Length: 2572288
Snap-Sha3-384: bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea
Date: Sat, 01 Feb 2020 02:16:31 GMT
(also note this change only has snapd supporting ranges like this one,
ie "bytes=<start>-"; everything else results in a 200).
I haven't had time to write unit tests for this, other than for some
of the store bits which are better covered than before (but need
more). Notably the 200 vs 206 thing I describe below is NOT covered.
I've tested it "live" using curl and httpie, and it's sensible and
works, and can resume downloading a snap that's in the cache. Right
now the store isn't ever replying with a 206 when I ask for a range,
so I haven't been able to end-to-end that path, but it's not anything
I'm doing wrong AFAICT; the store also doesn't 206 when curl or httpie
talk to it directly. I think this is a bug in the store, but it's also
correct behaviour AFAIK (from reading the RFC).
The client will need to check whether a ranged request comes back with
a 206 or a 200 to know whether it's actually resuming or not.
NOTE the store itself (`downloadImpl`) doesn't support this behaviour;
this needs fixing as currently it'll be downloading the whole thing
twice as soon as it tries to resume once and gets the full content
instead (the sha3 won't match). I tried to dive into fixing this but
it's waaay too late.
2020-02-01 01:31:15 +00:00
|
|
|
)
|
2019-11-01 19:53:53 +00:00
|
|
|
|
|
|
|
|
func (action *snapDownloadAction) validate() error {
|
|
|
|
|
if action.SnapName == "" {
|
|
|
|
|
return errDownloadNameRequired
|
|
|
|
|
}
|
2020-02-14 13:02:54 +01:00
|
|
|
if action.HeaderPeek && (action.resumePosition > 0 || action.ResumeToken != "") {
|
2020-02-14 10:22:35 +01:00
|
|
|
return errDownloadHeaderPeekResume
|
daemon, store: support resuming downloads
This implements a first pass at the snapd side of resumable-via-snapd
downloads.
There's a little dance the client needs to do to find out what it's
going to download: right now snapd replies with the sha3, and the
client should pass that to snapd when wanting to resume. It's not
prefixed but probably should be, so we can switch to using hmac
without having to add another blob field in the api.
It goes like this: first, get the hash and other info,
$ sudo curl -D- --unix-socket /run/snapd.socket -d '{"snap-name":"http","no-body":true}' http://localhost/v2/download
HTTP/1.1 200 OK
Content-Disposition: attachment; filename=http_29.snap
Content-Type: application/octet-stream
Snap-Length: 2572288
Snap-Sha3-384: bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea
Date: Sat, 01 Feb 2020 02:07:30 GMT
Content-Length: 0
then you ask for the whole thing,
$ sudo curl -s -D- -o http_29.snap --unix-socket /run/snapd.socket -d '{"snap-name":"http","resume-stamp":"bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea"}' http://localhost/v2/download
HTTP/1.1 200 OK
Content-Disposition: attachment; filename=http_29.snap
Content-Length: 2572288
Content-Type: application/octet-stream
Snap-Length: 2572288
Snap-Sha3-384: bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea
Date: Sat, 01 Feb 2020 02:10:41 GMT
then you truncate the file
$ sudo truncate -s 1MB http_29.snap
and ask to continue (note in 16.04's curl at least the -C option
doesn't work well with POSTs -- it tries to continue the POST? I have
no idea),
$ sudo curl -s -D- -H Range:bytes=1000000- -o http_29.snap --unix-socket /run/snapd.socket -d '{"snap-name":"http","resume-stamp":"bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea"}' http://localhost/v2/download
HTTP/1.1 206 Partial Content
Content-Disposition: attachment; filename=http_29.snap
Content-Length: 1572288
Content-Range: bytes 1000000-2572287/2572288
Content-Type: application/octet-stream
Snap-Length: 2572288
Snap-Sha3-384: bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea
Date: Sat, 01 Feb 2020 02:16:31 GMT
(also note this change only has snapd supporting ranges like this one,
ie "bytes=<start>-"; everything else results in a 200).
I haven't had time to write unit tests for this, other than for some
of the store bits which are better covered than before (but need
more). Notably the 200 vs 206 thing I describe below is NOT covered.
I've tested it "live" using curl and httpie, and it's sensible and
works, and can resume downloading a snap that's in the cache. Right
now the store isn't ever replying with a 206 when I ask for a range,
so I haven't been able to end-to-end that path, but it's not anything
I'm doing wrong AFAICT; the store also doesn't 206 when curl or httpie
talk to it directly. I think this is a bug in the store, but it's also
correct behaviour AFAIK (from reading the RFC).
The client will need to check whether a ranged request comes back with
a 206 or a 200 to know whether it's actually resuming or not.
NOTE the store itself (`downloadImpl`) doesn't support this behaviour;
this needs fixing as currently it'll be downloading the whole thing
twice as soon as it tries to resume once and gets the full content
instead (the sha3 won't match). I tried to dive into fixing this but
it's waaay too late.
2020-02-01 01:31:15 +00:00
|
|
|
}
|
2020-02-14 13:02:54 +01:00
|
|
|
if action.resumePosition > 0 && action.ResumeToken == "" {
|
|
|
|
|
return errDownloadResumeNoToken
|
daemon, store: support resuming downloads
This implements a first pass at the snapd side of resumable-via-snapd
downloads.
There's a little dance the client needs to do to find out what it's
going to download: right now snapd replies with the sha3, and the
client should pass that to snapd when wanting to resume. It's not
prefixed but probably should be, so we can switch to using hmac
without having to add another blob field in the api.
It goes like this: first, get the hash and other info,
$ sudo curl -D- --unix-socket /run/snapd.socket -d '{"snap-name":"http","no-body":true}' http://localhost/v2/download
HTTP/1.1 200 OK
Content-Disposition: attachment; filename=http_29.snap
Content-Type: application/octet-stream
Snap-Length: 2572288
Snap-Sha3-384: bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea
Date: Sat, 01 Feb 2020 02:07:30 GMT
Content-Length: 0
then you ask for the whole thing,
$ sudo curl -s -D- -o http_29.snap --unix-socket /run/snapd.socket -d '{"snap-name":"http","resume-stamp":"bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea"}' http://localhost/v2/download
HTTP/1.1 200 OK
Content-Disposition: attachment; filename=http_29.snap
Content-Length: 2572288
Content-Type: application/octet-stream
Snap-Length: 2572288
Snap-Sha3-384: bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea
Date: Sat, 01 Feb 2020 02:10:41 GMT
then you truncate the file
$ sudo truncate -s 1MB http_29.snap
and ask to continue (note in 16.04's curl at least the -C option
doesn't work well with POSTs -- it tries to continue the POST? I have
no idea),
$ sudo curl -s -D- -H Range:bytes=1000000- -o http_29.snap --unix-socket /run/snapd.socket -d '{"snap-name":"http","resume-stamp":"bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea"}' http://localhost/v2/download
HTTP/1.1 206 Partial Content
Content-Disposition: attachment; filename=http_29.snap
Content-Length: 1572288
Content-Range: bytes 1000000-2572287/2572288
Content-Type: application/octet-stream
Snap-Length: 2572288
Snap-Sha3-384: bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea
Date: Sat, 01 Feb 2020 02:16:31 GMT
(also note this change only has snapd supporting ranges like this one,
ie "bytes=<start>-"; everything else results in a 200).
I haven't had time to write unit tests for this, other than for some
of the store bits which are better covered than before (but need
more). Notably the 200 vs 206 thing I describe below is NOT covered.
I've tested it "live" using curl and httpie, and it's sensible and
works, and can resume downloading a snap that's in the cache. Right
now the store isn't ever replying with a 206 when I ask for a range,
so I haven't been able to end-to-end that path, but it's not anything
I'm doing wrong AFAICT; the store also doesn't 206 when curl or httpie
talk to it directly. I think this is a bug in the store, but it's also
correct behaviour AFAIK (from reading the RFC).
The client will need to check whether a ranged request comes back with
a 206 or a 200 to know whether it's actually resuming or not.
NOTE the store itself (`downloadImpl`) doesn't support this behaviour;
this needs fixing as currently it'll be downloading the whole thing
twice as soon as it tries to resume once and gets the full content
instead (the sha3 won't match). I tried to dive into fixing this but
it's waaay too late.
2020-02-01 01:31:15 +00:00
|
|
|
}
|
2019-11-01 19:53:53 +00:00
|
|
|
return action.snapRevisionOptions.validate()
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-22 18:26:53 +02:00
|
|
|
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")
|
|
|
|
|
}
|
daemon, store: support resuming downloads
This implements a first pass at the snapd side of resumable-via-snapd
downloads.
There's a little dance the client needs to do to find out what it's
going to download: right now snapd replies with the sha3, and the
client should pass that to snapd when wanting to resume. It's not
prefixed but probably should be, so we can switch to using hmac
without having to add another blob field in the api.
It goes like this: first, get the hash and other info,
$ sudo curl -D- --unix-socket /run/snapd.socket -d '{"snap-name":"http","no-body":true}' http://localhost/v2/download
HTTP/1.1 200 OK
Content-Disposition: attachment; filename=http_29.snap
Content-Type: application/octet-stream
Snap-Length: 2572288
Snap-Sha3-384: bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea
Date: Sat, 01 Feb 2020 02:07:30 GMT
Content-Length: 0
then you ask for the whole thing,
$ sudo curl -s -D- -o http_29.snap --unix-socket /run/snapd.socket -d '{"snap-name":"http","resume-stamp":"bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea"}' http://localhost/v2/download
HTTP/1.1 200 OK
Content-Disposition: attachment; filename=http_29.snap
Content-Length: 2572288
Content-Type: application/octet-stream
Snap-Length: 2572288
Snap-Sha3-384: bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea
Date: Sat, 01 Feb 2020 02:10:41 GMT
then you truncate the file
$ sudo truncate -s 1MB http_29.snap
and ask to continue (note in 16.04's curl at least the -C option
doesn't work well with POSTs -- it tries to continue the POST? I have
no idea),
$ sudo curl -s -D- -H Range:bytes=1000000- -o http_29.snap --unix-socket /run/snapd.socket -d '{"snap-name":"http","resume-stamp":"bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea"}' http://localhost/v2/download
HTTP/1.1 206 Partial Content
Content-Disposition: attachment; filename=http_29.snap
Content-Length: 1572288
Content-Range: bytes 1000000-2572287/2572288
Content-Type: application/octet-stream
Snap-Length: 2572288
Snap-Sha3-384: bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea
Date: Sat, 01 Feb 2020 02:16:31 GMT
(also note this change only has snapd supporting ranges like this one,
ie "bytes=<start>-"; everything else results in a 200).
I haven't had time to write unit tests for this, other than for some
of the store bits which are better covered than before (but need
more). Notably the 200 vs 206 thing I describe below is NOT covered.
I've tested it "live" using curl and httpie, and it's sensible and
works, and can resume downloading a snap that's in the cache. Right
now the store isn't ever replying with a 206 when I ask for a range,
so I haven't been able to end-to-end that path, but it's not anything
I'm doing wrong AFAICT; the store also doesn't 206 when curl or httpie
talk to it directly. I think this is a bug in the store, but it's also
correct behaviour AFAIK (from reading the RFC).
The client will need to check whether a ranged request comes back with
a 206 or a 200 to know whether it's actually resuming or not.
NOTE the store itself (`downloadImpl`) doesn't support this behaviour;
this needs fixing as currently it'll be downloading the whole thing
twice as soon as it tries to resume once and gets the full content
instead (the sha3 won't match). I tried to dive into fixing this but
it's waaay too late.
2020-02-01 01:31:15 +00:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-11-01 19:53:53 +00:00
|
|
|
if err := action.validate(); err != nil {
|
|
|
|
|
return BadRequest(err.Error())
|
2019-05-22 18:26:53 +02:00
|
|
|
}
|
|
|
|
|
|
2024-06-28 14:54:52 +02:00
|
|
|
return streamOneSnap(r.Context(), c, action, user)
|
2019-05-22 18:26:53 +02:00
|
|
|
}
|
|
|
|
|
|
2024-06-28 14:54:52 +02:00
|
|
|
func streamOneSnap(ctx context.Context, c *Command, action snapDownloadAction, user *auth.UserState) Response {
|
2021-03-13 16:52:21 +01:00
|
|
|
secret, err := downloadTokensSecret(c.d)
|
2020-02-13 10:38:52 +01:00
|
|
|
if err != nil {
|
2020-02-14 13:02:54 +01:00
|
|
|
return InternalError(err.Error())
|
2019-05-22 18:26:53 +02:00
|
|
|
}
|
2021-03-13 20:28:22 +01:00
|
|
|
theStore := storeFrom(c.d)
|
daemon, store: support resuming downloads
This implements a first pass at the snapd side of resumable-via-snapd
downloads.
There's a little dance the client needs to do to find out what it's
going to download: right now snapd replies with the sha3, and the
client should pass that to snapd when wanting to resume. It's not
prefixed but probably should be, so we can switch to using hmac
without having to add another blob field in the api.
It goes like this: first, get the hash and other info,
$ sudo curl -D- --unix-socket /run/snapd.socket -d '{"snap-name":"http","no-body":true}' http://localhost/v2/download
HTTP/1.1 200 OK
Content-Disposition: attachment; filename=http_29.snap
Content-Type: application/octet-stream
Snap-Length: 2572288
Snap-Sha3-384: bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea
Date: Sat, 01 Feb 2020 02:07:30 GMT
Content-Length: 0
then you ask for the whole thing,
$ sudo curl -s -D- -o http_29.snap --unix-socket /run/snapd.socket -d '{"snap-name":"http","resume-stamp":"bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea"}' http://localhost/v2/download
HTTP/1.1 200 OK
Content-Disposition: attachment; filename=http_29.snap
Content-Length: 2572288
Content-Type: application/octet-stream
Snap-Length: 2572288
Snap-Sha3-384: bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea
Date: Sat, 01 Feb 2020 02:10:41 GMT
then you truncate the file
$ sudo truncate -s 1MB http_29.snap
and ask to continue (note in 16.04's curl at least the -C option
doesn't work well with POSTs -- it tries to continue the POST? I have
no idea),
$ sudo curl -s -D- -H Range:bytes=1000000- -o http_29.snap --unix-socket /run/snapd.socket -d '{"snap-name":"http","resume-stamp":"bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea"}' http://localhost/v2/download
HTTP/1.1 206 Partial Content
Content-Disposition: attachment; filename=http_29.snap
Content-Length: 1572288
Content-Range: bytes 1000000-2572287/2572288
Content-Type: application/octet-stream
Snap-Length: 2572288
Snap-Sha3-384: bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea
Date: Sat, 01 Feb 2020 02:16:31 GMT
(also note this change only has snapd supporting ranges like this one,
ie "bytes=<start>-"; everything else results in a 200).
I haven't had time to write unit tests for this, other than for some
of the store bits which are better covered than before (but need
more). Notably the 200 vs 206 thing I describe below is NOT covered.
I've tested it "live" using curl and httpie, and it's sensible and
works, and can resume downloading a snap that's in the cache. Right
now the store isn't ever replying with a 206 when I ask for a range,
so I haven't been able to end-to-end that path, but it's not anything
I'm doing wrong AFAICT; the store also doesn't 206 when curl or httpie
talk to it directly. I think this is a bug in the store, but it's also
correct behaviour AFAIK (from reading the RFC).
The client will need to check whether a ranged request comes back with
a 206 or a 200 to know whether it's actually resuming or not.
NOTE the store itself (`downloadImpl`) doesn't support this behaviour;
this needs fixing as currently it'll be downloading the whole thing
twice as soon as it tries to resume once and gets the full content
instead (the sha3 won't match). I tried to dive into fixing this but
it's waaay too late.
2020-02-01 01:31:15 +00:00
|
|
|
|
2020-02-14 13:02:54 +01:00
|
|
|
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,
|
|
|
|
|
}}
|
2024-06-28 14:54:52 +02:00
|
|
|
results, _, err := theStore.SnapAction(ctx, nil, actions, nil, user, nil)
|
2020-02-14 13:02:54 +01:00
|
|
|
if err != nil {
|
|
|
|
|
return errToResponse(err, []string{action.SnapName}, InternalError, "cannot download snap: %v")
|
|
|
|
|
}
|
2020-02-17 12:04:10 +01:00
|
|
|
if len(results) != 1 {
|
|
|
|
|
return InternalError("internal error: unexpected number %v of results for a single download", len(results))
|
2020-02-14 13:02:54 +01:00
|
|
|
}
|
2020-02-17 12:04:10 +01:00
|
|
|
info = results[0].Info
|
2019-05-22 18:26:53 +02:00
|
|
|
|
2020-02-14 13:02:54 +01:00
|
|
|
ss, err = newSnapStream(action.SnapName, info, secret)
|
daemon, store: support resuming downloads
This implements a first pass at the snapd side of resumable-via-snapd
downloads.
There's a little dance the client needs to do to find out what it's
going to download: right now snapd replies with the sha3, and the
client should pass that to snapd when wanting to resume. It's not
prefixed but probably should be, so we can switch to using hmac
without having to add another blob field in the api.
It goes like this: first, get the hash and other info,
$ sudo curl -D- --unix-socket /run/snapd.socket -d '{"snap-name":"http","no-body":true}' http://localhost/v2/download
HTTP/1.1 200 OK
Content-Disposition: attachment; filename=http_29.snap
Content-Type: application/octet-stream
Snap-Length: 2572288
Snap-Sha3-384: bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea
Date: Sat, 01 Feb 2020 02:07:30 GMT
Content-Length: 0
then you ask for the whole thing,
$ sudo curl -s -D- -o http_29.snap --unix-socket /run/snapd.socket -d '{"snap-name":"http","resume-stamp":"bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea"}' http://localhost/v2/download
HTTP/1.1 200 OK
Content-Disposition: attachment; filename=http_29.snap
Content-Length: 2572288
Content-Type: application/octet-stream
Snap-Length: 2572288
Snap-Sha3-384: bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea
Date: Sat, 01 Feb 2020 02:10:41 GMT
then you truncate the file
$ sudo truncate -s 1MB http_29.snap
and ask to continue (note in 16.04's curl at least the -C option
doesn't work well with POSTs -- it tries to continue the POST? I have
no idea),
$ sudo curl -s -D- -H Range:bytes=1000000- -o http_29.snap --unix-socket /run/snapd.socket -d '{"snap-name":"http","resume-stamp":"bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea"}' http://localhost/v2/download
HTTP/1.1 206 Partial Content
Content-Disposition: attachment; filename=http_29.snap
Content-Length: 1572288
Content-Range: bytes 1000000-2572287/2572288
Content-Type: application/octet-stream
Snap-Length: 2572288
Snap-Sha3-384: bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea
Date: Sat, 01 Feb 2020 02:16:31 GMT
(also note this change only has snapd supporting ranges like this one,
ie "bytes=<start>-"; everything else results in a 200).
I haven't had time to write unit tests for this, other than for some
of the store bits which are better covered than before (but need
more). Notably the 200 vs 206 thing I describe below is NOT covered.
I've tested it "live" using curl and httpie, and it's sensible and
works, and can resume downloading a snap that's in the cache. Right
now the store isn't ever replying with a 206 when I ask for a range,
so I haven't been able to end-to-end that path, but it's not anything
I'm doing wrong AFAICT; the store also doesn't 206 when curl or httpie
talk to it directly. I think this is a bug in the store, but it's also
correct behaviour AFAIK (from reading the RFC).
The client will need to check whether a ranged request comes back with
a 206 or a 200 to know whether it's actually resuming or not.
NOTE the store itself (`downloadImpl`) doesn't support this behaviour;
this needs fixing as currently it'll be downloading the whole thing
twice as soon as it tries to resume once and gets the full content
instead (the sha3 won't match). I tried to dive into fixing this but
it's waaay too late.
2020-02-01 01:31:15 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return InternalError(err.Error())
|
|
|
|
|
}
|
2020-02-14 13:02:54 +01:00
|
|
|
} 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 {
|
2024-06-28 14:54:52 +02:00
|
|
|
stream, status, err := theStore.DownloadStream(ctx, action.SnapName, ss.Info, action.resumePosition, user)
|
2020-02-14 13:02:54 +01:00
|
|
|
if err != nil {
|
|
|
|
|
return InternalError(err.Error())
|
|
|
|
|
}
|
2020-02-17 12:06:16 +01:00
|
|
|
ss.stream = stream
|
|
|
|
|
if status != 206 {
|
2020-02-13 10:38:52 +01:00
|
|
|
// store/cdn has no partial content (valid
|
|
|
|
|
// reply per RFC)
|
daemon, store: support resuming downloads
This implements a first pass at the snapd side of resumable-via-snapd
downloads.
There's a little dance the client needs to do to find out what it's
going to download: right now snapd replies with the sha3, and the
client should pass that to snapd when wanting to resume. It's not
prefixed but probably should be, so we can switch to using hmac
without having to add another blob field in the api.
It goes like this: first, get the hash and other info,
$ sudo curl -D- --unix-socket /run/snapd.socket -d '{"snap-name":"http","no-body":true}' http://localhost/v2/download
HTTP/1.1 200 OK
Content-Disposition: attachment; filename=http_29.snap
Content-Type: application/octet-stream
Snap-Length: 2572288
Snap-Sha3-384: bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea
Date: Sat, 01 Feb 2020 02:07:30 GMT
Content-Length: 0
then you ask for the whole thing,
$ sudo curl -s -D- -o http_29.snap --unix-socket /run/snapd.socket -d '{"snap-name":"http","resume-stamp":"bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea"}' http://localhost/v2/download
HTTP/1.1 200 OK
Content-Disposition: attachment; filename=http_29.snap
Content-Length: 2572288
Content-Type: application/octet-stream
Snap-Length: 2572288
Snap-Sha3-384: bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea
Date: Sat, 01 Feb 2020 02:10:41 GMT
then you truncate the file
$ sudo truncate -s 1MB http_29.snap
and ask to continue (note in 16.04's curl at least the -C option
doesn't work well with POSTs -- it tries to continue the POST? I have
no idea),
$ sudo curl -s -D- -H Range:bytes=1000000- -o http_29.snap --unix-socket /run/snapd.socket -d '{"snap-name":"http","resume-stamp":"bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea"}' http://localhost/v2/download
HTTP/1.1 206 Partial Content
Content-Disposition: attachment; filename=http_29.snap
Content-Length: 1572288
Content-Range: bytes 1000000-2572287/2572288
Content-Type: application/octet-stream
Snap-Length: 2572288
Snap-Sha3-384: bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea
Date: Sat, 01 Feb 2020 02:16:31 GMT
(also note this change only has snapd supporting ranges like this one,
ie "bytes=<start>-"; everything else results in a 200).
I haven't had time to write unit tests for this, other than for some
of the store bits which are better covered than before (but need
more). Notably the 200 vs 206 thing I describe below is NOT covered.
I've tested it "live" using curl and httpie, and it's sensible and
works, and can resume downloading a snap that's in the cache. Right
now the store isn't ever replying with a 206 when I ask for a range,
so I haven't been able to end-to-end that path, but it's not anything
I'm doing wrong AFAICT; the store also doesn't 206 when curl or httpie
talk to it directly. I think this is a bug in the store, but it's also
correct behaviour AFAIK (from reading the RFC).
The client will need to check whether a ranged request comes back with
a 206 or a 200 to know whether it's actually resuming or not.
NOTE the store itself (`downloadImpl`) doesn't support this behaviour;
this needs fixing as currently it'll be downloading the whole thing
twice as soon as it tries to resume once and gets the full content
instead (the sha3 won't match). I tried to dive into fixing this but
it's waaay too late.
2020-02-01 01:31:15 +00:00
|
|
|
logger.Debugf("store refused our range request")
|
2020-02-14 13:02:54 +01:00
|
|
|
ss.resume = 0
|
daemon, store: support resuming downloads
This implements a first pass at the snapd side of resumable-via-snapd
downloads.
There's a little dance the client needs to do to find out what it's
going to download: right now snapd replies with the sha3, and the
client should pass that to snapd when wanting to resume. It's not
prefixed but probably should be, so we can switch to using hmac
without having to add another blob field in the api.
It goes like this: first, get the hash and other info,
$ sudo curl -D- --unix-socket /run/snapd.socket -d '{"snap-name":"http","no-body":true}' http://localhost/v2/download
HTTP/1.1 200 OK
Content-Disposition: attachment; filename=http_29.snap
Content-Type: application/octet-stream
Snap-Length: 2572288
Snap-Sha3-384: bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea
Date: Sat, 01 Feb 2020 02:07:30 GMT
Content-Length: 0
then you ask for the whole thing,
$ sudo curl -s -D- -o http_29.snap --unix-socket /run/snapd.socket -d '{"snap-name":"http","resume-stamp":"bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea"}' http://localhost/v2/download
HTTP/1.1 200 OK
Content-Disposition: attachment; filename=http_29.snap
Content-Length: 2572288
Content-Type: application/octet-stream
Snap-Length: 2572288
Snap-Sha3-384: bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea
Date: Sat, 01 Feb 2020 02:10:41 GMT
then you truncate the file
$ sudo truncate -s 1MB http_29.snap
and ask to continue (note in 16.04's curl at least the -C option
doesn't work well with POSTs -- it tries to continue the POST? I have
no idea),
$ sudo curl -s -D- -H Range:bytes=1000000- -o http_29.snap --unix-socket /run/snapd.socket -d '{"snap-name":"http","resume-stamp":"bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea"}' http://localhost/v2/download
HTTP/1.1 206 Partial Content
Content-Disposition: attachment; filename=http_29.snap
Content-Length: 1572288
Content-Range: bytes 1000000-2572287/2572288
Content-Type: application/octet-stream
Snap-Length: 2572288
Snap-Sha3-384: bdbcbdac7e8701228b01381b4774419465b4bfbed489f0104f44e1ad691b983bfe05b1c60dd46f3c802b0cc3b3c800ea
Date: Sat, 01 Feb 2020 02:16:31 GMT
(also note this change only has snapd supporting ranges like this one,
ie "bytes=<start>-"; everything else results in a 200).
I haven't had time to write unit tests for this, other than for some
of the store bits which are better covered than before (but need
more). Notably the 200 vs 206 thing I describe below is NOT covered.
I've tested it "live" using curl and httpie, and it's sensible and
works, and can resume downloading a snap that's in the cache. Right
now the store isn't ever replying with a 206 when I ask for a range,
so I haven't been able to end-to-end that path, but it's not anything
I'm doing wrong AFAICT; the store also doesn't 206 when curl or httpie
talk to it directly. I think this is a bug in the store, but it's also
correct behaviour AFAIK (from reading the RFC).
The client will need to check whether a ranged request comes back with
a 206 or a 200 to know whether it's actually resuming or not.
NOTE the store itself (`downloadImpl`) doesn't support this behaviour;
this needs fixing as currently it'll be downloading the whole thing
twice as soon as it tries to resume once and gets the full content
instead (the sha3 won't match). I tried to dive into fixing this but
it's waaay too late.
2020-02-01 01:31:15 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-14 13:02:54 +01:00
|
|
|
return ss
|
2019-05-22 18:26:53 +02:00
|
|
|
}
|
2020-02-14 10:36:27 +01:00
|
|
|
|
2020-02-14 13:02:54 +01:00
|
|
|
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
|
|
|
|
|
}
|
2020-02-14 10:36:27 +01:00
|
|
|
return &snapStream{
|
|
|
|
|
SnapName: snapName,
|
2020-02-14 13:02:54 +01:00
|
|
|
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,
|
2020-02-17 13:02:50 +01:00
|
|
|
Token: tokStr,
|
2020-02-14 13:02:54 +01:00
|
|
|
}, 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)
|
2020-02-17 12:07:43 +01:00
|
|
|
// append the HMAC hash to b to build the full raw token tok
|
2020-02-14 13:02:54 +01:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-13 16:52:21 +01:00
|
|
|
func downloadTokensSecret(d *Daemon) (secret []byte, err error) {
|
|
|
|
|
st := d.overlord.State()
|
2020-02-14 13:02:54 +01:00
|
|
|
st.Lock()
|
|
|
|
|
defer st.Unlock()
|
|
|
|
|
const k = "api-download-tokens-secret"
|
|
|
|
|
err = st.Get(k, &secret)
|
|
|
|
|
if err == nil {
|
|
|
|
|
return secret, nil
|
|
|
|
|
}
|
2022-05-19 10:45:49 +01:00
|
|
|
if err != nil && !errors.Is(err, state.ErrNoState) {
|
2020-02-14 13:02:54 +01:00
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
secret, err = randutil.CryptoTokenBytes(32)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
st.Set(k, secret)
|
2020-02-17 13:02:50 +01:00
|
|
|
st.Set(k+"-time", time.Now().UTC())
|
2020-02-14 13:02:54 +01:00
|
|
|
return secret, nil
|
2020-02-14 10:36:27 +01:00
|
|
|
}
|