diff options
Diffstat (limited to 'hugolib')
-rw-r--r-- | hugolib/config.go | 9 | ||||
-rw-r--r-- | hugolib/js_test.go | 10 | ||||
-rw-r--r-- | hugolib/page_test.go | 73 | ||||
-rw-r--r-- | hugolib/resource_chain_babel_test.go | 14 | ||||
-rw-r--r-- | hugolib/resource_chain_test.go | 8 | ||||
-rw-r--r-- | hugolib/securitypolicies_test.go | 202 | ||||
-rw-r--r-- | hugolib/shortcode.go | 4 | ||||
-rw-r--r-- | hugolib/shortcode_test.go | 6 | ||||
-rw-r--r-- | hugolib/site.go | 42 | ||||
-rw-r--r-- | hugolib/testdata/cities.csv | 130 | ||||
-rw-r--r-- | hugolib/testdata/fruits.json | 5 | ||||
-rw-r--r-- | hugolib/testhelpers_test.go | 12 |
12 files changed, 441 insertions, 74 deletions
diff --git a/hugolib/config.go b/hugolib/config.go index 3b5ade598..e79899b94 100644 --- a/hugolib/config.go +++ b/hugolib/config.go @@ -18,6 +18,7 @@ import ( "path/filepath" "strings" + "github.com/gohugoio/hugo/common/hexec" "github.com/gohugoio/hugo/common/types" "github.com/gohugoio/hugo/common/maps" @@ -41,6 +42,7 @@ import ( "github.com/gohugoio/hugo/config" "github.com/gohugoio/hugo/config/privacy" + "github.com/gohugoio/hugo/config/security" "github.com/gohugoio/hugo/config/services" "github.com/gohugoio/hugo/helpers" "github.com/spf13/afero" @@ -377,6 +379,12 @@ func (l configLoader) collectModules(modConfig modules.Config, v1 config.Provide return nil, nil, err } + secConfig, err := security.DecodeConfig(v1) + if err != nil { + return nil, nil, err + } + ex := hexec.New(secConfig) + v1.Set("filecacheConfigs", filecacheConfigs) var configFilenames []string @@ -405,6 +413,7 @@ func (l configLoader) collectModules(modConfig modules.Config, v1 config.Provide modulesClient := modules.NewClient(modules.ClientConfig{ Fs: l.Fs, Logger: l.Logger, + Exec: ex, HookBeforeFinalize: hook, WorkingDir: workingDir, ThemesDir: themesDir, diff --git a/hugolib/js_test.go b/hugolib/js_test.go index 66c284d8b..69f528758 100644 --- a/hugolib/js_test.go +++ b/hugolib/js_test.go @@ -20,7 +20,6 @@ import ( "runtime" "testing" - "github.com/gohugoio/hugo/common/hexec" "github.com/gohugoio/hugo/config" "github.com/gohugoio/hugo/htesting" @@ -123,10 +122,9 @@ TS2: {{ template "print" $ts2 }} b.WithSourceFile("assets/js/included.js", includedJS) - cmd, err := hexec.SafeCommand("npm", "install") + cmd := b.NpmInstall() + err = cmd.Run() b.Assert(err, qt.IsNil) - out, err := cmd.CombinedOutput() - b.Assert(err, qt.IsNil, qt.Commentf(string(out))) b.Build(BuildCfg{}) @@ -195,8 +193,8 @@ require github.com/gohugoio/hugoTestProjectJSModImports v0.9.0 // indirect }`) b.Assert(os.Chdir(workDir), qt.IsNil) - cmd, _ := hexec.SafeCommand("npm", "install") - _, err = cmd.CombinedOutput() + cmd := b.NpmInstall() + err = cmd.Run() b.Assert(err, qt.IsNil) b.Build(BuildCfg{}) diff --git a/hugolib/page_test.go b/hugolib/page_test.go index 7a1ff6c4e..50263d483 100644 --- a/hugolib/page_test.go +++ b/hugolib/page_test.go @@ -24,8 +24,6 @@ import ( "github.com/gohugoio/hugo/htesting" - "github.com/gohugoio/hugo/markup/rst" - "github.com/gohugoio/hugo/markup/asciidocext" "github.com/gohugoio/hugo/config" @@ -370,6 +368,7 @@ func normalizeExpected(ext, str string) string { func testAllMarkdownEnginesForPages(t *testing.T, assertFunc func(t *testing.T, ext string, pages page.Pages), settings map[string]interface{}, pageSources ...string) { + engines := []struct { ext string shouldExecute func() bool @@ -377,7 +376,7 @@ func testAllMarkdownEnginesForPages(t *testing.T, {"md", func() bool { return true }}, {"mmark", func() bool { return true }}, {"ad", func() bool { return asciidocext.Supports() }}, - {"rst", func() bool { return rst.Supports() }}, + {"rst", func() bool { return true }}, } for _, e := range engines { @@ -385,47 +384,57 @@ func testAllMarkdownEnginesForPages(t *testing.T, continue } - cfg, fs := newTestCfg(func(cfg config.Provider) error { - for k, v := range settings { - cfg.Set(k, v) + t.Run(e.ext, func(t *testing.T) { + + cfg, fs := newTestCfg(func(cfg config.Provider) error { + for k, v := range settings { + cfg.Set(k, v) + } + return nil + }) + + contentDir := "content" + + if s := cfg.GetString("contentDir"); s != "" { + contentDir = s } - return nil - }) - contentDir := "content" + cfg.Set("security", map[string]interface{}{ + "exec": map[string]interface{}{ + "allow": []string{"^python$", "^rst2html.*", "^asciidoctor$"}, + }, + }) - if s := cfg.GetString("contentDir"); s != "" { - contentDir = s - } + var fileSourcePairs []string - var fileSourcePairs []string + for i, source := range pageSources { + fileSourcePairs = append(fileSourcePairs, fmt.Sprintf("p%d.%s", i, e.ext), source) + } - for i, source := range pageSources { - fileSourcePairs = append(fileSourcePairs, fmt.Sprintf("p%d.%s", i, e.ext), source) - } + for i := 0; i < len(fileSourcePairs); i += 2 { + writeSource(t, fs, filepath.Join(contentDir, fileSourcePairs[i]), fileSourcePairs[i+1]) + } - for i := 0; i < len(fileSourcePairs); i += 2 { - writeSource(t, fs, filepath.Join(contentDir, fileSourcePairs[i]), fileSourcePairs[i+1]) - } + // Add a content page for the home page + homePath := fmt.Sprintf("_index.%s", e.ext) + writeSource(t, fs, filepath.Join(contentDir, homePath), homePage) - // Add a content page for the home page - homePath := fmt.Sprintf("_index.%s", e.ext) - writeSource(t, fs, filepath.Join(contentDir, homePath), homePage) + b := newTestSitesBuilderFromDepsCfg(t, deps.DepsCfg{Fs: fs, Cfg: cfg}).WithNothingAdded() + b.Build(BuildCfg{}) - b := newTestSitesBuilderFromDepsCfg(t, deps.DepsCfg{Fs: fs, Cfg: cfg}).WithNothingAdded() - b.Build(BuildCfg{SkipRender: true}) + s := b.H.Sites[0] - s := b.H.Sites[0] + b.Assert(len(s.RegularPages()), qt.Equals, len(pageSources)) - b.Assert(len(s.RegularPages()), qt.Equals, len(pageSources)) + assertFunc(t, e.ext, s.RegularPages()) - assertFunc(t, e.ext, s.RegularPages()) + home, err := s.Info.Home() + b.Assert(err, qt.IsNil) + b.Assert(home, qt.Not(qt.IsNil)) + b.Assert(home.File().Path(), qt.Equals, homePath) + b.Assert(content(home), qt.Contains, "Home Page Content") - home, err := s.Info.Home() - b.Assert(err, qt.IsNil) - b.Assert(home, qt.Not(qt.IsNil)) - b.Assert(home.File().Path(), qt.Equals, homePath) - b.Assert(content(home), qt.Contains, "Home Page Content") + }) } } diff --git a/hugolib/resource_chain_babel_test.go b/hugolib/resource_chain_babel_test.go index 5cca22ba1..7a97e820a 100644 --- a/hugolib/resource_chain_babel_test.go +++ b/hugolib/resource_chain_babel_test.go @@ -21,8 +21,6 @@ import ( "github.com/gohugoio/hugo/config" - "github.com/gohugoio/hugo/common/hexec" - jww "github.com/spf13/jwalterweatherman" "github.com/gohugoio/hugo/htesting" @@ -51,7 +49,7 @@ func TestResourceChainBabel(t *testing.T) { "devDependencies": { "@babel/cli": "7.8.4", - "@babel/core": "7.9.0", + "@babel/core": "7.9.0", "@babel/preset-env": "7.9.5" } } @@ -94,6 +92,12 @@ class Car2 { v := config.New() v.Set("workingDir", workDir) v.Set("disableKinds", []string{"taxonomy", "term", "page"}) + v.Set("security", map[string]interface{}{ + "exec": map[string]interface{}{ + "allow": []string{"^npx$", "^babel$"}, + }, + }) + b := newTestSitesBuilder(t).WithLogger(logger) // Need to use OS fs for this. @@ -123,8 +127,8 @@ Transpiled3: {{ $transpiled.Permalink }} b.WithSourceFile("babel.config.js", babelConfig) b.Assert(os.Chdir(workDir), qt.IsNil) - cmd, _ := hexec.SafeCommand("npm", "install") - _, err = cmd.CombinedOutput() + cmd := b.NpmInstall() + err = cmd.Run() b.Assert(err, qt.IsNil) b.Build(BuildCfg{}) diff --git a/hugolib/resource_chain_test.go b/hugolib/resource_chain_test.go index 214dda216..0a5b9177c 100644 --- a/hugolib/resource_chain_test.go +++ b/hugolib/resource_chain_test.go @@ -32,8 +32,6 @@ import ( "testing" "time" - "github.com/gohugoio/hugo/common/hexec" - jww "github.com/spf13/jwalterweatherman" "github.com/gohugoio/hugo/common/herrors" @@ -387,8 +385,6 @@ T1: {{ $r.Content }} } func TestResourceChainBasic(t *testing.T) { - t.Parallel() - ts := httptest.NewServer(http.FileServer(http.Dir("testdata/"))) t.Cleanup(func() { ts.Close() @@ -1184,8 +1180,8 @@ class-in-b { b.WithSourceFile("postcss.config.js", postcssConfig) b.Assert(os.Chdir(workDir), qt.IsNil) - cmd, err := hexec.SafeCommand("npm", "install") - _, err = cmd.CombinedOutput() + cmd := b.NpmInstall() + err = cmd.Run() b.Assert(err, qt.IsNil) b.Build(BuildCfg{}) diff --git a/hugolib/securitypolicies_test.go b/hugolib/securitypolicies_test.go new file mode 100644 index 000000000..297f49479 --- /dev/null +++ b/hugolib/securitypolicies_test.go @@ -0,0 +1,202 @@ +// Copyright 2019 The Hugo Authors. All rights reserved. +// +// 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 +// http://www.apache.org/licenses/LICENSE-2.0 +// +// 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 ( + "fmt" + "net/http" + "net/http/httptest" + "runtime" + "testing" + + qt "github.com/frankban/quicktest" + "github.com/gohugoio/hugo/markup/asciidocext" + "github.com/gohugoio/hugo/markup/pandoc" + "github.com/gohugoio/hugo/markup/rst" + "github.com/gohugoio/hugo/resources/resource_transformers/tocss/dartsass" +) + +func TestSecurityPolicies(t *testing.T) { + c := qt.New(t) + + testVariant := func(c *qt.C, withBuilder func(b *sitesBuilder), expectErr string) { + c.Helper() + b := newTestSitesBuilder(c) + withBuilder(b) + + if expectErr != "" { + err := b.BuildE(BuildCfg{}) + b.Assert(err, qt.IsNotNil) + b.Assert(err, qt.ErrorMatches, expectErr) + } else { + b.Build(BuildCfg{}) + } + + } + + httpTestVariant := func(c *qt.C, templ, expectErr string, withBuilder func(b *sitesBuilder)) { + ts := httptest.NewServer(http.FileServer(http.Dir("testdata/"))) + c.Cleanup(func() { + ts.Close() + }) + cb := func(b *sitesBuilder) { + b.WithTemplatesAdded("index.html", fmt.Sprintf(templ, ts.URL)) + if withBuilder != nil { + withBuilder(b) + } + } + testVariant(c, cb, expectErr) + } + + c.Run("os.GetEnv, denied", func(c *qt.C) { + c.Parallel() + cb := func(b *sitesBuilder) { + b.WithTemplatesAdded("index.html", `{{ os.Getenv "FOOBAR" }}`) + } + testVariant(c, cb, `(?s).*"FOOBAR" is not whitelisted in policy "security\.funcs\.getenv".*`) + }) + + c.Run("os.GetEnv, OK", func(c *qt.C) { + c.Parallel() + cb := func(b *sitesBuilder) { + b.WithTemplatesAdded("index.html", `{{ os.Getenv "HUGO_FOO" }}`) + } + testVariant(c, cb, "") + }) + + c.Run("Asciidoc, denied", func(c *qt.C) { + c.Parallel() + if !asciidocext.Supports() { + c.Skip() + } + + cb := func(b *sitesBuilder) { + b.WithContent("page.ad", "foo") + } + + testVariant(c, cb, `(?s).*"asciidoctor" is not whitelisted in policy "security\.exec\.allow".*`) + }) + + c.Run("RST, denied", func(c *qt.C) { + c.Parallel() + if !rst.Supports() { + c.Skip() + } + + cb := func(b *sitesBuilder) { + b.WithContent("page.rst", "foo") + } + + if runtime.GOOS == "windows" { + testVariant(c, cb, `(?s).*python(\.exe)?" is not whitelisted in policy "security\.exec\.allow".*`) + } else { + testVariant(c, cb, `(?s).*"rst2html(\.py)?" is not whitelisted in policy "security\.exec\.allow".*`) + + } + + }) + + c.Run("Pandoc, denied", func(c *qt.C) { + c.Parallel() + if !pandoc.Supports() { + c.Skip() + } + + cb := func(b *sitesBuilder) { + b.WithContent("page.pdc", "foo") + } + + testVariant(c, cb, `"(?s).*pandoc" is not whitelisted in policy "security\.exec\.allow".*`) + }) + + c.Run("Dart SASS, OK", func(c *qt.C) { + c.Parallel() + if !dartsass.Supports() { + c.Skip() + } + cb := func(b *sitesBuilder) { + b.WithTemplatesAdded("index.html", `{{ $scss := "body { color: #333; }" | resources.FromString "foo.scss" | resources.ToCSS (dict "transpiler" "dartsass") }}`) + } + testVariant(c, cb, "") + }) + + c.Run("Dart SASS, denied", func(c *qt.C) { + c.Parallel() + if !dartsass.Supports() { + c.Skip() + } + cb := func(b *sitesBuilder) { + b.WithConfigFile("toml", ` + [security] + [security.exec] + allow="none" + + `) + b.WithTemplatesAdded("index.html", `{{ $scss := "body { color: #333; }" | resources.FromString "foo.scss" | resources.ToCSS (dict "transpiler" "dartsass") }}`) + } + testVariant(c, cb, `(?s).*"dart-sass-embedded" is not whitelisted in policy "security\.exec\.allow".*`) + }) + + c.Run("resources.Get, OK", func(c *qt.C) { + c.Parallel() + httpTestVariant(c, `{{ $json := resources.Get "%[1]s/fruits.json" }}{{ $json.Content }}`, "", nil) + }) + + c.Run("resources.Get, denied method", func(c *qt.C) { + c.Parallel() + httpTestVariant(c, `{{ $json := resources.Get "%[1]s/fruits.json" (dict "method" "DELETE" ) }}{{ $json.Content }}`, `(?s).*"DELETE" is not whitelisted in policy "security\.http\.method".*`, nil) + }) + + c.Run("resources.Get, denied URL", func(c *qt.C) { + c.Parallel() + httpTestVariant(c, `{{ $json := resources.Get "%[1]s/fruits.json" }}{{ $json.Content }}`, `(?s).*is not whitelisted in policy "security\.http\.urls".*`, + func(b *sitesBuilder) { + b.WithConfigFile("toml", ` +[security] +[security.http] +urls="none" +`) + }) + }) + + c.Run("getJSON, OK", func(c *qt.C) { + c.Parallel() + httpTestVariant(c, `{{ $json := getJSON "%[1]s/fruits.json" }}{{ $json.Content }}`, "", nil) + }) + + c.Run("getJSON, denied URL", func(c *qt.C) { + c.Parallel() + httpTestVariant(c, `{{ $json := getJSON "%[1]s/fruits.json" }}{{ $json.Content }}`, `(?s).*is not whitelisted in policy "security\.http\.urls".*`, + func(b *sitesBuilder) { + b.WithConfigFile("toml", ` +[security] +[security.http] +urls="none" +`) + }) + }) + + c.Run("getCSV, denied URL", func(c *qt.C) { + c.Parallel() + httpTestVariant(c, `{{ $d := getCSV ";" "%[1]s/cities.csv" }}{{ $d.Content }}`, `(?s).*is not whitelisted in policy "security\.http\.urls".*`, + func(b *sitesBuilder) { + b.WithConfigFile("toml", ` +[security] +[security.http] +urls="none" +`) + }) + }) + +} diff --git a/hugolib/shortcode.go b/hugolib/shortcode.go index 21d65de32..ec3a4a01b 100644 --- a/hugolib/shortcode.go +++ b/hugolib/shortcode.go @@ -257,7 +257,7 @@ func newShortcodeHandler(p *pageState, s *Site, placeholderFunc func() string) * sh := &shortcodeHandler{ p: p, s: s, - enableInlineShortcodes: s.enableInlineShortcodes, + enableInlineShortcodes: s.ExecHelper.Sec().EnableInlineShortcodes, shortcodes: make([]*shortcode, 0, 4), nameSet: make(map[string]bool), } @@ -287,7 +287,7 @@ func renderShortcode( var hasVariants bool if sc.isInline { - if !p.s.enableInlineShortcodes { + if !p.s.ExecHelper.Sec().EnableInlineShortcodes { return "", false, nil } templName := path.Join("_inline_shortcode", p.File().Path(), sc.name) diff --git a/hugolib/shortcode_test.go b/hugolib/shortcode_test.go index 6ef110c9b..6316afc98 100644 --- a/hugolib/shortcode_test.go +++ b/hugolib/shortcode_test.go @@ -619,6 +619,12 @@ title: "Foo" cfg.Set("uglyURLs", false) cfg.Set("verbose", true) + cfg.Set("security", map[string]interface{}{ + "exec": map[string]interface{}{ + "allow": []string{"^python$", "^rst2html.*", "^asciidoctor$"}, + }, + }) + cfg.Set("markup.highlight.noClasses", false) cfg.Set("markup.highlight.codeFences", true) cfg.Set("markup", map[string]interface{}{ diff --git a/hugolib/site.go b/hugolib/site.go index 96cf0b93c..dce4b8d25 100644 --- a/hugolib/site.go +++ b/hugolib/site.go @@ -120,8 +120,6 @@ type Site struct { disabledKinds map[string]bool - enableInlineShortcodes bool - // Output formats defined in site config per Page Kind, or some defaults // if not set. // Output formats defined in Page front matter will override these. @@ -378,25 +376,24 @@ func (s *Site) isEnabled(kind string) bool { // reset returns a new Site prepared for rebuild. func (s *Site) reset() *Site { return &Site{ - Deps: s.Deps, - disabledKinds: s.disabledKinds, - titleFunc: s.titleFunc, - relatedDocsHandler: s.relatedDocsHandler.Clone(), - siteRefLinker: s.siteRefLinker, - outputFormats: s.outputFormats, - rc: s.rc, - outputFormatsConfig: s.outputFormatsConfig, - frontmatterHandler: s.frontmatterHandler, - mediaTypesConfig: s.mediaTypesConfig, - language: s.language, - siteBucket: s.siteBucket, - h: s.h, - publisher: s.publisher, - siteConfigConfig: s.siteConfigConfig, - enableInlineShortcodes: s.enableInlineShortcodes, - init: s.init, - PageCollections: s.PageCollections, - siteCfg: s.siteCfg, + Deps: s.Deps, + disabledKinds: s.disabledKinds, + titleFunc: s.titleFunc, + relatedDocsHandler: s.relatedDocsHandler.Clone(), + siteRefLinker: s.siteRefLinker, + outputFormats: s.outputFormats, + rc: s.rc, + outputFormatsConfig: s.outputFormatsConfig, + frontmatterHandler: s.frontmatterHandler, + mediaTypesConfig: s.mediaTypesConfig, + language: s.language, + siteBucket: s.siteBucket, + h: s.h, + publisher: s.publisher, + siteConfigConfig: s.siteConfigConfig, + init: s.init, + PageCollections: s.PageCollections, + siteCfg: s.siteCfg, } } @@ -564,8 +561,7 @@ But this also means that your site configuration may not do what you expect. If outputFormatsConfig: siteOutputFormatsConfig, mediaTypesConfig: siteMediaTypesConfig, - enableInlineShortcodes: cfg.Language.GetBool("enableInlineShortcodes"), - siteCfg: siteConfig, + siteCfg: siteConfig, titleFunc: titleFunc, diff --git a/hugolib/testdata/cities.csv b/hugolib/testdata/cities.csv new file mode 100644 index 000000000..ee6b058b6 --- /dev/null +++ b/hugolib/testdata/cities.csv @@ -0,0 +1,130 @@ +"LatD", "LatM", "LatS", "NS", "LonD", "LonM", "LonS", "EW", "City", "State" + 41, 5, 59, "N", 80, 39, 0, "W", "Youngstown", OH + 42, 52, 48, "N", 97, 23, 23, "W", "Yankton", SD + 46, 35, 59, "N", 120, 30, 36, "W", "Yakima", WA + 42, 16, 12, "N", 71, 48, 0, "W", "Worcester", MA + 43, 37, 48, "N", 89, 46, 11, "W", "Wisconsin Dells", WI + 36, 5, 59, "N", 80, 15, 0, "W", "Winston-Salem", NC + 49, 52, 48, "N", 97, 9, 0, "W", "Winnipeg", MB + 39, 11, 23, "N", 78, 9, 36, "W", "Winchester", VA + 34, 14, 24, "N", 77, 55, 11, "W", "Wilmington", NC + 39, 45, 0, "N", 75, 33, 0, "W", "Wilmington", DE + 48, 9, 0, "N", 103, 37, 12, "W", "Williston", ND + 41, 15, 0, "N", 77, 0, 0, "W", "Williamsport", PA + 37, 40, 48, "N", 82, 16, 47, "W", "Williamson", WV + 33, 54, 0, "N", 98, 29, 23, "W", "Wichita Falls", TX + 37, 41, 23, "N", 97, 20, 23, "W", "Wichita", KS + 40, 4, 11, "N", 80, 43, 12, "W", "Wheeling", WV + 26, 43, 11, "N", 80, 3, 0, "W", "West Palm Beach", FL + 47, 25, 11, "N", 120, 19, 11, "W", "Wenatchee", WA + 41, 25, 11, "N", 122, 23, 23, "W", "Weed", CA + 31, 13, 11, "N", 82, 20, 59, "W", "Waycross", GA + 44, 57, 35, "N", 89, 38, 23, "W", "Wausau", WI + 42, 21, 36, "N", 87, 49, 48, "W", "Waukegan", IL + 44, 54, 0, "N", 97, 6, 36, "W", "Watertown", SD + 43, 58, 47, "N", 75, 55, 11, "W", "Watertown", NY + 42, 30, 0, "N", 92, 20, 23, "W", "Waterloo", IA + 41, 32, 59, "N", 73, 3, 0, "W", "Waterbury", CT + 38, 53, 23, "N", 77, 1, 47, "W", "Washington", DC + 41, 50, 59, "N", 79, 8, 23, "W", "Warren", PA + 46, 4, 11, "N", 118, 19, 48, "W", "Walla Walla", WA + 31, 32, 59, "N", 97, 8, 23, "W", "Waco", TX + 38, 40, 48, "N", 87, 31, 47, "W", "Vincennes", IN + 28, 48, 35, "N", 97, 0, 36, "W", "Victoria", TX + 32, 20, 59, "N", 90, 52, 47, "W", "Vicksburg", MS + 49, 16, 12, "N", 123, 7, 12, "W", "Vancouver", BC + 46, 55, 11, "N", 98, 0, 36, "W", "Valley City", ND + 30, 49, 47, "N", 83, 16, 47, "W", "Valdosta", GA + 43, 6, 36, "N", 75, 13, 48, "W", "Utica", NY + 39, 54, 0, "N", 79, 43, 48, "W", "Uniontown", PA + 32, 20, 59, "N", 95, 18, 0, "W", "Tyler", TX + 42, 33, 36, "N", 114, 28, 12, "W", "Twin Falls", ID + 33, 12, 35, "N", 87, 34, 11, "W", "Tuscaloosa", AL + 34, 15, 35, "N", 88, 42, 35, "W", "Tupelo", MS + 36, 9, 35, "N", 95, 54, 36, "W", "Tulsa", OK + 32, 13, 12, "N", 110, 58, 12, "W", "Tucson", AZ + 37, 10, 11, "N", 104, 30, 36, "W", "Trinidad", CO + 40, 13, 47, "N", 74, 46, 11, "W", "Trenton", NJ + 44, 45, 35, "N", 85, 37, 47, "W", "Traverse City", MI + 43, 39, 0, "N", 79, 22, 47, "W", "Toronto", ON + 39, 2, 59, "N", 95, 40, 11, "W", "Topeka", KS + 41, 39, 0, "N", 83, 32, 24, "W", "Toledo", OH + 33, 25, 48, "N", 94, 3, 0, "W", "Texarkana", TX + 39, 28, 12, "N", 87, 24, 36, "W", "Terre Haute", IN + 27, 57, 0, "N", 82, 26, 59, "W", "Tampa", FL + 30, 27, 0, "N", 84, 16, 47, "W", "Tallahassee", FL + 47, 14, 24, "N", 122, 25, 48, "W", "Tacoma", WA + 43, 2, 59, "N", 76, 9, 0, "W", "Syracuse", NY + 32, 35, 59, "N", 82, 20, 23, "W", "Swainsboro", GA + 33, 55, 11, "N", 80, 20, 59, "W", "Sumter", SC + 40, 59, 24, "N", 75, 11, 24, "W", "Stroudsburg", PA + 37, 57, 35, "N", 121, 17, 24, "W", "Stockton", CA + 44, 31, 12, "N", 89, 34, 11, "W", "Stevens Point", WI + 40, 21, 36, "N", 80, 37, 12, "W", "Steubenville", OH + 40, 37, 11, "N", 103, 13, 12, "W", "Sterling", CO + 38, 9, 0, "N", 79, 4, 11, "W", "Staunton", VA + 39, 55, 11, "N", 83, 48, 35, "W", "Springfield", OH + 37, 13, 12, "N", 93, 17, 24, "W", "Springfield", MO + 42, 5, 59, "N", 72, 35, 23, "W", "Springfield", MA + 39, 47, 59, "N", 89, 39, 0, "W", "Springfield", IL + 47, 40, 11, "N", 117, 24, 36, "W", "Spokane", WA + 41, 40, 48, "N", 86, 15, 0, "W", "South Bend", IN + 43, 32, 24, "N", 96, 43, 48, "W", "Sioux Falls", SD + 42, 29, 24, "N", 96, 23, 23, "W", "Sioux City", IA + 32, 30, 35, "N", 93, 45, 0, "W", "Shreveport", LA + 33, 38, 23, "N", 96, 36, 36, "W", "Sherman", TX + 44, 47, 59, "N", 106, 57, 35, "W", "Sheridan", WY + 35, 13, 47, "N", 96, 40, 48, "W", "Seminole", OK + 32, 25, 11, "N", 87, 1, 11, "W", "Selma", AL + 38, 42, 35, "N", 93, 13, 48, "W", "Sedalia", MO + 47, 35, 59, "N", 122, 19, 48, "W", "Seattle", WA + 41, 24, 35, "N", 75, 40, 11, "W", "Scranton", PA + 41, 52, 11, "N", 103, 39, 36, "W", "Scottsbluff", NB + 42, 49, 11, "N", 73, 56, 59, "W", "Schenectady", NY + 32, 4, 48, "N", 81, 5, 23, "W", "Savannah", GA + 46, 29, 24, "N", 84, 20, 59, "W", "Sault Sainte Marie", MI + 27, 20, 24, "N", 82, 31, 47, "W", "Sarasota", FL + 38, 26, 23, "N", 122, 43, 12, "W", "Santa Rosa", CA + 35, 40, 48, "N", 105, 56, 59, "W", "Santa Fe", NM + 34, 25, 11, "N", 119, 41, 59, "W", "Santa Barbara", CA + 33, 45, 35, "N", 117, 52, 12, "W", "Santa Ana", CA + 37, 20, 24, "N", 121, 52, 47, "W", "San Jose", CA + 37, 46, 47, "N", 122, 25, 11, "W", "San Francisco", CA + 41, 27, 0, "N", 82, 42, 35, "W", "Sandusky", OH + 32, 42, 35, "N", 117, 9, 0, "W", "San Diego", CA + 34, 6, 36, "N", 117, 18, 35, "W", "San Bernardino", CA + 29, 25, 12, "N", 98, 30, 0, "W", "San Antonio", TX + 31, 27, 35, "N", 100, 26, 24, "W", "San Angelo", TX + 40, 45, 35, "N", 111, 52, 47, "W", "Salt Lake City", UT + 38, 22, 11, "N", 75, 35, 59, "W", "Salisbury", MD + 36, 40, 11, "N", 121, 39, 0, "W", "Salinas", CA + 38, 50, 24, "N", 97, 36, 36, "W", "Salina", KS + 38, 31, 47, "N", 106, 0, 0, "W", "Salida", CO + 44, 56, 23, "N", 123, 1, 47, "W", "Salem", OR + 44, 57, 0, "N", 93, 5, 59, "W", "Saint Paul", MN + 38, 37, 11, "N", 90, 11, 24, "W", "Saint Louis", MO + 39, 46, 12, "N", 94, 50, 23, "W", "Saint Joseph", MO + 42, 5, 59, "N", 86, 28, 48, "W", "Saint Joseph", MI + 44, 25, 11, "N", 72, 1, 11, "W", "Saint Johnsbury", VT + 45, 34, 11, "N", 94, 10, 11, "W", "Saint Cloud", MN + 29, 53, 23, "N", 81, 19, 11, "W", "Saint Augustine", FL + 43, 25, 48, "N", 83, 56, 24, "W", "Saginaw", MI + 38, 35, 24, "N", 121, 29, 23, "W", "Sacramento", CA + 43, 36, 36, "N", 72, 58, 12, "W", "Rutland", VT + 33, 24, 0, "N", 104, 31, 47, "W", "Roswell", NM + 35, 56, 23, "N", 77, 48, 0, "W", "Rocky Mount", NC + 41, 35, 24, "N", 109, 13, 48, "W", "Rock Springs", WY + 42, 16, 12, "N", 89, 5, 59, "W", "Rockford", IL + 43, 9, 35, "N", 77, 36, 36, "W", "Rochester", NY + 44, 1, 12, "N", 92, 27, 35, "W", "Rochester", MN + 37, 16, 12, "N", 79, 56, 24, "W", "Roanoke", VA + 37, 32, 24, "N", 77, 26, 59, "W", "Richmond", VA + 39, 49, 48, "N", 84, 53, 23, "W", "Richmond", IN + 38, 46, 12, "N", 112, 5, 23, "W", "Richfield", UT + 45, 38, 23, "N", 89, 25, 11, "W", "Rhinelander", WI + 39, 31, 12, "N", 119, 48, 35, "W", "Reno", NV + 50, 25, 11, "N", 104, 39, 0, "W", "Regina", SA + 40, 10, 48, "N", 122, 14, 23, "W", "Red Bluff", CA + 40, 19, 48, "N", 75, 55, 48, "W", "Reading", PA + 41, 9, 35, "N", 81, 14, 23, "W", "Ravenna", OH + diff --git a/hugolib/testdata/fruits.json b/hugolib/testdata/fruits.json new file mode 100644 index 000000000..3bb802a16 --- /dev/null +++ b/hugolib/testdata/fruits.json @@ -0,0 +1,5 @@ +{ + "fruit": "Apple", + "size": "Large", + "color": "Red" +} diff --git a/hugolib/testhelpers_test.go b/hugolib/testhelpers_test.go index ba3965675..72e22ed1d 100644 --- a/hugolib/testhelpers_test.go +++ b/hugolib/testhelpers_test.go @@ -18,6 +18,7 @@ import ( "time" "unicode/utf8" + "github.com/gohugoio/hugo/config/security" "github.com/gohugoio/hugo/htesting" "github.com/gohugoio/hugo/output" @@ -30,6 +31,7 @@ import ( "github.com/fsnotify/fsnotify" "github.com/gohugoio/hugo/common/herrors" + "github.com/gohugoio/hugo/common/hexec" "github.com/gohugoio/hugo/common/maps" "github.com/gohugoio/hugo/config" "github.com/gohugoio/hugo/deps" @@ -791,6 +793,16 @@ func (s *sitesBuilder) GetPageRel(p page.Page, ref string) page.Page { return p } +func (s *sitesBuilder) NpmInstall() hexec.Runner { + sc := security.DefaultConfig |