Add Gitea read operations to the forge boundary
This commit is contained in:
@@ -69,22 +69,13 @@ func (c Client) InspectAssets(ctx context.Context, destination Destination, rele
|
||||
return Inspection{}, failure(FailureInvalid, "inspect release assets", 0, err)
|
||||
}
|
||||
apiRoot := strings.TrimRight(base.String(), "/") + "/api/v1/repos/" + url.PathEscape(destination.Owner) + "/" + url.PathEscape(destination.Repository)
|
||||
assetsURL := apiRoot + "/releases/" + strconv.FormatInt(releaseID, 10) + "/assets"
|
||||
var listed []assetResponse
|
||||
if err := c.getJSON(ctx, c.httpClient(), destination.Token, "list release assets", assetsURL, &listed); err != nil {
|
||||
listed, err := c.listAssets(ctx, c.httpClient(), destination.Token, releaseID, apiRoot)
|
||||
if err != nil {
|
||||
return Inspection{}, err
|
||||
}
|
||||
if listed == nil {
|
||||
return Inspection{}, failure(FailureMalformed, "decode release assets", http.StatusOK, errors.New("attachment response must be a JSON array"))
|
||||
}
|
||||
|
||||
remoteByName := make(map[string][]RemoteAsset, len(listed))
|
||||
for _, item := range listed {
|
||||
if item.ID <= 0 || item.Name == "" || item.Size < 0 {
|
||||
return Inspection{}, failure(FailureMalformed, "decode release assets", http.StatusOK, errors.New("attachment entry is missing its ID, name, or size"))
|
||||
}
|
||||
remote := RemoteAsset{ID: item.ID, Filename: item.Name, Size: item.Size, DownloadURL: item.BrowserDownloadURL}
|
||||
remoteByName[item.Name] = append(remoteByName[item.Name], remote)
|
||||
remoteByName[item.Filename] = append(remoteByName[item.Filename], item)
|
||||
}
|
||||
inspection := Inspection{Assets: make([]AssetInspection, 0, len(expected))}
|
||||
declared := make(map[string]struct{}, len(expected))
|
||||
@@ -111,32 +102,68 @@ func (c Client) InspectAssets(ctx context.Context, destination Destination, rele
|
||||
inspection.Assets = append(inspection.Assets, observation)
|
||||
}
|
||||
for _, item := range listed {
|
||||
if _, expected := declared[item.Name]; !expected {
|
||||
inspection.Unrelated = append(inspection.Unrelated, RemoteAsset{ID: item.ID, Filename: item.Name, Size: item.Size, DownloadURL: item.BrowserDownloadURL})
|
||||
if _, expected := declared[item.Filename]; !expected {
|
||||
inspection.Unrelated = append(inspection.Unrelated, item)
|
||||
}
|
||||
}
|
||||
return inspection, nil
|
||||
}
|
||||
|
||||
func (c Client) listAssets(ctx context.Context, client *http.Client, token string, releaseID int64, apiRoot string) ([]RemoteAsset, error) {
|
||||
if releaseID <= 0 {
|
||||
return nil, failure(FailureInvalid, "list release assets", 0, errors.New("release ID must be positive"))
|
||||
}
|
||||
assetsURL := apiRoot + "/releases/" + strconv.FormatInt(releaseID, 10) + "/assets"
|
||||
var listed []assetResponse
|
||||
if err := c.getJSON(ctx, client, token, "list release assets", assetsURL, &listed); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if listed == nil {
|
||||
return nil, failure(FailureMalformed, "decode release assets", http.StatusOK, errors.New("attachment response must be a JSON array"))
|
||||
}
|
||||
assets := make([]RemoteAsset, 0, len(listed))
|
||||
for _, item := range listed {
|
||||
if item.ID <= 0 || item.Name == "" || item.Size < 0 {
|
||||
return nil, failure(FailureMalformed, "decode release assets", http.StatusOK, errors.New("attachment entry is missing its ID, name, or size"))
|
||||
}
|
||||
assets = append(assets, RemoteAsset{ID: item.ID, Filename: item.Name, Size: item.Size, DownloadURL: item.BrowserDownloadURL})
|
||||
}
|
||||
return assets, nil
|
||||
}
|
||||
|
||||
func (c Client) downloadHash(ctx context.Context, client *http.Client, base *url.URL, token, rawURL string) (string, int64, error) {
|
||||
stream, err := c.openAsset(ctx, client, base, token, rawURL)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
defer stream.Close()
|
||||
hasher := sha256.New()
|
||||
size, err := io.Copy(hasher, stream)
|
||||
if err != nil {
|
||||
return "", size, failure(FailureUnverifiable, "read release asset", 0, err)
|
||||
}
|
||||
return hex.EncodeToString(hasher.Sum(nil)), size, nil
|
||||
}
|
||||
|
||||
func (c Client) openAsset(ctx context.Context, client *http.Client, base *url.URL, token, rawURL string) (io.ReadCloser, error) {
|
||||
if strings.TrimSpace(rawURL) == "" {
|
||||
return "", 0, failure(FailureUnverifiable, "download release asset", 0, errors.New("attachment has no download URL"))
|
||||
return nil, failure(FailureUnverifiable, "download release asset", 0, errors.New("attachment has no download URL"))
|
||||
}
|
||||
assetURL, err := base.Parse(rawURL)
|
||||
if err != nil || assetURL.User != nil {
|
||||
return "", 0, failure(FailureUnverifiable, "download release asset", 0, errors.New("invalid attachment URL"))
|
||||
return nil, failure(FailureUnverifiable, "download release asset", 0, errors.New("invalid attachment URL"))
|
||||
}
|
||||
assetURL.Fragment = ""
|
||||
visited := make(map[string]struct{})
|
||||
for {
|
||||
key := assetURL.String()
|
||||
if _, exists := visited[key]; exists {
|
||||
return "", 0, failure(FailureUnverifiable, "download release asset", 0, errors.New("asset redirect loop"))
|
||||
return nil, failure(FailureUnverifiable, "download release asset", 0, errors.New("asset redirect loop"))
|
||||
}
|
||||
visited[key] = struct{}{}
|
||||
request, err := http.NewRequestWithContext(ctx, http.MethodGet, key, nil)
|
||||
if err != nil {
|
||||
return "", 0, failure(FailureUnverifiable, "download release asset", 0, err)
|
||||
return nil, failure(FailureUnverifiable, "download release asset", 0, err)
|
||||
}
|
||||
if sameOrigin(base, assetURL) {
|
||||
request.Header.Set("Authorization", "token "+token)
|
||||
@@ -144,28 +171,23 @@ func (c Client) downloadHash(ctx context.Context, client *http.Client, base *url
|
||||
request.Header.Set("Accept", "application/octet-stream")
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
return "", 0, failure(FailureUnverifiable, "download release asset", 0, err)
|
||||
return nil, failure(FailureUnverifiable, "download release asset", 0, err)
|
||||
}
|
||||
if isRedirect(response.StatusCode) {
|
||||
location, locationErr := response.Location()
|
||||
_ = response.Body.Close()
|
||||
if locationErr != nil || location.User != nil {
|
||||
return "", 0, failure(FailureUnverifiable, "redirect release asset", response.StatusCode, errors.New("invalid attachment redirect"))
|
||||
return nil, failure(FailureUnverifiable, "redirect release asset", response.StatusCode, errors.New("invalid attachment redirect"))
|
||||
}
|
||||
location.Fragment = ""
|
||||
assetURL = location
|
||||
continue
|
||||
}
|
||||
defer response.Body.Close()
|
||||
if response.StatusCode < 200 || response.StatusCode >= 300 {
|
||||
return "", 0, responseFailure("download release asset", response.StatusCode)
|
||||
_ = response.Body.Close()
|
||||
return nil, responseFailure("download release asset", response.StatusCode)
|
||||
}
|
||||
hasher := sha256.New()
|
||||
size, err := io.Copy(hasher, response.Body)
|
||||
if err != nil {
|
||||
return "", size, failure(FailureUnverifiable, "read release asset", response.StatusCode, err)
|
||||
}
|
||||
return hex.EncodeToString(hasher.Sum(nil)), size, nil
|
||||
return response.Body, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user