From 67f0abf7a25ffe8fa4929991e5c7f638df117e6a Mon Sep 17 00:00:00 2001 From: AudioStakes Date: Mon, 31 Jan 2022 16:48:57 +0900 Subject: [PATCH 01/30] =?UTF-8?q?=E3=83=86=E3=82=B9=E3=83=88=E3=81=97?= =?UTF-8?q?=E3=82=84=E3=81=99=E3=81=8F=E3=81=99=E3=82=8B=E3=81=9F=E3=82=81?= =?UTF-8?q?=E3=81=AB=20Checker=20=E3=81=AB=E3=83=81=E3=82=A7=E3=83=83?= =?UTF-8?q?=E3=82=AF=E5=AF=BE=E8=B1=A1=E3=83=AA=E3=83=B3=E3=82=AF=E3=82=92?= =?UTF-8?q?=E5=BC=95=E6=95=B0=E3=81=A7=E6=B8=A1=E3=81=9B=E3=82=8B=E3=82=88?= =?UTF-8?q?=E3=81=86=E3=81=AB=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/link_checker/checker.rb | 9 +++++---- test/models/link_checker/checker_test.rb | 7 +++++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/app/models/link_checker/checker.rb b/app/models/link_checker/checker.rb index c863b16203d..519c2e044a7 100644 --- a/app/models/link_checker/checker.rb +++ b/app/models/link_checker/checker.rb @@ -10,7 +10,8 @@ class Checker ].freeze attr_reader :errors - def initialize + def initialize(links = []) + @links = links @errors = [] @error_links = [] end @@ -30,13 +31,13 @@ def notify_missing_links def check locks = Queue.new 5.times { locks.push :lock } - all_links.reject! do |link| + @links.reject! do |link| url = URI.encode_www_form_component(link.url) uri = URI.parse(url) - !uri || DENY_LIST.include?(uri.host) end - all_links.map do |link| + + @links.map do |link| Thread.new do lock = locks.pop response = Client.request(link.url) diff --git a/test/models/link_checker/checker_test.rb b/test/models/link_checker/checker_test.rb index ba33082f66d..fc103d55647 100644 --- a/test/models/link_checker/checker_test.rb +++ b/test/models/link_checker/checker_test.rb @@ -51,11 +51,14 @@ class CheckerTest < ActiveSupport::TestCase test '#check' do VCR.use_cassette 'link_checker/checker/check' do - checker = LinkChecker::Checker.new + links = [@link_hdd, @link_cpu, @link_not_exist, @link_example, @link_mac] + checker = Checker.new(links) + @link_hdd.response = false @link_not_exist.response = 404 expected = [@link_hdd, @link_not_exist] - assert_equal Set.new(expected), Set.new(checker.check) + + assert_equal expected, checker.check end end From 824de67312ec9cc0fd005ae137436fc1a80970d0 Mon Sep 17 00:00:00 2001 From: AudioStakes Date: Sun, 30 Jan 2022 16:19:07 +0900 Subject: [PATCH 02/30] =?UTF-8?q?=E3=83=AA=E3=83=B3=E3=82=AF=E5=88=87?= =?UTF-8?q?=E3=82=8C=E3=81=AE=E8=A1=A8=E8=A8=98(error/missing=5Flink)?= =?UTF-8?q?=E3=82=92=20broken=5Flink=20=E3=81=AB=E7=B5=B1=E4=B8=80?= =?UTF-8?q?=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/scheduler/link_checker_controller.rb | 2 +- app/models/link_checker/checker.rb | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/controllers/scheduler/link_checker_controller.rb b/app/controllers/scheduler/link_checker_controller.rb index 1368d20d0bd..40dca49d5a9 100644 --- a/app/controllers/scheduler/link_checker_controller.rb +++ b/app/controllers/scheduler/link_checker_controller.rb @@ -3,7 +3,7 @@ class Scheduler::LinkCheckerController < SchedulerController def show checker = LinkChecker::Checker.new - checker.notify_missing_links + checker.notify_broken_links render plain: checker.errors.join("\n") end end diff --git a/app/models/link_checker/checker.rb b/app/models/link_checker/checker.rb index 519c2e044a7..fa96622bf15 100644 --- a/app/models/link_checker/checker.rb +++ b/app/models/link_checker/checker.rb @@ -13,15 +13,15 @@ class Checker def initialize(links = []) @links = links @errors = [] - @error_links = [] + @broken_links = [] end - def notify_missing_links + def notify_broken_links check - return if @error_links.empty? + return if @broken_links.empty? texts = ['リンク切れがありました。'] - @error_links.map do |link| + @broken_links.map do |link| texts << "- <#{link.url}|#{link.title}> in: <#{link.source_url}|#{link.source_title}>" end @@ -42,12 +42,12 @@ def check lock = locks.pop response = Client.request(link.url) link.response = response - @error_links << link if !response || response > 403 + @broken_links << link if !response || response > 403 locks.push lock end end.each(&:join) - @error_links.sort { |a, b| b.source_url <=> a.source_url } + @broken_links.sort { |a, b| b.source_url <=> a.source_url } end def all_links From 579c43a5fec96f24d9c5356c10442d9370ee8563 Mon Sep 17 00:00:00 2001 From: AudioStakes Date: Sun, 30 Jan 2022 14:00:34 +0900 Subject: [PATCH 03/30] =?UTF-8?q?=E3=83=9E=E3=83=BC=E3=82=AF=E3=83=80?= =?UTF-8?q?=E3=82=A6=E3=83=B3=E3=81=8B=E3=82=89=E3=83=AA=E3=83=B3=E3=82=AF?= =?UTF-8?q?=E3=82=92=E6=8A=BD=E5=87=BA=E3=81=99=E3=82=8B=E5=87=A6=E7=90=86?= =?UTF-8?q?=E3=82=92=20Checker=20=E3=82=AF=E3=83=A9=E3=82=B9=E3=81=8B?= =?UTF-8?q?=E3=82=89=E5=8F=96=E3=82=8A=E9=99=A4=E3=81=84=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../scheduler/link_checker_controller.rb | 5 ++- app/models/link_checker/checker.rb | 40 ------------------- app/models/link_checker/extractor.rb | 14 ++++++- app/models/practice.rb | 4 ++ test/models/link_checker/checker_test.rb | 6 --- test/models/link_checker/extractor_test.rb | 4 +- 6 files changed, 23 insertions(+), 50 deletions(-) diff --git a/app/controllers/scheduler/link_checker_controller.rb b/app/controllers/scheduler/link_checker_controller.rb index 40dca49d5a9..b2411a0412c 100644 --- a/app/controllers/scheduler/link_checker_controller.rb +++ b/app/controllers/scheduler/link_checker_controller.rb @@ -2,8 +2,11 @@ class Scheduler::LinkCheckerController < SchedulerController def show - checker = LinkChecker::Checker.new + documents = Page.all + Practice.all + links = LinkChecker::Extractor.extract_all_links(documents) + checker = LinkChecker::Checker.new(links) checker.notify_broken_links + render plain: checker.errors.join("\n") end end diff --git a/app/models/link_checker/checker.rb b/app/models/link_checker/checker.rb index fa96622bf15..cc1d707016a 100644 --- a/app/models/link_checker/checker.rb +++ b/app/models/link_checker/checker.rb @@ -49,45 +49,5 @@ def check @broken_links.sort { |a, b| b.source_url <=> a.source_url } end - - def all_links - page_links + practice_links - end - - private - - def page_links - links = [] - Page.order(:created_at).each do |page| - extractor = Extractor.new( - page.body, - page.title, - "https://bootcamp.fjord.jp#{Rails.application.routes.url_helpers.polymorphic_path(page)}" - ) - links += extractor.extract - end - links - end - - def practice_links - links = [] - Practice.order(:created_at).each do |practice| - practice_url = Rails.application.routes.url_helpers.polymorphic_path(practice) - extractor = Extractor.new( - practice.description, - practice.title, - "https://bootcamp.fjord.jp#{practice_url}" - ) - links += extractor.extract - - extractor = Extractor.new( - practice.goal, - practice.title, - "https://bootcamp.fjord.jp#{practice_url}" - ) - links += extractor.extract - end - links - end end end diff --git a/app/models/link_checker/extractor.rb b/app/models/link_checker/extractor.rb index 11ac6a549d1..264e80154ff 100644 --- a/app/models/link_checker/extractor.rb +++ b/app/models/link_checker/extractor.rb @@ -4,13 +4,25 @@ module LinkChecker Link = Struct.new(:title, :url, :source_title, :source_url, :response) class Extractor + class << self + def extract_all_links(documents) + documents.flat_map do |document| + new( + document.body, + document.title, + "https://bootcamp.fjord.jp#{document.path}" + ) + end + end + end + def initialize(markdown_text, source_title, source_url) @markdown_text = markdown_text @source_title = source_title @source_url = source_url end - def extract + def extract_links links = @markdown_text.scan(/\[(.*?)\]\((.+?)\)/)&.map do |match| title = match[0].strip url = match[1].strip diff --git a/app/models/practice.rb b/app/models/practice.rb index e34f0776c69..9869746d1c4 100644 --- a/app/models/practice.rb +++ b/app/models/practice.rb @@ -99,6 +99,10 @@ def all_text [title, description, goal].join("\n") end + def body + [description, goal].join("\n") + end + def product(user) products.find_by(user: user) end diff --git a/test/models/link_checker/checker_test.rb b/test/models/link_checker/checker_test.rb index fc103d55647..49c390829ee 100644 --- a/test/models/link_checker/checker_test.rb +++ b/test/models/link_checker/checker_test.rb @@ -61,11 +61,5 @@ class CheckerTest < ActiveSupport::TestCase assert_equal expected, checker.check end end - - test '#all_links' do - checker = LinkChecker::Checker.new - expected = [@link_example, @link_not_exist, @link_cpu, @link_hdd, @link_mac] - assert_equal Set.new(expected), Set.new(checker.all_links) - end end end diff --git a/test/models/link_checker/extractor_test.rb b/test/models/link_checker/extractor_test.rb index 23ebe3b3486..1480e28951b 100644 --- a/test/models/link_checker/extractor_test.rb +++ b/test/models/link_checker/extractor_test.rb @@ -4,7 +4,7 @@ module LinkChecker class ExtractorTest < ActiveSupport::TestCase - test '#extract' do + test '#extract_links' do extractor = LinkChecker::Extractor.new(<<~TEXT, 'apt', 'https://bootcamp.fjord.jp/1234') aptとはdebianでソフトウェアをネットワークからインストールするコマンドです。 #{' '} @@ -44,7 +44,7 @@ class ExtractorTest < ActiveSupport::TestCase ) ] - assert_equal expected, extractor.extract + assert_equal expected, extractor.extract_links end end end From 9617aad82590b335842003507ef720227d4cc5f6 Mon Sep 17 00:00:00 2001 From: AudioStakes Date: Sat, 29 Jan 2022 16:18:31 +0900 Subject: [PATCH 04/30] =?UTF-8?q?extractor=5Ftest.rb=20=E3=81=A7=E4=BD=9C?= =?UTF-8?q?=E6=88=90=E3=81=97=E3=81=A6=E3=81=84=E3=82=8B=20Page=20?= =?UTF-8?q?=E3=81=AE=E3=83=86=E3=82=B9=E3=83=88=E3=83=87=E3=83=BC=E3=82=BF?= =?UTF-8?q?=E3=82=92=E3=83=95=E3=82=A3=E3=82=AF=E3=82=B9=E3=83=81=E3=83=A3?= =?UTF-8?q?=E3=81=B8=E7=A7=BB=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/fixtures/pages.yml | 20 +++++++++++++++++ test/models/link_checker/extractor_test.rb | 25 +++++----------------- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/test/fixtures/pages.yml b/test/fixtures/pages.yml index 8a08d5ffa79..c2581312004 100644 --- a/test/fixtures/pages.yml +++ b/test/fixtures/pages.yml @@ -45,3 +45,23 @@ page7: user: komagata practice: practice1 published_at: "2021-10-01 00:00:00" + +page8: + title: apt + body: |- + aptとはdebianでソフトウェアをネットワークからインストールするコマンドです。 + + [TEST](/test)(/test2) + + [missing](test) + + - 参考 + - [APT - Wikipedia](http://ja.wikipedia.org/wiki/APT) + + ## Q&A + + - Q. `$ apt-cache search vim` の検索結果が多すぎる + - A. [正規表現](https://ja.wikipedia.org/wiki/%E6%AD%A3%E8%A6%8F%E8%A1%A8%E7%8F%BE) を使う。 + - 完全一致: `$ apt-cache search ^vim$` + - 前方一致: `$ apt-cache search ^vim` + user: komagata diff --git a/test/models/link_checker/extractor_test.rb b/test/models/link_checker/extractor_test.rb index 1480e28951b..1d4944beb0a 100644 --- a/test/models/link_checker/extractor_test.rb +++ b/test/models/link_checker/extractor_test.rb @@ -5,42 +5,27 @@ module LinkChecker class ExtractorTest < ActiveSupport::TestCase test '#extract_links' do - extractor = LinkChecker::Extractor.new(<<~TEXT, 'apt', 'https://bootcamp.fjord.jp/1234') - aptとはdebianでソフトウェアをネットワークからインストールするコマンドです。 - #{' '} - [TEST](/test)(/test2) - #{' '} - [missing](test) - #{' '} - - 参考 - - [APT - Wikipedia](http://ja.wikipedia.org/wiki/APT) - #{' '} - ## Q&A - #{' '} - - Q. `$ apt-cache search vim` の検索結果が多すぎる - - A. [正規表現](https://ja.wikipedia.org/wiki/%E6%AD%A3%E8%A6%8F%E8%A1%A8%E7%8F%BE) を使う。 - - 完全一致: `$ apt-cache search ^vim$` - - 前方一致: `$ apt-cache search ^vim` - TEXT + page = pages(:page8) + extractor = LinkChecker::Extractor.new(page.body, page.title, "https://bootcamp.fjord.jp/#{page.path}") expected = [ LinkChecker::Link.new( 'TEST', 'https://bootcamp.fjord.jp/test', 'apt', - 'https://bootcamp.fjord.jp/1234' + "https://bootcamp.fjord.jp/#{page.path}" ), LinkChecker::Link.new( 'APT - Wikipedia', 'http://ja.wikipedia.org/wiki/APT', 'apt', - 'https://bootcamp.fjord.jp/1234' + "https://bootcamp.fjord.jp/#{page.path}" ), LinkChecker::Link.new( '正規表現', 'https://ja.wikipedia.org/wiki/%E6%AD%A3%E8%A6%8F%E8%A1%A8%E7%8F%BE', 'apt', - 'https://bootcamp.fjord.jp/1234' + "https://bootcamp.fjord.jp/#{page.path}" ) ] From 9873ba8bd160938f7deb582aeae90b854cb33483 Mon Sep 17 00:00:00 2001 From: AudioStakes Date: Sun, 30 Jan 2022 15:29:40 +0900 Subject: [PATCH 05/30] =?UTF-8?q?Extractor=20=E3=82=92=E5=88=9D=E6=9C=9F?= =?UTF-8?q?=E5=8C=96=E3=81=99=E3=82=8B=E6=99=82=E3=81=AE=E5=BC=95=E6=95=B0?= =?UTF-8?q?=E3=81=A8=E3=81=97=E3=81=A6=E3=83=89=E3=82=AD=E3=83=A5=E3=83=A1?= =?UTF-8?q?=E3=83=B3=E3=83=88=E3=81=AE=E3=81=BF=E3=82=92=E6=B8=A1=E3=81=99?= =?UTF-8?q?=E3=82=88=E3=81=86=E3=81=AB=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/link_checker/extractor.rb | 25 ++++---------- test/models/link_checker/extractor_test.rb | 38 +++++++++++++++++++--- 2 files changed, 40 insertions(+), 23 deletions(-) diff --git a/app/models/link_checker/extractor.rb b/app/models/link_checker/extractor.rb index 264e80154ff..e551cd4eca1 100644 --- a/app/models/link_checker/extractor.rb +++ b/app/models/link_checker/extractor.rb @@ -6,32 +6,21 @@ module LinkChecker class Extractor class << self def extract_all_links(documents) - documents.flat_map do |document| - new( - document.body, - document.title, - "https://bootcamp.fjord.jp#{document.path}" - ) - end + documents.flat_map { |document| new(document).extract_links } end end - def initialize(markdown_text, source_title, source_url) - @markdown_text = markdown_text - @source_title = source_title - @source_url = source_url + def initialize(document) + @document = document end def extract_links - links = @markdown_text.scan(/\[(.*?)\]\((.+?)\)/)&.map do |match| + links = @document.body.scan(/\[(.*?)\]\((.+?)\)/)&.map do |match| title = match[0].strip url = match[1].strip - if url.match?(%r{^/}) - uri = URI(@source_url) - uri.path = '' - url = uri.to_s + url - end - Link.new(title, url, @source_title, @source_url) + url = "https://bootcamp.fjord.jp#{url}" if url.match?(%r{^/}) + + Link.new(title, url, @document.title, "https://bootcamp.fjord.jp#{@document.path}") end links.select { |link| URI::DEFAULT_PARSER.make_regexp.match(link.url) } diff --git a/test/models/link_checker/extractor_test.rb b/test/models/link_checker/extractor_test.rb index 1d4944beb0a..9f1de53063e 100644 --- a/test/models/link_checker/extractor_test.rb +++ b/test/models/link_checker/extractor_test.rb @@ -4,28 +4,56 @@ module LinkChecker class ExtractorTest < ActiveSupport::TestCase - test '#extract_links' do + test '#extract_links from a practice' do page = pages(:page8) - extractor = LinkChecker::Extractor.new(page.body, page.title, "https://bootcamp.fjord.jp/#{page.path}") + extractor = LinkChecker::Extractor.new(page) expected = [ LinkChecker::Link.new( 'TEST', 'https://bootcamp.fjord.jp/test', 'apt', - "https://bootcamp.fjord.jp/#{page.path}" + "https://bootcamp.fjord.jp#{page.path}" ), LinkChecker::Link.new( 'APT - Wikipedia', 'http://ja.wikipedia.org/wiki/APT', 'apt', - "https://bootcamp.fjord.jp/#{page.path}" + "https://bootcamp.fjord.jp#{page.path}" ), LinkChecker::Link.new( '正規表現', 'https://ja.wikipedia.org/wiki/%E6%AD%A3%E8%A6%8F%E8%A1%A8%E7%8F%BE', 'apt', - "https://bootcamp.fjord.jp/#{page.path}" + "https://bootcamp.fjord.jp#{page.path}" + ) + ] + + assert_equal expected, extractor.extract_links + end + + test '#extract_links from a page' do + practice = practices(:practice3) + extractor = LinkChecker::Extractor.new(practice) + + expected = [ + LinkChecker::Link.new( + 'CPUとは', + 'https://www.pc-master.jp/words/cpu.html', + 'PC性能の見方を知る', + "https://bootcamp.fjord.jp#{practice.path}" + ), + LinkChecker::Link.new( + 'HDDが分かる', + 'http://homepage2.nifty.com/kamurai/HDD.htm', + 'PC性能の見方を知る', + "https://bootcamp.fjord.jp#{practice.path}" + ), + LinkChecker::Link.new( + 'Macの型番調べ辛い', + 'https://docs.komagata.org/4433', + 'PC性能の見方を知る', + "https://bootcamp.fjord.jp#{practice.path}" ) ] From 12084f9f1e2d4482aa83c791e1fdab9a9b790a9b Mon Sep 17 00:00:00 2001 From: AudioStakes Date: Sun, 30 Jan 2022 15:11:29 +0900 Subject: [PATCH 06/30] =?UTF-8?q?=E3=83=86=E3=82=B9=E3=83=88=E3=81=A7?= =?UTF-8?q?=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E3=81=99=E3=82=8B?= =?UTF-8?q?=E3=81=9F=E3=82=81=20URL=20=E3=81=8C=20valid=20=E3=81=A7?= =?UTF-8?q?=E3=81=82=E3=82=8B=E3=81=93=E3=81=A8=E3=81=AE=E7=A2=BA=E8=AA=8D?= =?UTF-8?q?=E3=82=92=E3=83=A1=E3=82=BD=E3=83=83=E3=83=89=E3=81=AB=E3=81=97?= =?UTF-8?q?=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/link_checker/checker.rb | 7 +++++++ test/models/link_checker/checker_test.rb | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/app/models/link_checker/checker.rb b/app/models/link_checker/checker.rb index cc1d707016a..d84b663beca 100644 --- a/app/models/link_checker/checker.rb +++ b/app/models/link_checker/checker.rb @@ -10,6 +10,13 @@ class Checker ].freeze attr_reader :errors + class << self + def valid_url?(url) + url = URI.encode_www_form_component(url) + URI.parse(url) + end + end + def initialize(links = []) @links = links @errors = [] diff --git a/test/models/link_checker/checker_test.rb b/test/models/link_checker/checker_test.rb index 49c390829ee..e120914d02e 100644 --- a/test/models/link_checker/checker_test.rb +++ b/test/models/link_checker/checker_test.rb @@ -49,6 +49,14 @@ class CheckerTest < ActiveSupport::TestCase ) end + test '.valid_url? returns true with a valid url' do + assert Checker.valid_url?('http://example.com') + end + + test '.valid_url? returns false with an invalid url' do + assert_not Checker.valid_url?('http://invalid space exists') + end + test '#check' do VCR.use_cassette 'link_checker/checker/check' do links = [@link_hdd, @link_cpu, @link_not_exist, @link_example, @link_mac] From 3f26c088ad1362f33d104531245dd9d7da664804 Mon Sep 17 00:00:00 2001 From: AudioStakes Date: Sun, 30 Jan 2022 15:12:29 +0900 Subject: [PATCH 07/30] =?UTF-8?q?=E3=83=86=E3=82=B9=E3=83=88=E3=81=A7?= =?UTF-8?q?=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E3=81=99=E3=82=8B?= =?UTF-8?q?=E3=81=9F=E3=82=81=20URL=20=E3=81=AE=20host=20=E3=81=8C?= =?UTF-8?q?=E3=83=AA=E3=83=B3=E3=82=AF=E5=88=87=E3=82=8C=E5=88=A4=E5=AE=9A?= =?UTF-8?q?=E5=AF=BE=E8=B1=A1=E5=A4=96=E3=81=8B=E7=A2=BA=E8=AA=8D=E3=81=99?= =?UTF-8?q?=E3=82=8B=E3=83=A1=E3=82=BD=E3=83=83=E3=83=89=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/link_checker/checker.rb | 14 ++++++++------ test/models/link_checker/checker_test.rb | 9 +++++++++ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/app/models/link_checker/checker.rb b/app/models/link_checker/checker.rb index d84b663beca..f6b81ee0b4c 100644 --- a/app/models/link_checker/checker.rb +++ b/app/models/link_checker/checker.rb @@ -4,7 +4,7 @@ module LinkChecker class Checker - DENY_LIST = %w[ + DENY_HOST = %w[ codepen.io www.amazon.co.jp ].freeze @@ -15,6 +15,11 @@ def valid_url?(url) url = URI.encode_www_form_component(url) URI.parse(url) end + + def denied_host?(url) + uri = URI.parse(url) + DENY_HOST.include?(uri.host) + end end def initialize(links = []) @@ -38,11 +43,8 @@ def notify_broken_links def check locks = Queue.new 5.times { locks.push :lock } - @links.reject! do |link| - url = URI.encode_www_form_component(link.url) - uri = URI.parse(url) - !uri || DENY_LIST.include?(uri.host) - end + + @links = @links.select { |link| self.class.valid_url?(link.url) && !self.class.denied_host?(link.url) } @links.map do |link| Thread.new do diff --git a/test/models/link_checker/checker_test.rb b/test/models/link_checker/checker_test.rb index e120914d02e..0ebcc5ddb86 100644 --- a/test/models/link_checker/checker_test.rb +++ b/test/models/link_checker/checker_test.rb @@ -57,6 +57,15 @@ class CheckerTest < ActiveSupport::TestCase assert_not Checker.valid_url?('http://invalid space exists') end + test '.denied_host? returns true when an url contains a denied host' do + assert Checker.denied_host?('https://codepen.io/') + assert Checker.denied_host?('https://www.amazon.co.jp') + end + + test '.denied_host? returns false when an url doesn\'t contain a denied host' do + assert_not Checker.denied_host?('http://example.com') + end + test '#check' do VCR.use_cassette 'link_checker/checker/check' do links = [@link_hdd, @link_cpu, @link_not_exist, @link_example, @link_mac] From e3e230403f075327469bcd0889bfc1de5203f8cb Mon Sep 17 00:00:00 2001 From: AudioStakes Date: Sun, 30 Jan 2022 14:44:36 +0900 Subject: [PATCH 08/30] =?UTF-8?q?Check.valid=5Furl=3F=20=E3=81=AE=E3=83=86?= =?UTF-8?q?=E3=82=B9=E3=83=88=E5=AF=BE=E8=B1=A1=E3=81=AB=E9=9D=9EASCII(?= =?UTF-8?q?=E6=97=A5=E6=9C=AC=E8=AA=9E)=E3=81=AE=20URL=20=E3=82=92?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/models/link_checker/checker_test.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/test/models/link_checker/checker_test.rb b/test/models/link_checker/checker_test.rb index 0ebcc5ddb86..5c755966d20 100644 --- a/test/models/link_checker/checker_test.rb +++ b/test/models/link_checker/checker_test.rb @@ -51,6 +51,7 @@ class CheckerTest < ActiveSupport::TestCase test '.valid_url? returns true with a valid url' do assert Checker.valid_url?('http://example.com') + assert Checker.valid_url?('https://ja.wikipedia.org/wiki/あ') end test '.valid_url? returns false with an invalid url' do From 2ab1ac092f7a2cc25de0fe72fe951d92bef93304 Mon Sep 17 00:00:00 2001 From: AudioStakes Date: Fri, 28 Jan 2022 16:33:14 +0900 Subject: [PATCH 09/30] =?UTF-8?q?=E9=9D=9E=20ASCII(=E6=97=A5=E6=9C=AC?= =?UTF-8?q?=E8=AA=9E)=20=E3=82=92=E5=90=AB=E3=82=80=20URL=20=E3=81=AE?= =?UTF-8?q?=E3=83=91=E3=83=BC=E3=82=B9=E3=82=92=20gem=20=E3=81=AE=20Addres?= =?UTF-8?q?sable=20=E3=81=A7=E5=AF=BE=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Gemfile | 1 + Gemfile.lock | 1 + app/models/link_checker/checker.rb | 8 +++++--- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index a5d68b07d9a..3741b56211c 100644 --- a/Gemfile +++ b/Gemfile @@ -20,6 +20,7 @@ gem 'active_flag' gem 'active_storage_validations' gem 'acts_as_list' gem 'acts-as-taggable-on', '~> 7.0' +gem 'addressable' gem 'any_login' gem 'cocoon' gem 'coffee-rails', '~> 5.0.0' diff --git a/Gemfile.lock b/Gemfile.lock index 1342bb93836..90525dccda5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -527,6 +527,7 @@ DEPENDENCIES active_storage_validations acts-as-taggable-on (~> 7.0) acts_as_list + addressable any_login bootsnap (>= 1.4.4) bullet diff --git a/app/models/link_checker/checker.rb b/app/models/link_checker/checker.rb index f6b81ee0b4c..e1b48ca9743 100644 --- a/app/models/link_checker/checker.rb +++ b/app/models/link_checker/checker.rb @@ -12,12 +12,14 @@ class Checker class << self def valid_url?(url) - url = URI.encode_www_form_component(url) - URI.parse(url) + uri = Addressable::URI.parse(url) + uri.scheme && uri.host + rescue Addressable::URI::InvalidURIError + false end def denied_host?(url) - uri = URI.parse(url) + uri = Addressable::URI.parse(url) DENY_HOST.include?(uri.host) end end From cbc5bf76439ee4da1c5abc05eb47695bb42b898b Mon Sep 17 00:00:00 2001 From: AudioStakes Date: Fri, 28 Jan 2022 16:47:10 +0900 Subject: [PATCH 10/30] =?UTF-8?q?=E9=9D=9E=20ASCII(=E6=97=A5=E6=9C=AC?= =?UTF-8?q?=E8=AA=9E)=20=E3=81=AE=20URL=20=E3=82=92=20Addressable#normaliz?= =?UTF-8?q?e=20=E3=81=A7=E3=82=A8=E3=83=B3=E3=82=B3=E3=83=BC=E3=83=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/link_checker/client.rb | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/app/models/link_checker/client.rb b/app/models/link_checker/client.rb index 39f826907b8..6a90fb32f36 100644 --- a/app/models/link_checker/client.rb +++ b/app/models/link_checker/client.rb @@ -11,22 +11,11 @@ def initialize(url) end def request - @url = encode_ja(@url) - uri = URI.parse(@url) - response = Net::HTTP.get_response(uri) + uri = Addressable::URI.parse(@url) + response = Net::HTTP.get_response(uri.normalize) response.code.to_i rescue StandardError => _e false end - - def encode_ja(url) - url.split(//).map do |c| - if c.match?(%r{[-_.!~*'()a-zA-Z0-9;/?:@&=+$,%#]}) - c - else - CGI.escape(c) - end - end.join - end end end From 471321ed8f1d2a7c2ed0059a770ffa446bb26ea2 Mon Sep 17 00:00:00 2001 From: AudioStakes Date: Fri, 28 Jan 2022 15:01:18 +0900 Subject: [PATCH 11/30] =?UTF-8?q?=E3=82=B9=E3=83=AC=E3=83=83=E3=83=89?= =?UTF-8?q?=E3=81=AE=E3=82=AD=E3=83=A5=E3=83=BC=20=E3=82=92=20Checker=20?= =?UTF-8?q?=E3=81=AE=20=E3=82=A4=E3=83=B3=E3=82=B9=E3=82=BF=E3=83=B3?= =?UTF-8?q?=E3=82=B9=E5=A4=89=E6=95=B0=E3=81=A8=E3=81=97=E3=81=A6=E6=8C=81?= =?UTF-8?q?=E3=81=9F=E3=81=9B=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E3=81=97?= =?UTF-8?q?=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/link_checker/checker.rb | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/app/models/link_checker/checker.rb b/app/models/link_checker/checker.rb index e1b48ca9743..d0a86668dd3 100644 --- a/app/models/link_checker/checker.rb +++ b/app/models/link_checker/checker.rb @@ -28,6 +28,8 @@ def initialize(links = []) @links = links @errors = [] @broken_links = [] + @locks = Queue.new + 5.times { @locks.push :lock } end def notify_broken_links @@ -43,18 +45,15 @@ def notify_broken_links end def check - locks = Queue.new - 5.times { locks.push :lock } - @links = @links.select { |link| self.class.valid_url?(link.url) && !self.class.denied_host?(link.url) } @links.map do |link| Thread.new do - lock = locks.pop + lock = @locks.pop response = Client.request(link.url) link.response = response @broken_links << link if !response || response > 403 - locks.push lock + @locks.push lock end end.each(&:join) From f0afbb88d5ba19748304bb46ed8d1f77d722e50f Mon Sep 17 00:00:00 2001 From: AudioStakes Date: Sun, 30 Jan 2022 15:06:46 +0900 Subject: [PATCH 12/30] =?UTF-8?q?=E6=9C=AB=E5=B0=BE=E3=81=8C=20`)`=20?= =?UTF-8?q?=E3=81=AE=20URL=20=E3=82=92=E6=AD=A3=E8=A6=8F=E8=A1=A8=E7=8F=BE?= =?UTF-8?q?=E3=81=A7=E5=AE=8C=E5=85=A8=E4=B8=80=E8=87=B4=E3=81=A7=E3=81=8D?= =?UTF-8?q?=E3=82=8B=E3=81=93=E3=81=A8=E3=82=92=E7=A2=BA=E8=AA=8D=E3=81=99?= =?UTF-8?q?=E3=82=8B=E3=83=86=E3=82=B9=E3=83=88=E3=82=92=E8=BF=BD=E5=8A=A0?= =?UTF-8?q?=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/link_checker/extractor.rb | 4 +++- test/models/link_checker/extractor_test.rb | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/app/models/link_checker/extractor.rb b/app/models/link_checker/extractor.rb index e551cd4eca1..a8d9f439706 100644 --- a/app/models/link_checker/extractor.rb +++ b/app/models/link_checker/extractor.rb @@ -4,6 +4,8 @@ module LinkChecker Link = Struct.new(:title, :url, :source_title, :source_url, :response) class Extractor + MARKDOWN_LINK_REGEXP = /\[(.*?)\]\((.+?)\)/.freeze + class << self def extract_all_links(documents) documents.flat_map { |document| new(document).extract_links } @@ -15,7 +17,7 @@ def initialize(document) end def extract_links - links = @document.body.scan(/\[(.*?)\]\((.+?)\)/)&.map do |match| + links = @document.body.scan(MARKDOWN_LINK_REGEXP)&.map do |match| title = match[0].strip url = match[1].strip url = "https://bootcamp.fjord.jp#{url}" if url.match?(%r{^/}) diff --git a/test/models/link_checker/extractor_test.rb b/test/models/link_checker/extractor_test.rb index 9f1de53063e..2bd5cf8fe90 100644 --- a/test/models/link_checker/extractor_test.rb +++ b/test/models/link_checker/extractor_test.rb @@ -59,5 +59,24 @@ class ExtractorTest < ActiveSupport::TestCase assert_equal expected, extractor.extract_links end + + test '::MARKDOWN_LINK_REGEXP' do + actual = pages(:page8).body.scan(Extractor::MARKDOWN_LINK_REGEXP) + expected = [ + ['TEST', '/test'], + %w[missing test], + ['APT - Wikipedia', 'http://ja.wikipedia.org/wiki/APT'], + ['正規表現', 'https://ja.wikipedia.org/wiki/%E6%AD%A3%E8%A6%8F%E8%A1%A8%E7%8F%BE'] + ] + + assert_equal expected, actual + end + + test '::MARKDOWN_LINK_REGEXP matches exactly an url that ends with closing parentheses' do + actual = '[末尾が閉じ括弧の URL のリンク](https://example.com/(hoge))'.scan(Extractor::MARKDOWN_LINK_REGEXP) + expected = [['末尾が閉じ括弧の URL のリンク', 'https://example.com/(hoge)']] + + assert_equal expected, actual + end end end From 111d4d465fc2ea036d66b067b2bdbdf9643ec7d7 Mon Sep 17 00:00:00 2001 From: AudioStakes Date: Wed, 26 Jan 2022 16:35:33 +0900 Subject: [PATCH 13/30] =?UTF-8?q?=E6=9C=AB=E5=B0=BE=E3=81=8C=20`)`=20?= =?UTF-8?q?=E3=81=AE=20URL=20=E3=82=92=E6=AD=A3=E8=A6=8F=E8=A1=A8=E7=8F=BE?= =?UTF-8?q?=E3=81=A7=E5=AE=8C=E5=85=A8=E4=B8=80=E8=87=B4=E3=81=A7=E3=81=8D?= =?UTF-8?q?=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/link_checker/extractor.rb | 8 ++++---- test/models/link_checker/extractor_test.rb | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/models/link_checker/extractor.rb b/app/models/link_checker/extractor.rb index a8d9f439706..e3f3014cdcf 100644 --- a/app/models/link_checker/extractor.rb +++ b/app/models/link_checker/extractor.rb @@ -4,7 +4,7 @@ module LinkChecker Link = Struct.new(:title, :url, :source_title, :source_url, :response) class Extractor - MARKDOWN_LINK_REGEXP = /\[(.*?)\]\((.+?)\)/.freeze + MARKDOWN_LINK_REGEXP = /\[(.*?)\]\((#{URI::DEFAULT_PARSER.make_regexp}|.+?)\)/.freeze class << self def extract_all_links(documents) @@ -17,9 +17,9 @@ def initialize(document) end def extract_links - links = @document.body.scan(MARKDOWN_LINK_REGEXP)&.map do |match| - title = match[0].strip - url = match[1].strip + links = @document.body.scan(MARKDOWN_LINK_REGEXP).map do |title, url| + title = title.strip + url = url.strip url = "https://bootcamp.fjord.jp#{url}" if url.match?(%r{^/}) Link.new(title, url, @document.title, "https://bootcamp.fjord.jp#{@document.path}") diff --git a/test/models/link_checker/extractor_test.rb b/test/models/link_checker/extractor_test.rb index 2bd5cf8fe90..7c912f407fc 100644 --- a/test/models/link_checker/extractor_test.rb +++ b/test/models/link_checker/extractor_test.rb @@ -61,7 +61,7 @@ class ExtractorTest < ActiveSupport::TestCase end test '::MARKDOWN_LINK_REGEXP' do - actual = pages(:page8).body.scan(Extractor::MARKDOWN_LINK_REGEXP) + actual = pages(:page8).body.scan(Extractor::MARKDOWN_LINK_REGEXP).map { |match| match.take(2) } expected = [ ['TEST', '/test'], %w[missing test], @@ -73,7 +73,7 @@ class ExtractorTest < ActiveSupport::TestCase end test '::MARKDOWN_LINK_REGEXP matches exactly an url that ends with closing parentheses' do - actual = '[末尾が閉じ括弧の URL のリンク](https://example.com/(hoge))'.scan(Extractor::MARKDOWN_LINK_REGEXP) + actual = '[末尾が閉じ括弧の URL のリンク](https://example.com/(hoge))'.scan(Extractor::MARKDOWN_LINK_REGEXP).map { |match| match.take(2) } expected = [['末尾が閉じ括弧の URL のリンク', 'https://example.com/(hoge)']] assert_equal expected, actual From 9e2536caf13bd1d32d6eed07f9a3a7f7e1e8da40 Mon Sep 17 00:00:00 2001 From: AudioStakes Date: Sun, 30 Jan 2022 13:38:18 +0900 Subject: [PATCH 14/30] =?UTF-8?q?Extractor=20=E3=82=AF=E3=83=A9=E3=82=B9?= =?UTF-8?q?=E3=81=AE=E6=AD=A3=E8=A6=8F=E8=A1=A8=E7=8F=BE=E3=82=92=20URL=20?= =?UTF-8?q?=E3=82=82=E3=81=97=E3=81=8F=E3=81=AF=E3=83=91=E3=82=B9=E3=81=AE?= =?UTF-8?q?=E3=81=BF=E3=81=AB=E3=83=9E=E3=83=83=E3=83=81=E3=81=99=E3=82=8B?= =?UTF-8?q?=E3=82=88=E3=81=86=E3=81=AB=E4=BF=AE=E6=AD=A3=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/link_checker/extractor.rb | 10 +++++----- test/models/link_checker/extractor_test.rb | 1 - 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/app/models/link_checker/extractor.rb b/app/models/link_checker/extractor.rb index e3f3014cdcf..72da8caf438 100644 --- a/app/models/link_checker/extractor.rb +++ b/app/models/link_checker/extractor.rb @@ -4,7 +4,7 @@ module LinkChecker Link = Struct.new(:title, :url, :source_title, :source_url, :response) class Extractor - MARKDOWN_LINK_REGEXP = /\[(.*?)\]\((#{URI::DEFAULT_PARSER.make_regexp}|.+?)\)/.freeze + MARKDOWN_LINK_REGEXP = %r{\[(.*?)\]\((#{URI::DEFAULT_PARSER.make_regexp}|/.*?)\)}.freeze class << self def extract_all_links(documents) @@ -17,12 +17,12 @@ def initialize(document) end def extract_links - links = @document.body.scan(MARKDOWN_LINK_REGEXP).map do |title, url| + links = @document.body.scan(MARKDOWN_LINK_REGEXP).map do |title, url_or_path| title = title.strip - url = url.strip - url = "https://bootcamp.fjord.jp#{url}" if url.match?(%r{^/}) + url_or_path = url_or_path.strip + url_or_path = "https://bootcamp.fjord.jp#{url_or_path}" if url_or_path.match?(%r{^/}) - Link.new(title, url, @document.title, "https://bootcamp.fjord.jp#{@document.path}") + Link.new(title, url_or_path, @document.title, "https://bootcamp.fjord.jp#{@document.path}") end links.select { |link| URI::DEFAULT_PARSER.make_regexp.match(link.url) } diff --git a/test/models/link_checker/extractor_test.rb b/test/models/link_checker/extractor_test.rb index 7c912f407fc..760ad9724e9 100644 --- a/test/models/link_checker/extractor_test.rb +++ b/test/models/link_checker/extractor_test.rb @@ -64,7 +64,6 @@ class ExtractorTest < ActiveSupport::TestCase actual = pages(:page8).body.scan(Extractor::MARKDOWN_LINK_REGEXP).map { |match| match.take(2) } expected = [ ['TEST', '/test'], - %w[missing test], ['APT - Wikipedia', 'http://ja.wikipedia.org/wiki/APT'], ['正規表現', 'https://ja.wikipedia.org/wiki/%E6%AD%A3%E8%A6%8F%E8%A1%A8%E7%8F%BE'] ] From 2dede3e137b8536ccdb4ecbdba1221355241f4ea Mon Sep 17 00:00:00 2001 From: AudioStakes Date: Fri, 28 Jan 2022 15:04:15 +0900 Subject: [PATCH 15/30] =?UTF-8?q?=E3=83=86=E3=82=AD=E3=82=B9=E3=83=88?= =?UTF-8?q?=E3=81=8B=E3=82=89=E6=8A=BD=E5=87=BA=E3=81=97=E3=81=9F=20URL=20?= =?UTF-8?q?=E3=81=AE=E3=83=90=E3=83=AA=E3=83=87=E3=83=BC=E3=82=B7=E3=83=A7?= =?UTF-8?q?=E3=83=B3=E3=82=92=20Extractor=20=E3=82=AF=E3=83=A9=E3=82=B9?= =?UTF-8?q?=E3=81=A7=E8=A1=8C=E3=82=8F=E3=81=AA=E3=81=84=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/link_checker/extractor.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/models/link_checker/extractor.rb b/app/models/link_checker/extractor.rb index 72da8caf438..36a350a6ea3 100644 --- a/app/models/link_checker/extractor.rb +++ b/app/models/link_checker/extractor.rb @@ -17,15 +17,13 @@ def initialize(document) end def extract_links - links = @document.body.scan(MARKDOWN_LINK_REGEXP).map do |title, url_or_path| + @document.body.scan(MARKDOWN_LINK_REGEXP).map do |title, url_or_path| title = title.strip url_or_path = url_or_path.strip url_or_path = "https://bootcamp.fjord.jp#{url_or_path}" if url_or_path.match?(%r{^/}) Link.new(title, url_or_path, @document.title, "https://bootcamp.fjord.jp#{@document.path}") end - - links.select { |link| URI::DEFAULT_PARSER.make_regexp.match(link.url) } end end end From a88c60eae813ccfcdd95cc478e78cd80df93d401 Mon Sep 17 00:00:00 2001 From: AudioStakes Date: Mon, 31 Jan 2022 13:40:51 +0900 Subject: [PATCH 16/30] =?UTF-8?q?SSL=E3=82=B5=E3=83=BC=E3=83=90=E3=83=BC?= =?UTF-8?q?=E8=A8=BC=E6=98=8E=E6=9B=B8=E3=81=AE=E6=A4=9C=E8=A8=BC=E3=81=AB?= =?UTF-8?q?=E5=A4=B1=E6=95=97=E3=81=99=E3=82=8B=E3=82=B5=E3=82=A4=E3=83=88?= =?UTF-8?q?=E3=81=AE=E3=83=AC=E3=82=B9=E3=83=9D=E3=83=B3=E3=82=B9=E3=82=82?= =?UTF-8?q?=E7=A2=BA=E8=AA=8D=E3=81=A7=E3=81=8D=E3=82=8B=E3=81=93=E3=81=A8?= =?UTF-8?q?=E3=82=92=E3=83=86=E3=82=B9=E3=83=88=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/models/link_checker/client_test.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/models/link_checker/client_test.rb b/test/models/link_checker/client_test.rb index 4edddc9374c..6afd7c41366 100644 --- a/test/models/link_checker/client_test.rb +++ b/test/models/link_checker/client_test.rb @@ -39,5 +39,11 @@ class ClientTest < ActiveSupport::TestCase assert_equal 200, Client.new('https://developer.mozilla.org/ja/docs/Web/JavaScript#Tutorials').request end end + + test '#request can get a response from the server whose server certificate cannot be verified' do + VCR.use_cassette 'link_checker/client/request/www.tablesgenerator.com' do + assert_equal 200, Client.request('https://www.tablesgenerator.com/markdown_tables') + end + end end end From bf1429a9be51fd9bbe14657e2468b8cb6b266fed Mon Sep 17 00:00:00 2001 From: AudioStakes Date: Mon, 31 Jan 2022 16:52:08 +0900 Subject: [PATCH 17/30] =?UTF-8?q?SSL=E3=82=B5=E3=83=BC=E3=83=90=E3=83=BC?= =?UTF-8?q?=E8=A8=BC=E6=98=8E=E6=9B=B8=E3=81=AE=E6=A4=9C=E8=A8=BC=E3=81=AB?= =?UTF-8?q?=E5=A4=B1=E6=95=97=E3=81=99=E3=82=8B=E3=82=B5=E3=82=A4=E3=83=88?= =?UTF-8?q?=E3=81=AB=E3=82=A2=E3=82=AF=E3=82=BB=E3=82=B9=E3=81=99=E3=82=8B?= =?UTF-8?q?=E3=81=A8=E3=81=8D=E3=81=AF=E8=A8=BC=E6=98=8E=E6=9B=B8=E3=82=92?= =?UTF-8?q?=E6=A4=9C=E8=A8=BC=E3=81=97=E3=81=AA=E3=81=84=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit host が www.tablesgenerator.com のサーバーへ Net::HTTP.get_response でアクセスすると、次の例外が発生した。 OpenSSL::SSL::SSLError: SSL_connect returned=1 errno=0 state=error: certificate verify failed (unable to get local issuer certificate) 調査した結果、この host のサーバーに中間証明書が設定されていないことが原因で証明書を検証できていないようだった。 そのためこの host の URL は証明書を検証せずにレスポンスを確認することとした。 --- app/models/link_checker/client.rb | 14 +- .../request/www_tablesgenerator_com.yml | 786 ++++++++++++++++++ 2 files changed, 797 insertions(+), 3 deletions(-) create mode 100644 test/cassettes/link_checker/client/request/www_tablesgenerator_com.yml diff --git a/app/models/link_checker/client.rb b/app/models/link_checker/client.rb index 6a90fb32f36..1ee456dd10b 100644 --- a/app/models/link_checker/client.rb +++ b/app/models/link_checker/client.rb @@ -2,6 +2,10 @@ module LinkChecker class Client + SSL_VERIFY_NONE_HOST = [ + 'www.tablesgenerator.com' + ].freeze + def self.request(url) new(url).request end @@ -11,9 +15,13 @@ def initialize(url) end def request - uri = Addressable::URI.parse(@url) - response = Net::HTTP.get_response(uri.normalize) - response.code.to_i + uri = Addressable::URI.parse(@url).normalize + options = {} + options[:ssl_verify_mode] = OpenSSL::SSL::VERIFY_NONE if SSL_VERIFY_NONE_HOST.include?(uri.host) + response = OpenURI.open_uri(uri, **options) + response.status.first.to_i + rescue OpenURI::HTTPError => e + e.io.status.first.to_i rescue StandardError => _e false end diff --git a/test/cassettes/link_checker/client/request/www_tablesgenerator_com.yml b/test/cassettes/link_checker/client/request/www_tablesgenerator_com.yml new file mode 100644 index 00000000000..ec2e5e52a48 --- /dev/null +++ b/test/cassettes/link_checker/client/request/www_tablesgenerator_com.yml @@ -0,0 +1,786 @@ +--- +http_interactions: +- request: + method: get + uri: https://www.tablesgenerator.com/markdown_tables + body: + encoding: US-ASCII + string: '' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 200 + message: OK + headers: + Content-Type: + - text/html; charset=utf-8 + Vary: + - Accept-Encoding + X-Cloud-Trace-Context: + - b21f3d017cec79b869bd22f04aab9e8d + Date: + - Mon, 31 Jan 2022 09:42:22 GMT + Server: + - Google Frontend + Cache-Control: + - private + Transfer-Encoding: + - chunked + body: + encoding: UTF-8 + base64_string: | + PCFET0NUWVBFIGh0bWw+CjxodG1sIGxhbmc9ImVuIj4KICA8aGVhZD4KICAg + IDxtZXRhIGNoYXJzZXQ9InV0Zi04Ij4KICAgIDx0aXRsZT5NYXJrZG93biBU + YWJsZXMgZ2VuZXJhdG9yIC0gVGFibGVzR2VuZXJhdG9yLmNvbTwvdGl0bGU+ + CiAgICA8bWV0YSBuYW1lPSJ2aWV3cG9ydCIgY29udGVudD0id2lkdGg9ZGV2 + aWNlLXdpZHRoLCBpbml0aWFsLXNjYWxlPTEuMCI+CiAgICA8bWV0YSBuYW1l + PSJkZXNjcmlwdGlvbiIgY29udGVudD0iRWFzaWx5IGNyZWF0ZSB0YWJsZXMg + aW4gZXh0ZW5kZWQgTWFya2Rvd24gZm9ybWF0IHN1cHBvcnRlZCBieSBNYXJr + ZG93biBIZXJlIGFuZCBHRk0uIj4KICAgIDxtZXRhIG5hbWU9ImF1dGhvciIg + Y29udGVudD0iIj4KICAgIDxsaW5rIHJlbD0ic2hvcnRjdXQgaWNvbiIgaHJl + Zj0iL3N0YXRpYy9pbWcvZmF2aWNvbi5wbmciPgogICAgPCEtLVtpZiBJRV0+ + CiAgICA8bWV0YSBodHRwLWVxdWl2PSJYLVVBLUNvbXBhdGlibGUiIGNvbnRl + bnQ9IklFPUVkZ2UiIC8+CiAgICA8IVtlbmRpZl0tLT4KCiAgICA8bGluayBo + cmVmPSdodHRwczovL2ZvbnRzLmdvb2dsZWFwaXMuY29tL2Nzcz9mYW1pbHk9 + UXVpY2tzYW5kJyByZWw9J3N0eWxlc2hlZXQnIHR5cGU9J3RleHQvY3NzJz4K + ICAgIDxsaW5rIHJlbD0ic3R5bGVzaGVldCIgaHJlZj0iL3N0YXRpYy9jc3Mv + Y29tYmluZWQuNDI3LmNzcyIgdHlwZT0idGV4dC9jc3MiIG1lZGlhPSJhbGwi + IHRpdGxlPSJubyB0aXRsZSI+CgogICAgPHNjcmlwdCB0eXBlPSJ0ZXh0L2ph + dmFzY3JpcHQiIHNyYz0iL3N0YXRpYy9qcy9jb21iaW5lZC5iYXNlLjQyNy5q + cyIgZGF0YS1tYW51YWw+PC9zY3JpcHQ+CgogICAgCiAgICAKICAgIAoKICAg + IAoKICAgIAogICAgCiAgICAKICAgIAogICAgCiAgICAKICAgIAoKICAgIAog + ICAgCiAgICAKICAgIAoKICAgIAogICAgCiAgICAKICAgIAoKICAgIAoKICAg + IAogICAgCiAgICAKICAgIAogICAgCiAgICAKICAgIAogICAgCiAgICAKICAg + IAogICAgCiAgICAKICAgIAogICAgCiAgICAKICAgIAogICAgCiAgICAKICAg + IAogICAgCiAgICAKICAgIAoKICAgIAogICAgCgogICAgCiAgICAKICAgIAog + ICAgCiAgICAKICAgIAogICAgCiAgICAKICAgIAoKICAgIAogIDwvaGVhZD4K + CiAgPGJvZHk+CiAgICA8ZGl2IGNsYXNzPSJjb250YWluZXIiPgoKICAgICAg + PGRpdiBjbGFzcz0ibWFzdGhlYWQiPgogICAgICAgIAogICAgICAgIDxkaXYg + Y2xhc3M9Im5hdmJhciI+CiAgICAgICAgICA8aDEgaWQ9ImxvZ28iPjxhIGhy + ZWY9Ii8iIHRpdGxlPSJUYWJsZXMgR2VuZXJhdG9yIj5UYWJsZXMgR2VuZXJh + dG9yPC9hPjwvaDE+CiAgICAgICAgICAKCjx1bCBjbGFzcz0ibmF2IiBpZD0i + dG9wLW5hdiI+CiAgICA8bGkgPgogICAgICAgIDxhIGhyZWY9Ii9sYXRleF90 + YWJsZXMiIAogICAgICAgICAgICB0aXRsZT0iTGFUZVggdGFibGVzIGdlbmVy + YXRvciI+TGFUZVg8L2E+CiAgICA8L2xpPgogICAgPGxpID4KICAgICAgIDxh + IGhyZWY9Ii9odG1sX3RhYmxlcyIgCiAgICAgICAgICAgdGl0bGU9IkhUTUwg + dGFibGVzIGdlbmVyYXRvciI+SFRNTDwvYT4KICAgIDwvbGk+CiAgICA8bGkg + PgogICAgICAgPGEgaHJlZj0iL3RleHRfdGFibGVzIiAKICAgICAgICAgICB0 + aXRsZT0iUGxhaW4gdGV4dCB0YWJsZXMgZ2VuZXJhdG9yIj5UZXh0PC9hPgog + ICAgPC9saT4KICAgIDxsaSBjbGFzcz0iYWN0aXZlIj4KICAgICAgIDxhIGhy + ZWY9Ii9tYXJrZG93bl90YWJsZXMiIAogICAgICAgICAgIHRpdGxlPSJNYXJr + ZG93biB0YWJsZXMgZ2VuZXJhdG9yIj5NYXJrZG93bjwvYT4KICAgIDwvbGk+ + CiAgICA8bGkgPgogICAgICAgPGEgaHJlZj0iL21lZGlhd2lraV90YWJsZXMi + IAogICAgICAgICAgIHRpdGxlPSJNZWRpYVdpa2kgdGFibGVzIGdlbmVyYXRv + ciI+TWVkaWFXaWtpPC9hPgogICAgPC9saT4KPC91bD4KCiAgICAgICAgPC9k + aXY+PCEtLSAvLm5hdmJhciAtLT4KICAgICAgICAKICAgICAgICAKICAgICAg + PC9kaXY+CgogICAgICAKCjxkaXYgaWQ9Im1haW4taGVhZGVyIj4KPC9kaXY+ + Cgo8ZGl2IGlkPSJtaWRkbGUtc2VjdGlvbiI+CiAgICA8ZGl2IGlkPSJtYWlu + LWJsb2NrIj4KICAgICAgICA8ZGl2IHN0eWxlPSJwb3NpdGlvbjogcmVsYXRp + dmUiPgogICAgICAgICAgICA8ZGl2IGlkPSJtZW51LXRvb2xiYXItd3JhcHBl + ciI+CiAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJtYWluLW1lbnUiPgog + ICAgICAgICAgICAgICAgICAgIAo8dWwgY2xhc3M9Im5hdiBuYXYtcGlsbHMi + PgogICAgPGxpIGNsYXNzPSJkcm9wZG93biI+PCEtLSBGaWxlIGl0ZW0gLS0+ + CjxhIGNsYXNzPSJkcm9wZG93bi10b2dnbGUiIGlkPSJmaWxlX2l0ZW0iIHJv + bGU9ImJ1dHRvbiIKICAgIGRhdGEtdG9nZ2xlPSJkcm9wZG93biIgaHJlZj0i + IyI+RmlsZSA8YiBjbGFzcz0iY2FyZXQiPjwvYj48L2E+Cjx1bCBpZD0ibWVu + dTMiIGNsYXNzPSJkcm9wZG93bi1tZW51IiByb2xlPSJtZW51IgogICAgYXJp + YS1sYWJlbGxlZGJ5PSJmaWxlX2l0ZW0iPgoKICAgIDxsaSByb2xlPSJwcmVz + ZW50YXRpb24iPjxhIHJvbGU9Im1lbnVpdGVtIiB0YWJpbmRleD0iLTEiIGhy + ZWY9IiMiCiAgICAgICAgaWQ9Im5ld190YWJsZV9pdGVtIiAgCiAgICAgICAg + ZGF0YS10b2dnbGU9Im1vZGFsIgogICAgICAgIGRhdGEtdGFyZ2V0PSIjbmV3 + X3RhYmxlX2RpYWxvZyI+TmV3IHRhYmxlLi4uPC9hPjwvbGk+CgogICAgPGxp + IHJvbGU9InByZXNlbnRhdGlvbiIgY2xhc3M9ImRpdmlkZXIiPjwvbGk+Cgog + ICAgPGxpIHJvbGU9InByZXNlbnRhdGlvbiI+PGEgcm9sZT0ibWVudWl0ZW0i + IHRhYmluZGV4PSItMSIgaHJlZj0iIyIKICAgICAgICBpZD0iaW1wb3J0X2Nz + dl9pdGVtIiAgCiAgICAgICAgZGF0YS10b2dnbGU9Im1vZGFsIgogICAgICAg + IGRhdGEtdGFyZ2V0PSIjaW1wb3J0X2RpYWxvZyI+SW1wb3J0IENTViBmaWxl + Li4uPC9hPjwvbGk+CgogICAgPGxpIHJvbGU9InByZXNlbnRhdGlvbiI+PGEg + cm9sZT0ibWVudWl0ZW0iIHRhYmluZGV4PSItMSIgaHJlZj0iIyIKICAgICAg + ICBpZD0iaW1wb3J0X3Bhc3RlZF9pdGVtIiAgCiAgICAgICAgZGF0YS10b2dn + bGU9Im1vZGFsIgogICAgICAgIGRhdGEtdGFyZ2V0PSIjaW1wb3J0X3Bhc3Rl + ZF9kaWFsb2ciPlBhc3RlIHRhYmxlIGRhdGEuLi48L2E+PC9saT4KCiAgICAK + CiAgICA8bGkgcm9sZT0icHJlc2VudGF0aW9uIiBjbGFzcz0iZGl2aWRlciI+ + PC9saT4KCiAgICA8bGkgcm9sZT0icHJlc2VudGF0aW9uIj48YSByb2xlPSJt + ZW51aXRlbSIgdGFiaW5kZXg9Ii0xIiBocmVmPSIjIgogICAgICAgIGlkPSJz + YXZlX3RhYmxlX2l0ZW0iICAKICAgICAgICBkYXRhLXRvZ2dsZT0ibW9kYWwi + CiAgICAgICAgZGF0YS10YXJnZXQ9IiNzYXZlX3RhYmxlX2RpYWxvZyI+U2F2 + ZSB0YWJsZS4uLjwvYT48L2xpPgoKICAgIDxsaSByb2xlPSJwcmVzZW50YXRp + b24iPjxhIHJvbGU9Im1lbnVpdGVtIiB0YWJpbmRleD0iLTEiIGhyZWY9IiMi + CiAgICAgICAgaWQ9ImxvYWRfdGFibGVfaXRlbSIgIAogICAgICAgIGRhdGEt + dG9nZ2xlPSJtb2RhbCIKICAgICAgICBkYXRhLXRhcmdldD0iI2xvYWRfdGFi + bGVfZGlhbG9nIj5Mb2FkIHRhYmxlLi4uPC9hPjwvbGk+CgogICAgPGxpIHJv + bGU9InByZXNlbnRhdGlvbiIgY2xhc3M9ImRpdmlkZXIiPjwvbGk+CiAgICA8 + bGk+CiAgICA8YSByb2xlPSJidXR0b24iCiAgIGhyZWY9IiMiCiAgIHRpdGxl + PSJDcmVhdGVzIGFuIGV4YW1wbGUgdGFibGUuIFByZXNzIENUUkwrWiAob3Ig + bWVudSBFZGl0IC8gVW5kbykgdG8gZ28gYmFjayB0byB5b3VyIHRhYmxlIgog + ICBpZD0ic2hvd19leGFtcGxlX2J0biI+Q3JlYXRlIGFuIGV4YW1wbGUgdGFi + bGUgPC9hPgogICAgPC9saT4KICAgIAoKPC91bD4KPC9saT48IS0tIEZpbGUg + aXRlbSAtLT4KICAgIDxsaSBjbGFzcz0iZHJvcGRvd24iPgo8YSBjbGFzcz0i + ZHJvcGRvd24tdG9nZ2xlIiBpZD0iY29sdW1uLWRyb3Bkb3duIiByb2xlPSJi + dXR0b24iCiAgIGRhdGEtdG9nZ2xlPSJkcm9wZG93biIgaHJlZj0iIyI+RWRp + dCA8YiBjbGFzcz0iY2FyZXQiPjwvYj48L2E+Cjx1bCBjbGFzcz0iZHJvcGRv + d24tbWVudSIgcm9sZT0ibWVudSIgYXJpYS1sYWJlbGxlZGJ5PSJjb2x1bW4t + ZHJvcGRvd24iPgoKICAgIDxsaSByb2xlPSJwcmVzZW50YXRpb24iPjxhIHJv + bGU9Im1lbnVpdGVtIiB0YWJpbmRleD0iLTEiCiAgICAgICAgaHJlZj0iIyIg + aWQ9ImVkaXRfdW5kbyI+PGkgY2xhc3M9Imljb24tZml4ZWQtd2lkdGggaWNv + bi1yb3RhdGUtbGVmdCI+PC9pPiBVbmRvIChDdHJsK1opPC9hPjwvbGk+Cgog + ICAgPGxpIHJvbGU9InByZXNlbnRhdGlvbiI+PGEgcm9sZT0ibWVudWl0ZW0i + IHRhYmluZGV4PSItMSIKICAgICAgICBocmVmPSIjIiBpZD0iZWRpdF9yZWRv + Ij48aSBjbGFzcz0iaWNvbi1maXhlZC13aWR0aCBpY29uLXJvdGF0ZS1yaWdo + dCI+PC9pPiBSZXBlYXQgKEN0cmwrWSk8L2E+PC9saT4KCiAgICA8bGkgcm9s + ZT0icHJlc2VudGF0aW9uIiBjbGFzcz0iZGl2aWRlciI+PC9saT4KCiAgICA8 + bGkgcm9sZT0icHJlc2VudGF0aW9uIj4KICAgICAgICA8YSByb2xlPSJtZW51 + aXRlbSIKICAgICAgICAgICB0YWJpbmRleD0iLTEiCiAgICAgICAgICAgaHJl + Zj0iIyIKICAgICAgICAgICBpZD0iZWRpdF9maW5kX3JlcGxhY2UiPjxpIGNs + YXNzPSJpY29uLWZpeGVkLXdpZHRoIGljb24tc2VhcmNoIj48L2k+IEZpbmQg + YW5kIHJlcGxhY2UuLi48L2E+PC9saT4KCiAgICA8bGkgcm9sZT0icHJlc2Vu + dGF0aW9uIiBjbGFzcz0iZGl2aWRlciI+PC9saT4KCiAgICA8bGkgY2xhc3M9 + ImRyb3Bkb3duLXN1Ym1lbnUiPgogICAgICAgIDxhIGhyZWY9IiMiIHRhYmlu + ZGV4PSItMSIgdGl0bGU9IkNoYW5nZSBudW1iZXIgZm9ybWF0dGluZyBpbiB0 + aGUgc2VsZWN0ZWQgY2VsbHMiPkZvcm1hdCBudW1iZXJzPC9hPgogICAgICAg + IDx1bCBjbGFzcz0iZHJvcGRvd24tbWVudSIgaWQ9Im51bWJlci1mb3JtYXQt + bWVudSI+PC91bD4KICAgIDwvbGk+CiAgICAgICAgCiAgICA8bGkgcm9sZT0i + cHJlc2VudGF0aW9uIiBjbGFzcz0iZGl2aWRlciI+PC9saT4KCiAgICA8bGkg + cm9sZT0icHJlc2VudGF0aW9uIj4KICAgICAgICA8YSByb2xlPSJtZW51aXRl + bSIgdGFiaW5kZXg9Ii0xIiBocmVmPSIjIiBpZD0iZWRpdF9hdXRvc2F2ZSI+ + CiAgICAgICAgPGkgY2xhc3M9Imljb24tZml4ZWQtd2lkdGggaWNvbi1jaGVj + ay1taW51cyI+PC9pPiBBdXRvc2F2ZSB0YWJsZSBsb2NhbGx5PC9hPgogICAg + PC9saT4KPC91bD4KPC9saT4KCiAgICA8bGkgY2xhc3M9ImRyb3Bkb3duIj4K + ICAgIDxhIGNsYXNzPSJkcm9wZG93bi10b2dnbGUiIGlkPSJtZW51X3RhYmxl + IiByb2xlPSJidXR0b24iIGRhdGEtdG9nZ2xlPSJkcm9wZG93biIgaHJlZj0i + IyI+VGFibGUgPGIgY2xhc3M9ImNhcmV0Ij48L2I+PC9hPgogICAgPHVsIGlk + PSJtZW51MSIgY2xhc3M9ImRyb3Bkb3duLW1lbnUiIHJvbGU9Im1lbnUiIAog + ICAgICAgIGFyaWEtbGFiZWxsZWRieT0iZHJvcF90YWJsZSI+CiAgICAgICAg + PGxpIHJvbGU9InByZXNlbnRhdGlvbiIgY2xhc3M9ImRyb3Bkb3duLXN1Ym1l + bnUiPgogICAgPGEgcm9sZT0ibWVudWl0ZW0iIHRhYmluZGV4PSItMSIgaHJl + Zj0iIyIgaWQ9InRhYmxlX3Jlc2l6ZSI+CiAgICAgICAgPGkgY2xhc3M9Imlj + b24tZml4ZWQtd2lkdGggaWNvbi1yZXNpemUtZnVsbCI+PC9pPiBTZXQgc2l6 + ZQogICAgPC9hPgogICAgPHVsIGNsYXNzPSJkcm9wZG93bi1tZW51Ij4KICAg + ICAgICA8bGk+PGRpdiBjbGFzcz0idGFibGVfc2l6ZV9jaG9vc2VyIj48L2Rp + dj48L2xpPgogICAgPC91bD4KPC9saT4KICAgICAgICA8bGkgcm9sZT0icHJl + c2VudGF0aW9uIiBjbGFzcz0iZGl2aWRlciI+PC9saT4KPGxpIHJvbGU9InBy + ZXNlbnRhdGlvbiI+CiAgICA8YSByb2xlPSJtZW51aXRlbSIgdGFiaW5kZXg9 + Ii0xIiBocmVmPSIjIiBpZD0idGFibGVfcmVzZXQiPgogICAgICAgIDxpIGNs + YXNzPSJpY29uLWZpeGVkLXdpZHRoIGljb24tY2lyY2xlLWJsYW5rIj48L2k+ + IENsZWFyIHRhYmxlCiAgICA8L2E+CjwvbGk+CiAgICAgICAgPGxpIHJvbGU9 + InByZXNlbnRhdGlvbiIgY2xhc3M9ImRpdmlkZXIiPjwvbGk+CjxsaSByb2xl + PSJwcmVzZW50YXRpb24iPgogICAgPGEgcm9sZT0ibWVudWl0ZW0iIHRhYmlu + ZGV4PSItMSIgaHJlZj0iIyIgaWQgPSJ0YWJsZV90cmFuc3Bvc2UiPgogICAg + ICAgIDxpIGNsYXNzPSJpY29uLWZpeGVkLXdpZHRoIGljb24tcm90YXRlLXJp + Z2h0Ij48L2k+IFRyYW5zcG9zZSB0YWJsZTwvYT4KICAgIDwvYT4KPC9saT4K + ICAgIDwvdWw+CiAgICA8L2xpPgoKICAgIDxsaSBjbGFzcz0iZHJvcGRvd24i + Pgo8YSBjbGFzcz0iZHJvcGRvd24tdG9nZ2xlIiBpZD0iY29sdW1uLWRyb3Bk + b3duIiByb2xlPSJidXR0b24iCiAgIGRhdGEtdG9nZ2xlPSJkcm9wZG93biIg + aHJlZj0iIyI+Q29sdW1uIDxiIGNsYXNzPSJjYXJldCI+PC9iPjwvYT4KCjx1 + bCBpZD0ibWVudTEiIGNsYXNzPSJkcm9wZG93bi1tZW51IiByb2xlPSJtZW51 + IiBhcmlhLWxhYmVsbGVkYnk9ImNvbHVtbi1kcm9wZG93biI+CiAgICA8bGkg + Y2xhc3M9ImRyb3Bkb3duLXN1Ym1lbnUiPgogICAgICAgIDxhIGhyZWY9IiMi + IHRhYmluZGV4PSItMSI+VGV4dCBhbGlnbjwvYT4KICAgICAgICA8dWwgY2xh + c3M9ImRyb3Bkb3duLW1lbnUiPgogICAgICAgICAgICA8bGk+PGEgaHJlZj0i + IyIgdGFiaW5kZXg9Ii0xIiBjbGFzcz0ibWFya2VkIiBpZD0iY29sX2FsaWdu + X2xlZnQiPgogICAgICAgICAgICAgICAgPGkgY2xhc3M9Imljb24tZml4ZWQt + d2lkdGggaWNvbi1hbGlnbi1sZWZ0Ij48L2k+IExlZnQ8L2E+PC9saT4KICAg + ICAgICAgICAgPGxpPjxhIGhyZWY9IiMiIHRhYmluZGV4PSItMSIgaWQ9ImNv + bF9hbGlnbl9jZW50ZXIiPgogICAgICAgICAgICAgICAgPGkgY2xhc3M9Imlj + b24tZml4ZWQtd2lkdGggaWNvbi1hbGlnbi1jZW50ZXIiPjwvaT4gQ2VudGVy + PC9hPjwvbGk+CiAgICAgICAgICAgIDxsaT48YSBocmVmPSIjIiB0YWJpbmRl + eD0iLTEiIGlkPSJjb2xfYWxpZ25fcmlnaHQiPgogICAgICAgICAgICAgICAg + PGkgY2xhc3M9Imljb24tZml4ZWQtd2lkdGggaWNvbi1hbGlnbi1yaWdodCI+ + PC9pPiBSaWdodDwvYT48L2xpPgogICAgICAgIDwvdWw+CiAgICA8L2xpPgoK + ICAgIDxsaSByb2xlPSJwcmVzZW50YXRpb24iIGNsYXNzPSJkaXZpZGVyIj48 + L2xpPgoKICAgIDxsaSByb2xlPSJwcmVzZW50YXRpb24iPjxhIHJvbGU9Im1l + bnVpdGVtIiB0YWJpbmRleD0iLTEiCiAgICAgICAgaHJlZj0iIyIgaWQ9ImNv + bF9pbnNlcnRfbGVmdCI+PGkgY2xhc3M9Imljb24tZml4ZWQtd2lkdGggaWNv + bi1hcnJvdy1sZWZ0Ij48L2k+IEluc2VydCB0byB0aGUgbGVmdDwvYT48L2xp + PgoKICAgIDxsaSByb2xlPSJwcmVzZW50YXRpb24iPjxhIHJvbGU9Im1lbnVp + dGVtIiB0YWJpbmRleD0iLTEiCiAgICAgICAgaHJlZj0iIyIgaWQ9ImNvbF9p + bnNlcnRfcmlnaHQiPjxpIGNsYXNzPSJpY29uLWZpeGVkLXdpZHRoIGljb24t + YXJyb3ctcmlnaHQiPjwvaT4gSW5zZXJ0IHRvIHRoZSByaWdodDwvYT48L2xp + PgoKICAgIDxsaSByb2xlPSJwcmVzZW50YXRpb24iIGNsYXNzPSJkaXZpZGVy + Ij48L2xpPgoKICAgIDxsaSByb2xlPSJwcmVzZW50YXRpb24iPjxhIHJvbGU9 + Im1lbnVpdGVtIiB0YWJpbmRleD0iLTEiIAogICAgICAgIGhyZWY9IiMiIGlk + PSJjb2xfcmVtb3ZlIj48aSBjbGFzcz0iaWNvbi1maXhlZC13aWR0aCBpY29u + LXRyYXNoIj48L2k+IFJlbW92ZTwvYT48L2xpPgo8L3VsPgo8L2xpPgogICAg + PGxpIGNsYXNzPSJkcm9wZG93biI+CjxhIGNsYXNzPSJkcm9wZG93bi10b2dn + bGUiIGlkPSJyb3ctZHJvcGRvd24iIHJvbGU9ImJ1dHRvbiIgZGF0YS10b2dn + bGU9ImRyb3Bkb3duIiBocmVmPSIjIj5Sb3cgPGIgY2xhc3M9ImNhcmV0Ij48 + L2I+PC9hPgo8dWwgaWQ9Im1lbnUyIiBjbGFzcz0iZHJvcGRvd24tbWVudSIg + cm9sZT0ibWVudSIgYXJpYS1sYWJlbGxlZGJ5PSJyb3ctZHJvcGRvd24iPgog + ICAgPGxpIHJvbGU9InByZXNlbnRhdGlvbiI+PGEgcm9sZT0ibWVudWl0ZW0i + IHRhYmluZGV4PSItMSIgaHJlZj0iIyIKICAgICAgICBpZD0icm93X2luc2Vy + dF9hYm92ZSI+PGkgY2xhc3M9Imljb24tZml4ZWQtd2lkdGggaWNvbi1hcnJv + dy11cCI+PC9pPiBJbnNlcnQgYWJvdmU8L2E+PC9saT4KICAgIDxsaSByb2xl + PSJwcmVzZW50YXRpb24iPjxhIHJvbGU9Im1lbnVpdGVtIiB0YWJpbmRleD0i + LTEiIGhyZWY9IiMiCiAgICAgICAgaWQ9InJvd19pbnNlcnRfYmVsb3ciPjxp + IGNsYXNzPSJpY29uLWZpeGVkLXdpZHRoIGljb24tYXJyb3ctZG93biI+PC9p + PiBJbnNlcnQgYmVsb3c8L2E+PC9saT4KICAgIDxsaSByb2xlPSJwcmVzZW50 + YXRpb24iIGNsYXNzPSJkaXZpZGVyIj48L2xpPgogICAgPGxpIHJvbGU9InBy + ZXNlbnRhdGlvbiI+PGEgcm9sZT0ibWVudWl0ZW0iIHRhYmluZGV4PSItMSIK + ICAgICAgICBocmVmPSIjIiBpZD0icm93X3JlbW92ZSI+PGkgY2xhc3M9Imlj + b24tZml4ZWQtd2lkdGggaWNvbi10cmFzaCI+PC9pPiBSZW1vdmU8L2E+PC9s + aT4KPC91bD4KPC9saT4KCiAgICA8bGkgY2xhc3M9ImRyb3Bkb3duIj48IS0t + IEhlbHAgbWVudSAtLT4KICAgIDxhIGNsYXNzPSJkcm9wZG93bi10b2dnbGUi + CiAgICAgICBpZD0iZmlsZV9pdGVtIiByb2xlPSJidXR0b24iCiAgICAgICBk + YXRhLXRvZ2dsZT0iZHJvcGRvd24iIGhyZWY9IiMiPkhlbHAgPGIgY2xhc3M9 + ImNhcmV0Ij48L2I+PC9hPgogICAgPHVsIGNsYXNzPSJkcm9wZG93bi1tZW51 + IgogICAgICAgIHJvbGU9Im1lbnUiCiAgICAgICAgYXJpYS1sYWJlbGxlZGJ5 + PSJmaWxlX2l0ZW0iPgogICAgICAgIDxsaSByb2xlPSJwcmVzZW50YXRpb24i + PjxhIHJvbGU9Im1lbnVpdGVtIiB0YWJpbmRleD0iLTEiIGhyZWY9IiMiCiAg + ICAgICAgICAgIGlkPSJzaG93X2hlbHBfaXRlbSIKICAgICAgICAgICAgZGF0 + YS10b2dnbGU9Im1vZGFsIgogICAgICAgICAgICBkYXRhLXRhcmdldD0iI3F1 + aWNrX2hlbHBfZGlhbG9nIj5RdWljayBoZWxwPC9hPjwvbGk+CgogICAgICAg + IDxsaSByb2xlPSJwcmVzZW50YXRpb24iPgogICAgICAgICAgICA8YSByb2xl + PSJtZW51aXRlbSIgdGFiaW5kZXg9Ii0xIgogICAgICAgICAgICAgICB0aXRs + ZT0iRG9jdW1lbnRhdGlvbiBvZiB0aGUgVGFibGVzIEdlbmVyYXRvciIKICAg + ICAgICAgICAgICAgaHJlZj0iL2hlbHAvIj5Eb2N1bWVudGF0aW9uPC9hPgog + ICAgICAgIDwvbGk+CgogICAgICAgIDxsaSByb2xlPSJwcmVzZW50YXRpb24i + PgogICAgICAgICAgICA8YSByb2xlPSJtZW51aXRlbSIgdGFiaW5kZXg9Ii0x + IgogICAgICAgICAgICAgICBocmVmPSIvY2hhbmdlbG9nIj5DaGFuZ2Vsb2c8 + L2E+CiAgICAgICAgPC9saT4KCiAgICAgICAgPGxpIHJvbGU9InByZXNlbnRh + dGlvbiIgY2xhc3M9ImRpdmlkZXIiPjwvbGk+CiAgICAgICAgPGxpIHJvbGU9 + InByZXNlbnRhdGlvbiI+CiAgICAgICAgICAgIDxhIGhyZWY9Ii9jb250YWN0 + IiAKICAgICAgICAgICAgICAgdGl0bGU9IkNvbnRhY3Qgd2l0aCB1cyI+Q29u + dGFjdCB3aXRoIHVzPC9hPgogICAgICAgIDwvbGk+CiAgICA8L3VsPgo8L2xp + PjwhLS0gSGVscCBtZW51IC0tPgo8L3VsPgoKICAgICAgICAgICAgICAgIDwv + ZGl2PgogICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0iYnRuLXRvb2xiYXIi + PgogICAgICAgICAgICAgICAgICAgIAoKPGRpdiBjbGFzcz0iYnRuLWdyb3Vw + Ij4KICAgPGEgY2xhc3M9InRvb2xiYXItYnRuIiAKICAgICAgIHRpdGxlPSJM + ZWZ0IGFsaWduIHRoZSBjb250ZW50cyBvZiBzZWxlY3RlZCBjZWxscyIKICAg + ICAgIGlkPSJsZWZ0X2FsaWduX2J0biI+PGkgY2xhc3M9InRvb2xiYXItaWNv + biBpY29uLWFsaWduLWxlZnQiPjwvaT48L2E+CiAgIDxhIGNsYXNzPSJ0b29s + YmFyLWJ0biIgCiAgICAgICB0aXRsZT0iQ2VudGVyIHRoZSBjb250ZW50cyBv + ZiBzZWxlY3RlZCBjZWxscyIKICAgICAgIGlkPSJjZW50ZXJfYWxpZ25fYnRu + Ij48aSBjbGFzcz0idG9vbGJhci1pY29uIGljb24tYWxpZ24tY2VudGVyIj48 + L2k+PC9hPgogICA8YSBjbGFzcz0idG9vbGJhci1idG4iIAogICAgICAgdGl0 + bGU9IlJpZ2h0IGFsaWduIHRoZSBjb250ZW50cyBvZiBzZWxlY3RlZCBjZWxs + cyIKICAgICAgIGlkPSJyaWdodF9hbGlnbl9idG4iPjxpIGNsYXNzPSJ0b29s + YmFyLWljb24gaWNvbi1hbGlnbi1yaWdodCI+PC9pPjwvYT4KPC9kaXY+Cgog + ICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgIDwvZGl2PgoKICAg + ICAgICAgICAgPGRpdiBzdHlsZT0icG9zaXRpb246IHJlbGF0aXZlIj4KICAg + ICAgICAgICAgICAgIDxkaXYgaWQ9ImVkaXRlZF90YWJsZV9jb250YWluZXIi + PjwvZGl2PgogICAgICAgICAgICA8L2Rpdj4KICAgICAgICA8L2Rpdj4KCiAg + ICAgICAgPGRpdiBpZD0idGFibGVfZWRpdG9yX2V4cGFuZGVyIj48c3Bhbj7i + hqcgRXhwYW5kIOKGpzwvc3Bhbj48c3Bhbj7ihqUgQ29sbGFwc2Ug4oalPC9z + cGFuPjwvZGl2PgogICAgICAgIDxkaXYgaWQ9ImdlbmVyYXRlX3RhYmxlX3Bh + bmVsIj48L2Rpdj4KICAgICAgICA8ZGl2IGlkPSJyZXN1bHQtYm94Ij4KICAg + ICAgICAgICAgPGRpdiBjbGFzcz0ibm90ZSI+PHN0cm9uZz5SZXN1bHQ8L3N0 + cm9uZz4gPHNwYW4gY2xhc3M9Im11dGVkIj4oY2xpY2sgIkdlbmVyYXRlIiB0 + byByZWZyZXNoKTwvc3Bhbj48L2Rpdj4KICAgICAgICAgICAgPGRpdiBjbGFz + cz0iYnRuIGJ0bi1zdWNjZXNzIGNvcHktdG8tY2xpcGJvYXJkLXdvcmthcm91 + bmQiIAogICAgICAgICAgICAgICAgc3R5bGU9ImRpc3BsYXk6IG5vbmU7IGZs + b2F0OiByaWdodDsgZm9udC13ZWlnaHQ6IGJvbGQiPgogICAgICAgICAgICAg + ICAgPGkgY2xhc3M9Imljb24tcGFzdGUiPjwvaT4gQ29weSB0byBjbGlwYm9h + cmQKICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgIDxkaXYgCiAgICAg + ICAgICAgICAgICBjbGFzcz0iYnRuIGJ0bi1zdWNjZXNzIHdpdGgtemVyby1j + bGlwYm9hcmQiCiAgICAgICAgICAgICAgICBpZD0iY29weS10by1jbGlwYm9h + cmQiCiAgICAgICAgICAgICAgICBkYXRhLWFjdGlvbi1pZD0iY29weS10by1j + bGlwYm9hcmQiCiAgICAgICAgICAgICAgICBzdHlsZT0iZm9udC13ZWlnaHQ6 + IGJvbGQ7IGNvbG9yOiB3aGl0ZTsiPjxpIGNsYXNzPSJpY29uLXBhc3RlIj48 + L2k+IENvcHkgdG8gY2xpcGJvYXJkPC9kaXY+CiAgICAgICAgICAgIAo8ZGl2 + IGNsYXNzPSJidG4gcHVsbC1yaWdodCIgCiAgICAgICAgaWQ9InByZXZpZXct + dGFibGUtYnRuIgogICAgICAgIHN0eWxlPSJtYXJnaW46IDAgMTBweCAwIDAi + CiAgICAgICAgPjxpIGNsYXNzPSJpY29uLWV5ZS1vcGVuIj48L2k+Jm5ic3A7 + Jm5ic3A7UHJldmlldzwvZGl2PgoKICAgICAgICAgICAgPGRpdiBjbGFzcz0i + Y2xlYXJmaXgiPjwvZGl2PgogICAgICAgIDwvZGl2PgoKICAgICAgICAKPHBy + ZSBjbGFzcz0ibGluZS1udW1iZXJzIiB0aXRsZT0iRG91YmxlIGNsaWNrIHRv + IHNlbGVjdCBhbGwiPjxjb2RlCmNsYXNzPSJsYW5ndWFnZS1tYXJrZG93biIg + aWQ9InJlc3VsdC1jb2RlIj48L2NvZGU+PC9wcmU+Cgo8cD4KWW91IGNhbiBu + b3cgaW1wb3J0IE1hcmtkb3duIHRhYmxlIGNvZGUgZGlyZWN0bHkgdXNpbmcg + PHN0cm9uZz5GaWxlL1Bhc3RlIHRhYmxlIGRhdGEuLi48L3N0cm9uZz4gZGlh + bG9nLgo8L3A+CgoKICAgICAgICA8ZGl2IGlkPSJib3R0b20tYmxvY2siPgog + ICAgICAgICAgICA8ZGl2IGNsYXNzPSJtYWluLWJvdHRvbSI+CiAgICAgICAg + ICAgIAo8ZGl2IGNsYXNzPSJzcGFuNiI+CgogICAgPGgzPkhvdyB0byB1c2Ug + aXQ/PC9oMz4KCiAgICA8b2w+CiAgICAgICAgPGxpPlVzaW5nIHRoZSA8ZW0+ + VGFibGU8L2VtPiBtZW51IHNldCB0aGUgZGVzaXJlZCBzaXplIG9mIHRoZSB0 + YWJsZS48L2xpPgogICAgICAgIDxsaT5FbnRlciB0aGUgdGFibGUgZGF0YSBp + bnRvIHRoZSB0YWJsZToKICAgICAgICA8dWw+CiAgICAgICAgICAgIDxsaT5z + ZWxlY3QgYW5kIGNvcHkgKEN0cmwrQykgYSB0YWJsZSBmcm9tIHRoZSBzcHJl + YWRzaGVldCAoZS5nLiBHb29nbGUgRG9jcywKICAgICAgICAgICAgTGlicmVP + ZmZpY2UgQ2FsYywgd2VicGFnZSkgYW5kIHBhc3RlIGl0IGludG8gb3VyIGVk + aXRvciAtLSBjbGljayBhIGNlbGwKICAgICAgICAgICAgYW5kIHByZXNzIEN0 + cmwrVjwvbGk+CiAgICAgICAgICAgIDxsaT5vciBqdXN0IDxzdHJvbmc+ZG91 + YmxlIGNsaWNrIGFueSBjZWxsPC9zdHJvbmc+IHRvIHN0YXJ0IGVkaXRpbmcg + aXQncyBjb250ZW50cyAtLQogICAgICAgICAgICBUYWIgYW5kIEFycm93IGtl + eXMgY2FuIGJlIHVzZWQgdG8gbmF2aWdhdGUgdGFibGUgY2VsbHM8L2xpPgog + ICAgICAgIDwvdWw+CiAgICAgICAgPC9saT4KICAgICAgICA8bGk+QWRqdXN0 + IHRleHQgYWxpZ25tZW50IGFuZCB0YWJsZSBib3JkZXJzIHVzaW5nIHRoZSBv + cHRpb25zIGZyb20gdGhlCiAgICAgICAgbWVudSBhbmQgdXNpbmcgdGhlIHRv + b2xiYXIgYnV0dG9ucyAtLSBmb3JtYXR0aW5nIGlzIGFwcGxpZWQgdG8gYWxs + IHRoZQogICAgICAgIHNlbGVjdGVkIGNlbGxzLjwvbGk+CiAgICAgICAgPGxp + PkNsaWNrICJHZW5lcmF0ZSIgYnV0dG9uIHRvIHNlZSB0aGUgZ2VuZXJhdGVk + IHRhYmxlIC0tIHNlbGVjdCBpdCBhbmQgY29weSB0byB5b3VyIGRvY3VtZW50 + LjwvbGk+CiAgICA8L29sPgoKICAgIDxoMz5NYXJrZG93biB0YWJsZXMgc3Vw + cG9ydDwvaDM+CiAgICA8cD4KICAgIEFzIHRoZSBvZmZpY2lhbCA8YSBocmVm + PSJodHRwOi8vZGFyaW5nZmlyZWJhbGwubmV0L3Byb2plY3RzL21hcmtkb3du + L3N5bnRheCIKICAgICAgICB0aXRsZT0iTWFya2Rvd24gc3ludGF4IGRvY3Vt + ZW50YXRpb24iPk1hcmtkb3duIGRvY3VtZW50YXRpb248L2E+CiAgICBzdGF0 + ZXMsIE1hcmtkb3duIGRvZXMgbm90IHByb3ZpZGUgYW55IHNwZWNpYWwgc3lu + dGF4IGZvciB0YWJsZXMuIEluc3RlYWQgaXQKICAgIHVzZXMgSFRNTCAmbHQ7 + dGFibGUmZ3Q7IHN5bnRheC4gQnV0IHRoZXJlIGV4aXN0IE1hcmtkb3duIHN5 + bnRheAogICAgZXh0ZW5zaW9ucyB3aGljaCBwcm92aWRlIGFkZGl0aW9uYWwg + c3ludGF4IGZvciBjcmVhdGluZyBzaW1wbGUgdGFibGVzLgogICAgPC9wPgoK + ICAgIDxwPgogICAgT25lIG9mIHRoZSBtb3N0IHBvcHVsYXIgaXMgPGEgaHJl + Zj0iaHR0cDovL21hcmtkb3duLWhlcmUuY29tLyIKICAgICAgICB0aXRsZT0i + TWFya2RvdyBoZXJlIj5NYXJrZG93biBIZXJlPC9hPiAmbWRhc2g7IGFuIGV4 + dGVuc2lvbiBmb3IKICAgIHBvcHVsYXIgYnJvd3NlcnMgd2hpY2ggYWxsb3dz + IHlvdSB0byBlYXNpbHkgcHJlcGFyZSBnb29kLWxvb2tpbmcgZS1tYWlscwog + ICAgdXNpbmcgTWFya2Rvd24gc3ludGF4LgogICAgPC9wPgoKICAgIDxwPgog + ICAgU2ltaWxhciB0YWJsZSBzeW50YXggaXMgdXNlZCBpbiB0aGUgPGVtPkdp + dGh1YiBGbGF2b3JlZCBNYXJrZG93bjwvZW0+LCBpbgogICAgc2hvcnQgR0ZN + IHRhYmxlcy4KICAgIDwvcD4KCjwvZGl2PjwhLS1zcGFuNi0tPgoKPGRpdiBj + bGFzcz0ic3BhbjYiPgoKICAgIDxoMz5FeGFtcGxlPC9oMz4KCiAgICA8cD4K + ICAgIEdGTSBNYXJrZG93biB0YWJsZSBzeW50YXggaXMgcXVpdGUgc2ltcGxl + LiBJdCBkb2VzIG5vdCBhbGxvdyByb3cgb3IgY2VsbAogICAgc3Bhbm5pbmcg + YXMgd2VsbCBhcyBwdXR0aW5nIG11bHRpLWxpbmUgdGV4dCBpbiBhIGNlbGwu + IFRoZSBmaXJzdCByb3cgaXMKICAgIGFsd2F5cyB0aGUgaGVhZGVyIGZvbGxv + d2VkIGJ5IGFuIGV4dHJhIGxpbmUgd2l0aCBkYXNoZXMgIi0iIGFuZCBvcHRp + b25hbAogICAgY29sb25zICI6IiBmb3IgZm9yY2luZyBjb2x1bW4gYWxpZ25t + ZW50LgogICAgPC9wPgogICAgPHByZT4KfCBUYWJsZXMgICB8ICAgICAgQXJl + ICAgICAgfCAgQ29vbCB8CnwtLS0tLS0tLS0tfDotLS0tLS0tLS0tLS0tOnwt + LS0tLS06fAp8IGNvbCAxIGlzIHwgIGxlZnQtYWxpZ25lZCB8ICQxNjAwIHwK + fCBjb2wgMiBpcyB8ICAgIGNlbnRlcmVkICAgfCAgICQxMiB8CnwgY29sIDMg + aXMgfCByaWdodC1hbGlnbmVkIHwgICAgJDEgfAogICAgPC9wcmU+Cgo8L2Rp + dj48IS0tc3BhbjYtLT4KCiAgICAgICAgICAgIDwvZGl2PgogICAgICAgIDwv + ZGl2PgogICAgPC9kaXY+CgogICAgCiAgICA8ZGl2IGlkPSJyaWdodC1jb2x1 + bW4tcmV2Ij4KICAgIDxzcGFuIHN0eWxlPSJjb2xvcjogIzU1NTsiPkFkdmVy + dGlzZW1lbnQ8L3NwYW4+PGJyPgogICAgPGRpdiBjbGFzcz0iYWQtd3JhcHBl + ciBhZC1za3lzY3JhcHBlciI+CiAgICAgICAgCjxpbnMgY2xhc3M9ImFkc2J5 + Z29vZ2xlIgogICAgIHN0eWxlPSJkaXNwbGF5OmJsb2NrIgogICAgIGRhdGEt + YWQtY2xpZW50PSJjYS1wdWItMjg2NTU4MTY3NjI1Njg2OCIKICAgICBkYXRh + LWFkLXNsb3Q9IjMyODY4MjMyMzAiCiAgICAgZGF0YS1hZC1mb3JtYXQ9ImF1 + dG8iCiAgICAgZGF0YS1mdWxsLXdpZHRoLXJlc3BvbnNpdmU9InRydWUiPjwv + aW5zPgo8c2NyaXB0PihhZHNieWdvb2dsZSA9IHdpbmRvdy5hZHNieWdvb2ds + ZSB8fCBbXSkucHVzaCh7fSk7PC9zY3JpcHQ+CgogICAgPC9kaXY+CiAgICA8 + L2Rpdj4KICAgIAogICAgCjwvZGl2PgoKCiAgICAgIDxkaXYgaWQ9ImZvb3Rl + ciI+CiAgICAgICAgPGRpdiBzdHlsZT0iZGlzcGxheTogZmxleDsgZmxleC1k + aXJlY3Rpb246IHJvdzsganVzdGlmeS1jb250ZW50OiBmbGV4LWVuZDsiPgog + ICAgICAgICAgPGEgaHJlZj0iL2Fib3V0IiB0aXRsZT0iRmluZCBvdXQgbW9y + ZSBhYm91dCBvdXQgVGFibGUgR2VuZXJhdG9yIj5BYm91dDwvYT4KICAgICAg + ICAgIDxhIGhyZWY9Ii9jaGFuZ2Vsb2ciIHRpdGxlPSJSZWFkIGFib3V0IHJl + Y2VudCBjaGFuZ2VzIHRvIG91ciBhcHBsaWNhdGlvbiI+Q2hhbmdlbG9nPC9h + PgogICAgICAgICAgPGEgaHJlZj0iIyIgb25jbGljaz0iQ29va2llQ29uc2Vu + dC5zaG93X2RpYWxvZygpO2V2ZW50LnByZXZlbnREZWZhdWx0KCk7Ij5Db29r + aWUgU2V0dGluZ3M8L2E+CiAgICAgICAgICA8YSBocmVmPSIvcHJpdmFjeS1w + b2xpY3kiPlByaXZhY3kgUG9saWN5PC9hPgogICAgICAgICAgPGEgaHJlZj0i + L2NvbnRhY3QiIHRpdGxlPSJDb250YWN0IHdpdGggdXMiPkNvbnRhY3Q8L2E+ + CiAgICAgICAgPC9kaXY+CiAgICAgICAgPGRpdj4mY29weTsgVGFibGVzR2Vu + ZXJhdG9yLmNvbTwvZGl2PgogICAgICA8L2Rpdj4KICAgIDwvZGl2PiA8IS0t + IC9jb250YWluZXIgLS0+CgogICAgCiAgICA8ZGl2IGlkPSJtZXNzYWdlX2Rp + YWxvZyIgY2xhc3M9Im1vZGFsIGhpZGUgZmFkZSIgdGFiaW5kZXg9Ii0xIiBy + b2xlPSJkaWFsb2ciIGFyaWEtbGFiZWxsZWRieT0ibWVzc2FnZV9kaWFsb2df + bGFiZWwiIGFyaWEtaGlkZGVuPSJ0cnVlIj4KICA8ZGl2IGNsYXNzPSJtb2Rh + bC1oZWFkZXIiPgogICAgPGJ1dHRvbiB0eXBlPSJidXR0b24iIGNsYXNzPSJj + bG9zZSIgZGF0YS1kaXNtaXNzPSJtb2RhbCIgYXJpYS1oaWRkZW49InRydWUi + PsOXPC9idXR0b24+CiAgICA8aDMgaWQ9Im1lc3NhZ2VfZGlhbG9nX2xhYmVs + Ij5Ob3RpZmljYXRpb248L2gzPgogIDwvZGl2PgogIDxkaXYgY2xhc3M9Im1v + ZGFsLWJvZHkiPgogICAgPHAgaWQ9Im1lc3NhZ2VfZGlhbG9nX2JvZHkiPjwv + cD4KICA8L2Rpdj4KICA8ZGl2IGNsYXNzPSJtb2RhbC1mb290ZXIiPgogICAg + PGJ1dHRvbiBjbGFzcz0iYnRuIiBkYXRhLWRpc21pc3M9Im1vZGFsIiBhcmlh + LWhpZGRlbj0idHJ1ZSI+Q2xvc2U8L2J1dHRvbj4KICA8L2Rpdj4KPC9kaXY+ + CiAgICA8ZGl2IGlkPSJuZXdfdGFibGVfZGlhbG9nIiBjbGFzcz0ibW9kYWwg + aGlkZSBmYWRlIiB0YWJpbmRleD0iLTEiIHJvbGU9ImRpYWxvZyIgYXJpYS1s + YWJlbGxlZGJ5PSJteU1vZGFsTGFiZWwiIGFyaWEtaGlkZGVuPSJ0cnVlIj4K + ICAgIDxkaXYgY2xhc3M9Im1vZGFsLWhlYWRlciI+CiAgICAgICAgPGJ1dHRv + biB0eXBlPSJidXR0b24iIGNsYXNzPSJjbG9zZSIgZGF0YS1kaXNtaXNzPSJt + b2RhbCIgYXJpYS1oaWRkZW49InRydWUiPsOXPC9idXR0b24+CiAgICAgICAg + PGgzIGlkPSJteU1vZGFsTGFiZWwiPkNyZWF0ZSBuZXcgdGFibGU8L2gzPgog + ICAgPC9kaXY+Cgo8Zm9ybSBjbGFzcz0iZm9ybS1ob3Jpem9udGFsIj4KICAg + IDxkaXYgY2xhc3M9Im1vZGFsLWJvZHkiPgogICAgICAgIDxwPgogICAgICAg + IEVudGVyIHRhYmxlIHNpemUuIFBsZWFzZSwgcmVtZW1iZXIgdGhhdCB0aGUg + Y3VycmVudCB0YWJsZSBjb250ZW50cyB3aWxsCiAgICAgICAgYmUgbG9zdC4K + ICAgICAgICA8L3A+CgogIDxkaXYgY2xhc3M9ImNvbnRyb2wtZ3JvdXAiPgog + ICAgPGxhYmVsIGNsYXNzPSJjb250cm9sLWxhYmVsIiBmb3I9Im5ld190YWJs + ZV9yb3dzIj5Sb3dzPC9sYWJlbD4KICAgIDxkaXYgY2xhc3M9ImNvbnRyb2xz + Ij4KICAgICAgPGlucHV0IHR5cGU9Im51bWJlciIgbWluPSIxIiBtYXg9IjUw + MCIgCiAgICAgICAgICAgICBpZD0ibmV3X3RhYmxlX3Jvd3NfaW5wdXQiIAog + ICAgICAgICAgICAgdmFsdWU9IjMiPgogICAgICA8c3BhbiBjbGFzcz0iaGVs + cC1pbmxpbmUiPlZhbGlkIHJhbmdlOiAxLTUwMDwvc3Bhbj4KICAgIDwvZGl2 + PgogIDwvZGl2PgogIDxkaXYgY2xhc3M9ImNvbnRyb2wtZ3JvdXAiPgogICAg + PGxhYmVsIGNsYXNzPSJjb250cm9sLWxhYmVsIiBmb3I9Im5ld190YWJsZV9j + b2x1bW5zIj5Db2x1bW5zPC9sYWJlbD4KICAgIDxkaXYgY2xhc3M9ImNvbnRy + b2xzIj4KICAgICAgPGlucHV0IHR5cGU9Im51bWJlciIgbWluPSIxIiBtYXg9 + IjIwIiAKICAgICAgICAgICAgIGlkPSJuZXdfdGFibGVfY29sdW1uc19pbnB1 + dCIgCiAgICAgICAgICAgICB2YWx1ZT0iMyI+CiAgICAgIDxzcGFuIGNsYXNz + PSJoZWxwLWlubGluZSI+VmFsaWQgcmFuZ2U6IDEtMjA8L3NwYW4+CiAgICA8 + L2Rpdj4KICA8L2Rpdj4KCiAgICA8L2Rpdj4KICAgIDxkaXYgY2xhc3M9Im1v + ZGFsLWZvb3RlciI+CiAgICAgICAgPGJ1dHRvbiBjbGFzcz0iYnRuIiBkYXRh + LWRpc21pc3M9Im1vZGFsIiBhcmlhLWhpZGRlbj0idHJ1ZSI+Q2FuY2VsPC9i + dXR0b24+CiAgICAgICAgPGlucHV0IHR5cGU9InN1Ym1pdCIgaWQ9ImNyZWF0 + ZV9uZXdfdGFibGVfYnRuIiB2YWx1ZT0iQ3JlYXRlIiBjbGFzcz0iYnRuIGJ0 + bi1wcmltYXJ5Ij4KICAgIDwvZGl2PgogICAgPC9mb3JtPgo8L2Rpdj4KICAg + IDwhLS0gSW1wb3J0IGRpYWxvZyAtLT4KPGRpdiBpZD0iaW1wb3J0X2RpYWxv + ZyIgY2xhc3M9Im1vZGFsIGhpZGUgZmFkZSIgdGFiaW5kZXg9Ii0xIiByb2xl + PSJkaWFsb2ciIGFyaWEtbGFiZWxsZWRieT0ibXlNb2RhbExhYmVsIiBhcmlh + LWhpZGRlbj0idHJ1ZSI+CiAgICA8ZGl2IGNsYXNzPSJtb2RhbC1oZWFkZXIi + PgogICAgICAgIDxidXR0b24gdHlwZT0iYnV0dG9uIiBjbGFzcz0iY2xvc2Ui + IGRhdGEtZGlzbWlzcz0ibW9kYWwiIGFyaWEtaGlkZGVuPSJ0cnVlIj7Dlzwv + YnV0dG9uPgogICAgICAgIDxoMyBpZD0ibXlNb2RhbExhYmVsIj5JbXBvcnQg + ZGF0YSBmcm9tIENTViBmaWxlPC9oMz4KICAgIDwvZGl2PgogICAgPGZvcm0g + YWN0aW9uPSIvbWFya2Rvd25fdGFibGVzIiAKICAgICAgICAgIGVuY3R5cGU9 + Im11bHRpcGFydC9mb3JtLWRhdGEiIAogICAgICAgICAgbWV0aG9kPSJwb3N0 + IgogICAgICAgICAgc3R5bGU9Im1hcmdpbjogMDsiPgogICAgPGRpdiBjbGFz + cz0ibW9kYWwtYm9keSI+CiAgICAgICAgPHA+WW91IGNhbiBpbXBvcnQgdGFi + bGUgZGF0YSBieSB1cGxvYWRpbmcgZmlsZSBpbiBDU1YgZm9ybWF0IChDb21t + YQogICAgICAgIFNlcGFyYXRlZCBWYWx1ZSkuIE1vc3Qgc3ByZWFkc2hlZXQg + c29mdHdhcmUsIGJvdGggZGVza3RvcCBhbmQKICAgICAgICBvbmxpbmUsIGFs + bG93cyB0byBzYXZlIHRhYnVsYXIgZGF0YSBpbiBDU1YgZm9ybWF0ICZtZGFz + aDsKICAgICAgICBpdCBpcyB1c3VhbGx5IGF2YWlsYWJsZSBpbiB0aGUgRmls + ZSBtZW51IHVuZGVyIHRoZSBuYW1lICJTYXZlIEFzLi4uIiBvcgogICAgICAg + ICJFeHBvcnQiLiAKICAgICAgICA8L3A+CiAgICAgICAgPGRpdj4KICAgICAg + ICAgICAgPGxhYmVsIHN0eWxlPSJkaXNwbGF5OiBpbmxpbmUtYmxvY2s7Ij5T + ZWxlY3QgQ1NWIGZpbGUgPC9sYWJlbD4KICAgICAgICAgICAgPGlucHV0IHR5 + cGU9ImZpbGUiIG5hbWU9ImltcG9ydGVkIi8+CiAgICAgICAgPC9kaXY+CiAg + ICAgICAgPHAgY2xhc3M9InRleHQtaW5mbyIgc3R5bGU9InBhZGRpbmctdG9w + OiAxMHB4OyI+CiAgICAgICAgUGxlYXNlLCBlbnN1cmUgdGhhdCB0aGUgY2hh + cmFjdGVyIGVuY29kaW5nIG9mIHlvdXIgQ1NWIGZpbGUgd2FzIHNldCB0byBV + VEYtOAogICAgICAgIG90aGVyd2lzZSBzb21lIG9mIHRoZSBjaGFyYWN0ZXJz + IG1heSBiZSBkaXNwbGF5ZWQgaW5jb3JyZWN0bHkuCiAgICAgICAgPC9wPgog + ICAgPC9kaXY+CiAgICA8ZGl2IGNsYXNzPSJtb2RhbC1mb290ZXIiPgogICAg + ICAgIDxidXR0b24gY2xhc3M9ImJ0biIgZGF0YS1kaXNtaXNzPSJtb2RhbCIg + YXJpYS1oaWRkZW49InRydWUiPkNhbmNlbDwvYnV0dG9uPgogICAgICAgIDxp + bnB1dCB0eXBlPSJzdWJtaXQiIHZhbHVlPSJJbXBvcnQiIGNsYXNzPSJidG4g + YnRuLXByaW1hcnkiPgogICAgPC9kaXY+CiAgICA8L2Zvcm0+CjwvZGl2Pjwh + LS0gSW1wb3J0IGRpYWxvZyAtLT4KICAgIDwhLS0gSW1wb3J0IGRpYWxvZyAt + LT4KPGRpdiBpZD0iaW1wb3J0X3Bhc3RlZF9kaWFsb2ciIAogICAgIGNsYXNz + PSJtb2RhbCBoaWRlIGZhZGUiCiAgICAgdGFiaW5kZXg9Ii0xIgogICAgIHJv + bGU9ImRpYWxvZyIKICAgICBhcmlhLWxhYmVsbGVkYnk9Im15TW9kYWxMYWJl + bCIKICAgICBhcmlhLWhpZGRlbj0idHJ1ZSI+CiAgICA8ZGl2IGNsYXNzPSJt + b2RhbC1oZWFkZXIiPgogICAgICAgIDxidXR0b24gdHlwZT0iYnV0dG9uIiBj + bGFzcz0iY2xvc2UiIGRhdGEtZGlzbWlzcz0ibW9kYWwiIGFyaWEtaGlkZGVu + PSJ0cnVlIj7DlzwvYnV0dG9uPgogICAgICAgIDxoMyBpZD0ibXlNb2RhbExh + YmVsIj5QYXN0ZSB0YWJsZSBkYXRhPC9oMz4KICAgIDwvZGl2PgogICAgPGZv + cm0gYWN0aW9uPSIvbWFya2Rvd25fdGFibGVzIiAKICAgICAgICAgIG1ldGhv + ZD0icG9zdCIKICAgICAgICAgIGFjY2VwdC1jaGFyc2V0PSJVVEYtOCIKICAg + ICAgICAgIHN0eWxlPSJtYXJnaW46IDA7IgogICAgICAgICAgaWQ9ImltcG9y + dF9wYXN0ZWRfZm9ybSIKICAgICAgICAgID4KICAgIDxkaXYgY2xhc3M9Im1v + ZGFsLWJvZHkiIHN0eWxlPSJoZWlnaHQ6IDUwMHB4OyBkaXNwbGF5OiBmbGV4 + OyBmbGV4LWRpcmVjdGlvbjogY29sdW1uOyI+CiAgICAgICAgPHA+CiAgICAg + ICAgUGFzdGUgKEN0cmwgKyBWKSBiZWxvdyBhbiBleGlzdGluZyB0YWJsZSBj + b3BpZWQgKEN0cmwgKyBDKSBmcm9tIGEKICAgICAgICA8ZW0+c3ByZWFkc2hl + ZXQ8L2VtPiAoZS5nLiBNaWNyb3NvZnQgRXhjZWwpLCBhIDxlbT50ZXh0IGRv + Y3VtZW50PC9lbT4sIGEgPGVtPk1hcmtkb3duIC8gSFRNPC9lbT4gY29kZSwg + b3IKICAgICAgICBldmVuIGEgPGVtPndlYnNpdGU8L2VtPi4KICAgICAgICA8 + L3A+CiAgICAgICAgPGRpdiBpZD0idGFibGVfaW1wb3J0X3Bhc3RlX3Rhcmdl + dCIgY29udGVudGVkaXRhYmxlPSJ0cnVlIj48L2Rpdj4KCiAgICAgICAgPGxh + YmVsIGNsYXNzPSJjaGVja2JveCIgc3R5bGU9Im1hcmdpbjogNXB4IDAgMCAw + OyBkaXNwbGF5OiBub25lOyI+PGlucHV0IHR5cGU9ImNoZWNrYm94IgogICAg + ICAgICAgICAgICBpZD0iaW1wb3J0X3Bhc3RlZF93aXRoX3N0eWxlIj5QYXN0 + ZSB3aXRoIGZvcm1hdHRpbmcgKGNvbG9ycywgdGV4dCBzdHlsZSwgZXRjLik8 + L2xhYmVsPgoKICAgICAgICA8dGV4dGFyZWEgaWQ9ImltcG9ydF9wYXN0ZWQi + IG5hbWU9ImltcG9ydF9wYXN0ZWQiIGFjY2VwdC1jaGFyc2V0PSJ1dGYtOCIg + c3R5bGU9ImRpc3BsYXk6IG5vbmU7Ij48L3RleHRhcmVhPgogICAgPC9kaXY+ + CiAgICA8ZGl2IGNsYXNzPSJtb2RhbC1mb290ZXIiPgogICAgICAgIDxidXR0 + b24gY2xhc3M9ImJ0biIgZGF0YS1kaXNtaXNzPSJtb2RhbCIgYXJpYS1oaWRk + ZW49InRydWUiPkNhbmNlbDwvYnV0dG9uPgogICAgICAgIDxpbnB1dCB0eXBl + PSJzdWJtaXQiIHZhbHVlPSJMb2FkIiBjbGFzcz0iYnRuIGJ0bi1wcmltYXJ5 + IiBpZD0idGFibGVfaW1wb3J0X3N1Ym1pdF9idG4iPgogICAgPC9kaXY+CiAg + ICA8L2Zvcm0+CjwvZGl2PjwhLS0gSW1wb3J0IGRpYWxvZyAtLT4KICAgIDxk + aXYgaWQ9InNhdmVfdGFibGVfZGlhbG9nIiBjbGFzcz0ibW9kYWwgaGlkZSBm + YWRlIiB0YWJpbmRleD0iLTEiIHJvbGU9ImRpYWxvZyIgYXJpYS1sYWJlbGxl + ZGJ5PSJteU1vZGFsTGFiZWwiIGFyaWEtaGlkZGVuPSJ0cnVlIj4KICAgIDxk + aXYgY2xhc3M9Im1vZGFsLWhlYWRlciI+CiAgICAgICAgPGJ1dHRvbiB0eXBl + PSJidXR0b24iIGNsYXNzPSJjbG9zZSIgZGF0YS1kaXNtaXNzPSJtb2RhbCIg + YXJpYS1oaWRkZW49InRydWUiPsOXPC9idXR0b24+CiAgICAgICAgPGgzIGlk + PSJteU1vZGFsTGFiZWwiPlNhdmUgdGFibGU8L2gzPgogICAgPC9kaXY+Cgog + ICAgPGRpdiBjbGFzcz0ibW9kYWwtYm9keSI+CiAgICAgICAgPHA+CiAgICAg + ICAgSWYgeW91IHdhbnQgdG8ga2VlcCB0aGUgdGFibGUgZm9yIGFueSBmdXR1 + cmUgZWRpdGluZyBjbGljayB0aGUKICAgICAgICAiRG93bmxvYWQiIGJ1dHRv + biBiZWxvdy4gVGhlIGZpbGUgd2l0aCB0aGUgdGFibGUgY2FuIGJlIGxvYWRl + ZCBiYWNrIHVzaW5nCiAgICAgICAgdGhlICJMb2FkIHRhYmxlIiBvcHRpb24g + ZnJvbSB0aGUgbWVudS4KICAgICAgICA8L3A+CgogICAgICAgIDxmb3JtIGlk + PSJ0YWJsZS1zYXZlLWZvcm0iIGFjdGlvbj0iL3RhYmxlLXNhdmUiIG1ldGhv + ZD0iUE9TVCIgYWNjZXB0LWNoYXJzZXQ9InV0Zi04IiBjbGFzcz0iZm9ybS1o + b3Jpem9udGFsIj4KICAgICAgICAgIDxkaXYgY2xhc3M9ImNvbnRyb2wtZ3Jv + dXAiPgogICAgICAgICAgICA8bGFiZWwgY2xhc3M9ImNvbnRyb2wtbGFiZWwi + IGZvcj0idGFibGUtbmFtZSI+VGFibGUgbmFtZTwvbGFiZWw+CiAgICAgICAg + ICAgIDxkaXYgY2xhc3M9ImNvbnRyb2xzIj4KICAgICAgICAgICAgICA8aW5w + dXQgdHlwZT0idGV4dCIgaWQ9InRhYmxlLW5hbWUiIG5hbWU9InRhYmxlLW5h + bWUiIHBsYWNlaG9sZGVyPSJFbnRlciBuYW1lLi4uIj4KICAgICAgICAgICAg + PC9kaXY+CiAgICAgICAgICA8L2Rpdj4KICAgICAgICAgIDxkaXYgY2xhc3M9 + ImNvbnRyb2wtZ3JvdXAiPgogICAgICAgICAgICA8ZGl2IGNsYXNzPSJjb250 + cm9scyI+CiAgICAgICAgICAgICAgPGEgaHJlZj0iIyIgY2xhc3M9ImJ0biBi + dG4tcHJpbWFyeSIgaWQ9InRhYmxlLXNhdmUtbGluayIgdGl0bGU9IkNsaWNr + IHRvIGRvd25sb2FkIGZpbGUgd2l0aCB0aGUgdGFibGUiPkRvd25sb2FkPC9h + PgogICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgIDwvZGl2PgogICAgICAg + ICAgICA8aW5wdXQgbmFtZT0idGFibGUtZGF0YSIgaWQ9InRhYmxlLXNhdmUt + ZGF0YSIgdHlwZT0iaGlkZGVuIj4KICAgICAgICA8L2Zvcm0+CgogICAgICAg + IDxwPgogICAgICAgIDwvcD4KICAgIDwvZGl2PgoKICAgIDxkaXYgY2xhc3M9 + Im1vZGFsLWZvb3RlciI+CiAgICAgICAgPGJ1dHRvbiBjbGFzcz0iYnRuIiBk + YXRhLWRpc21pc3M9Im1vZGFsIiBhcmlhLWhpZGRlbj0idHJ1ZSI+Q2FuY2Vs + PC9idXR0b24+CiAgICA8L2Rpdj4KPC9kaXY+PCEtLSBJbXBvcnQgZGlhbG9n + IC0tPgogICAgPCEtLSBMb2FkIHRhYmxlIGRpYWxvZyAtLT4KPGRpdiBpZD0i + bG9hZF90YWJsZV9kaWFsb2ciIGNsYXNzPSJtb2RhbCBoaWRlIGZhZGUiIHRh + YmluZGV4PSItMSIgcm9sZT0iZGlhbG9nIgogICAgIGFyaWEtbGFiZWxsZWRi + eT0ibG9hZF90YWJsZV9kaWFsb2dfbGFiZWwiIGFyaWEtaGlkZGVuPSJ0cnVl + Ij4KICAgIDxkaXYgY2xhc3M9Im1vZGFsLWhlYWRlciI+CiAgICAgICAgPGJ1 + dHRvbiB0eXBlPSJidXR0b24iIGNsYXNzPSJjbG9zZSIgZGF0YS1kaXNtaXNz + PSJtb2RhbCIgYXJpYS1oaWRkZW49InRydWUiPsOXPC9idXR0b24+CiAgICAg + ICAgPGgzIGlkPSJsb2FkX3RhYmxlX2RpYWxvZ19sYWJlbCI+TG9hZCB0YWJs + ZS4uLjwvaDM+CiAgICA8L2Rpdj4KICAgIDxmb3JtIGFjdGlvbj0iL21hcmtk + b3duX3RhYmxlcy9sb2FkIiAKICAgICAgICAgIGVuY3R5cGU9Im11bHRpcGFy + dC9mb3JtLWRhdGEiIAogICAgICAgICAgbWV0aG9kPSJwb3N0IgogICAgICAg + ICAgc3R5bGU9Im1hcmdpbjogMDsiPgogICAgPGRpdiBjbGFzcz0ibW9kYWwt + Ym9keSI+CiAgICAgICAgPHA+TG9hZCB0YWJsZSBmcm9tIGEgcHJldmlvdXNs + eSBzYXZlZCBmaWxlLjwvcD4KICAgICAgICA8ZGl2PgogICAgICAgICAgICA8 + bGFiZWwgc3R5bGU9ImRpc3BsYXk6IGlubGluZS1ibG9jazsiPlNlbGVjdCBU + R04gZmlsZSA8L2xhYmVsPgogICAgICAgICAgICA8aW5wdXQgdHlwZT0iZmls + ZSIgbmFtZT0ibG9hZF90YWJsZV9maWxlIi8+CiAgICAgICAgPC9kaXY+CiAg + ICA8L2Rpdj4KICAgIDxkaXYgY2xhc3M9Im1vZGFsLWZvb3RlciI+CiAgICAg + ICAgPGJ1dHRvbiBjbGFzcz0iYnRuIiBkYXRhLWRpc21pc3M9Im1vZGFsIiBh + cmlhLWhpZGRlbj0idHJ1ZSI+Q2FuY2VsPC9idXR0b24+CiAgICAgICAgPGlu + cHV0IHR5cGU9InN1Ym1pdCIgdmFsdWU9IkxvYWQiIGNsYXNzPSJidG4gYnRu + LXByaW1hcnkiPgogICAgPC9kaXY+CiAgICA8L2Zvcm0+CjwvZGl2PjwhLS0g + SW1wb3J0IGRpYWxvZyAtLT4KCiAgICA8ZGl2IGlkPSJxdWlja19oZWxwX2Rp + YWxvZyIgY2xhc3M9Im1vZGFsIGhpZGUgZmFkZSIgdGFiaW5kZXg9Ii0xIiBy + b2xlPSJkaWFsb2ciIAogICAgIGFyaWEtbGFiZWxsZWRieT0icXVpY2tfaGVs + cF9kaWFsb2dfbGFiZWwiIGFyaWEtaGlkZGVuPSJ0cnVlIj4KICAgIDxkaXYg + Y2xhc3M9Im1vZGFsLWhlYWRlciI+CiAgICAgICAgPGJ1dHRvbiB0eXBlPSJi + dXR0b24iIGNsYXNzPSJjbG9zZSIgZGF0YS1kaXNtaXNzPSJtb2RhbCIgYXJp + YS1oaWRkZW49InRydWUiPsOXPC9idXR0b24+CiAgICAgICAgPGgzIGlkPSJx + dWlja19oZWxwX2RpYWxvZ19sYWJlbCI+UXVpY2sgaGVscDwvaDM+CiAgICA8 + L2Rpdj4KCiAgICA8ZGl2IGNsYXNzPSJtb2RhbC1ib2R5Ij4KICAgICAgICA8 + cD5CYXNpYyBrZXkgY29tbWFuZHM8L3A+CiAgICAgICAgPHVsPgogICAgICAg + ICAgICA8bGk+PHN0cm9uZz5FTlRFUiBvciBkb3VibGUgY2xpY2s8L3N0cm9u + Zz4gJm1kYXNoOyB0byBzdGFydCBlZGl0aW5nIGEgY2VsbDwvbGk+CiAgICAg + ICAgICAgIDxsaT48c3Ryb25nPkVTQzwvc3Ryb25nPiAmbWRhc2g7IHRvIHN0 + b3AgZWRpdGluZyBhIGNlbGw8L2xpPgogICAgICAgICAgICA8bGk+PHN0cm9u + Zz5DVFJMK0MsIENUUkwrWCwgQ1RSTCtWPC9zdHJvbmc+ICZtZGFzaDsgdG8g + Y29weSwgY3V0CiAgICAgICAgICAgICAgICBhbmQgcGFzdGUsIHJlc3BlY3Rp + dmVseTwvbGk+CiAgICAgICAgICAgIDxsaT48c3Ryb25nPlRBQiBvciBBcnJv + dyBLZXlzPC9zdHJvbmc+ICZtZGFzaDsgdG8gc2VsZWN0IGEgZGlmZmVyZW50 + IGNlbGw8L2xpPgogICAgICAgICAgICA8bGk+PHN0cm9uZz5DVFJMK1o8L3N0 + cm9uZz4gJm1kYXNoOyB0byB1bmRvIGEgY2hhbmdlPC9saT4KICAgICAgICAg + ICAgPGxpPjxzdHJvbmc+Q1RSTCtZPC9zdHJvbmc+ICZtZGFzaDsgdG8gcmVk + byBhIGNoYW5nZTwvbGk+CiAgICAgICAgICAgIDxsaT48c3Ryb25nPkxlZnQg + bW91c2UgYnV0dG9uPC9zdHJvbmc+ICZtZGFzaDsgY2xpY2sgYSBjZWxsIHRv + CiAgICAgICAgICAgIHNlbGVjdCBpdCAoaG9sZCBsZWZ0IGJ1dHRvbiBwcmVz + c2VkIHRvIHNlbGVjdCBhZGphY2VudCBjZWxscyk8L2xpPgogICAgICAgICAg + ICA8bGk+PHN0cm9uZz5Ib2xkIENUUkw8L3N0cm9uZz4gdG8gc2VsZWN0IG11 + bHRpcGxlIGNlbGxzIChub3QgbmVjZXNzYXJpbHkgYWRqYWNlbnQpLjwvbGk+ + CiAgICAgICAgPC91bD4KCiAgICAgICAgPHA+CiAgICAgICAgU29tZSBvZiB0 + aGUgZnVuY3Rpb25hbGl0aWVzIG1heSBub3Qgd29yayBpbiBhbGwgYnJvd3Nl + cnMgJm1kYXNoOyBpZiB5b3UKICAgICAgICBlbmNvdW50ZXIgYSBwcm9ibGVt + LCBwbGVhc2UgdHJ5IHRvIHVzZSBhbm90aGVyIGJyb3dzZXIgKEdvb2dsZSBD + aHJvbWUsCiAgICAgICAgTW96aWxsYSBGaXJlZm94KSBvciB1cGRhdGUgeW91 + ciBicm93c2VyIHRvIGEgbmV3ZXIgdmVyc2lvbiBpZiBhdmFpbGFibGUuIElm + IHRoZSBwcm9ibGVtCiAgICAgICAgc3RpbGwgcGVyc2lzdHMgcGxlYXNlIDxh + IGhyZWY9Ii9jb250YWN0IiB0aXRsZT0iQ29udGFjdCB3aXRoIHVzIj5sZXQg + dXMga25vdzwvYT4uCiAgICAgICAgPC9wPgogICAgPC9kaXY+CgogICAgPGRp + diBjbGFzcz0ibW9kYWwtZm9vdGVyIj4KICAgICAgICA8YnV0dG9uIGNsYXNz + PSJidG4iIGRhdGEtZGlzbWlzcz0ibW9kYWwiIGFyaWEtaGlkZGVuPSJ0cnVl + Ij5DbG9zZTwvYnV0dG9uPgogICAgPC9kaXY+CjwvZGl2PgogICAgPGRpdiBp + ZD0idGFibGUtYXV4LWNvbC1tZW51IiBjbGFzcz0iY29udGV4dC1tZW51LXdy + YXAiPgogICAgPHVsIGNsYXNzPSJjb250ZXh0LW1lbnUiIHJvbGU9Im1lbnUi + PgogICAgICAgIDxsaSBkYXRhLW1lbnUtaXRlbS1pZD0iYWRkX3RvX3RoZV9s + ZWZ0Ij4KICAgICAgICA8YSB0YWItaW5kZXg9Ii0xIj5BZGQgY29sdW1uIHRv + IHRoZSBsZWZ0PC9hPgogICAgICAgIDwvbGk+CgogICAgICAgIDxsaSBkYXRh + LW1lbnUtaXRlbS1pZD0iYWRkX3RvX3RoZV9yaWdodCI+CiAgICAgICAgPGEg + dGFiLWluZGV4PSItMSI+QWRkIGNvbHVtbiB0byB0aGUgcmlnaHQ8L2E+CiAg + ICAgICAgPC9saT4KCiAgICAgICAgPGxpIGRhdGEtbWVudS1pdGVtLWlkPSJy + ZW1vdmVfY29sdW1uIj4KICAgICAgICA8YSB0YWItaW5kZXg9Ii0xIj5SZW1v + dmUgY29sdW1uKHMpPC9hPgogICAgICAgIDwvbGk+CgogICAgICAgIAogICAg + PC91bD4KPC9kaXY+Cgo8ZGl2IGlkPSJ0YWJsZS1hdXgtcm93LW1lbnUiIGNs + YXNzPSJjb250ZXh0LW1lbnUtd3JhcCI+CiAgICA8dWwgY2xhc3M9ImNvbnRl + eHQtbWVudSIgcm9sZT0ibWVudSI+CiAgICAgICAgPGxpIGRhdGEtbWVudS1p + dGVtLWlkPSJhZGRfcm93X2Fib3ZlIj4KICAgICAgICA8YSB0YWItaW5kZXg9 + Ii0xIj5BZGQgcm93IGFib3ZlPC9hPgogICAgICAgIDwvbGk+CgogICAgICAg + IDxsaSBkYXRhLW1lbnUtaXRlbS1pZD0iYWRkX3Jvd19iZWxvdyI+CiAgICAg + ICAgPGEgdGFiLWluZGV4PSItMSI+QWRkIHJvdyBiZWxvdzwvYT4KICAgICAg + ICA8L2xpPgoKICAgICAgICA8bGkgZGF0YS1tZW51LWl0ZW0taWQ9InJlbW92 + ZV9yb3ciPgogICAgICAgIDxhIHRhYi1pbmRleD0iLTEiPlJlbW92ZSByb3co + cyk8L2E+CiAgICAgICAgPC9saT4KCiAgICA8L3VsPgo8L2Rpdj4KCgo8ZGl2 + IGlkPSJ0YWJsZS1jZWxsLW1lbnUiIGNsYXNzPSJjb250ZXh0LW1lbnUtd3Jh + cCI+CiAgICA8dWwgY2xhc3M9ImNvbnRleHQtbWVudSIgcm9sZT0ibWVudSI+ + CgogICAgICAgIDxsaSBkYXRhLW1lbnUtaXRlbS1pZD0iY2VsbF9jb250ZW50 + c19jdXQiCiAgICAgICAgICAgIGNsYXNzPSJ3aXRoLXplcm8tY2xpcGJvYXJk + IGNvbnRleHQtbWVudS1pdGVtIgogICAgICAgICAgICBkYXRhLWFjdGlvbi1p + ZD0iY2VsbF9jb250ZW50c19jdXQiPgogICAgICAgICAgICA8YSB0YWItaW5k + ZXg9Ii0xIj48c3BhbiBjbGFzcz0iaWNvbi1jdXQiPiZuYnNwOzwvc3Bhbj5D + dXQgPHNwYW4gY2xhc3M9ImNvbnRleHQtbWVudS1pdGVtX19rZXlzIj4oQ3Ry + bCtYKTwvc3Bhbj48L2E+CiAgICAgICAgPC9saT4KCiAgICAgICAgPGxpIGRh + dGEtbWVudS1pdGVtLWlkPSJjZWxsX2NvbnRlbnRzX2NvcHkiCiAgICAgICAg + ICAgIGNsYXNzPSJ3aXRoLXplcm8tY2xpcGJvYXJkIgogICAgICAgICAgICBk + YXRhLWFjdGlvbi1pZD0iY2VsbF9jb250ZW50c19jb3B5Ij4KICAgICAgICAg + ICAgPGEgdGFiLWluZGV4PSItMSI+PHNwYW4gY2xhc3M9Imljb24tY29weSI+ + Jm5ic3A7PC9zcGFuPkNvcHkgPHNwYW4gY2xhc3M9ImNvbnRleHQtbWVudS1p + dGVtX19rZXlzIj4oQ3RybCtDKTwvc3Bhbj48L2E+CiAgICAgICAgPC9saT4K + CiAgICAgICAgPGxpPgogICAgICAgICAgICA8YSB0YWItaW5kZXg9Ii0xIj48 + c3BhbiBjbGFzcz0iaWNvbi1wYXN0ZSI+Jm5ic3A7PC9zcGFuPlByZXNzIEN0 + cmwrViB0byBwYXN0ZTwvc3Bhbj48L2E+CiAgICAgICAgPC9saT4KCiAgICAg + ICAgPGxpIGNsYXNzPSJkaXZpZGVyIj48L2xpPgoKICAgICAgICA8bGkgY2xh + c3M9ImRyb3Bkb3duLXN1Ym1lbnUiPgogICAgICAgICAgICA8YSB0YWJpbmRl + eD0iLTEiIGhyZWY9IiMiPlJvdzwvYT4KICAgICAgICAgICAgPHVsIGNsYXNz + PSJkcm9wZG93bi1tZW51Ij4KICAgICAgICAgICAgICAgIDxsaSBkYXRhLW1l + bnUtaXRlbS1pZD0iYWRkX3Jvd19hYm92ZSI+CiAgICAgICAgICAgICAgICAg + ICAgPGEgdGFiLWluZGV4PSItMSI+QWRkIHJvdyBhYm92ZTwvYT4KICAgICAg + ICAgICAgICAgIDwvbGk+CgogICAgICAgICAgICAgICAgPGxpIGRhdGEtbWVu + dS1pdGVtLWlkPSJhZGRfcm93X2JlbG93Ij4KICAgICAgICAgICAgICAgICAg + ICA8YSB0YWItaW5kZXg9Ii0xIj5BZGQgcm93IGJlbG93PC9hPgogICAgICAg + ICAgICAgICAgPC9saT4KCiAgICAgICAgICAgICAgICA8bGkgZGF0YS1tZW51 + LWl0ZW0taWQ9InJlbW92ZV9yb3ciPgogICAgICAgICAgICAgICAgICAgIDxh + IHRhYi1pbmRleD0iLTEiPlJlbW92ZSByb3cocyk8L2E+CiAgICAgICAgICAg + ICAgICA8L2xpPgoKICAgICAgICAgICAgPC91bD4KICAgICAgICA8L2xpPgoK + ICAgICAgICA8bGkgY2xhc3M9ImRyb3Bkb3duLXN1Ym1lbnUiPgogICAgICAg + ICAgICA8YSB0YWJpbmRleD0iLTEiIGhyZWY9IiMiPkNvbHVtbjwvYT4KICAg + ICAgICAgICAgPHVsIGNsYXNzPSJkcm9wZG93bi1tZW51IiByb2xlPSJtZW51 + Ij4KICAgICAgICAgICAgICAgIDxsaSBkYXRhLW1lbnUtaXRlbS1pZD0iYWRk + X3RvX3RoZV9sZWZ0Ij4KICAgICAgICAgICAgICAgIDxhIHRhYi1pbmRleD0i + LTEiPkFkZCBjb2x1bW4gdG8gdGhlIGxlZnQ8L2E+CiAgICAgICAgICAgICAg + ICA8L2xpPgoKICAgICAgICAgICAgICAgIDxsaSBkYXRhLW1lbnUtaXRlbS1p + ZD0iYWRkX3RvX3RoZV9yaWdodCI+CiAgICAgICAgICAgICAgICA8YSB0YWIt + aW5kZXg9Ii0xIj5BZGQgY29sdW1uIHRvIHRoZSByaWdodDwvYT4KICAgICAg + ICAgICAgICAgIDwvbGk+CgogICAgICAgICAgICAgICAgPGxpIGRhdGEtbWVu + dS1pdGVtLWlkPSJyZW1vdmVfY29sdW1uIj4KICAgICAgICAgICAgICAgIDxh + IHRhYi1pbmRleD0iLTEiPlJlbW92ZSBjb2x1bW4ocyk8L2E+CiAgICAgICAg + ICAgICAgICA8L2xpPgogICAgICAgICAgICA8L3VsPgogICAgICAgPC9saT4K + CiAgICAgICAgPGxpIGNsYXNzPSJkaXZpZGVyIj48L2xpPgoKICAgICAgICA8 + bGkgY2xhc3M9ImRyb3Bkb3duLXN1Ym1lbnUiPgogICAgICAgICAgICA8YSB0 + YWJpbmRleD0iLTEiIGhyZWY9IiMiPlRleHQgYWxpZ248L2E+CiAgICAgICAg + ICAgIDx1bCBjbGFzcz0iZHJvcGRvd24tbWVudSIgcm9sZT0ibWVudSI+CiAg + ICAgICAgICAgICAgICA8bGkgZGF0YS1tZW51LWl0ZW0taWQ9ImNlbGxfYWxp + Z25fbGVmdCI+CiAgICAgICAgICAgICAgICA8YSB0YWItaW5kZXg9Ii0xIj4K + ICAgICAgICAgICAgICAgICAgIDxzcGFuIGNsYXNzPSJtZW51LWl0ZW0taWNv + bi1hbGlnbi1sZWZ0Ij4mbmJzcDs8L3NwYW4+CiAgICAgICAgICAgICAgICAg + ICAgTGVmdDwvYT4KICAgICAgICAgICAgICAgIDwvbGk+CgogICAgICAgICAg + ICAgICAgPGxpIGRhdGEtbWVudS1pdGVtLWlkPSJjZWxsX2FsaWduX2NlbnRl + ciI+CiAgICAgICAgICAgICAgICA8YSB0YWItaW5kZXg9Ii0xIj4KICAgICAg + ICAgICAgICAgICAgIDxzcGFuIGNsYXNzPSJtZW51LWl0ZW0taWNvbi1hbGln + bi1jZW50ZXIiPiZuYnNwOzwvc3Bhbj4KICAgICAgICAgICAgICAgICAgICBD + ZW50ZXI8L2E+CiAgICAgICAgICAgICAgICA8L2xpPgoKICAgICAgICAgICAg + ICAgIDxsaSBkYXRhLW1lbnUtaXRlbS1pZD0iY2VsbF9hbGlnbl9yaWdodCI+ + CiAgICAgICAgICAgICAgICA8YSB0YWItaW5kZXg9Ii0xIj4KICAgICAgICAg + ICAgICAgICAgIDxzcGFuIGNsYXNzPSJtZW51LWl0ZW0taWNvbi1hbGlnbi1y + aWdodCI+Jm5ic3A7PC9zcGFuPgogICAgICAgICAgICAgICAgICAgIFJpZ2h0 + PC9hPgogICAgICAgICAgICAgICAgPC9saT4KCiAgICAgICAgICAgICAgICA8 + bGkgZGF0YS1tZW51LWl0ZW0taWQ9ImNlbGxfYWxpZ25fdG9wIgogICAgICAg + ICAgICAgICAgICAgIHN0eWxlPSJib3JkZXItdG9wOiAxcHggbGlnaHRncmF5 + IHNvbGlkOyI+CiAgICAgICAgICAgICAgICA8YSB0YWItaW5kZXg9Ii0xIgog + ICAgICAgICAgICAgICAgICAgdGl0bGU9IkFsaWduIHRvIHRoZSB0b3AgKHZl + cnRpY2FsbHkpIj4KICAgICAgICAgICAgICAgICAgIDxzcGFuIGNsYXNzPSJt + ZW51LWl0ZW0taWNvbi1hbGlnbi10b3AiPiZuYnNwOzwvc3Bhbj4KICAgICAg + ICAgICAgICAgICAgIFRvcDwvYT4KICAgICAgICAgICAgICAgIDwvbGk+Cgog + ICAgICAgICAgICAgICAgPGxpIGRhdGEtbWVudS1pdGVtLWlkPSJjZWxsX2Fs + aWduX21pZGRsZSI+CiAgICAgICAgICAgICAgICA8YSB0YWItaW5kZXg9Ii0x + IgogICAgICAgICAgICAgICAgICAgdGl0bGU9IkFsaWduIHRvIHRoZSBtaWRk + bGUgKHZlcnRpY2FsbHkpIj4KICAgICAgICAgICAgICAgICAgIDxzcGFuIGNs + YXNzPSJtZW51LWl0ZW0taWNvbi1hbGlnbi1taWRkbGUiPiZuYnNwOzwvc3Bh + bj4KICAgICAgICAgICAgICAgICAgIE1pZGRsZTwvYT4KICAgICAgICAgICAg + ICAgIDwvbGk+CgogICAgICAgICAgICAgICAgPGxpIGRhdGEtbWVudS1pdGVt + LWlkPSJjZWxsX2FsaWduX2JvdHRvbSI+CiAgICAgICAgICAgICAgICA8YSB0 + YWItaW5kZXg9Ii0xIgogICAgICAgICAgICAgICAgICAgdGl0bGU9IkFsaWdu + IHRvIHRoZSBib3R0b20gKHZlcnRpY2FsbHkpIj4KICAgICAgICAgICAgICAg + ICAgIDxzcGFuIGNsYXNzPSJtZW51LWl0ZW0taWNvbi1hbGlnbi1ib3R0b20i + PiZuYnNwOzwvc3Bhbj4KICAgICAgICAgICAgICAgICAgIEJvdHRvbTwvYT4K + ICAgICAgICAgICAgICAgIDwvbGk+CiAgICAgICAgICAgIDwvdWw+CiAgICAg + ICA8L2xpPgoKICAgIDwvdWw+CjwvZGl2PgoKCiAgICA8ZGl2IGlkPSJjb2x1 + bW5fd2lkdGhfZGlhbG9nIiBjbGFzcz0ibW9kYWwgaGlkZSBmYWRlIiB0YWJp + bmRleD0iLTEiIHJvbGU9ImRpYWxvZyIgIGFyaWEtaGlkZGVuPSJ0cnVlIj4K + ICAgIDxkaXYgY2xhc3M9Im1vZGFsLWhlYWRlciI+CiAgICAgICAgPGJ1dHRv + biB0eXBlPSJidXR0b24iIGNsYXNzPSJjbG9zZSIgZGF0YS1kaXNtaXNzPSJt + b2RhbCIgYXJpYS1oaWRkZW49InRydWUiPsOXPC9idXR0b24+CiAgICAgICAg + PGgzIGlkPSJteU1vZGFsTGFiZWwiPlNldCBjb2x1bW4gd2lkdGg8L2gzPgog + ICAgPC9kaXY+CgogICAgPGZvcm0gY2xhc3M9ImZvcm0taG9yaXpvbnRhbCI+ + CiAgICAgICAgPGRpdiBjbGFzcz0ibW9kYWwtYm9keSI+CiAgICAgICAgICA8 + ZGl2IGNsYXNzPSJjb250cm9sLWdyb3VwIj4KICAgICAgICAgICAgPGxhYmVs + IGNsYXNzPSJjb250cm9sLWxhYmVsIiBmb3I9ImNvbHVtbl93aWR0aF9pbnB1 + dCI+V2lkdGg8L2xhYmVsPgogICAgICAgICAgICA8ZGl2IGNsYXNzPSJjb250 + cm9scyI+CiAgICAgICAgICAgICAgPGlucHV0IHR5cGU9Im51bWJlciIgbWlu + PSIxIiBtYXg9IjI1NjAiIAogICAgICAgICAgICAgICAgICAgICBpZD0iY29s + dW1uX3dpZHRoX2lucHV0IiAKICAgICAgICAgICAgICAgICAgICAgdmFsdWU9 + IjMiPgogICAgICAgICAgICAgIDxzcGFuIGNsYXNzPSJoZWxwLWlubGluZSI+ + cHg8L3NwYW4+CiAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgPC9kaXY+ + CiAgICAgICAgPC9kaXY+CiAgICAgICAgPGRpdiBjbGFzcz0ibW9kYWwtZm9v + dGVyIj4KICAgICAgICAgICAgPGJ1dHRvbiBjbGFzcz0iYnRuIiBkYXRhLWRp + c21pc3M9Im1vZGFsIiBhcmlhLWhpZGRlbj0idHJ1ZSI+Q2FuY2VsPC9idXR0 + b24+CiAgICAgICAgICAgIDxpbnB1dCB0eXBlPSJzdWJtaXQiIGlkPSJzZXRf + Y29sdW1uX3dpZHRoX2J0biIgdmFsdWU9Ik9LIiBjbGFzcz0iYnRuIGJ0bi1w + cmltYXJ5Ij4KICAgICAgICA8L2Rpdj4KICAgIDwvZm9ybT4KPC9kaXY+CiAg + ICA8ZGl2IGlkPSJkcm9wYm94X3NhdmVfdGFibGVfZGlhbG9nIiBjbGFzcz0i + bW9kYWwgaGlkZSBmYWRlIiB0YWJpbmRleD0iLTEiIHJvbGU9ImRpYWxvZyIg + YXJpYS1sYWJlbGxlZGJ5PSJteU1vZGFsTGFiZWwiIGFyaWEtaGlkZGVuPSJ0 + cnVlIj4KICAgIDxkaXYgY2xhc3M9Im1vZGFsLWhlYWRlciI+CiAgICAgICAg + PGJ1dHRvbiB0eXBlPSJidXR0b24iIGNsYXNzPSJjbG9zZSIgZGF0YS1kaXNt + aXNzPSJtb2RhbCIgYXJpYS1oaWRkZW49InRydWUiPsOXPC9idXR0b24+CiAg + ICAgICAgPGgzIGlkPSJteU1vZGFsTGFiZWwiPlNhdmUgdGFibGUgdG8geW91 + ciBEcm9wYm94IGFjY291bnQ8L2gzPgogICAgPC9kaXY+CgogICAgPGRpdiBj + bGFzcz0ibW9kYWwtYm9keSI+CiAgICAgICAgPHA+CiAgICAgICAgWW91ciB0 + YWJsZSB3aWxsIGJlIHNhdmVkIHVzaW5nIHlvdXIgRHJvcGJveCBhY2NvdW50 + LgogICAgICAgIFBsZWFzZSBlbnRlciBhIG5hbWUgZm9yIHRoZSB0YWJsZS4K + ICAgICAgICBZb3UgY2FuIGxvYWQgbGF0ZXIgeW91ciBzYXZlZCB0YWJsZXMg + dXNpbmcgIkZpbGUvRHJvcGJveC9Mb2FkIHRhYmxlIiBtZW51IGl0ZW0uCiAg + ICAgICAgPC9wPgoKICAgICAgICA8Zm9ybSBpZD0iZHJvcGJveC10YWJsZS1z + YXZlLWZvcm0iIGFjY2VwdC1jaGFyc2V0PSJ1dGYtOCIgY2xhc3M9ImZvcm0t + aG9yaXpvbnRhbCI+CiAgICAgICAgICA8ZGl2IGNsYXNzPSJjb250cm9sLWdy + b3VwIj4KICAgICAgICAgICAgPGxhYmVsIGNsYXNzPSJjb250cm9sLWxhYmVs + IiBmb3I9InRhYmxlLW5hbWUiPlRhYmxlIG5hbWU8L2xhYmVsPgogICAgICAg + ICAgICA8ZGl2IGNsYXNzPSJjb250cm9scyI+CiAgICAgICAgICAgICAgPGlu + cHV0IHR5cGU9InRleHQiIGlkPSJkcm9wYm94X3RhYmxlX25hbWUiIAogICAg + ICAgICAgICAgICAgICAgICBuYW1lPSJkcm9wYm94X3RhYmxlX25hbWUiIHBs + YWNlaG9sZGVyPSJFbnRlciBuYW1lLi4uIj4KICAgICAgICAgICAgPC9kaXY+ + CiAgICAgICAgICA8L2Rpdj4KICAgICAgICA8L2Zvcm0+CgogICAgICAgIDxw + PgogICAgICAgIDwvcD4KICAgIDwvZGl2PgoKICAgIDxkaXYgY2xhc3M9Im1v + ZGFsLWZvb3RlciI+CiAgICAgICAgPGJ1dHRvbiBjbGFzcz0iYnRuIiBkYXRh + LWRpc21pc3M9Im1vZGFsIiBhcmlhLWhpZGRlbj0idHJ1ZSI+Q2FuY2VsPC9i + dXR0b24+CiAgICAgICAgPGJ1dHRvbiBjbGFzcz0iYnRuIGJ0bi1wcmltYXJ5 + IiAKICAgICAgICAgICAgaWQ9ImRyb3Bib3hfdGFibGVfc2F2ZSI+PGRpdiBj + bGFzcz0ic3Bpbi1idXR0b24iPjwvZGl2PjxzcGFuPlNhdmU8L3NwYW4+PC9i + dXR0b24+CiAgICA8L2Rpdj4KPC9kaXY+CiAgICA8ZGl2IGlkPSJkcm9wYm94 + X2xvYWRfdGFibGVfZGlhbG9nIiAKICAgICBjbGFzcz0ibW9kYWwgaGlkZSBm + YWRlIiAKICAgICB0YWJpbmRleD0iLTEiIHJvbGU9ImRpYWxvZyIKICAgICBh + cmlhLWxhYmVsbGVkYnk9ImRyb3Bib3hfbG9hZF90YWJsZV9kaWFsb2dfbGFi + ZWwiIAogICAgIGFyaWEtaGlkZGVuPSJ0cnVlIj4KICAgIDxkaXYgY2xhc3M9 + Im1vZGFsLWhlYWRlciI+CiAgICAgICAgPGJ1dHRvbiB0eXBlPSJidXR0b24i + IGNsYXNzPSJjbG9zZSIgZGF0YS1kaXNtaXNzPSJtb2RhbCIgYXJpYS1oaWRk + ZW49InRydWUiPsOXPC9idXR0b24+CiAgICAgICAgPGgzIGlkPSJkcm9wYm94 + X2xvYWRfdGFibGVfZGlhbG9nX2xhYmVsIj5Mb2FkIHRhYmxlIGZyb20gRHJv + cGJveC4uLjwvaDM+CiAgICA8L2Rpdj4KICAgIDxkaXYgY2xhc3M9Im1vZGFs + LWJvZHkiPgogICAgICAgIDxwPlNlbGVjdCBhIHRhYmxlIHRvIGxvYWQuPC9w + PgogICAgICAgIDxkaXYgaWQ9ImRyb3Bib3hfdGFibGVzX2xpc3QiPjwvZGl2 + PgogICAgPC9kaXY+CiAgICA8ZGl2IGNsYXNzPSJtb2RhbC1mb290ZXIiPgog + ICAgICAgIDxidXR0b24gY2xhc3M9ImJ0biIgZGF0YS1kaXNtaXNzPSJtb2Rh + bCIgYXJpYS1oaWRkZW49InRydWUiPkNhbmNlbDwvYnV0dG9uPgogICAgPC9k + aXY+CjwvZGl2PgoKCiAgICAKPHNjcmlwdCB0eXBlPSJ0ZXh0L2phdmFzY3Jp + cHQiIGNoYXJzZXQ9InV0Zi04Ij4KJChkb2N1bWVudCkucmVhZHkoIGZ1bmN0 + aW9uKCkgewogICAgY29uc3QganNvbl90YWJsZSA9ICcnOwogICAgY29uc3Qg + bG9hZGVkX3RhYmxlID0gJyc7CiAgICBtYXJrZG93bl90YWJsZXNfdWkoanNv + bl90YWJsZSwgbG9hZGVkX3RhYmxlKTsKfSkKPC9zY3JpcHQ+CgoKICAgIAoK + CgoKCgoKPHNjcmlwdCBhc3luYyBzcmM9Imh0dHBzOi8vd3d3Lmdvb2dsZXRh + Z21hbmFnZXIuY29tL2d0YWcvanM/aWQ9VUEtNDA2NjY2MDAtMSI+PC9zY3Jp + cHQ+CjxzY3JpcHQ+CiAgICB3aW5kb3cuZGF0YUxheWVyID0gd2luZG93LmRh + dGFMYXllciB8fCBbXTsKICAgIGZ1bmN0aW9uIGd0YWcoKSB7IGRhdGFMYXll + ci5wdXNoKGFyZ3VtZW50cyk7IH0KCiAgICBndGFnKCdjb25zZW50JywgJ2Rl + ZmF1bHQnLCB7CiAgICAgICAgICdhbmFseXRpY3Nfc3RvcmFnZSc6ICdkZW5p + ZWQnLAogICAgICAgICAnYWRfc3RvcmFnZSc6ICdkZW5pZWQnLAogICAgICAg + ICAnd2FpdF9mb3JfdXBkYXRlJzogMTAwCiAgICB9KTsKICAgIGd0YWcoJ3Nl + dCcsICdhZHNfZGF0YV9yZWRhY3Rpb24nLCB0cnVlKTsKICAgIGd0YWcoJ2pz + JywgbmV3IERhdGUoKSk7CiAgICBndGFnKCdjb25maWcnLCAnVUEtNDA2NjY2 + MDAtMScpOwogICAgZ3RhZygnY29uZmlnJywgJ2NhLXB1Yi0yODY1NTgxNjc2 + MjU2ODY4Jyk7Cjwvc2NyaXB0PgoKCgoKPHNjcmlwdCBhc3luYyBzcmM9Imh0 + dHBzOi8vcGFnZWFkMi5nb29nbGVzeW5kaWNhdGlvbi5jb20vcGFnZWFkL2pz + L2Fkc2J5Z29vZ2xlLmpzP2NsaWVudD1jYS1wdWItMjg2NTU4MTY3NjI1Njg2 + OCIgY3Jvc3NvcmlnaW49ImFub255bW91cyI+PC9zY3JpcHQ+CgoKCjxzY3Jp + cHQ+KGFkc2J5Z29vZ2xlPXdpbmRvdy5hZHNieWdvb2dsZXx8W10pLnBhdXNl + QWRSZXF1ZXN0cz0xOzwvc2NyaXB0PgoKCgo8c2NyaXB0Pgp3aW5kb3cuYWRk + RXZlbnRMaXN0ZW5lcignRE9NQ29udGVudExvYWRlZCcsIGZ1bmN0aW9uKGV2 + ZW50KSB7CiAgICBmdW5jdGlvbiB1cGRhdGVfY29uc2VudChjYXRzKSB7CiAg + ICAgICAgZnVuY3Rpb24gaXNfZ3JhbnRlZChjYXQpIHsgcmV0dXJuIChjYXRz + LmluZGV4T2YoY2F0KSAhPT0gLTEpOyB9CgogICAgICAgIGlmIChpc19ncmFu + dGVkKCdhbmFseXRpY3MnKSkgewogICAgICAgICAgICBndGFnKCdjb25zZW50 + JywgJ3VwZGF0ZScsIHsgJ2FuYWx5dGljc19zdG9yYWdlJzogJ2dyYW50ZWQn + IH0pOwogICAgICAgIH0KICAgICAgICBpZiAoaXNfZ3JhbnRlZCgndGFyZ2V0 + aW5nJykpIHsKICAgICAgICAgICAgZ3RhZygnY29uc2VudCcsICd1cGRhdGUn + LCB7ICdhZF9zdG9yYWdlJzogJ2dyYW50ZWQnIH0pOwogICAgICAgIH0KICAg + ICAgICBpZiAoaXNfZ3JhbnRlZCgndGFyZ2V0aW5nJykgfHwgaXNfZ3JhbnRl + ZCgnYWRkaXRpb25hbCcpKSB7CiAgICAgICAgICAgIChhZHNieWdvb2dsZT13 + aW5kb3cuYWRzYnlnb29nbGV8fFtdKS5wYXVzZUFkUmVxdWVzdHM9MDsKICAg + ICAgICB9CiAgICB9CiAgICBDb29raWVDb25zZW50LmluaXQoJ0pQJywgJzAx + JywgdXBkYXRlX2NvbnNlbnQpOwp9KTsKPC9zY3JpcHQ+CgoKCiAgPC9ib2R5 + Pgo8L2h0bWw+ + recorded_at: Mon, 31 Jan 2022 09:42:22 GMT +recorded_with: VCR 6.0.0 From 1e6cbdeec94a88f12c535a8f68959897ddb35d05 Mon Sep 17 00:00:00 2001 From: AudioStakes Date: Thu, 27 Jan 2022 17:31:47 +0900 Subject: [PATCH 18/30] =?UTF-8?q?Amazon=20=E3=81=AE=20URL=20=E3=82=92?= =?UTF-8?q?=E3=83=AA=E3=83=B3=E3=82=AF=E5=88=87=E3=82=8C=E5=88=A4=E5=AE=9A?= =?UTF-8?q?=E3=81=AE=E5=AF=BE=E8=B1=A1=E5=A4=96=E3=81=A8=E3=81=99=E3=82=8B?= =?UTF-8?q?=E7=90=86=E7=94=B1=E3=82=92=E3=82=B3=E3=83=A1=E3=83=B3=E3=83=88?= =?UTF-8?q?=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/link_checker/checker.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/link_checker/checker.rb b/app/models/link_checker/checker.rb index d0a86668dd3..310c784be61 100644 --- a/app/models/link_checker/checker.rb +++ b/app/models/link_checker/checker.rb @@ -4,9 +4,9 @@ module LinkChecker class Checker - DENY_HOST = %w[ - codepen.io - www.amazon.co.jp + DENY_HOST = [ + 'codepen.io', + 'www.amazon.co.jp' # アクセスを繰り返すとリンク切れ判定のレスポンスが返されるようになるため ].freeze attr_reader :errors From 03d357e1d5eb6092ab1441d8c639ed0b94d15ddd Mon Sep 17 00:00:00 2001 From: AudioStakes Date: Fri, 28 Jan 2022 19:38:33 +0900 Subject: [PATCH 19/30] =?UTF-8?q?=E3=83=AA=E3=83=B3=E3=82=AF=E5=88=87?= =?UTF-8?q?=E3=82=8C=E3=81=AE=E9=80=9A=E7=9F=A5=E5=85=88=E3=83=81=E3=83=A3?= =?UTF-8?q?=E3=83=B3=E3=83=8D=E3=83=AB=E3=82=92=E3=80=8C=E3=83=90=E3=82=B0?= =?UTF-8?q?=E3=80=81=E8=AA=A4=E5=AD=97=E8=84=B1=E5=AD=97=E5=A0=B1=E5=91=8A?= =?UTF-8?q?=E3=80=81=E6=A9=9F=E8=83=BD=E8=A6=81=E6=9C=9B=F0=9F=99=87?= =?UTF-8?q?=E3=80=8D=E3=81=B8=E5=A4=89=E6=9B=B4=E3=81=A7=E3=81=8D=E3=82=8B?= =?UTF-8?q?=E3=82=88=E3=81=86=E3=81=AB=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/link_checker/checker.rb | 2 +- cloudbuild.yaml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/models/link_checker/checker.rb b/app/models/link_checker/checker.rb index 310c784be61..401d7f68f0f 100644 --- a/app/models/link_checker/checker.rb +++ b/app/models/link_checker/checker.rb @@ -41,7 +41,7 @@ def notify_broken_links texts << "- <#{link.url}|#{link.title}> in: <#{link.source_url}|#{link.source_title}>" end - ChatNotifier.message(texts.join("\n"), username: 'リンクチェッカー') + ChatNotifier.message(texts.join("\n"), username: 'リンクチェッカー', webhook_url: ENV['DISCORD_BUG_WEBHOOK_URL']) end def check diff --git a/cloudbuild.yaml b/cloudbuild.yaml index ec5b99beae3..fa0de6d5aaa 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -77,6 +77,7 @@ steps: - '--set-env-vars=DISCORD_NOTICE_WEBHOOK_URL=$_DISCORD_NOTICE_WEBHOOK_URL' - '--set-env-vars=DISCORD_ALL_WEBHOOK_URL=$_DISCORD_ALL_WEBHOOK_URL' - '--set-env-vars=DISCORD_ADMIN_WEBHOOK_URL=$_DISCORD_ADMIN_WEBHOOK_URL' + - '--set-env-vars=DISCORD_BUG_WEBHOOK_URL=$_DISCORD_BUG_WEBHOOK_URL' - '--set-env-vars=BASIC_AUTH_USER=$_BASIC_AUTH_USER' - '--set-env-vars=BASIC_AUTH_PASSWORD=$_BASIC_AUTH_PASSWORD' - '--set-env-vars=ROLLBAR_CLIENT_TOKEN=$_ROLLBAR_CLIENT_TOKEN' From ba1c01f3a8f24319d7282fe0254a8d3a31123649 Mon Sep 17 00:00:00 2001 From: AudioStakes Date: Mon, 31 Jan 2022 09:02:50 +0900 Subject: [PATCH 20/30] =?UTF-8?q?Checker=20=E3=82=AF=E3=83=A9=E3=82=B9?= =?UTF-8?q?=E3=81=AE=E4=BD=BF=E3=82=8F=E3=82=8C=E3=81=A6=E3=81=84=E3=81=AA?= =?UTF-8?q?=E3=81=84=E3=82=A4=E3=83=B3=E3=82=B9=E3=82=BF=E3=83=B3=E3=82=B9?= =?UTF-8?q?=E5=A4=89=E6=95=B0=E3=82=92=E5=89=8A=E9=99=A4=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/scheduler/link_checker_controller.rb | 2 +- app/models/link_checker/checker.rb | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/controllers/scheduler/link_checker_controller.rb b/app/controllers/scheduler/link_checker_controller.rb index b2411a0412c..f21c1b7e3e0 100644 --- a/app/controllers/scheduler/link_checker_controller.rb +++ b/app/controllers/scheduler/link_checker_controller.rb @@ -7,6 +7,6 @@ def show checker = LinkChecker::Checker.new(links) checker.notify_broken_links - render plain: checker.errors.join("\n") + render plain: checker.broken_links.join("\n") end end diff --git a/app/models/link_checker/checker.rb b/app/models/link_checker/checker.rb index 401d7f68f0f..a5183d53d18 100644 --- a/app/models/link_checker/checker.rb +++ b/app/models/link_checker/checker.rb @@ -8,7 +8,7 @@ class Checker 'codepen.io', 'www.amazon.co.jp' # アクセスを繰り返すとリンク切れ判定のレスポンスが返されるようになるため ].freeze - attr_reader :errors + attr_reader :broken_links class << self def valid_url?(url) @@ -26,7 +26,6 @@ def denied_host?(url) def initialize(links = []) @links = links - @errors = [] @broken_links = [] @locks = Queue.new 5.times { @locks.push :lock } From 579d713d38958d505cc3f974c988062cff8ce6d1 Mon Sep 17 00:00:00 2001 From: AudioStakes Date: Mon, 31 Jan 2022 12:16:34 +0900 Subject: [PATCH 21/30] =?UTF-8?q?=E3=83=AA=E3=83=B3=E3=82=AF=E3=81=AE=20UR?= =?UTF-8?q?L=20=E3=81=A8=E3=82=BF=E3=82=A4=E3=83=88=E3=83=AB=E3=81=AE?= =?UTF-8?q?=E5=8C=BA=E5=88=87=E3=82=8A=E6=96=87=E5=AD=97=20`|`=20=E3=81=8C?= =?UTF-8?q?=20URL=20=E3=81=AE=E4=B8=80=E9=83=A8=E3=81=A8=E3=81=97=E3=81=A6?= =?UTF-8?q?=E8=A7=A3=E9=87=88=E3=81=95=E3=82=8C=E3=81=AA=E3=81=84=E3=82=88?= =?UTF-8?q?=E3=81=86=E3=81=AB=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/link_checker/checker.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/link_checker/checker.rb b/app/models/link_checker/checker.rb index a5183d53d18..f985933c302 100644 --- a/app/models/link_checker/checker.rb +++ b/app/models/link_checker/checker.rb @@ -37,7 +37,7 @@ def notify_broken_links texts = ['リンク切れがありました。'] @broken_links.map do |link| - texts << "- <#{link.url}|#{link.title}> in: <#{link.source_url}|#{link.source_title}>" + texts << "- <#{link.url} | #{link.title}> in: <#{link.source_url} | #{link.source_title}>" end ChatNotifier.message(texts.join("\n"), username: 'リンクチェッカー', webhook_url: ENV['DISCORD_BUG_WEBHOOK_URL']) From 54625c2f5d21d0067e7450dc325b00d331e65d7c Mon Sep 17 00:00:00 2001 From: AudioStakes Date: Mon, 31 Jan 2022 16:47:41 +0900 Subject: [PATCH 22/30] =?UTF-8?q?development=20=E3=83=A2=E3=83=BC=E3=83=89?= =?UTF-8?q?=E3=81=A7=E3=81=AE=E3=83=AA=E3=83=B3=E3=82=AF=E5=88=87=E3=82=8C?= =?UTF-8?q?=E3=83=81=E3=82=A7=E3=83=83=E3=82=AB=E3=83=BC=E3=81=AE=E5=8B=95?= =?UTF-8?q?=E4=BD=9C=E7=A2=BA=E8=AA=8D=E7=94=A8=E3=81=AB=E3=83=95=E3=82=A3?= =?UTF-8?q?=E3=82=AF=E3=82=B9=E3=83=81=E3=83=A3=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- db/fixtures/pages.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/db/fixtures/pages.yml b/db/fixtures/pages.yml index 9ae9e86cde8..5253c5ba747 100644 --- a/db/fixtures/pages.yml +++ b/db/fixtures/pages.yml @@ -78,3 +78,27 @@ page11: user: komagata practice: practice1 published_at: "2021-10-01 00:00:00" + +page12: + title: apt + body: |- + aptとはdebianでソフトウェアをネットワークからインストールするコマンドです。 + [TEST](/test)(/test2) + [missing](test) + - 参考 + - [APT - Wikipedia](http://ja.wikipedia.org/wiki/APT) + ## Q&A + - Q. `$ apt-cache search vim` の検索結果が多すぎる + - A. [正規表現](https://ja.wikipedia.org/wiki/%E6%AD%A3%E8%A6%8F%E8%A1%A8%E7%8F%BE) を使う。 + - 完全一致: `$ apt-cache search ^vim$` + - 前方一致: `$ apt-cache search ^vim` + user: komagata + +page13: + title: リンク切れチェッカーのテスト用リンクを載せたページ + body: |- + [リンク切れ判定対象外の URL へのリンク](https://www.amazon.co.jp) + [末尾が閉じ括弧の URL へのリンク](https://ja.wikipedia.org/wiki/マジックナンバー_(プログラム)) + [SSLサーバー証明書の検証に失敗する host へのリンク](https://www.tablesgenerator.com/markdown_tables) + [日本語を含む URL へのリンク](https://ja.wikipedia.org/wiki/あ) + user: komagata From b4953cd8c10c2a30adf776da631f761a561e9ad2 Mon Sep 17 00:00:00 2001 From: AudioStakes Date: Tue, 1 Feb 2022 13:14:21 +0900 Subject: [PATCH 23/30] =?UTF-8?q?=E3=83=AA=E3=83=B3=E3=82=AF=E5=88=87?= =?UTF-8?q?=E3=82=8C=E9=80=9A=E7=9F=A5=E7=94=A8=E3=81=AE=20path=20?= =?UTF-8?q?=E3=81=B8=E3=82=A2=E3=82=AF=E3=82=BB=E3=82=B9=E3=81=97=E3=81=9F?= =?UTF-8?q?=E3=81=A8=E3=81=8D=E9=80=9A=E7=9F=A5=E5=86=85=E5=AE=B9=E3=81=A8?= =?UTF-8?q?=E5=90=8C=E3=81=98=E3=83=86=E3=82=AD=E3=82=B9=E3=83=88=E3=81=8C?= =?UTF-8?q?=E7=94=BB=E9=9D=A2=E3=81=AB=E8=A1=A8=E7=A4=BA=E3=81=95=E3=82=8C?= =?UTF-8?q?=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/scheduler/link_checker_controller.rb | 5 +++-- app/models/link_checker/checker.rb | 10 ++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/app/controllers/scheduler/link_checker_controller.rb b/app/controllers/scheduler/link_checker_controller.rb index f21c1b7e3e0..e3fe53666f9 100644 --- a/app/controllers/scheduler/link_checker_controller.rb +++ b/app/controllers/scheduler/link_checker_controller.rb @@ -5,8 +5,9 @@ def show documents = Page.all + Practice.all links = LinkChecker::Extractor.extract_all_links(documents) checker = LinkChecker::Checker.new(links) - checker.notify_broken_links - render plain: checker.broken_links.join("\n") + ChatNotifier.message(checker.summary_of_broken_links, username: 'リンクチェッカー', webhook_url: ENV['DISCORD_BUG_WEBHOOK_URL']) + + render plain: checker.summary_of_broken_links end end diff --git a/app/models/link_checker/checker.rb b/app/models/link_checker/checker.rb index f985933c302..8585f91f298 100644 --- a/app/models/link_checker/checker.rb +++ b/app/models/link_checker/checker.rb @@ -8,7 +8,6 @@ class Checker 'codepen.io', 'www.amazon.co.jp' # アクセスを繰り返すとリンク切れ判定のレスポンスが返されるようになるため ].freeze - attr_reader :broken_links class << self def valid_url?(url) @@ -26,13 +25,15 @@ def denied_host?(url) def initialize(links = []) @links = links + @is_checked = false @broken_links = [] @locks = Queue.new 5.times { @locks.push :lock } end - def notify_broken_links - check + def summary_of_broken_links + check unless @is_checked + return if @broken_links.empty? texts = ['リンク切れがありました。'] @@ -40,7 +41,7 @@ def notify_broken_links texts << "- <#{link.url} | #{link.title}> in: <#{link.source_url} | #{link.source_title}>" end - ChatNotifier.message(texts.join("\n"), username: 'リンクチェッカー', webhook_url: ENV['DISCORD_BUG_WEBHOOK_URL']) + texts.join("\n") end def check @@ -56,6 +57,7 @@ def check end end.each(&:join) + @is_checked = true @broken_links.sort { |a, b| b.source_url <=> a.source_url } end end From c9d9f8307ada94c1804cf250b1f4f5336f949418 Mon Sep 17 00:00:00 2001 From: AudioStakes Date: Wed, 2 Feb 2022 08:36:00 +0900 Subject: [PATCH 24/30] =?UTF-8?q?Tables=20Generator=20=E3=81=AESSL?= =?UTF-8?q?=E3=82=B5=E3=83=BC=E3=83=90=E3=83=BC=E8=A8=BC=E6=98=8E=E6=9B=B8?= =?UTF-8?q?=E3=82=92=E6=A4=9C=E8=A8=BC=E3=81=97=E3=81=AA=E3=81=84=E7=90=86?= =?UTF-8?q?=E7=94=B1=E3=82=92=E3=82=B3=E3=83=A1=E3=83=B3=E3=83=88=E3=81=97?= =?UTF-8?q?=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/link_checker/client.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/link_checker/client.rb b/app/models/link_checker/client.rb index 1ee456dd10b..46a4b6c5278 100644 --- a/app/models/link_checker/client.rb +++ b/app/models/link_checker/client.rb @@ -3,7 +3,7 @@ module LinkChecker class Client SSL_VERIFY_NONE_HOST = [ - 'www.tablesgenerator.com' + 'www.tablesgenerator.com' # 中間証明書を取得できず、SSLサーバー証明書の検証に失敗するため ].freeze def self.request(url) From e73e10330d0e73acdc51525b8529612aff9c3fee Mon Sep 17 00:00:00 2001 From: AudioStakes Date: Thu, 3 Feb 2022 13:44:51 +0900 Subject: [PATCH 25/30] =?UTF-8?q?LinkChecker::Extractor=20=E3=82=AF?= =?UTF-8?q?=E3=83=A9=E3=82=B9=E3=82=92=E3=83=A2=E3=82=B8=E3=83=A5=E3=83=BC?= =?UTF-8?q?=E3=83=AB=E3=81=AB=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../scheduler/link_checker_controller.rb | 2 +- app/models/link_checker/extractor.rb | 18 +++++------ test/models/link_checker/extractor_test.rb | 31 ++++++------------- 3 files changed, 18 insertions(+), 33 deletions(-) diff --git a/app/controllers/scheduler/link_checker_controller.rb b/app/controllers/scheduler/link_checker_controller.rb index e3fe53666f9..bb7fade9d68 100644 --- a/app/controllers/scheduler/link_checker_controller.rb +++ b/app/controllers/scheduler/link_checker_controller.rb @@ -3,7 +3,7 @@ class Scheduler::LinkCheckerController < SchedulerController def show documents = Page.all + Practice.all - links = LinkChecker::Extractor.extract_all_links(documents) + links = LinkChecker::Extractor.extract_links_from_multi(documents) checker = LinkChecker::Checker.new(links) ChatNotifier.message(checker.summary_of_broken_links, username: 'リンクチェッカー', webhook_url: ENV['DISCORD_BUG_WEBHOOK_URL']) diff --git a/app/models/link_checker/extractor.rb b/app/models/link_checker/extractor.rb index 36a350a6ea3..5d65fdff159 100644 --- a/app/models/link_checker/extractor.rb +++ b/app/models/link_checker/extractor.rb @@ -3,26 +3,22 @@ module LinkChecker Link = Struct.new(:title, :url, :source_title, :source_url, :response) - class Extractor + module Extractor MARKDOWN_LINK_REGEXP = %r{\[(.*?)\]\((#{URI::DEFAULT_PARSER.make_regexp}|/.*?)\)}.freeze - class << self - def extract_all_links(documents) - documents.flat_map { |document| new(document).extract_links } - end - end + module_function - def initialize(document) - @document = document + def extract_links_from_multi(documents) + documents.flat_map { |document| extract_links_from_a(document) } end - def extract_links - @document.body.scan(MARKDOWN_LINK_REGEXP).map do |title, url_or_path| + def extract_links_from_a(document) + document.body.scan(MARKDOWN_LINK_REGEXP).map do |title, url_or_path| title = title.strip url_or_path = url_or_path.strip url_or_path = "https://bootcamp.fjord.jp#{url_or_path}" if url_or_path.match?(%r{^/}) - Link.new(title, url_or_path, @document.title, "https://bootcamp.fjord.jp#{@document.path}") + Link.new(title, url_or_path, document.title, "https://bootcamp.fjord.jp#{document.path}") end end end diff --git a/test/models/link_checker/extractor_test.rb b/test/models/link_checker/extractor_test.rb index 760ad9724e9..eda5770ec51 100644 --- a/test/models/link_checker/extractor_test.rb +++ b/test/models/link_checker/extractor_test.rb @@ -4,52 +4,41 @@ module LinkChecker class ExtractorTest < ActiveSupport::TestCase - test '#extract_links from a practice' do + test '.extract_links_from_multi' do page = pages(:page8) - extractor = LinkChecker::Extractor.new(page) - + practice = practices(:practice3) expected = [ - LinkChecker::Link.new( + Link.new( 'TEST', 'https://bootcamp.fjord.jp/test', 'apt', "https://bootcamp.fjord.jp#{page.path}" ), - LinkChecker::Link.new( + Link.new( 'APT - Wikipedia', 'http://ja.wikipedia.org/wiki/APT', 'apt', "https://bootcamp.fjord.jp#{page.path}" ), - LinkChecker::Link.new( + Link.new( '正規表現', 'https://ja.wikipedia.org/wiki/%E6%AD%A3%E8%A6%8F%E8%A1%A8%E7%8F%BE', 'apt', "https://bootcamp.fjord.jp#{page.path}" - ) - ] - - assert_equal expected, extractor.extract_links - end - - test '#extract_links from a page' do - practice = practices(:practice3) - extractor = LinkChecker::Extractor.new(practice) - - expected = [ - LinkChecker::Link.new( + ), + Link.new( 'CPUとは', 'https://www.pc-master.jp/words/cpu.html', 'PC性能の見方を知る', "https://bootcamp.fjord.jp#{practice.path}" ), - LinkChecker::Link.new( + Link.new( 'HDDが分かる', 'http://homepage2.nifty.com/kamurai/HDD.htm', 'PC性能の見方を知る', "https://bootcamp.fjord.jp#{practice.path}" ), - LinkChecker::Link.new( + Link.new( 'Macの型番調べ辛い', 'https://docs.komagata.org/4433', 'PC性能の見方を知る', @@ -57,7 +46,7 @@ class ExtractorTest < ActiveSupport::TestCase ) ] - assert_equal expected, extractor.extract_links + assert_equal expected, Extractor.extract_links_from_multi([page, practice]) end test '::MARKDOWN_LINK_REGEXP' do From 4707a618ead17b071372f4fe1c395886d499c7a1 Mon Sep 17 00:00:00 2001 From: AudioStakes Date: Thu, 3 Feb 2022 14:00:45 +0900 Subject: [PATCH 26/30] =?UTF-8?q?LinkChecker::Checker=20=E3=82=AF=E3=83=A9?= =?UTF-8?q?=E3=82=B9=E3=82=92=E3=83=A2=E3=82=B8=E3=83=A5=E3=83=BC=E3=83=AB?= =?UTF-8?q?=E3=81=AB=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../scheduler/link_checker_controller.rb | 6 +- app/models/link_checker/checker.rb | 72 ++- test/cassettes/link_checker/checker/check.yml | 573 ------------------ .../link_checker/checker/check_response.yml | 145 +++++ test/models/link_checker/checker_test.rb | 50 +- 5 files changed, 212 insertions(+), 634 deletions(-) delete mode 100644 test/cassettes/link_checker/checker/check.yml create mode 100644 test/cassettes/link_checker/checker/check_response.yml diff --git a/app/controllers/scheduler/link_checker_controller.rb b/app/controllers/scheduler/link_checker_controller.rb index bb7fade9d68..1ef88c0308f 100644 --- a/app/controllers/scheduler/link_checker_controller.rb +++ b/app/controllers/scheduler/link_checker_controller.rb @@ -4,10 +4,10 @@ class Scheduler::LinkCheckerController < SchedulerController def show documents = Page.all + Practice.all links = LinkChecker::Extractor.extract_links_from_multi(documents) - checker = LinkChecker::Checker.new(links) + summary_of_broken_links = LinkChecker::Checker.check_broken_links(links) - ChatNotifier.message(checker.summary_of_broken_links, username: 'リンクチェッカー', webhook_url: ENV['DISCORD_BUG_WEBHOOK_URL']) + ChatNotifier.message(summary_of_broken_links, username: 'リンクチェッカー', webhook_url: ENV['DISCORD_BUG_WEBHOOK_URL']) - render plain: checker.summary_of_broken_links + render plain: summary_of_broken_links end end diff --git a/app/models/link_checker/checker.rb b/app/models/link_checker/checker.rb index 8585f91f298..270882eb044 100644 --- a/app/models/link_checker/checker.rb +++ b/app/models/link_checker/checker.rb @@ -3,62 +3,58 @@ require 'net/http' module LinkChecker - class Checker + module Checker DENY_HOST = [ 'codepen.io', 'www.amazon.co.jp' # アクセスを繰り返すとリンク切れ判定のレスポンスが返されるようになるため ].freeze - class << self - def valid_url?(url) - uri = Addressable::URI.parse(url) - uri.scheme && uri.host - rescue Addressable::URI::InvalidURIError - false - end + module_function - def denied_host?(url) - uri = Addressable::URI.parse(url) - DENY_HOST.include?(uri.host) - end + def check_broken_links(links) + links_with_valid_url = links.select { |link| valid_url?(link.url) && !denied_host?(link.url) } + links_with_response = check_response(links_with_valid_url) + broken_links = links_with_response.select { |link| !link.response || link.response > 403 } + + summary(broken_links) end - def initialize(links = []) - @links = links - @is_checked = false - @broken_links = [] - @locks = Queue.new - 5.times { @locks.push :lock } + def valid_url?(url) + uri = Addressable::URI.parse(url) + uri.scheme && uri.host + rescue Addressable::URI::InvalidURIError + false end - def summary_of_broken_links - check unless @is_checked + def denied_host?(url) + uri = Addressable::URI.parse(url) + DENY_HOST.include?(uri.host) + end - return if @broken_links.empty? + def check_response(links) + locks = Queue.new + 5.times { locks.push :lock } - texts = ['リンク切れがありました。'] - @broken_links.map do |link| - texts << "- <#{link.url} | #{link.title}> in: <#{link.source_url} | #{link.source_title}>" + links.each do |link| + Thread.new do + lock = locks.pop + link.response = Client.request(link.url) + locks.push lock + end.join end - texts.join("\n") + links end - def check - @links = @links.select { |link| self.class.valid_url?(link.url) && !self.class.denied_host?(link.url) } + def summary(broken_links) + return if broken_links.empty? - @links.map do |link| - Thread.new do - lock = @locks.pop - response = Client.request(link.url) - link.response = response - @broken_links << link if !response || response > 403 - @locks.push lock - end - end.each(&:join) + texts = ['リンク切れがありました。'] + broken_links.sort { |a, b| b.source_url <=> a.source_url }.map do |link| + texts << "- <#{link.url} | #{link.title}> in: <#{link.source_url} | #{link.source_title}>" + end - @is_checked = true - @broken_links.sort { |a, b| b.source_url <=> a.source_url } + texts.join("\n") end end end diff --git a/test/cassettes/link_checker/checker/check.yml b/test/cassettes/link_checker/checker/check.yml deleted file mode 100644 index 21639b71852..00000000000 --- a/test/cassettes/link_checker/checker/check.yml +++ /dev/null @@ -1,573 +0,0 @@ ---- -http_interactions: -- request: - method: get - uri: http://example.com/ - body: - encoding: US-ASCII - string: '' - headers: - Accept-Encoding: - - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 - Accept: - - "*/*" - User-Agent: - - Ruby - Host: - - example.com - response: - status: - code: 200 - message: OK - headers: - Accept-Ranges: - - bytes - Age: - - '193944' - Cache-Control: - - max-age=604800 - Content-Type: - - text/html; charset=UTF-8 - Date: - - Sun, 20 Jun 2021 14:24:38 GMT - Etag: - - '"3147526947"' - Expires: - - Sun, 27 Jun 2021 14:24:38 GMT - Last-Modified: - - Thu, 17 Oct 2019 07:18:26 GMT - Server: - - ECS (oxr/830D) - Vary: - - Accept-Encoding - X-Cache: - - HIT - Content-Length: - - '648' - body: - encoding: UTF-8 - string: "\n\n\n Example Domain\n\n - \ \n \n \n \n\n\n\n
\n

Example Domain

\n - \

This domain is for use in illustrative examples in documents. You may - use this\n domain in literature without prior coordination or asking for - permission.

\n

More - information...

\n
\n\n\n" - recorded_at: Sun, 20 Jun 2021 14:24:38 GMT -- request: - method: get - uri: http://example.com/xxxxx - body: - encoding: US-ASCII - string: '' - headers: - Accept-Encoding: - - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 - Accept: - - "*/*" - User-Agent: - - Ruby - Host: - - example.com - response: - status: - code: 404 - message: Not Found - headers: - Cache-Control: - - max-age=604800 - Content-Type: - - text/html; charset=UTF-8 - Date: - - Sun, 20 Jun 2021 14:24:38 GMT - Expires: - - Sun, 27 Jun 2021 14:24:38 GMT - Server: - - EOS (vny/0453) - Vary: - - Accept-Encoding - Content-Length: - - '648' - body: - encoding: UTF-8 - string: "\n\n\n Example Domain\n\n - \ \n \n \n \n\n\n\n
\n

Example Domain

\n - \

This domain is for use in illustrative examples in documents. You may - use this\n domain in literature without prior coordination or asking for - permission.

\n

More - information...

\n
\n\n\n" - recorded_at: Sun, 20 Jun 2021 14:24:38 GMT -- request: - method: get - uri: https://www.pc-master.jp/words/cpu.html - body: - encoding: US-ASCII - string: '' - headers: - Accept-Encoding: - - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 - Accept: - - "*/*" - User-Agent: - - Ruby - Host: - - www.pc-master.jp - response: - status: - code: 200 - message: OK - headers: - Server: - - nginx - Date: - - Sun, 20 Jun 2021 14:24:39 GMT - Content-Type: - - text/html - Content-Length: - - '18342' - Connection: - - keep-alive - Last-Modified: - - Fri, 11 Jun 2021 12:51:00 GMT - Etag: - - '"47a6-5c47cf5a87500"' - Accept-Ranges: - - bytes - body: - encoding: UTF-8 - string: |- - - - - - CPUとは パソコン初心者講座 - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - - - - - - -
- -
- - - - - - -
- - -
- - -

コンピューターの中枢を担うCPU

-
-

CPUは、メモリーやHDD・SSDと並んでコンピューターを構成する代表的なデバイスです。Central Processing Unitの略。別名:プロセッサー。パソコンには必ず搭載されています。

-

マウス、キーボード、ハードディスク、メモリー、周辺機器などからデータを受け取り、コンピューターでは 制御・演算を担当します。

-

CPUそのものは小さいのですが、その働きはパソコン随一です。そのためCPUの性能は、パソコンの性能や価格に大きく関わってきます。

-

CPUを製造しているメーカーは、Intel(インテル)とAMD(エーエムディー)が有名です。ともに米国の会社です。特に Intelは、WindowsやMacなど世界中のコンピューターに搭載されています。

-
- - - -

CPUの調べ方

-
-

- デスクトップ上 右クリック→ディスプレイ設定→詳細情報。またはコントロールパネルからシステム。

-

プロセッサのところに表示されています。

-

プロセッサ

-

Intelのデスクトップ版 CPU。

-

CPU

-

CPUの裏側。

-

CPUの裏側

-

パソコンのマザーボードのCPUソケットにCPUが入っています。

-

CPUソケット

-

CPUはパソコンの中でも最も発熱するパーツのひとつです。CPUを冷却するためヒートシンクとよばれる金属製のものとファンが付いています。これをCPUクーラーといいます。

-

CPUファン

-

ノートパソコンでも 基本的な構造は同じで、CPU上に金属製のヒートシンクが取り付けられ、発せられた熱は ファンによって排出される仕組みになっています。

-

ノートパソコン

-
- - - - -

制御・演算

-
-

CPUは 数十億ともいわれるトランジスタ・半導体素子が集積しています。コンピューターの中では中枢を担うデバイスです。

-

コンピューター内の各機器と バスでつながっていて制御している、またメモリー上のデータを読み書きしています。

-

制御部と演算部

-

CPUは、制御を行う部分と演算を行う部分に分かれます。

-

制御部は、次に処理すべきデータが メモリー上のどのアドレス・番地にあるかプログラムカウンターというところに記憶します。そしてそれを順番に演算部に伝えていきます。つまり命令・指令を出すところです。

-

演算部は、制御部から伝達されたメモリー上のアドレスを手がかりに 実際のデータを処理します。

-

データの処理や演算は 算術論理演算回路 ALUが担い、0と1の膨大なデジタルデータを超高速に処理します。

-
- - - - -

キャッシュメモリ

-
-

キャッシュメモリは CPUに内蔵されている高速なメモリーです。

-

通常のメモリーは DRAMですが、キャッシュメモリには高速なSRAMが使われています。

-

CPUは、メモリーに格納されているデータを読みだして処理していますが、メモリーとはデバイス間の距離があることと、CPUに比べてメモリーの方が処理速度が遅いため、データの処理が遅くなってしまします。

-

そこでCPUに高速なキャッシュメモリをおくことで、何度もメモリーから読み出すデータ、よく使うデータをキャッシュメモリにおいて処理を高速化しています。

-

キャッシュメモリ

-

キャッシュメモリも1次・2次に分かれており、CPUが最初にアクセスするのを L1キャッシュ、次にアクセスするのをL2キャッシュといいます。最近のCPUでは L3キャッシュもあります。

-

性能が良いといわれるCPUは、他のCPUよりもキャッシュメモリも多く搭載されています。

-
- - - - -

レジスタ

-
-

レジスタとは、キャッシュメモリとは別に CPU内部にある記憶領域。キャッシュメモリと同様、SRAMが使われています。

-

制御部や処理部にそれぞれあり、CPUが処理を行う際に一時的な記憶領域として使っています。

-

レジスタ

-

レジスタとキャッシュメモリはCPU内部にあるので、CPU内部メモリーともいいます。

-

メモリーと呼ばれる記憶を担当するものの中では、レジスタから順に キャッシュメモリ(SRAM)、メインメモリー(DRAM)、SSD、HDD、CD/DVDなどがあります。レジスタが最も高速で、CPUに近いメモリーほど速くなっています。

-

このような記憶を担当するメモリーが、高速なものから低速へ、容量の小さいものから大きいものへと連なっている様を、メモリーの階層、メモリーチェーンということがあります。

-
- - - - -

クロック周波数

-
-

CPUは、クロックという周期的な信号で動作しています。単位はGHzです。

-

クロック周波数とは、1秒間でどれだけクロックがあるかを表しています。

-

クロック

-

例えば 3GHzのCPUなら、一秒間に約30億回のクロックがあります。

-

CPUは、このクロックに合わせて処理や作業を行います。クロック周波数が高いほど 処理できる回数や量が多く、処理スピードが速くなります

-

一般的に、周波数が高いものほど発熱も大きいという傾向があります。そのため省電力PCやノートパソコンというのは、周波数がやや低めのCPUが使われている傾向があります。

-
- - - -

種類

-
-

CPUには、IntelのものとAMDのものとがあります。IntelのCPUのほうがシェアは高くなっています。

-

CPUの性能を左右するのは、コアの数です。コアというのはCPUの核の部分であり、制御部と演算部が1セットになっているものです。

-

これが1つあるのと2つあるのとでは性能に差が出てきます。コアが2つあるものをデュアルコア、4つあるものをクアッドコア、または複数あるものをマルチコアといいます。

-

IntelのCPUは、現在 Core iシリーズとその下位版のPentium、Celeron、AMDは、RyzenシリーズとAthlonが主流となっています。

-

CPUのブランド名は以下の通りです。同じブランド名でも世代や型番によりコア数は異なることや例外もあります。(上にいくほど新、下が旧)

-

Intel(デスクトップ)

-
    -
  • Core i7・・・コア4~8
  • -
  • Core i5・・・コア4~6
  • -
  • Core i3・・・コア2~4
  • -
  • Pentium・・・コア2
  • -
  • Celeron ・・・コア1~2
  • -
-

Core iシリーズの前に、Core 2 Quad(コア4)、Core 2 Duo(コア2)、Pentium D(コア2)、Pentium 4(コア1)、Celeron D(コア1)などがあります。

-

ノートパソコン用のCPUは末尾にU・Y・M・Eが付いていることが多くなっています。コア数はデスクトップ版とは異なることがあります。

-

IntelのCPUには他に、上位のものとして Core i9、消費電力を抑えたATOM、サーバー向けのXeonなどがあります。

-

AMD(デスクトップ)

-
    -
  • Ryzen7・・・コア4~8
  • -
  • Ryzen5・・・コア4~6
  • -
  • Ryzen3・・・コア2~4
  • -
  • Athlon・・・コア2
  • -
-

Ryzenシリーズの前に、A series(コア2~4)、FX series(コア4~8)、Phenom II(コア2~6)、Athlon II(コア2、4)、Phenom(コア3、4)、Athlon 64 X2(コア2)などがあります。 -

AMDのCPUもノートパソコンに搭載されていますが、コア数などは多少異なります。

-

AMDは他に上位のCPUとして、Ryzen 9、Ryzen Threadripperなどのブランドがあります。AMDはCPUだけではなく、グラフィックボードに搭載されるグラフィックチップ GPUを開発・製造している会社としても知られています。

-
- - - - -

コアとスレッド

-
-

Windowsのタスクマネージャーでは、CPUのコア数などを調べることができます。

-

タスクマネージャー→パフォーマンスタブ、CPU。

-

タスクマネージャー

-

CPUのコア数、論理プロセッサ数、CPUの型番、キャッシュ、クロック周波数などが表示されています。

-

この例では、2コアで論理プロセッサ数は4。論理プロセッサとは、OSから見たプロセッサの数をいいます。

-

例えば、IntelのCPUには、1つのコアを OS側から2コアに見せて処理速度を向上させる技術があります。これを ハイパー・スレッディング・テクノロジー(HT)といいます。コアと区別してスレッドともいいます。

-

ハイパースレッディング・テクノロジー対応のCPUは、タスクマネージャーでコア 2、論理プロセッサ数 4のように表示されます。コア数の倍が論理プロセッサ数です。

-

コアやスレッドが多いと、分担して処理を行うのでマルチタスク、処理速度の向上などにつながります。

-
- - - - -

内蔵グラフィック

-
-

近年のCPUは、グラフィック機能も担っています。内蔵グラフィック、iGPUともいいます。コンピューターで処理した情報を、ディスプレイなどの画面に描画する機能です。

-

内蔵グラフィック

-

CPUには内蔵グラフィックが搭載されているものがほとんどですが、内蔵グラフィック機能が搭載されていないCPUやグラフィック機能を向上させる目的で、デスクトップパソコンではグラフィックボード、ノートパソコンではグラフィック専用のチップが用いられることがあります。

-

パソコンで使われているグラフィック機能は、タスクマネージャーのGPUの項目やデバイスマネージャーのディスプレイアダプターなどで確認することができます。

-
- - - - -

ムーアの法則

-
-

CPUを語る上では、ムーアの法則というものがしばしば出てきます。

-

半導体・トランジスタの集積度は、年数に対して指数関数的に増加するというものです。集積度は、1年半で2倍、3年で4倍、5年で10倍という感じです。

-

同一面積において集積度があがるため、それだけ性能が高くなるということになります。

-

10年前あるいは数年前に比べ、CPUの性能が格段に向上しているのは、このムーアの法則をそのまま表しているといえます。

-

仮にCPUの性能があまり実感できないとしても、近年のスマートフォンやタブレットの小型化・高性能化、情報機器の普及、情報化社会の進展をみれば、それを実感することができるのではないでしょうか。

- (ムーアの法則:Intel創業者 ゴードン・ムーアが1965年に提唱 ) -
- - - - -

アーキテクチャ

-
-

CPUの性能を決めるのは、コア数、スレッド数、クロック周波数、キャッシュなどになります。また消費電力も関係してくるので、世代が新しいCPUほど総合的な性能は高くなります。

-

例えば XPの頃のCerelon DやPentium 4と現在のPentiumやCore i7とでは、比較にならないほどの性能差があります。

-

新しいCPUほど 半導体の集積度が高くなり、アーキテクチャ、設計に改良が加えられるため、低消費電力、低発熱、高性能という傾向があります。

-

例えば、Intelならアーキテクチャの違いは、第七世代 Core i、第十世代 Core iなどのようになります。

-
- - - - -
- - - - - -
- - - -
- - - - - - - - -
- - -
- - - - - - - - - - - - - - - - recorded_at: Sun, 20 Jun 2021 14:24:39 GMT -- request: - method: get - uri: https://docs.komagata.org/4433 - body: - encoding: US-ASCII - string: '' - headers: - Accept-Encoding: - - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 - Accept: - - "*/*" - User-Agent: - - Ruby - Host: - - docs.komagata.org - response: - status: - code: 200 - message: OK - headers: - Connection: - - keep-alive - Strict-Transport-Security: - - max-age=31536000 - Content-Type: - - text/html;charset=utf-8 - Content-Length: - - '3286' - X-Xss-Protection: - - 1; mode=block - X-Content-Type-Options: - - nosniff - X-Frame-Options: - - SAMEORIGIN - Server: - - WEBrick/1.6.0 (Ruby/2.7.0/2019-12-25) - Date: - - Sun, 20 Jun 2021 14:24:39 GMT - Set-Cookie: - - rack.session=BAh7CEkiD3Nlc3Npb25faWQGOgZFVG86HVJhY2s6OlNlc3Npb246OlNlc3Np%0Ab25JZAY6D0BwdWJsaWNfaWRJIkU4NDkyNzI3YmZlZWEzOWM2ODZhNTgyNjkw%0AOGI4Zjc4MGU0MjEyZTM1NmUxODVjNDA3MTgwNTdjZjA0YTI5YzE0BjsARkki%0ACWNzcmYGOwBGSSIlYmFhYzRjMTkzMjljYzE1ZGRmN2QyMGE3MTkxMWU5ODcG%0AOwBGSSINdHJhY2tpbmcGOwBGewdJIhRIVFRQX1VTRVJfQUdFTlQGOwBUSSIt%0AMThlNDBlMTQwMWVlZjY3ZTFhZTY5ZWZhYjA5YWZiNzFmODdmZmI4MQY7AEZJ%0AIhlIVFRQX0FDQ0VQVF9MQU5HVUFHRQY7AFRJIi1kYTM5YTNlZTVlNmI0YjBk%0AMzI1NWJmZWY5NTYwMTg5MGFmZDgwNzA5BjsARg%3D%3D%0A--0173da89f15577fe91a2ed6bc81bab79d770f756; - path=/; expires=Fri, 02 Jul 2021 14:24:39 -0000; HttpOnly; secure - - rack.session=BAh7CEkiD3Nlc3Npb25faWQGOgZFVG86HVJhY2s6OlNlc3Npb246OlNlc3Np%0Ab25JZAY6D0BwdWJsaWNfaWRJIkU4NDkyNzI3YmZlZWEzOWM2ODZhNTgyNjkw%0AOGI4Zjc4MGU0MjEyZTM1NmUxODVjNDA3MTgwNTdjZjA0YTI5YzE0BjsARkki%0ACWNzcmYGOwBGSSIlYmFhYzRjMTkzMjljYzE1ZGRmN2QyMGE3MTkxMWU5ODcG%0AOwBGSSINdHJhY2tpbmcGOwBGewdJIhRIVFRQX1VTRVJfQUdFTlQGOwBUSSIt%0AMThlNDBlMTQwMWVlZjY3ZTFhZTY5ZWZhYjA5YWZiNzFmODdmZmI4MQY7AEZJ%0AIhlIVFRQX0FDQ0VQVF9MQU5HVUFHRQY7AFRJIi1kYTM5YTNlZTVlNmI0YjBk%0AMzI1NWJmZWY5NTYwMTg5MGFmZDgwNzA5BjsARg%3D%3D%0A--6de3b0fbe6701a3ce7b9ccc99dc185af16a7beba; - path=/; expires=Fri, 02 Jul 2021 14:24:39 -0000; HttpOnly; secure - Via: - - 1.1 vegur - body: - encoding: UTF-8 - string: |- - Macの型番調べ辛い - komagataのブログ -

持ってるMacBookの型番:MB466J/A

MacBook 2.0GHz Core 2 Duo/13.3"/2G/160G/8xSuperDrive DL/Gigabit/802.11n/BT/Mini DisplayPort

    Comments

    -
    -

    - -
    - -

    -

    - - Option -
    - -

    -

    - -
    - -

    - -
    - recorded_at: Sun, 20 Jun 2021 14:24:39 GMT -recorded_with: VCR 6.0.0 diff --git a/test/cassettes/link_checker/checker/check_response.yml b/test/cassettes/link_checker/checker/check_response.yml new file mode 100644 index 00000000000..1e0becb240f --- /dev/null +++ b/test/cassettes/link_checker/checker/check_response.yml @@ -0,0 +1,145 @@ +--- +http_interactions: +- request: + method: get + uri: http://example.com/xxxxx + body: + encoding: US-ASCII + string: '' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 404 + message: Not Found + headers: + Age: + - '305' + Cache-Control: + - max-age=604800 + Content-Type: + - text/html; charset=UTF-8 + Date: + - Fri, 04 Feb 2022 05:05:23 GMT + Expires: + - Fri, 11 Feb 2022 05:05:23 GMT + Last-Modified: + - Fri, 04 Feb 2022 05:00:18 GMT + Server: + - ECS (oxr/8321) + Vary: + - Accept-Encoding + X-Cache: + - 404-HIT + Content-Length: + - '648' + body: + encoding: UTF-8 + base64_string: | + PCFkb2N0eXBlIGh0bWw+CjxodG1sPgo8aGVhZD4KICAgIDx0aXRsZT5FeGFt + cGxlIERvbWFpbjwvdGl0bGU+CgogICAgPG1ldGEgY2hhcnNldD0idXRmLTgi + IC8+CiAgICA8bWV0YSBodHRwLWVxdWl2PSJDb250ZW50LXR5cGUiIGNvbnRl + bnQ9InRleHQvaHRtbDsgY2hhcnNldD11dGYtOCIgLz4KICAgIDxtZXRhIG5h + bWU9InZpZXdwb3J0IiBjb250ZW50PSJ3aWR0aD1kZXZpY2Utd2lkdGgsIGlu + aXRpYWwtc2NhbGU9MSIgLz4KICAgIDxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+ + CiAgICBib2R5IHsKICAgICAgICBiYWNrZ3JvdW5kLWNvbG9yOiAjZjBmMGYy + OwogICAgICAgIG1hcmdpbjogMDsKICAgICAgICBwYWRkaW5nOiAwOwogICAg + ICAgIGZvbnQtZmFtaWx5OiAtYXBwbGUtc3lzdGVtLCBzeXN0ZW0tdWksIEJs + aW5rTWFjU3lzdGVtRm9udCwgIlNlZ29lIFVJIiwgIk9wZW4gU2FucyIsICJI + ZWx2ZXRpY2EgTmV1ZSIsIEhlbHZldGljYSwgQXJpYWwsIHNhbnMtc2VyaWY7 + CiAgICAgICAgCiAgICB9CiAgICBkaXYgewogICAgICAgIHdpZHRoOiA2MDBw + eDsKICAgICAgICBtYXJnaW46IDVlbSBhdXRvOwogICAgICAgIHBhZGRpbmc6 + IDJlbTsKICAgICAgICBiYWNrZ3JvdW5kLWNvbG9yOiAjZmRmZGZmOwogICAg + ICAgIGJvcmRlci1yYWRpdXM6IDAuNWVtOwogICAgICAgIGJveC1zaGFkb3c6 + IDJweCAzcHggN3B4IDJweCByZ2JhKDAsMCwwLDAuMDIpOwogICAgfQogICAg + YTpsaW5rLCBhOnZpc2l0ZWQgewogICAgICAgIGNvbG9yOiAjMzg0ODhmOwog + ICAgICAgIHRleHQtZGVjb3JhdGlvbjogbm9uZTsKICAgIH0KICAgIEBtZWRp + YSAobWF4LXdpZHRoOiA3MDBweCkgewogICAgICAgIGRpdiB7CiAgICAgICAg + ICAgIG1hcmdpbjogMCBhdXRvOwogICAgICAgICAgICB3aWR0aDogYXV0bzsK + ICAgICAgICB9CiAgICB9CiAgICA8L3N0eWxlPiAgICAKPC9oZWFkPgoKPGJv + ZHk+CjxkaXY+CiAgICA8aDE+RXhhbXBsZSBEb21haW48L2gxPgogICAgPHA+ + VGhpcyBkb21haW4gaXMgZm9yIHVzZSBpbiBpbGx1c3RyYXRpdmUgZXhhbXBs + ZXMgaW4gZG9jdW1lbnRzLiBZb3UgbWF5IHVzZSB0aGlzCiAgICBkb21haW4g + aW4gbGl0ZXJhdHVyZSB3aXRob3V0IHByaW9yIGNvb3JkaW5hdGlvbiBvciBh + c2tpbmcgZm9yIHBlcm1pc3Npb24uPC9wPgogICAgPHA+PGEgaHJlZj0iaHR0 + cHM6Ly93d3cuaWFuYS5vcmcvZG9tYWlucy9leGFtcGxlIj5Nb3JlIGluZm9y + bWF0aW9uLi4uPC9hPjwvcD4KPC9kaXY+CjwvYm9keT4KPC9odG1sPgo= + recorded_at: Fri, 04 Feb 2022 05:05:23 GMT +- request: + method: get + uri: http://example.com/ + body: + encoding: US-ASCII + string: '' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 200 + message: OK + headers: + Age: + - '101048' + Cache-Control: + - max-age=604800 + Content-Type: + - text/html; charset=UTF-8 + Date: + - Fri, 04 Feb 2022 05:05:23 GMT + Etag: + - '"3147526947+gzip"' + Expires: + - Fri, 11 Feb 2022 05:05:23 GMT + Last-Modified: + - Thu, 17 Oct 2019 07:18:26 GMT + Server: + - ECS (sab/56AA) + Vary: + - Accept-Encoding + X-Cache: + - HIT + Content-Length: + - '648' + body: + encoding: UTF-8 + base64_string: | + PCFkb2N0eXBlIGh0bWw+CjxodG1sPgo8aGVhZD4KICAgIDx0aXRsZT5FeGFt + cGxlIERvbWFpbjwvdGl0bGU+CgogICAgPG1ldGEgY2hhcnNldD0idXRmLTgi + IC8+CiAgICA8bWV0YSBodHRwLWVxdWl2PSJDb250ZW50LXR5cGUiIGNvbnRl + bnQ9InRleHQvaHRtbDsgY2hhcnNldD11dGYtOCIgLz4KICAgIDxtZXRhIG5h + bWU9InZpZXdwb3J0IiBjb250ZW50PSJ3aWR0aD1kZXZpY2Utd2lkdGgsIGlu + aXRpYWwtc2NhbGU9MSIgLz4KICAgIDxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+ + CiAgICBib2R5IHsKICAgICAgICBiYWNrZ3JvdW5kLWNvbG9yOiAjZjBmMGYy + OwogICAgICAgIG1hcmdpbjogMDsKICAgICAgICBwYWRkaW5nOiAwOwogICAg + ICAgIGZvbnQtZmFtaWx5OiAtYXBwbGUtc3lzdGVtLCBzeXN0ZW0tdWksIEJs + aW5rTWFjU3lzdGVtRm9udCwgIlNlZ29lIFVJIiwgIk9wZW4gU2FucyIsICJI + ZWx2ZXRpY2EgTmV1ZSIsIEhlbHZldGljYSwgQXJpYWwsIHNhbnMtc2VyaWY7 + CiAgICAgICAgCiAgICB9CiAgICBkaXYgewogICAgICAgIHdpZHRoOiA2MDBw + eDsKICAgICAgICBtYXJnaW46IDVlbSBhdXRvOwogICAgICAgIHBhZGRpbmc6 + IDJlbTsKICAgICAgICBiYWNrZ3JvdW5kLWNvbG9yOiAjZmRmZGZmOwogICAg + ICAgIGJvcmRlci1yYWRpdXM6IDAuNWVtOwogICAgICAgIGJveC1zaGFkb3c6 + IDJweCAzcHggN3B4IDJweCByZ2JhKDAsMCwwLDAuMDIpOwogICAgfQogICAg + YTpsaW5rLCBhOnZpc2l0ZWQgewogICAgICAgIGNvbG9yOiAjMzg0ODhmOwog + ICAgICAgIHRleHQtZGVjb3JhdGlvbjogbm9uZTsKICAgIH0KICAgIEBtZWRp + YSAobWF4LXdpZHRoOiA3MDBweCkgewogICAgICAgIGRpdiB7CiAgICAgICAg + ICAgIG1hcmdpbjogMCBhdXRvOwogICAgICAgICAgICB3aWR0aDogYXV0bzsK + ICAgICAgICB9CiAgICB9CiAgICA8L3N0eWxlPiAgICAKPC9oZWFkPgoKPGJv + ZHk+CjxkaXY+CiAgICA8aDE+RXhhbXBsZSBEb21haW48L2gxPgogICAgPHA+ + VGhpcyBkb21haW4gaXMgZm9yIHVzZSBpbiBpbGx1c3RyYXRpdmUgZXhhbXBs + ZXMgaW4gZG9jdW1lbnRzLiBZb3UgbWF5IHVzZSB0aGlzCiAgICBkb21haW4g + aW4gbGl0ZXJhdHVyZSB3aXRob3V0IHByaW9yIGNvb3JkaW5hdGlvbiBvciBh + c2tpbmcgZm9yIHBlcm1pc3Npb24uPC9wPgogICAgPHA+PGEgaHJlZj0iaHR0 + cHM6Ly93d3cuaWFuYS5vcmcvZG9tYWlucy9leGFtcGxlIj5Nb3JlIGluZm9y + bWF0aW9uLi4uPC9hPjwvcD4KPC9kaXY+CjwvYm9keT4KPC9odG1sPgo= + recorded_at: Fri, 04 Feb 2022 05:05:23 GMT +recorded_with: VCR 6.0.0 diff --git a/test/models/link_checker/checker_test.rb b/test/models/link_checker/checker_test.rb index 5c755966d20..73859c483c1 100644 --- a/test/models/link_checker/checker_test.rb +++ b/test/models/link_checker/checker_test.rb @@ -34,13 +34,6 @@ class CheckerTest < ActiveSupport::TestCase "https://bootcamp.fjord.jp/pages/#{pages(:page2).id}" ) - @link_example = LinkChecker::Link.new( - 'example', - 'http://example.com', - 'テスト', - "https://bootcamp.fjord.jp/pages/#{pages(:page2).id}" - ) - @link_mac = LinkChecker::Link.new( 'Macの型番調べ辛い', 'https://docs.komagata.org/4433', @@ -49,6 +42,36 @@ class CheckerTest < ActiveSupport::TestCase ) end + test '.check_response' do + VCR.use_cassette 'link_checker/checker/check_response' do + expected = [ + LinkChecker::Link.new( + 'HDDが分かる', + 'http://homepage2.nifty.com/kamurai/HDD.htm', + 'PC性能の見方を知る', + "https://bootcamp.fjord.jp/practices/#{practices(:practice3).id}", + false + ), + LinkChecker::Link.new( + '存在しないページ', + 'http://example.com/xxxxx', + 'Docsページ', + "https://bootcamp.fjord.jp/pages/#{pages(:page3).id}", + 404 + ), + LinkChecker::Link.new( + 'example', + 'http://example.com', + 'テスト', + "https://bootcamp.fjord.jp/pages/#{pages(:page2).id}", + 200 + ) + ] + + assert_equal expected, Checker.check_response([@link_hdd, @link_not_exist, @link_example]) + end + end + test '.valid_url? returns true with a valid url' do assert Checker.valid_url?('http://example.com') assert Checker.valid_url?('https://ja.wikipedia.org/wiki/あ') @@ -66,18 +89,5 @@ class CheckerTest < ActiveSupport::TestCase test '.denied_host? returns false when an url doesn\'t contain a denied host' do assert_not Checker.denied_host?('http://example.com') end - - test '#check' do - VCR.use_cassette 'link_checker/checker/check' do - links = [@link_hdd, @link_cpu, @link_not_exist, @link_example, @link_mac] - checker = Checker.new(links) - - @link_hdd.response = false - @link_not_exist.response = 404 - expected = [@link_hdd, @link_not_exist] - - assert_equal expected, checker.check - end - end end end From cb8e73ec5a9ee40a913d45e69ae8d07663b32463 Mon Sep 17 00:00:00 2001 From: AudioStakes Date: Thu, 3 Feb 2022 14:10:07 +0900 Subject: [PATCH 27/30] =?UTF-8?q?LinkChecker::Link=20=E3=82=AF=E3=83=A9?= =?UTF-8?q?=E3=82=B9=E3=82=92=E4=B8=80=E3=81=A4=E3=81=AE=E3=83=95=E3=82=A1?= =?UTF-8?q?=E3=82=A4=E3=83=AB=E3=81=A7=E5=AE=9A=E7=BE=A9=E3=81=99=E3=82=8B?= =?UTF-8?q?=E3=82=88=E3=81=86=E3=81=AB=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/link_checker/extractor.rb | 2 -- app/models/link_checker/link.rb | 5 +++++ test/models/link_checker/checker_test.rb | 11 +++++------ 3 files changed, 10 insertions(+), 8 deletions(-) create mode 100644 app/models/link_checker/link.rb diff --git a/app/models/link_checker/extractor.rb b/app/models/link_checker/extractor.rb index 5d65fdff159..36c082935a1 100644 --- a/app/models/link_checker/extractor.rb +++ b/app/models/link_checker/extractor.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true module LinkChecker - Link = Struct.new(:title, :url, :source_title, :source_url, :response) - module Extractor MARKDOWN_LINK_REGEXP = %r{\[(.*?)\]\((#{URI::DEFAULT_PARSER.make_regexp}|/.*?)\)}.freeze diff --git a/app/models/link_checker/link.rb b/app/models/link_checker/link.rb new file mode 100644 index 00000000000..076f28b7f89 --- /dev/null +++ b/app/models/link_checker/link.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +module LinkChecker + Link = Struct.new(:title, :url, :source_title, :source_url, :response) +end diff --git a/test/models/link_checker/checker_test.rb b/test/models/link_checker/checker_test.rb index 73859c483c1..213aa0fb458 100644 --- a/test/models/link_checker/checker_test.rb +++ b/test/models/link_checker/checker_test.rb @@ -1,40 +1,39 @@ # frozen_string_literal: true require 'test_helper' -require 'link_checker/extractor' module LinkChecker class CheckerTest < ActiveSupport::TestCase setup do - @link_hdd = LinkChecker::Link.new( + @link_hdd = Link.new( 'HDDが分かる', 'http://homepage2.nifty.com/kamurai/HDD.htm', 'PC性能の見方を知る', "https://bootcamp.fjord.jp/practices/#{practices(:practice3).id}" ) - @link_cpu = LinkChecker::Link.new( + @link_cpu = Link.new( 'CPUとは', 'https://www.pc-master.jp/words/cpu.html', 'PC性能の見方を知る', "https://bootcamp.fjord.jp/practices/#{practices(:practice3).id}" ) - @link_not_exist = LinkChecker::Link.new( + @link_not_exist = Link.new( '存在しないページ', 'http://example.com/xxxxx', 'Docsページ', "https://bootcamp.fjord.jp/pages/#{pages(:page3).id}" ) - @link_example = LinkChecker::Link.new( + @link_example = Link.new( 'example', 'http://example.com', 'テスト', "https://bootcamp.fjord.jp/pages/#{pages(:page2).id}" ) - @link_mac = LinkChecker::Link.new( + @link_mac = Link.new( 'Macの型番調べ辛い', 'https://docs.komagata.org/4433', 'PC性能の見方を知る', From 4309569f7223f916da52e44f9f9b5cba6c8be26d Mon Sep 17 00:00:00 2001 From: AudioStakes Date: Thu, 3 Feb 2022 14:28:31 +0900 Subject: [PATCH 28/30] =?UTF-8?q?Checker.summary=20=E3=81=AE=E3=83=86?= =?UTF-8?q?=E3=82=B9=E3=83=88=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/models/link_checker/checker_test.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/models/link_checker/checker_test.rb b/test/models/link_checker/checker_test.rb index 213aa0fb458..55c5a75d19a 100644 --- a/test/models/link_checker/checker_test.rb +++ b/test/models/link_checker/checker_test.rb @@ -88,5 +88,18 @@ class CheckerTest < ActiveSupport::TestCase test '.denied_host? returns false when an url doesn\'t contain a denied host' do assert_not Checker.denied_host?('http://example.com') end + + test '.summary' do + expected = <<~TEXT + リンク切れがありました。 + - in: + - in: + - in: + - in: + - in: + TEXT + + assert_equal expected.chomp, Checker.summary([@link_hdd, @link_cpu, @link_not_exist, @link_example, @link_mac]) + end end end From 4036231f737b48598134dab9fe903a31f3bad34e Mon Sep 17 00:00:00 2001 From: AudioStakes Date: Thu, 3 Feb 2022 14:29:13 +0900 Subject: [PATCH 29/30] =?UTF-8?q?Link=20=E3=82=AF=E3=83=A9=E3=82=B9?= =?UTF-8?q?=E3=81=AE=E6=96=87=E5=AD=97=E5=88=97=E8=A1=A8=E7=8F=BE=E3=81=A8?= =?UTF-8?q?<=3D>=E6=BC=94=E7=AE=97=E5=AD=90=E3=82=92=20Link=20=E3=82=AF?= =?UTF-8?q?=E3=83=A9=E3=82=B9=E3=81=AB=E5=AE=9A=E7=BE=A9=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/link_checker/checker.rb | 5 +---- app/models/link_checker/link.rb | 10 +++++++++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/app/models/link_checker/checker.rb b/app/models/link_checker/checker.rb index 270882eb044..7902307e0f4 100644 --- a/app/models/link_checker/checker.rb +++ b/app/models/link_checker/checker.rb @@ -50,10 +50,7 @@ def summary(broken_links) return if broken_links.empty? texts = ['リンク切れがありました。'] - broken_links.sort { |a, b| b.source_url <=> a.source_url }.map do |link| - texts << "- <#{link.url} | #{link.title}> in: <#{link.source_url} | #{link.source_title}>" - end - + texts << broken_links.sort.map(&:to_s) texts.join("\n") end end diff --git a/app/models/link_checker/link.rb b/app/models/link_checker/link.rb index 076f28b7f89..602b60f5582 100644 --- a/app/models/link_checker/link.rb +++ b/app/models/link_checker/link.rb @@ -1,5 +1,13 @@ # frozen_string_literal: true module LinkChecker - Link = Struct.new(:title, :url, :source_title, :source_url, :response) + Link = Struct.new(:title, :url, :source_title, :source_url, :response) do + def to_s + "- <#{url} | #{title}> in: <#{source_url} | #{source_title}>" + end + + def <=>(other) + (source_url <=> other.source_url).nonzero? || url <=> other.url + end + end end From 09e20c9b6fa6ba7ee09c5a2b92ad690b2f95bb02 Mon Sep 17 00:00:00 2001 From: AudioStakes Date: Mon, 7 Feb 2022 11:11:24 +0900 Subject: [PATCH 30/30] =?UTF-8?q?pages=20=E3=81=AE=E3=83=95=E3=82=A3?= =?UTF-8?q?=E3=82=AF=E3=82=B9=E3=83=81=E3=83=A3=E3=81=AB=20published=5Fat?= =?UTF-8?q?=20=E3=81=AE=E5=80=A4=E3=82=92=E8=A8=AD=E5=AE=9A=E3=81=97?= =?UTF-8?q?=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- db/fixtures/pages.yml | 3 ++- test/fixtures/pages.yml | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/db/fixtures/pages.yml b/db/fixtures/pages.yml index 5253c5ba747..2cf96436c3c 100644 --- a/db/fixtures/pages.yml +++ b/db/fixtures/pages.yml @@ -35,7 +35,6 @@ page5: title: WIPのテスト body: WIP user: komagata - published_at: "2021-04-01 00:00:00" page6: title: ヘルプのページ @@ -93,6 +92,7 @@ page12: - 完全一致: `$ apt-cache search ^vim$` - 前方一致: `$ apt-cache search ^vim` user: komagata + published_at: "2022-01-01 00:00:00" page13: title: リンク切れチェッカーのテスト用リンクを載せたページ @@ -102,3 +102,4 @@ page13: [SSLサーバー証明書の検証に失敗する host へのリンク](https://www.tablesgenerator.com/markdown_tables) [日本語を含む URL へのリンク](https://ja.wikipedia.org/wiki/あ) user: komagata + published_at: "2022-01-01 00:00:00" diff --git a/test/fixtures/pages.yml b/test/fixtures/pages.yml index c2581312004..f6e774d25eb 100644 --- a/test/fixtures/pages.yml +++ b/test/fixtures/pages.yml @@ -21,6 +21,7 @@ page3: ## 存在しないDocsページ遷移テスト [存在しないページ](http://example.com/xxxxx) user: komagata + published_at: "2020-01-01 00:00:00" page4: title: Bootcampの作業のページ @@ -28,6 +29,7 @@ page4: ## テスト テスト user: komagata + published_at: "2020-01-01 00:00:00" page5: title: WIPのテスト @@ -38,6 +40,7 @@ page6: title: Docsの検索結果テスト用 body: Docsの検索結果テスト用 user: komagata + published_at: "2020-01-01 00:00:00" page7: title: プラクティスに紐付いたDocs @@ -65,3 +68,4 @@ page8: - 完全一致: `$ apt-cache search ^vim$` - 前方一致: `$ apt-cache search ^vim` user: komagata + published_at: "2022-01-01 00:00:00"