govuk_tech_docs 3.3.0 → 3.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1fe53bb05a63a24a2d7d501d04fa172433d89e8f5fcd48bc4fc9313e97325ad0
4
- data.tar.gz: 4b53d1f86522ae3807c40ba806befd97c5da2b42d92666fde35ac00bc0c89eba
3
+ metadata.gz: ac3b9630e3b7c40e2029a0e115dc1054e025f14d35fc07b81b4ab531cee7bea2
4
+ data.tar.gz: e414f5b7ab807ef05e95ec5f3281d68abd444e712f8355ed4bbac2dcd7c17dfd
5
5
  SHA512:
6
- metadata.gz: 1b5bbfc06fe8387ddc6eae181fef27fca01d2afdefa68a7a5833e77d8eeb30e76335dc32fd13ca20501b88a107e8f0dfe3f0a64ba3cd8dcd9cfa1dc73e0c738f
7
- data.tar.gz: 50751bf31f0ab377dca9618e749b9f8cbc029cf7a096085e8fdcea3d7004f42ce5901703bbb2c3cb11768aed369232226d74764a9347f02a5787b8822a9ef90d
6
+ metadata.gz: c3b71ef93eac71413d405bb1a504b15a2692a498ba397c93f0d4aabb8f231650176e9b6b55edc620128fdec5fc0fa8a60f02d70eacd1092b1c52611966f84685
7
+ data.tar.gz: 1a6f93c4c7f2079adb79e02e734e7fe7f375d503ad0d20c3be59df2696d0efacbbbd189f6822e2263e9245925407d88fe96f02dbcd5aa6e099d636954c3440e1
@@ -16,22 +16,24 @@ jobs:
16
16
  go: ${{ steps.gem_version.outputs.new_version }}
17
17
 
18
18
  steps:
19
- - uses: actions/checkout@v2
19
+ - uses: actions/checkout@v3
20
20
 
21
21
  - uses: ruby/setup-ruby@v1
22
+ with:
23
+ ruby-version: '3'
22
24
 
23
25
  - name: Check if new version to release
24
26
  id: gem_version
25
27
  run: |
26
28
  gem_version=$(ruby -r rubygems -e "puts Gem::Specification::load('govuk_tech_docs.gemspec').version")
27
- echo "::set-output name=gem_version::$gem_version"
29
+ echo "gem_version=$gem_version" >> "$GITHUB_OUTPUT"
28
30
 
29
31
  if git fetch origin "refs/tags/v$gem_version" >/dev/null 2>&1
30
32
  then
31
33
  echo "Tag 'v$gem_version' already exists"
32
- echo "::set-output name=new_version::false"
34
+ echo "new_version=false" >> "$GITHUB_OUTPUT"
33
35
  else
34
- echo "::set-output name=new_version::true"
36
+ echo "new_version=true" >> "$GITHUB_OUTPUT"
35
37
  fi
36
38
 
37
39
  deploy:
@@ -44,15 +46,16 @@ jobs:
44
46
  if: ${{ needs.pre.outputs.go == 'true' }}
45
47
 
46
48
  steps:
47
- - uses: actions/checkout@v2
49
+ - uses: actions/checkout@v3
48
50
 
49
- - uses: actions/setup-node@v2
51
+ - uses: actions/setup-node@v3
50
52
  with:
53
+ node-version-file: '.nvmrc'
51
54
  cache: 'npm'
52
- node-version: '14'
53
55
 
54
56
  - uses: ruby/setup-ruby@v1
55
57
  with:
58
+ ruby-version: '3'
56
59
  bundler-cache: true
57
60
 
58
61
  - name: Publish
@@ -7,16 +7,21 @@ jobs:
7
7
  name: Test
8
8
  runs-on: ubuntu-latest
9
9
 
10
+ strategy:
11
+ matrix:
12
+ ruby: ['2.7', '3.2']
13
+
10
14
  steps:
11
- - uses: actions/checkout@v2
15
+ - uses: actions/checkout@v3
12
16
 
13
- - uses: actions/setup-node@v2
17
+ - uses: actions/setup-node@v3
14
18
  with:
15
- node-version: '14'
19
+ node-version-file: '.nvmrc'
16
20
  cache: 'npm'
17
21
 
18
22
  - uses: ruby/setup-ruby@v1
19
23
  with:
24
+ ruby-version: ${{ matrix.ruby }}
20
25
  bundler-cache: true
21
26
 
22
27
  - name: Run tests
data/.nvmrc CHANGED
@@ -1 +1 @@
1
- 14
1
+ 18
data/.rubocop.yml CHANGED
@@ -2,6 +2,9 @@ inherit_gem:
2
2
  rubocop-govuk:
3
3
  - config/default.yml
4
4
 
5
+ AllCops:
6
+ TargetRubyVersion: 2.7
7
+
5
8
  Layout/HeredocIndentation:
6
9
  Enabled: false
7
10
 
data/CHANGELOG.md CHANGED
@@ -2,6 +2,24 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 3.4.0
6
+
7
+ ### New features
8
+
9
+ - Footer and header links now work with relative links. Thanks to [@eddgrant](https://github.com/eddgrant) for contributing this feature.
10
+
11
+ See [pull request #325: Support sites deployed on a path other than "/" when generating header and footer links](https://github.com/alphagov/tech-docs-gem/pull/325) for more details.
12
+
13
+ ### Fixes
14
+
15
+ - You no longer need to downgrade Haml yourself, `bundle install` will now make sure Haml 6 is not installed (see issue [#318: Error: Filters is not a module](https://github.com/alphagov/tech-docs/gem/issues/318)).
16
+
17
+ ## 3.3.1
18
+
19
+ This change solves a potential security issue with HTML snippets. Pages indexed in search results have their entire contents indexed, including any HTML code snippets. These HTML snippets would appear in the search results unsanitised, making it possible to render arbitrary HTML or run arbitrary scripts.
20
+
21
+ You can see more detail about this issue at [#323: Fix XSS vulnerability on search results page](https://github.com/alphagov/tech-docs-gem/pull/323)
22
+
5
23
  ## 3.3.0
6
24
 
7
25
  ### New features
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
- source 'https://rubygems.org'
1
+ source "https://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in govuk_tech_docs.gemspec
4
4
  gemspec
@@ -1,13 +1,14 @@
1
- # coding: utf-8
2
- lib = File.expand_path("../lib", __FILE__)
1
+ require "English"
2
+
3
+ lib = File.expand_path("lib", __dir__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
5
  require "govuk_tech_docs/version"
5
6
 
6
7
  `npm install`
7
- abort 'npm install failed' unless $?.success?
8
+ abort "npm install failed" unless $CHILD_STATUS.success?
8
9
 
9
- unless File.exist?('node_modules/govuk-frontend/govuk/all.scss')
10
- abort 'govuk-frontend npm package not installed'
10
+ unless File.exist?("node_modules/govuk-frontend/govuk/all.scss")
11
+ abort "govuk-frontend npm package not installed"
11
12
  end
12
13
 
13
14
  Gem::Specification.new do |spec|
@@ -16,8 +17,8 @@ Gem::Specification.new do |spec|
16
17
  spec.authors = ["Government Digital Service"]
17
18
  spec.email = ["govuk-dev@digital.cabinet-office.gov.uk"]
18
19
 
19
- spec.summary = %q{Gem to distribute the GOV.UK Tech Docs Template}
20
- spec.description = %q{Gem to distribute the GOV.UK Tech Docs Template. See https://github.com/alphagov/tech-docs-gem for the project.}
20
+ spec.summary = "Gem to distribute the GOV.UK Tech Docs Template"
21
+ spec.description = "Gem to distribute the GOV.UK Tech Docs Template. See https://github.com/alphagov/tech-docs-gem for the project."
21
22
  spec.homepage = "https://github.com/alphagov/tech-docs-gem"
22
23
  spec.license = "MIT"
23
24
 
@@ -30,10 +31,13 @@ Gem::Specification.new do |spec|
30
31
 
31
32
  spec.bindir = "exe"
32
33
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
33
- spec.require_paths = ["lib"]
34
+ spec.require_paths = %w[lib]
35
+
36
+ spec.required_ruby_version = ">= 2.7.0"
34
37
 
35
38
  spec.add_dependency "autoprefixer-rails", "~> 10.2"
36
39
  spec.add_dependency "chronic", "~> 0.10.2"
40
+ spec.add_dependency "haml", "< 6.0.0"
37
41
  spec.add_dependency "middleman", "~> 4.0"
38
42
  spec.add_dependency "middleman-autoprefixer", "~> 2.10.0"
39
43
  spec.add_dependency "middleman-compass", ">= 4.0.0"
@@ -50,5 +54,5 @@ Gem::Specification.new do |spec|
50
54
  spec.add_development_dependency "jasmine", "~> 3.5.0"
51
55
  spec.add_development_dependency "rake", "~> 13.0"
52
56
  spec.add_development_dependency "rspec", "~> 3.9.0"
53
- spec.add_development_dependency "rubocop-govuk", "~> 3.5.0"
57
+ spec.add_development_dependency "rubocop-govuk", "~> 4.10.0"
54
58
  end
@@ -169,8 +169,8 @@
169
169
 
170
170
  this.processContent = function processContent (content, query) {
171
171
  var output
172
- content = '<div>' + content + '</div>'
173
- content = $(content).mark(query)
172
+ var sanitizedContent = $('<div></div>').text(content).html()
173
+ content = $('<div>' + sanitizedContent + '</div>').mark(query)
174
174
 
175
175
  // Split content by sentence.
176
176
  var sentences = content.html().replace(/(\.+|:|!|\?|\r|\n)("*|'*|\)*|}*|]*)/gm, '|').split('|')
@@ -37,7 +37,7 @@ module GovukTechDocs
37
37
 
38
38
  def uri?(string)
39
39
  uri = URI.parse(string)
40
- %w(http https).include?(uri.scheme)
40
+ %w[http https].include?(uri.scheme)
41
41
  rescue URI::BadURIError
42
42
  false
43
43
  rescue URI::InvalidURIError
@@ -124,15 +124,15 @@ module GovukTechDocs
124
124
  properties.merge!(all_of_schema.properties.to_h)
125
125
  end
126
126
 
127
- properties.each_with_object({}) do |(name, schema), memo|
128
- memo[name] = case schema.type
129
- when "object"
130
- schema_properties(schema.items || schema)
131
- when "array"
132
- schema.items ? [schema_properties(schema.items)] : []
133
- else
134
- schema.example || schema.type
135
- end
127
+ properties.transform_values do |schema|
128
+ case schema.type
129
+ when "object"
130
+ schema_properties(schema.items || schema)
131
+ when "array"
132
+ schema.items ? [schema_properties(schema.items)] : []
133
+ else
134
+ schema.example || schema.type
135
+ end
136
136
  end
137
137
  end
138
138
 
@@ -144,7 +144,7 @@ module GovukTechDocs
144
144
  end
145
145
 
146
146
  def get_renderer(file)
147
- template_path = File.join(File.dirname(__FILE__), "templates/" + file)
147
+ template_path = File.join(File.dirname(__FILE__), "templates/#{file}")
148
148
  template = File.open(template_path, "r").read
149
149
  ERB.new(template)
150
150
  end
@@ -1,18 +1,29 @@
1
+ require "uri"
1
2
  module GovukTechDocs
2
3
  module PathHelpers
3
- def get_path_to_resource(config, resource, current_page)
4
- if config[:relative_links]
5
- resource_path_segments = resource.path.split("/").reject(&:empty?)[0..-2]
6
- resource_file_name = resource.path.split("/")[-1]
4
+ # Some useful notes from https://www.rubydoc.info/github/middleman/middleman/Middleman/Sitemap/Resource#url-instance_method :
5
+ # 'resource.path' is "The source path of this resource (relative to the source directory, without template extensions)."
6
+ # 'resource.destination_path', which is: "The output path in the build directory for this resource."
7
+ # 'resource.url' is based on 'resource.destination_path', but is further tweaked to optionally strip the index file and prefixed with any :http_prefix.
7
8
 
8
- path_to_site_root = path_to_site_root config, current_page.path
9
- resource_path = path_to_site_root + resource_path_segments
10
- .push(resource_file_name)
11
- .join("/")
9
+ # Calculates the path to the sought resource, taking in to account whether the site has been configured
10
+ # to generate relative or absolute links.
11
+ # Identifies whether the sought resource is an internal or external target: External targets are returned untouched. Path calculation is performed for internal targets.
12
+ # Works for both "Middleman::Sitemap::Resource" resources and plain strings (which may be passed from the site configuration when generating header links).
13
+ #
14
+ # @param [Object] config
15
+ # @param [Object] resource
16
+ # @param [Object] current_page
17
+ def get_path_to_resource(config, resource, current_page)
18
+ if resource.is_a?(Middleman::Sitemap::Resource)
19
+ config[:relative_links] ? get_resource_path_relative_to_current_page(config, current_page.path, resource.path) : resource.url
20
+ elsif external_url?(resource)
21
+ resource
22
+ elsif config[:relative_links]
23
+ get_resource_path_relative_to_current_page(config, current_page.path, resource)
12
24
  else
13
- resource_path = resource.url
25
+ resource
14
26
  end
15
- resource_path
16
27
  end
17
28
 
18
29
  def path_to_site_root(config, page_path)
@@ -26,5 +37,24 @@ module GovukTechDocs
26
37
  end
27
38
  path_to_site_root
28
39
  end
40
+
41
+ private
42
+
43
+ # Calculates the path to the sought resource, relative to the current page.
44
+ # @param [Object] config Middleman config.
45
+ # @param [String] current_page path of the current page, from the site root.
46
+ # @param [String] resource_path_from_site_root path of the sought resource, from the site root.
47
+ def get_resource_path_relative_to_current_page(config, current_page, resource_path_from_site_root)
48
+ path_segments = resource_path_from_site_root.split("/").reject(&:empty?)[0..-2]
49
+ path_file_name = resource_path_from_site_root.split("/")[-1]
50
+
51
+ path_to_site_root = path_to_site_root config, current_page
52
+ path_to_site_root + path_segments.push(path_file_name).join("/")
53
+ end
54
+
55
+ def external_url?(url)
56
+ uri = URI.parse(url)
57
+ uri.scheme || uri.to_s.split("/")[0]&.include?(".")
58
+ end
29
59
  end
30
60
  end
@@ -1,6 +1,6 @@
1
1
  module GovukTechDocs
2
2
  class Redirects
3
- LEADING_SLASH = %r[\A\/].freeze
3
+ LEADING_SLASH = %r{\A/}.freeze
4
4
 
5
5
  def initialize(context)
6
6
  @context = context
@@ -11,7 +11,7 @@ module GovukTechDocs
11
11
 
12
12
  all_redirects.map do |from, to|
13
13
  # Middleman needs paths without leading slashes
14
- [from.sub(LEADING_SLASH, ""), to: to.sub(LEADING_SLASH, "")]
14
+ [from.sub(LEADING_SLASH, ""), { to: to.sub(LEADING_SLASH, "") }]
15
15
  end
16
16
  end
17
17
 
@@ -9,14 +9,14 @@ module GovukTechDocs
9
9
  end
10
10
 
11
11
  def size
12
- @element_name.scan(/h(\d)/) && $1 && Integer($1)
12
+ @element_name.scan(/h(\d)/) && ::Regexp.last_match(1) && Integer(::Regexp.last_match(1))
13
13
  end
14
14
 
15
15
  def href
16
16
  if @page_url != "" && size == 1
17
17
  @page_url
18
18
  else
19
- @page_url + "#" + @attributes["id"]
19
+ "#{@page_url}##{@attributes['id']}"
20
20
  end
21
21
  end
22
22
 
@@ -20,23 +20,23 @@ module GovukTechDocs
20
20
  output = ""
21
21
 
22
22
  if tree.heading
23
- output += indentation + %{<a href="#{tree.heading.href}"><span>#{tree.heading.title}</span></a>\n}
23
+ output += indentation + %(<a href="#{tree.heading.href}"><span>#{tree.heading.title}</span></a>\n)
24
24
  end
25
25
 
26
26
  if tree.children.any? && level < @max_level
27
- output += indentation + "<ul>\n" unless level.zero?
27
+ output += "#{indentation}<ul>\n" unless level.zero?
28
28
 
29
29
  tree.children.each do |child|
30
- output += indentation + INDENTATION_INCREMENT + "<li>\n"
30
+ output += "#{indentation}#{INDENTATION_INCREMENT}<li>\n"
31
31
  output += render_tree(
32
32
  child,
33
33
  indentation: indentation + INDENTATION_INCREMENT * 2,
34
34
  level: level + 1,
35
35
  )
36
- output += indentation + INDENTATION_INCREMENT + "</li>\n"
36
+ output += "#{indentation}#{INDENTATION_INCREMENT}</li>\n"
37
37
  end
38
38
 
39
- output += indentation + "</ul>\n" unless level.zero?
39
+ output += "#{indentation}</ul>\n" unless level.zero?
40
40
  end
41
41
 
42
42
  output
@@ -32,7 +32,7 @@ module GovukTechDocs
32
32
  headings = HeadingsBuilder.new(html, url).headings
33
33
 
34
34
  if headings.none? { |heading| heading.size == 1 }
35
- raise "No H1 tag found. You have to at least add one H1 heading to the page: " + url
35
+ raise "No H1 tag found. You have to at least add one H1 heading to the page: #{url}"
36
36
  end
37
37
 
38
38
  tree = HeadingTreeBuilder.new(headings).tree
@@ -67,12 +67,12 @@ module GovukTechDocs
67
67
  if config[:http_prefix].end_with?("/")
68
68
  config[:http_prefix]
69
69
  else
70
- config[:http_prefix] + "/"
70
+ "#{config[:http_prefix]}/"
71
71
  end
72
72
 
73
73
  link_value = get_path_to_resource(config, resource, current_page)
74
74
  if resource.children.any? && resource.url != home_url
75
- output += %{<li><a href="#{link_value}"><span>#{resource.data.title}</span></a>\n}
75
+ output += %(<li><a href="#{link_value}"><span>#{resource.data.title}</span></a>\n)
76
76
  output += render_page_tree(resource.children, current_page, config, current_page_html)
77
77
  output += "</li>\n"
78
78
  else
@@ -1,3 +1,3 @@
1
1
  module GovukTechDocs
2
- VERSION = "3.3.0".freeze
2
+ VERSION = "3.4.0".freeze
3
3
  end
@@ -86,8 +86,8 @@ module GovukTechDocs
86
86
  def active_page(page_path)
87
87
  [
88
88
  page_path == "/" && current_page.path == "index.html",
89
- ("/" + current_page.path) == page_path,
90
- current_page.data.parent != nil && current_page.data.parent.to_s == page_path,
89
+ "/#{current_page.path}" == page_path,
90
+ !current_page.data.parent.nil? && current_page.data.parent.to_s == page_path,
91
91
  ].any?
92
92
  end
93
93
  end
@@ -109,9 +109,9 @@ module GovukTechDocs
109
109
  search.resources = [""]
110
110
 
111
111
  search.fields = {
112
- title: { boost: 100, store: true, required: true },
112
+ title: { boost: 100, store: true, required: true },
113
113
  content: { boost: 50, store: true },
114
- url: { index: false, store: true },
114
+ url: { index: false, store: true },
115
115
  }
116
116
 
117
117
  search.pipeline_remove = %w[stemmer stopWordFilter]
@@ -6,7 +6,7 @@
6
6
  <ul class="govuk-footer__inline-list">
7
7
  <% config[:tech_docs][:footer_links].each do |title, path| %>
8
8
  <li class="govuk-footer__inline-list-item">
9
- <a class="govuk-footer__link" href="<%= url_for path %>"><%= title %></a>
9
+ <a class="govuk-footer__link" href="<%= get_path_to_resource config, path, current_page %>"><%= title %></a>
10
10
  </li>
11
11
  <% end %>
12
12
  </ul>
@@ -2,7 +2,7 @@
2
2
  <div class="govuk-header__container govuk-header__container--full-width">
3
3
  <div class="govuk-header__logo">
4
4
  <% if config[:tech_docs][:service_link] %>
5
- <a href="<%= url_for config[:tech_docs][:service_link] %>" class="govuk-header__link govuk-header__link--homepage">
5
+ <a href="<%= get_path_to_resource config, config[:tech_docs][:service_link], current_page %>" class="govuk-header__link govuk-header__link--homepage">
6
6
  <% else %>
7
7
  <span class="govuk-header__link govuk-header__link--homepage">
8
8
  <% end %>
@@ -46,7 +46,7 @@
46
46
  <ul id="navigation" class="govuk-header__navigation-list">
47
47
  <% config[:tech_docs][:header_links].each do |title, path| %>
48
48
  <li class="govuk-header__navigation-item<% if active_page(path) %> govuk-header__navigation-item--active<% end %>">
49
- <a class="govuk-header__link" href="<%= url_for path %>"><%= title %></a>
49
+ <a class="govuk-header__link" href="<%= get_path_to_resource config, path, current_page %>"><%= title %></a>
50
50
  </li>
51
51
  <% end %>
52
52
  </ul>