| // 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 ( |
| "errors" |
| "fmt" |
| "net/http" |
| "strings" |
| |
| "github.com/google/safehtml/template" |
| "golang.org/x/pkgsite/internal" |
| "golang.org/x/pkgsite/internal/derrors" |
| "golang.org/x/pkgsite/internal/log" |
| ) |
| |
| // legacyServePackagePage serves details pages for the package with import path |
| // pkgPath, in the module specified by modulePath and version. |
| func (s *Server) legacyServePackagePage(w http.ResponseWriter, r *http.Request, ds internal.DataSource, pkgPath, modulePath, requestedVersion, resolvedVersion string) (err error) { |
| ctx := r.Context() |
| |
| // This function handles top level behavior related to the existence of the |
| // requested pkgPath@version. |
| // 1. If a package exists at this version, serve it. |
| // 2. If there is a directory at this version, serve it. |
| // 3. If there is another version that contains this package path: serve a |
| // 404 and suggest these versions. |
| // 4. Just serve a 404 |
| pkg, err := ds.LegacyGetPackage(ctx, pkgPath, modulePath, resolvedVersion) |
| if err == nil { |
| return s.legacyServePackagePageWithPackage(w, r, ds, pkg, requestedVersion) |
| } |
| if !errors.Is(err, derrors.NotFound) { |
| return err |
| } |
| if requestedVersion == internal.LatestVersion { |
| // If we've already checked the latest version, then we know that this path |
| // is not a package at any version, so just skip ahead and serve the |
| // directory page. |
| dbDir, err := ds.LegacyGetDirectory(ctx, pkgPath, modulePath, resolvedVersion, internal.AllFields) |
| if err != nil { |
| if errors.Is(err, derrors.NotFound) { |
| return pathNotFoundError(pkgPath, requestedVersion) |
| } |
| return err |
| } |
| return s.legacyServeDirectoryPage(ctx, w, r, ds, dbDir, requestedVersion) |
| } |
| dir, err := ds.LegacyGetDirectory(ctx, pkgPath, modulePath, resolvedVersion, internal.AllFields) |
| if err == nil { |
| return s.legacyServeDirectoryPage(ctx, w, r, ds, dir, requestedVersion) |
| } |
| if !errors.Is(err, derrors.NotFound) { |
| // The only error we expect is NotFound, so serve an 500 here, otherwise |
| // whatever response we resolve below might be inconsistent or misleading. |
| return fmt.Errorf("checking for directory: %v", err) |
| } |
| _, err = ds.LegacyGetPackage(ctx, pkgPath, modulePath, internal.LatestVersion) |
| if err == nil { |
| return legacyPathFoundAtLatestError("package", pkgPath, requestedVersion) |
| } |
| if !errors.Is(err, derrors.NotFound) { |
| // Unlike the error handling for LegacyGetDirectory above, we don't serve an |
| // InternalServerError here. The reasoning for this is that regardless of |
| // the result of LegacyGetPackage(..., "latest"), we're going to serve a NotFound |
| // response code. So the semantics of the endpoint are the same whether or |
| // not we get an unexpected error from GetPackage -- we just don't serve a |
| // more informative error response. |
| log.Errorf(ctx, "error checking for latest package: %v", err) |
| return nil |
| } |
| return pathNotFoundError(pkgPath, requestedVersion) |
| } |
| |
| func (s *Server) legacyServePackagePageWithPackage(w http.ResponseWriter, r *http.Request, ds internal.DataSource, pkg *internal.LegacyVersionedPackage, requestedVersion string) (err error) { |
| defer func() { |
| if _, ok := err.(*serverError); !ok { |
| derrors.Wrap(&err, "legacyServePackagePageWithPackage(w, r, %q, %q, %q)", pkg.Path, pkg.ModulePath, requestedVersion) |
| } |
| }() |
| pkgHeader, err := createPackage( |
| packageMetaFromLegacyPackage(&pkg.LegacyPackage), |
| &pkg.ModuleInfo, |
| requestedVersion == internal.LatestVersion) |
| if err != nil { |
| return fmt.Errorf("creating package header for %s@%s: %v", pkg.Path, pkg.Version, err) |
| } |
| |
| settings, err := packageSettings(r.FormValue("tab")) |
| if err != nil { |
| http.Redirect(w, r, r.URL.Path, http.StatusFound) |
| return nil |
| } |
| canShowDetails := pkg.LegacyPackage.IsRedistributable || settings.AlwaysShowDetails |
| |
| var details interface{} |
| if canShowDetails { |
| var err error |
| details, err = legacyFetchDetailsForPackage(r, settings.Name, ds, pkg) |
| if err != nil { |
| return fmt.Errorf("fetching page for %q: %v", settings.Name, err) |
| } |
| } |
| |
| var ( |
| pageType = pageTypePackage |
| pageName = pkg.Name |
| ) |
| if pkg.Name == "main" { |
| pageName = effectiveName(pkg.Path, pkg.Name) |
| pageType = pageTypeCommand |
| } |
| page := &DetailsPage{ |
| basePage: s.newBasePage(r, packageHTMLTitle(pkg.Path, pkg.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( |
| pkg.Path, |
| pkg.ModulePath, |
| linkVersion(pkg.Version, pkg.ModulePath), |
| ), |
| } |
| page.basePage.AllowWideContent = settings.Name == tabDoc |
| s.servePage(r.Context(), w, settings.TemplateName, page) |
| return nil |
| } |
| |
| // packageMetaFromLegacyPackage returns a PackageMeta based on data from a |
| // LegacyPackage. |
| func packageMetaFromLegacyPackage(pkg *internal.LegacyPackage) *internal.PackageMeta { |
| return &internal.PackageMeta{ |
| Path: pkg.Path, |
| IsRedistributable: pkg.IsRedistributable, |
| Name: pkg.Name, |
| Synopsis: pkg.Synopsis, |
| Licenses: pkg.Licenses, |
| } |
| } |
| |
| // legacyPathFoundAtLatestError returns an error page when the fullPath exists, but |
| // the version that is requested does not. |
| func legacyPathFoundAtLatestError(pathType, fullPath, requestedVersion string) error { |
| return &serverError{ |
| status: http.StatusNotFound, |
| epage: &errorPage{ |
| messageTemplate: template.MakeTrustedTemplate(` |
| <h3 class="Error-message">{{.TType}} {{.Path}}@{{.Version}} is not available.</h3> |
| <p class="Error-message"> |
| There are other versions of this {{.Type}} that are! To view them, |
| <a href="/{{.Path}}?tab=versions">click here</a>. |
| </p>`), |
| MessageData: struct{ TType, Type, Path, Version string }{ |
| strings.Title(pathType), pathType, fullPath, displayVersion(requestedVersion, fullPath)}, |
| }, |
| } |
| } |