hugo

Fork of github.com/gohugoio/hugo with reverse pagination support

git clone git://git.shimmy1996.com/hugo.git

alias.go (4972B)

    1 // Copyright 2019 The Hugo Authors. All rights reserved.
    2 //
    3 // Licensed under the Apache License, Version 2.0 (the "License");
    4 // you may not use this file except in compliance with the License.
    5 // You may obtain a copy of the License at
    6 // http://www.apache.org/licenses/LICENSE-2.0
    7 //
    8 // Unless required by applicable law or agreed to in writing, software
    9 // distributed under the License is distributed on an "AS IS" BASIS,
   10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   11 // See the License for the specific language governing permissions and
   12 // limitations under the License.
   13 
   14 package hugolib
   15 
   16 import (
   17 	"bytes"
   18 	"errors"
   19 	"fmt"
   20 	"io"
   21 	"path"
   22 	"path/filepath"
   23 	"runtime"
   24 	"strings"
   25 
   26 	"github.com/gohugoio/hugo/common/loggers"
   27 
   28 	"github.com/gohugoio/hugo/output"
   29 	"github.com/gohugoio/hugo/publisher"
   30 	"github.com/gohugoio/hugo/resources/page"
   31 	"github.com/gohugoio/hugo/tpl"
   32 )
   33 
   34 type aliasHandler struct {
   35 	t         tpl.TemplateHandler
   36 	log       loggers.Logger
   37 	allowRoot bool
   38 }
   39 
   40 func newAliasHandler(t tpl.TemplateHandler, l loggers.Logger, allowRoot bool) aliasHandler {
   41 	return aliasHandler{t, l, allowRoot}
   42 }
   43 
   44 type aliasPage struct {
   45 	Permalink string
   46 	page.Page
   47 }
   48 
   49 func (a aliasHandler) renderAlias(permalink string, p page.Page) (io.Reader, error) {
   50 	var templ tpl.Template
   51 	var found bool
   52 
   53 	templ, found = a.t.Lookup("alias.html")
   54 	if !found {
   55 		// TODO(bep) consolidate
   56 		templ, found = a.t.Lookup("_internal/alias.html")
   57 		if !found {
   58 			return nil, errors.New("no alias template found")
   59 		}
   60 	}
   61 
   62 	data := aliasPage{
   63 		permalink,
   64 		p,
   65 	}
   66 
   67 	buffer := new(bytes.Buffer)
   68 	err := a.t.Execute(templ, buffer, data)
   69 	if err != nil {
   70 		return nil, err
   71 	}
   72 	return buffer, nil
   73 }
   74 
   75 func (s *Site) writeDestAlias(path, permalink string, outputFormat output.Format, p page.Page) (err error) {
   76 	return s.publishDestAlias(false, path, permalink, outputFormat, p)
   77 }
   78 
   79 func (s *Site) publishDestAlias(allowRoot bool, path, permalink string, outputFormat output.Format, p page.Page) (err error) {
   80 	handler := newAliasHandler(s.Tmpl(), s.Log, allowRoot)
   81 
   82 	s.Log.Debugln("creating alias:", path, "redirecting to", permalink)
   83 
   84 	targetPath, err := handler.targetPathAlias(path)
   85 	if err != nil {
   86 		return err
   87 	}
   88 
   89 	aliasContent, err := handler.renderAlias(permalink, p)
   90 	if err != nil {
   91 		return err
   92 	}
   93 
   94 	pd := publisher.Descriptor{
   95 		Src:          aliasContent,
   96 		TargetPath:   targetPath,
   97 		StatCounter:  &s.PathSpec.ProcessingStats.Aliases,
   98 		OutputFormat: outputFormat,
   99 	}
  100 
  101 	if s.Info.relativeURLs || s.Info.canonifyURLs {
  102 		pd.AbsURLPath = s.absURLPath(targetPath)
  103 	}
  104 
  105 	return s.publisher.Publish(pd)
  106 }
  107 
  108 func (a aliasHandler) targetPathAlias(src string) (string, error) {
  109 	originalAlias := src
  110 	if len(src) <= 0 {
  111 		return "", fmt.Errorf("alias \"\" is an empty string")
  112 	}
  113 
  114 	alias := path.Clean(filepath.ToSlash(src))
  115 
  116 	if !a.allowRoot && alias == "/" {
  117 		return "", fmt.Errorf("alias \"%s\" resolves to website root directory", originalAlias)
  118 	}
  119 
  120 	components := strings.Split(alias, "/")
  121 
  122 	// Validate against directory traversal
  123 	if components[0] == ".." {
  124 		return "", fmt.Errorf("alias \"%s\" traverses outside the website root directory", originalAlias)
  125 	}
  126 
  127 	// Handle Windows file and directory naming restrictions
  128 	// See "Naming Files, Paths, and Namespaces" on MSDN
  129 	// https://msdn.microsoft.com/en-us/library/aa365247%28v=VS.85%29.aspx?f=255&MSPPError=-2147217396
  130 	msgs := []string{}
  131 	reservedNames := []string{"CON", "PRN", "AUX", "NUL", "COM0", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT0", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"}
  132 
  133 	if strings.ContainsAny(alias, ":*?\"<>|") {
  134 		msgs = append(msgs, fmt.Sprintf("Alias \"%s\" contains invalid characters on Windows: : * ? \" < > |", originalAlias))
  135 	}
  136 	for _, ch := range alias {
  137 		if ch < ' ' {
  138 			msgs = append(msgs, fmt.Sprintf("Alias \"%s\" contains ASCII control code (0x00 to 0x1F), invalid on Windows: : * ? \" < > |", originalAlias))
  139 			continue
  140 		}
  141 	}
  142 	for _, comp := range components {
  143 		if strings.HasSuffix(comp, " ") || strings.HasSuffix(comp, ".") {
  144 			msgs = append(msgs, fmt.Sprintf("Alias \"%s\" contains component with a trailing space or period, problematic on Windows", originalAlias))
  145 		}
  146 		for _, r := range reservedNames {
  147 			if comp == r {
  148 				msgs = append(msgs, fmt.Sprintf("Alias \"%s\" contains component with reserved name \"%s\" on Windows", originalAlias, r))
  149 			}
  150 		}
  151 	}
  152 	if len(msgs) > 0 {
  153 		if runtime.GOOS == "windows" {
  154 			for _, m := range msgs {
  155 				a.log.Errorln(m)
  156 			}
  157 			return "", fmt.Errorf("cannot create \"%s\": Windows filename restriction", originalAlias)
  158 		}
  159 		for _, m := range msgs {
  160 			a.log.Infoln(m)
  161 		}
  162 	}
  163 
  164 	// Add the final touch
  165 	alias = strings.TrimPrefix(alias, "/")
  166 	if strings.HasSuffix(alias, "/") {
  167 		alias = alias + "index.html"
  168 	} else if !strings.HasSuffix(alias, ".html") {
  169 		alias = alias + "/" + "index.html"
  170 	}
  171 
  172 	return filepath.FromSlash(alias), nil
  173 }