2019-01-02 06:33:26 -05:00
|
|
|
// Copyright 2019 The Hugo Authors. All rights reserved.
|
2017-03-03 04:47:43 -05:00
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
2017-03-19 10:25:32 -04:00
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
2017-03-03 04:47:43 -05:00
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
|
|
package hugolib
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2019-12-10 13:56:44 -05:00
|
|
|
"errors"
|
2017-03-16 04:09:26 -04:00
|
|
|
"fmt"
|
2017-03-03 04:47:43 -05:00
|
|
|
"io"
|
2019-03-30 12:08:25 -04:00
|
|
|
"path"
|
2017-03-16 04:09:26 -04:00
|
|
|
"path/filepath"
|
|
|
|
"runtime"
|
|
|
|
"strings"
|
|
|
|
|
2018-10-03 08:58:09 -04:00
|
|
|
"github.com/gohugoio/hugo/common/loggers"
|
|
|
|
|
2018-08-05 05:13:49 -04:00
|
|
|
"github.com/gohugoio/hugo/output"
|
|
|
|
"github.com/gohugoio/hugo/publisher"
|
2019-01-02 06:33:26 -05:00
|
|
|
"github.com/gohugoio/hugo/resources/page"
|
2017-06-13 12:42:45 -04:00
|
|
|
"github.com/gohugoio/hugo/tpl"
|
2017-03-03 04:47:43 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
type aliasHandler struct {
|
2019-12-10 13:56:44 -05:00
|
|
|
t tpl.TemplateHandler
|
2018-10-03 08:58:09 -04:00
|
|
|
log *loggers.Logger
|
2017-03-16 05:04:30 -04:00
|
|
|
allowRoot bool
|
2017-03-03 04:47:43 -05:00
|
|
|
}
|
|
|
|
|
2019-12-10 13:56:44 -05:00
|
|
|
func newAliasHandler(t tpl.TemplateHandler, l *loggers.Logger, allowRoot bool) aliasHandler {
|
2017-03-16 05:04:30 -04:00
|
|
|
return aliasHandler{t, l, allowRoot}
|
2017-03-03 04:47:43 -05:00
|
|
|
}
|
|
|
|
|
2019-01-02 06:33:26 -05:00
|
|
|
type aliasPage struct {
|
|
|
|
Permalink string
|
|
|
|
page.Page
|
|
|
|
}
|
|
|
|
|
2019-12-10 13:56:44 -05:00
|
|
|
func (a aliasHandler) renderAlias(permalink string, p page.Page) (io.Reader, error) {
|
2017-03-03 04:47:43 -05:00
|
|
|
|
Add Hugo Piper with SCSS support and much more
Before this commit, you would have to use page bundles to do image processing etc. in Hugo.
This commit adds
* A new `/assets` top-level project or theme dir (configurable via `assetDir`)
* A new template func, `resources.Get` which can be used to "get a resource" that can be further processed.
This means that you can now do this in your templates (or shortcodes):
```bash
{{ $sunset := (resources.Get "images/sunset.jpg").Fill "300x200" }}
```
This also adds a new `extended` build tag that enables powerful SCSS/SASS support with source maps. To compile this from source, you will also need a C compiler installed:
```
HUGO_BUILD_TAGS=extended mage install
```
Note that you can use output of the SCSS processing later in a non-SCSSS-enabled Hugo.
The `SCSS` processor is a _Resource transformation step_ and it can be chained with the many others in a pipeline:
```bash
{{ $css := resources.Get "styles.scss" | resources.ToCSS | resources.PostCSS | resources.Minify | resources.Fingerprint }}
<link rel="stylesheet" href="{{ $styles.RelPermalink }}" integrity="{{ $styles.Data.Digest }}" media="screen">
```
The transformation funcs above have aliases, so it can be shortened to:
```bash
{{ $css := resources.Get "styles.scss" | toCSS | postCSS | minify | fingerprint }}
<link rel="stylesheet" href="{{ $styles.RelPermalink }}" integrity="{{ $styles.Data.Digest }}" media="screen">
```
A quick tip would be to avoid the fingerprinting part, and possibly also the not-superfast `postCSS` when you're doing development, as it allows Hugo to be smarter about the rebuilding.
Documentation will follow, but have a look at the demo repo in https://github.com/bep/hugo-sass-test
New functions to create `Resource` objects:
* `resources.Get` (see above)
* `resources.FromString`: Create a Resource from a string.
New `Resource` transformation funcs:
* `resources.ToCSS`: Compile `SCSS` or `SASS` into `CSS`.
* `resources.PostCSS`: Process your CSS with PostCSS. Config file support (project or theme or passed as an option).
* `resources.Minify`: Currently supports `css`, `js`, `json`, `html`, `svg`, `xml`.
* `resources.Fingerprint`: Creates a fingerprinted version of the given Resource with Subresource Integrity..
* `resources.Concat`: Concatenates a list of Resource objects. Think of this as a poor man's bundler.
* `resources.ExecuteAsTemplate`: Parses and executes the given Resource and data context (e.g. .Site) as a Go template.
Fixes #4381
Fixes #4903
Fixes #4858
2018-02-20 04:02:14 -05:00
|
|
|
var templ tpl.Template
|
|
|
|
var found bool
|
2017-03-27 14:43:49 -04:00
|
|
|
|
2019-12-10 13:56:44 -05:00
|
|
|
templ, found = a.t.Lookup("alias.html")
|
Add Hugo Piper with SCSS support and much more
Before this commit, you would have to use page bundles to do image processing etc. in Hugo.
This commit adds
* A new `/assets` top-level project or theme dir (configurable via `assetDir`)
* A new template func, `resources.Get` which can be used to "get a resource" that can be further processed.
This means that you can now do this in your templates (or shortcodes):
```bash
{{ $sunset := (resources.Get "images/sunset.jpg").Fill "300x200" }}
```
This also adds a new `extended` build tag that enables powerful SCSS/SASS support with source maps. To compile this from source, you will also need a C compiler installed:
```
HUGO_BUILD_TAGS=extended mage install
```
Note that you can use output of the SCSS processing later in a non-SCSSS-enabled Hugo.
The `SCSS` processor is a _Resource transformation step_ and it can be chained with the many others in a pipeline:
```bash
{{ $css := resources.Get "styles.scss" | resources.ToCSS | resources.PostCSS | resources.Minify | resources.Fingerprint }}
<link rel="stylesheet" href="{{ $styles.RelPermalink }}" integrity="{{ $styles.Data.Digest }}" media="screen">
```
The transformation funcs above have aliases, so it can be shortened to:
```bash
{{ $css := resources.Get "styles.scss" | toCSS | postCSS | minify | fingerprint }}
<link rel="stylesheet" href="{{ $styles.RelPermalink }}" integrity="{{ $styles.Data.Digest }}" media="screen">
```
A quick tip would be to avoid the fingerprinting part, and possibly also the not-superfast `postCSS` when you're doing development, as it allows Hugo to be smarter about the rebuilding.
Documentation will follow, but have a look at the demo repo in https://github.com/bep/hugo-sass-test
New functions to create `Resource` objects:
* `resources.Get` (see above)
* `resources.FromString`: Create a Resource from a string.
New `Resource` transformation funcs:
* `resources.ToCSS`: Compile `SCSS` or `SASS` into `CSS`.
* `resources.PostCSS`: Process your CSS with PostCSS. Config file support (project or theme or passed as an option).
* `resources.Minify`: Currently supports `css`, `js`, `json`, `html`, `svg`, `xml`.
* `resources.Fingerprint`: Creates a fingerprinted version of the given Resource with Subresource Integrity..
* `resources.Concat`: Concatenates a list of Resource objects. Think of this as a poor man's bundler.
* `resources.ExecuteAsTemplate`: Parses and executes the given Resource and data context (e.g. .Site) as a Go template.
Fixes #4381
Fixes #4903
Fixes #4858
2018-02-20 04:02:14 -05:00
|
|
|
if !found {
|
2019-12-10 13:56:44 -05:00
|
|
|
// TODO(bep) consolidate
|
|
|
|
templ, found = a.t.Lookup("_internal/alias.html")
|
|
|
|
if !found {
|
|
|
|
return nil, errors.New("no alias template found")
|
2017-03-27 14:43:49 -04:00
|
|
|
}
|
|
|
|
}
|
2019-12-10 13:56:44 -05:00
|
|
|
|
2019-01-02 06:33:26 -05:00
|
|
|
data := aliasPage{
|
2017-03-03 04:47:43 -05:00
|
|
|
permalink,
|
2019-01-02 06:33:26 -05:00
|
|
|
p,
|
2017-03-03 04:47:43 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
buffer := new(bytes.Buffer)
|
2019-12-10 13:56:44 -05:00
|
|
|
err := a.t.Execute(templ, buffer, data)
|
2017-03-03 04:47:43 -05:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return buffer, nil
|
|
|
|
}
|
2017-03-16 04:09:26 -04:00
|
|
|
|
2019-01-02 06:33:26 -05:00
|
|
|
func (s *Site) writeDestAlias(path, permalink string, outputFormat output.Format, p page.Page) (err error) {
|
2018-08-05 05:13:49 -04:00
|
|
|
return s.publishDestAlias(false, path, permalink, outputFormat, p)
|
2017-03-16 04:09:26 -04:00
|
|
|
}
|
|
|
|
|
2019-01-02 06:33:26 -05:00
|
|
|
func (s *Site) publishDestAlias(allowRoot bool, path, permalink string, outputFormat output.Format, p page.Page) (err error) {
|
2020-01-15 09:59:56 -05:00
|
|
|
handler := newAliasHandler(s.Tmpl(), s.Log, allowRoot)
|
2017-03-16 04:09:26 -04:00
|
|
|
|
|
|
|
s.Log.DEBUG.Println("creating alias:", path, "redirecting to", permalink)
|
|
|
|
|
|
|
|
targetPath, err := handler.targetPathAlias(path)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-12-10 13:56:44 -05:00
|
|
|
aliasContent, err := handler.renderAlias(permalink, p)
|
2017-03-16 04:09:26 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-08-05 05:13:49 -04:00
|
|
|
pd := publisher.Descriptor{
|
|
|
|
Src: aliasContent,
|
|
|
|
TargetPath: targetPath,
|
|
|
|
StatCounter: &s.PathSpec.ProcessingStats.Aliases,
|
|
|
|
OutputFormat: outputFormat,
|
|
|
|
}
|
|
|
|
|
2020-06-14 05:14:56 -04:00
|
|
|
if s.Info.relativeURLs || s.Info.canonifyURLs {
|
|
|
|
pd.AbsURLPath = s.absURLPath(targetPath)
|
|
|
|
}
|
2017-03-16 04:09:26 -04:00
|
|
|
|
2020-06-14 05:14:56 -04:00
|
|
|
return s.publisher.Publish(pd)
|
2017-03-16 04:09:26 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func (a aliasHandler) targetPathAlias(src string) (string, error) {
|
|
|
|
originalAlias := src
|
|
|
|
if len(src) <= 0 {
|
2019-01-02 06:33:26 -05:00
|
|
|
return "", fmt.Errorf("alias \"\" is an empty string")
|
2017-03-16 04:09:26 -04:00
|
|
|
}
|
|
|
|
|
2019-03-30 12:08:25 -04:00
|
|
|
alias := path.Clean(filepath.ToSlash(src))
|
2017-03-16 04:09:26 -04:00
|
|
|
|
2019-03-30 12:08:25 -04:00
|
|
|
if !a.allowRoot && alias == "/" {
|
2019-01-02 06:33:26 -05:00
|
|
|
return "", fmt.Errorf("alias \"%s\" resolves to website root directory", originalAlias)
|
2017-03-16 04:09:26 -04:00
|
|
|
}
|
|
|
|
|
2019-03-30 12:08:25 -04:00
|
|
|
components := strings.Split(alias, "/")
|
|
|
|
|
2017-03-16 04:09:26 -04:00
|
|
|
// Validate against directory traversal
|
|
|
|
if components[0] == ".." {
|
2019-01-02 06:33:26 -05:00
|
|
|
return "", fmt.Errorf("alias \"%s\" traverses outside the website root directory", originalAlias)
|
2017-03-16 04:09:26 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Handle Windows file and directory naming restrictions
|
|
|
|
// See "Naming Files, Paths, and Namespaces" on MSDN
|
|
|
|
// https://msdn.microsoft.com/en-us/library/aa365247%28v=VS.85%29.aspx?f=255&MSPPError=-2147217396
|
|
|
|
msgs := []string{}
|
|
|
|
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"}
|
|
|
|
|
|
|
|
if strings.ContainsAny(alias, ":*?\"<>|") {
|
|
|
|
msgs = append(msgs, fmt.Sprintf("Alias \"%s\" contains invalid characters on Windows: : * ? \" < > |", originalAlias))
|
|
|
|
}
|
|
|
|
for _, ch := range alias {
|
|
|
|
if ch < ' ' {
|
|
|
|
msgs = append(msgs, fmt.Sprintf("Alias \"%s\" contains ASCII control code (0x00 to 0x1F), invalid on Windows: : * ? \" < > |", originalAlias))
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for _, comp := range components {
|
|
|
|
if strings.HasSuffix(comp, " ") || strings.HasSuffix(comp, ".") {
|
|
|
|
msgs = append(msgs, fmt.Sprintf("Alias \"%s\" contains component with a trailing space or period, problematic on Windows", originalAlias))
|
|
|
|
}
|
|
|
|
for _, r := range reservedNames {
|
|
|
|
if comp == r {
|
|
|
|
msgs = append(msgs, fmt.Sprintf("Alias \"%s\" contains component with reserved name \"%s\" on Windows", originalAlias, r))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(msgs) > 0 {
|
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
for _, m := range msgs {
|
|
|
|
a.log.ERROR.Println(m)
|
|
|
|
}
|
2019-01-02 06:33:26 -05:00
|
|
|
return "", fmt.Errorf("cannot create \"%s\": Windows filename restriction", originalAlias)
|
2017-03-16 04:09:26 -04:00
|
|
|
}
|
|
|
|
for _, m := range msgs {
|
Make WARN the new default log log level
This commit also pulls down the log level for a set of WARN statements to INFO. There should be no ERRORs or WARNINGs in a regular Hugo build. That is the story about the Boy Who Cried Wolf.
Since the WARN log is now more visible, this commit also improves on some of them, most notable the "layout not found", which now would look something like this:
```bash
WARN 2018/11/02 09:02:18 Found no layout for "home", language "en", output format "CSS": create a template below /layouts with one of these filenames: index.en.css.css, home.en.css.css, list.en.css.css, index.css.css, home.css.css, list.css.css, index.en.css, home.en.css, list.en.css, index.css, home.css, list.css, _default/index.en.css.css, _default/home.en.css.css, _default/list.en.css.css, _default/index.css.css, _default/home.css.css, _default/list.css.css, _default/index.en.css, _default/home.en.css, _default/list.en.css, _default/index.css, _default/home.css, _default/list.css
```
Fixes #5203
2018-11-01 17:27:42 -04:00
|
|
|
a.log.INFO.Println(m)
|
2017-03-16 04:09:26 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add the final touch
|
2019-03-30 12:08:25 -04:00
|
|
|
alias = strings.TrimPrefix(alias, "/")
|
|
|
|
if strings.HasSuffix(alias, "/") {
|
2017-03-16 04:09:26 -04:00
|
|
|
alias = alias + "index.html"
|
|
|
|
} else if !strings.HasSuffix(alias, ".html") {
|
2019-03-30 12:08:25 -04:00
|
|
|
alias = alias + "/" + "index.html"
|
2017-03-16 04:09:26 -04:00
|
|
|
}
|
|
|
|
|
2019-03-30 12:08:25 -04:00
|
|
|
return filepath.FromSlash(alias), nil
|
2017-03-16 04:09:26 -04:00
|
|
|
}
|