mirror of
https://github.com/go-task/task.git
synced 2026-09-02 04:02:08 +02:00
RemoteExists treated every non-200 as an absent file, so a server refusing the credentials ended up as "No Taskfile found", sending the user to check the URL rather than the token. A 401 now stops the search and reports the status code; the default names need the same credentials, so trying them would only add rejected requests. A 403 is left alone: it is also what a server without directory listing answers for a readable directory. That message being correct, the expansion no longer needs to refuse an undefined variable: os.ExpandEnv is inlined and expandEnv is gone. The `$$` escape goes with it, so a literal value can no longer hold a `$` followed by a name; a secret carried in an environment variable is unaffected, as os.Expand never rescans what it substituted. Header names are validated with httpguts.ValidHeaderFieldName, the table net/http itself uses, rather than a denylist that let X-Foo(bar) through. golang.org/x/net was already in the module graph, so tidy only moves it to the direct block. Finally, node_http_auth.go becomes http_auth.go: the node_ prefix is for files defining a Node type, and this one holds the auth concern of HTTPNode plus hostMatches, which reader.go uses for trusted hosts.
102 lines
3.0 KiB
Go
102 lines
3.0 KiB
Go
package taskfile
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"slices"
|
|
"strings"
|
|
|
|
"github.com/go-task/task/v3/errors"
|
|
)
|
|
|
|
var (
|
|
// DefaultTaskfiles is the list of Taskfile file names supported by default.
|
|
DefaultTaskfiles = []string{
|
|
"Taskfile.yml",
|
|
"taskfile.yml",
|
|
"Taskfile.yaml",
|
|
"taskfile.yaml",
|
|
"Taskfile.dist.yml",
|
|
"taskfile.dist.yml",
|
|
"Taskfile.dist.yaml",
|
|
"taskfile.dist.yaml",
|
|
}
|
|
allowedContentTypes = []string{
|
|
"text/plain",
|
|
"text/yaml",
|
|
"text/x-yaml",
|
|
"application/yaml",
|
|
"application/x-yaml",
|
|
"application/octet-stream",
|
|
}
|
|
)
|
|
|
|
// RemoteExists will check if a file at the given URL Exists. If it does, it
|
|
// will return its URL. If it does not, it will search the search for any files
|
|
// at the given URL with any of the default Taskfile files names. If any of
|
|
// these match a file, the first matching path will be returned. If no files are
|
|
// found, an error will be returned.
|
|
func RemoteExists(ctx context.Context, u url.URL, client *http.Client) (*url.URL, error) {
|
|
// Create a new HEAD request for the given URL to check if the resource exists
|
|
req, err := http.NewRequestWithContext(ctx, "HEAD", u.String(), nil)
|
|
if err != nil {
|
|
return nil, errors.TaskfileFetchFailedError{URI: u.Redacted()}
|
|
}
|
|
|
|
// Request the given URL
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
if ctx.Err() != nil {
|
|
return nil, fmt.Errorf("checking remote file: %w", ctx.Err())
|
|
}
|
|
return nil, errors.TaskfileFetchFailedError{URI: u.Redacted()}
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
// If the request was successful and the content type is allowed, return the
|
|
// URL The content type check is to avoid downloading files that are not
|
|
// Taskfiles It means we can try other files instead of downloading
|
|
// something that is definitely not a Taskfile
|
|
contentType := resp.Header.Get("Content-Type")
|
|
if resp.StatusCode == http.StatusOK && slices.ContainsFunc(allowedContentTypes, func(s string) bool {
|
|
return strings.Contains(contentType, s)
|
|
}) {
|
|
return &u, nil
|
|
}
|
|
|
|
// The default names need the same credentials, so trying them would only
|
|
// add rejected requests. A 403 is left alone: it is also what a server
|
|
// without directory listing answers for a readable directory.
|
|
if resp.StatusCode == http.StatusUnauthorized {
|
|
return nil, errors.TaskfileFetchFailedError{URI: u.Redacted(), HTTPStatusCode: resp.StatusCode}
|
|
}
|
|
|
|
// If the request was not successful, append the default Taskfile names to
|
|
// the URL and return the URL of the first successful request
|
|
for _, taskfile := range DefaultTaskfiles {
|
|
// Fixes a bug with JoinPath where a leading slash is not added to the
|
|
// path if it is empty
|
|
if u.Path == "" {
|
|
u.Path = "/"
|
|
}
|
|
alt := u.JoinPath(taskfile)
|
|
req.URL = alt
|
|
|
|
// Try the alternative URL
|
|
resp, err = client.Do(req)
|
|
if err != nil {
|
|
return nil, errors.TaskfileFetchFailedError{URI: u.Redacted()}
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
// If the request was successful, return the URL
|
|
if resp.StatusCode == http.StatusOK {
|
|
return alt, nil
|
|
}
|
|
}
|
|
|
|
return nil, errors.TaskfileNotFoundError{URI: u.Redacted(), Walk: false}
|
|
}
|