blob: 232069f456c09d74d3c2d1e87ad155775cc08e3c [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"
"fmt"
"net/http"
"strings"
"golang.org/x/pkgsite/internal"
"golang.org/x/pkgsite/internal/derrors"
"golang.org/x/pkgsite/internal/postgres"
"golang.org/x/pkgsite/internal/stdlib"
)
// handlePackageDetailsRedirect redirects all redirects to "/pkg" to "/".
func (s *Server) handlePackageDetailsRedirect(w http.ResponseWriter, r *http.Request) {
urlPath := strings.TrimPrefix(r.URL.Path, "/pkg")
http.Redirect(w, r, urlPath, http.StatusMovedPermanently)
}
// stdlibPathForShortcut returns a path in the stdlib that shortcut should redirect to,
// or the empty string if there is no such path.
func stdlibPathForShortcut(ctx context.Context, ds internal.DataSource, shortcut string) (path string, err error) {
defer derrors.Wrap(&err, "stdlibPathForShortcut(ctx, %q)", shortcut)
if !stdlib.Contains(shortcut) {
return "", nil
}
db, ok := ds.(*postgres.DB)
if !ok {
return "", proxydatasourceNotSupportedErr()
}
matches, err := db.GetStdlibPathsWithSuffix(ctx, shortcut)
if err != nil {
return "", err
}
if len(matches) == 1 {
return matches[0], nil
}
// No matches, or ambiguous.
return "", nil
}
// servePackagePage serves a package details page.
func (s *Server) servePackagePage(ctx context.Context,
w http.ResponseWriter, r *http.Request, ds internal.DataSource, um *internal.UnitMeta, requestedVersion string) error {
mi := &internal.ModuleInfo{
ModulePath: um.ModulePath,
Version: um.Version,
CommitTime: um.CommitTime,
IsRedistributable: um.IsRedistributable,
}
pkgHeader, err := createPackage(&internal.PackageMeta{
Path: um.Path,
Licenses: um.Licenses,
IsRedistributable: um.IsRedistributable,
Name: um.Name,
}, mi, requestedVersion == internal.LatestVersion)
if err != nil {
return fmt.Errorf("creating package header for %s@%s: %v", um.Path, um.Version, err)
}
settings, err := packageSettings(r.FormValue("tab"))
if err != nil {
http.Redirect(w, r, r.URL.Path, http.StatusFound)
return nil
}
canShowDetails := um.IsRedistributable || settings.AlwaysShowDetails
var details interface{}
if canShowDetails {
var err error
details, err = fetchDetailsForPackage(r, settings.Name, ds, um)
if err != nil {
return fmt.Errorf("fetching page for %q: %v", settings.Name, err)
}
}
var (
pageType = pageTypePackage
pageName = um.Name
)
if pageName == "main" {
pageName = effectiveName(um.Path, um.Name)
pageType = pageTypeCommand
}
page := &DetailsPage{
basePage: s.newBasePage(r, packageHTMLTitle(um.Path, um.Name)),
Name: pageName,
Settings: *settings,
Header: pkgHeader,
Breadcrumb: breadcrumbPath(pkgHeader.Path, pkgHeader.Module.ModulePath,
pkgHeader.Module.LinkVersion),
Details: details,
CanShowDetails: canShowDetails,
Tabs: packageTabSettings,
PageType: pageType,
CanonicalURLPath: constructPackageURL(
pkgHeader.Path,
pkgHeader.Module.ModulePath,
pkgHeader.Module.LinkVersion),
}
page.basePage.AllowWideContent = settings.Name == tabDoc
s.servePage(ctx, w, settings.TemplateName, page)
return nil
}
// packageSettings returns the TabSettings corresponding to tab.
// If tab is not a valid tab from packageTabLookup or tab=doc, an error will be
// returned and the user will be redirected to /<path> outside of this
// function. If tab is the empty string, the user will be shown the
// documentation page.
func packageSettings(tab string) (*TabSettings, error) {
if tab == tabDoc {
// Redirect "/<path>?tab=doc" to "/<path>".
return nil, derrors.NotFound
}
if tab == "" {
tab = tabDoc
}
settings, ok := packageTabLookup[tab]
if !ok {
return nil, derrors.NotFound
}
return &settings, nil
}