blob: 5cb5f8b269b9932e0237e7e10b2a06f54beb25a0 [file] [log] [blame]
// 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"
"fmt"
"html/template"
"net/http"
"strings"
"golang.org/x/discovery/internal"
"golang.org/x/discovery/internal/derrors"
"golang.org/x/discovery/internal/log"
)
// handleModuleDetails handles requests for non-stdlib module details pages. It
// expects paths of the form "/mod/<module-path>[@<version>?tab=<tab>]".
// stdlib module pages are handled at "/std".
func (s *Server) handleModuleDetails(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/mod/std" {
http.Redirect(w, r, "/std", http.StatusMovedPermanently)
return
}
urlPath := strings.TrimPrefix(r.URL.Path, "/mod")
path, _, version, err := parseDetailsURLPath(urlPath)
if err != nil {
log.Infof(r.Context(), "handleModuleDetails: %v", err)
s.serveErrorPage(w, r, http.StatusBadRequest, nil)
return
}
s.serveModulePage(w, r, path, version)
}
// serveModulePage serves details pages for the module specified by modulePath
// and version.
func (s *Server) serveModulePage(w http.ResponseWriter, r *http.Request, modulePath, version string) {
ctx := r.Context()
if code, epage := checkPathAndVersion(ctx, s.ds, modulePath, version); code != http.StatusOK {
s.serveErrorPage(w, r, code, epage)
return
}
// This function handles top level behavior related to the existence of the
// requested modulePath@version:
// TODO: fix
// 1. If the module version exists, serve it.
// 2. else if we got any unexpected error, serve a server error
// 3. else if the error is NotFound, serve the directory page
// 3. else, we didn't find the module so there are two cases:
// a. We don't know anything about this module: just serve a 404
// b. We have valid versions for this module path, but `version` isn't
// one of them. Serve a 404 but recommend the other versions.
mi, err := s.ds.GetModuleInfo(ctx, modulePath, version)
if err == nil {
s.serveModulePageWithModule(ctx, w, r, mi, version)
return
}
if !errors.Is(err, derrors.NotFound) {
s.serveErrorPage(w, r, http.StatusInternalServerError, nil)
return
}
if version != internal.LatestVersion {
if _, err := s.ds.GetModuleInfo(ctx, modulePath, internal.LatestVersion); err != nil {
log.Errorf(ctx, "error checking for latest module: %v", err)
} else {
epage := &errorPage{
Message: fmt.Sprintf("Module %s@%s is not available.", modulePath, displayVersion(version, modulePath)),
SecondaryMessage: template.HTML(
fmt.Sprintf(`There are other versions of this module that are! To view them, `+
`<a href="/mod/%s?tab=versions">click here</a>.</p>`,
modulePath)),
}
s.serveErrorPage(w, r, http.StatusNotFound, epage)
return
}
}
s.servePathNotFoundErrorPage(w, r, "module")
}
func (s *Server) serveModulePageWithModule(ctx context.Context, w http.ResponseWriter, r *http.Request, mi *internal.ModuleInfo, requestedVersion string) {
licenses, err := s.ds.GetModuleLicenses(ctx, mi.ModulePath, mi.Version)
if err != nil {
log.Errorf(ctx, "error getting module licenses: %v", err)
s.serveErrorPage(w, r, http.StatusInternalServerError, nil)
return
}
modHeader := createModule(mi, licensesToMetadatas(licenses), requestedVersion == internal.LatestVersion)
tab := r.FormValue("tab")
settings, ok := moduleTabLookup[tab]
if !ok {
tab = "overview"
settings = moduleTabLookup["overview"]
}
canShowDetails := modHeader.IsRedistributable || settings.AlwaysShowDetails
var details interface{}
if canShowDetails {
var err error
details, err = fetchDetailsForModule(ctx, r, tab, s.ds, mi, licenses)
if err != nil {
log.Errorf(ctx, "error fetching page for %q: %v", tab, err)
s.serveErrorPage(w, r, http.StatusInternalServerError, nil)
return
}
}
page := &DetailsPage{
basePage: newBasePage(r, moduleHTMLTitle(mi.ModulePath)),
Title: moduleTitle(mi.ModulePath),
Settings: settings,
Header: modHeader,
BreadcrumbPath: breadcrumbPath(modHeader.ModulePath, modHeader.ModulePath, modHeader.LinkVersion),
Details: details,
CanShowDetails: canShowDetails,
Tabs: moduleTabSettings,
PageType: "mod",
}
s.servePage(ctx, w, settings.TemplateName, page)
}