blob: 0323f040728491737a8a677dd370008ec7684ce5 [file] [log] [blame] [edit]
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package frontend
import (
"context"
"errors"
"net/http"
"strings"
"golang.org/x/pkgsite/internal/frontend/page"
"golang.org/x/pkgsite/internal/frontend/serrors"
"golang.org/x/pkgsite/internal/frontend/urlinfo"
mstats "golang.org/x/pkgsite/internal/middleware/stats"
"golang.org/x/pkgsite/internal"
"golang.org/x/pkgsite/internal/stdlib"
)
// serveDetails handles requests for package/directory/module details pages. It
// expects paths of the form "/<module-path>[@<version>?tab=<tab>]".
// stdlib module pages are handled at "/std", and requests to "/mod/std" will
// be redirected to that path.
func (s *Server) serveDetails(w http.ResponseWriter, r *http.Request, ds internal.DataSource) (err error) {
defer mstats.Elapsed(r.Context(), "serveDetails")()
ctx := r.Context()
if r.Method != http.MethodGet && r.Method != http.MethodHead {
return &serrors.ServerError{Status: http.StatusMethodNotAllowed}
}
if r.URL.Path == "/" {
s.serveHomepage(ctx, w, r)
return nil
}
if strings.HasSuffix(r.URL.Path, "/") {
url := *r.URL
url.Path = strings.TrimSuffix(r.URL.Path, "/")
http.Redirect(w, r, url.String(), http.StatusMovedPermanently)
return
}
// If page statistics are enabled, use the "exp" query param to adjust
// the active experiments.
if s.serveStats {
ctx = setExperimentsFromQueryParam(ctx, r)
}
urlInfo, err := urlinfo.ExtractURLPathInfo(r.URL.Path)
if err != nil {
var epage *page.ErrorPage
if uerr := new(urlinfo.UserError); errors.As(err, &uerr) {
epage = &page.ErrorPage{MessageData: uerr.UserMessage}
}
return &serrors.ServerError{
Status: http.StatusBadRequest,
Err: err,
Epage: epage,
}
}
if !urlinfo.IsSupportedVersion(urlInfo.FullPath, urlInfo.RequestedVersion) {
return serrors.InvalidVersionError(urlInfo.FullPath, urlInfo.RequestedVersion)
}
if urlPath := stdlibRedirectURL(urlInfo.FullPath); urlPath != "" {
http.Redirect(w, r, urlPath, http.StatusMovedPermanently)
return
}
if err := checkExcluded(ctx, ds, urlInfo.FullPath, urlInfo.RequestedVersion); err != nil {
return err
}
return s.serveUnitPage(ctx, w, r, ds, urlInfo)
}
func stdlibRedirectURL(fullPath string) string {
if !strings.HasPrefix(fullPath, stdlib.GitHubRepo) {
return ""
}
if fullPath == stdlib.GitHubRepo || fullPath == stdlib.GitHubRepo+"/src" {
return "/std"
}
urlPath2 := strings.TrimPrefix(strings.TrimPrefix(fullPath, stdlib.GitHubRepo+"/"), "src/")
if fullPath == urlPath2 {
return ""
}
return "/" + urlPath2
}
func checkExcluded(ctx context.Context, ds internal.DataSource, fullPath, version string) error {
db, ok := ds.(internal.PostgresDB)
if !ok {
return nil
}
if db.IsExcluded(ctx, fullPath, version) {
// Return NotFound; don't let the user know that the package was excluded.
return &serrors.ServerError{Status: http.StatusNotFound}
}
return nil
}