Skip to content

Commit 5d76eb7

Browse files
ycerutofabpot
authored andcommitted
[ErrorRenderer] Improving the exception page provided by HtmlErrorRenderer
1 parent 759f91c commit 5d76eb7

25 files changed

+1183
-216
lines changed

src/Symfony/Bundle/FrameworkBundle/Resources/config/error_renderer.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
<argument>%kernel.debug%</argument>
1515
<argument>%kernel.charset%</argument>
1616
<argument>%debug.file_link_format%</argument>
17+
<argument>%kernel.project_dir%</argument>
18+
<argument type="service" id="request_stack" />
19+
<argument type="service" id="logger" on-invalid="null" />
1720
</service>
1821

1922
<service id="error_renderer.renderer.json" class="Symfony\Component\ErrorRenderer\ErrorRenderer\JsonErrorRenderer">

src/Symfony/Component/ErrorHandler/Tests/ExceptionHandlerTest.php

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,16 +52,15 @@ public function testDebug()
5252
$response = ob_get_clean();
5353

5454
$this->assertContains('<h1 class="break-long-words exception-message">Foo</h1>', $response);
55-
$this->assertContains('<div class="trace trace-as-html">', $response);
55+
$this->assertContains('<div class="trace trace-as-html" id="trace-box-1">', $response);
5656

5757
// taken from https://www.owasp.org/index.php/Cross-site_Scripting_(XSS)
58-
$htmlWithXss = '<body onload=alert(\'test1\')> <b onmouseover=alert(\'Wufff!\')>click me!</b> <img src="j&#X41vascript:alert(\'test2\')"> <meta http-equiv="refresh"
59-
content="0;url=data:text/html;base64,PHNjcmlwdD5hbGVydCgndGVzdDMnKTwvc2NyaXB0Pg">';
58+
$htmlWithXss = '<body onload=alert(\'test1\')> <b onmouseover=alert(\'Wufff!\')>click me!</b> <img src="j&#X41vascript:alert(\'test2\')"> <meta http-equiv="refresh" content="0;url=data:text/html;base64,PHNjcmlwdD5hbGVydCgndGVzdDMnKTwvc2NyaXB0Pg">';
6059
ob_start();
6160
$handler->sendPhpResponse(new \RuntimeException($htmlWithXss));
6261
$response = ob_get_clean();
6362

64-
$this->assertContains(sprintf('<h1 class="break-long-words exception-message">%s</h1>', htmlspecialchars($htmlWithXss, ENT_COMPAT | ENT_SUBSTITUTE, 'UTF-8')), $response);
63+
$this->assertContains(sprintf('<h1 class="break-long-words exception-message long">%s</h1>', htmlspecialchars($htmlWithXss, ENT_COMPAT | ENT_SUBSTITUTE, 'UTF-8')), $response);
6564
}
6665

6766
public function testStatusCode()
@@ -106,7 +105,7 @@ public function testNestedExceptions()
106105
$handler->sendPhpResponse(new \RuntimeException('Foo', 0, new \RuntimeException('Bar')));
107106
$response = ob_get_clean();
108107

109-
$this->assertStringMatchesFormat('%A<p class="break-long-words trace-message">Foo</p>%A<p class="break-long-words trace-message">Bar</p>%A', $response);
108+
$this->assertStringMatchesFormat('%A<h1 class="break-long-words exception-message">Foo</h1>%A<p class="break-long-words trace-message">Bar</p>%A', $response);
110109
}
111110

112111
public function testHandle()

src/Symfony/Component/ErrorRenderer/ErrorRenderer/HtmlErrorRenderer.php

Lines changed: 201 additions & 210 deletions
Large diffs are not rendered by default.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
body { background-color: #fff; color: #222; font: 16px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; margin: 0; }
2+
.container { margin: 30px; max-width: 600px; }
3+
h1 { color: #dc3545; font-size: 24px; }
4+
h2 { font-size: 18px; }
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
/* This file is based on WebProfilerBundle/Resources/views/Profiler/profiler.css.twig.
2+
If you make any change in this file, verify the same change is needed in the other file. */
3+
:root {
4+
--font-sans-serif: Helvetica, Arial, sans-serif;
5+
--page-background: #f9f9f9;
6+
--color-text: #222;
7+
/* when updating any of these colors, do the same in toolbar.css.twig */
8+
--color-success: #4f805d;
9+
--color-warning: #a46a1f;
10+
--color-error: #b0413e;
11+
--color-muted: #999;
12+
--tab-background: #fff;
13+
--tab-color: #444;
14+
--tab-active-background: #666;
15+
--tab-active-color: #fafafa;
16+
--tab-disabled-background: #f5f5f5;
17+
--tab-disabled-color: #999;
18+
--metric-value-background: #fff;
19+
--metric-value-color: inherit;
20+
--metric-unit-color: #999;
21+
--metric-label-background: #e0e0e0;
22+
--metric-label-color: inherit;
23+
--table-border: #e0e0e0;
24+
--table-background: #fff;
25+
--table-header: #e0e0e0;
26+
--trace-selected-background: #F7E5A1;
27+
--tree-active-background: #F7E5A1;
28+
--exception-title-color: var(--base-2);
29+
--shadow: 0px 0px 1px rgba(128, 128, 128, .2);
30+
--border: 1px solid #e0e0e0;
31+
--background-error: var(--color-error);
32+
--highlight-comment: #969896;
33+
--highlight-default: #222222;
34+
--highlight-keyword: #a71d5d;
35+
--highlight-string: #183691;
36+
--base-0: #fff;
37+
--base-1: #f5f5f5;
38+
--base-2: #e0e0e0;
39+
--base-3: #ccc;
40+
--base-4: #666;
41+
--base-5: #444;
42+
--base-6: #222;
43+
}
44+
45+
html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}
46+
47+
html {
48+
/* always display the vertical scrollbar to avoid jumps when toggling contents */
49+
overflow-y: scroll;
50+
}
51+
body { background-color: #F9F9F9; color: var(--base-6); font: 14px/1.4 Helvetica, Arial, sans-serif; padding-bottom: 45px; }
52+
53+
a { cursor: pointer; text-decoration: none; }
54+
a:hover { text-decoration: underline; }
55+
abbr[title] { border-bottom: none; cursor: help; text-decoration: none; }
56+
57+
code, pre { font: 13px/1.5 Consolas, Monaco, Menlo, "Ubuntu Mono", "Liberation Mono", monospace; }
58+
59+
table, tr, th, td { background: #FFF; border-collapse: collapse; vertical-align: top; }
60+
table { background: #FFF; border: var(--border); box-shadow: 0px 0px 1px rgba(128, 128, 128, .2); margin: 1em 0; width: 100%; }
61+
table th, table td { border: solid var(--base-2); border-width: 1px 0; padding: 8px 10px; }
62+
table th { background-color: var(--base-2); font-weight: bold; text-align: left; }
63+
64+
.m-t-5 { margin-top: 5px; }
65+
.hidden-xs-down { display: none; }
66+
.block { display: block; }
67+
.full-width { width: 100%; }
68+
.hidden { display: none; }
69+
.prewrap { white-space: pre-wrap; }
70+
.nowrap { white-space: nowrap; }
71+
.newline { display: block; }
72+
.break-long-words { word-wrap: break-word; overflow-wrap: break-word; -webkit-hyphens: auto; -moz-hyphens: auto; hyphens: auto; min-width: 0; }
73+
.text-small { font-size: 12px !important; }
74+
.text-muted { color: #999; }
75+
.text-bold { font-weight: bold; }
76+
.empty { border: 4px dashed var(--base-2); color: #999; margin: 1em 0; padding: .5em 2em; }
77+
78+
.status-success { background: rgba(94, 151, 110, 0.3); }
79+
.status-warning { background: rgba(240, 181, 24, 0.3); }
80+
.status-error { background: rgba(176, 65, 62, 0.2); }
81+
.status-success td, .status-warning td, .status-error td { background: transparent; }
82+
tr.status-error td, tr.status-warning td { border-bottom: 1px solid #FAFAFA; border-top: 1px solid #FAFAFA; }
83+
.status-warning .colored { color: #A46A1F; }
84+
.status-error .colored { color: var(--color-error); }
85+
86+
.sf-toggle { cursor: pointer; }
87+
.sf-toggle-content { -moz-transition: display .25s ease; -webkit-transition: display .25s ease; transition: display .25s ease; }
88+
.sf-toggle-content.sf-toggle-hidden { display: none; }
89+
.sf-toggle-content.sf-toggle-visible { display: block; }
90+
thead.sf-toggle-content.sf-toggle-visible, tbody.sf-toggle-content.sf-toggle-visible { display: table-row-group; }
91+
.sf-toggle-off .icon-close, .sf-toggle-on .icon-open { display: none; }
92+
.sf-toggle-off .icon-open, .sf-toggle-on .icon-close { display: block; }
93+
94+
.tab-navigation { margin: 0 0 1em 0; padding: 0; }
95+
.tab-navigation li { background: var(--tab-background); border: 1px solid var(--table-border); color: var(--tab-color); cursor: pointer; display: inline-block; font-size: 16px; margin: 0 0 0 -1px; padding: .5em .75em; z-index: 1; }
96+
.tab-navigation li .badge { background-color: var(--base-1); color: var(--base-4); display: inline-block; font-size: 14px; font-weight: bold; margin-left: 8px; min-width: 10px; padding: 1px 6px; text-align: center; white-space: nowrap; }
97+
.tab-navigation li.disabled { background: var(--tab-disabled-background); color: var(--tab-disabled-color); }
98+
.tab-navigation li.active { background: var(--tab-active-background); color: var(--tab-active-color); z-index: 1100; }
99+
.tab-navigation li.active .badge { background-color: var(--base-5); color: var(--base-2); }
100+
.tab-content > *:first-child { margin-top: 0; }
101+
.tab-navigation li .badge.status-warning { background: var(--color-warning); color: #FFF; }
102+
.tab-navigation li .badge.status-error { background: var(--background-error); color: #FFF; }
103+
.sf-tabs .tab:not(:first-child) { display: none; }
104+
105+
[data-filters] { position: relative; }
106+
[data-filtered] { cursor: pointer; }
107+
[data-filtered]:after { content: '\00a0\25BE'; }
108+
[data-filtered]:hover .filter-list li { display: inline-flex; }
109+
[class*="filter-hidden-"] { display: none; }
110+
.filter-list { position: absolute; border: var(--border); box-shadow: var(--shadow); margin: 0; padding: 0; display: flex; flex-direction: column; }
111+
.filter-list :after { content: ''; }
112+
.filter-list li {
113+
background: var(--tab-disabled-background);
114+
border-bottom: var(--border);
115+
color: var(--tab-disabled-color);
116+
display: none;
117+
list-style: none;
118+
margin: 0;
119+
padding: 5px 10px;
120+
text-align: left;
121+
font-weight: normal;
122+
}
123+
.filter-list li.active {
124+
background: var(--tab-background);
125+
color: var(--tab-color);
126+
}
127+
.filter-list li.last-active {
128+
background: var(--tab-active-background);
129+
color: var(--tab-active-color);
130+
}
131+
132+
.filter-list-level li { cursor: s-resize; }
133+
.filter-list-level li.active { cursor: n-resize; }
134+
.filter-list-level li.last-active { cursor: default; }
135+
.filter-list-level li.last-active:before { content: '\2714\00a0'; }
136+
.filter-list-choice li:before { content: '\2714\00a0'; color: transparent; }
137+
.filter-list-choice li.active:before { color: unset; }
138+
139+
.container { max-width: 1024px; margin: 0 auto; padding: 0 15px; }
140+
.container::after { content: ""; display: table; clear: both; }
141+
142+
header { background-color: var(--base-6); color: rgba(255, 255, 255, 0.75); font-size: 13px; height: 33px; line-height: 33px; padding: 0; }
143+
header .container { display: flex; justify-content: space-between; }
144+
.logo { flex: 1; font-size: 13px; font-weight: normal; margin: 0; padding: 0; }
145+
.logo svg { height: 18px; width: 18px; opacity: .8; vertical-align: -5px; }
146+
147+
.help-link { margin-left: 15px; }
148+
.help-link a { color: inherit; }
149+
.help-link .icon svg { height: 15px; width: 15px; opacity: .7; vertical-align: -2px; }
150+
.help-link a:hover { color: #EEE; text-decoration: none; }
151+
.help-link a:hover svg { opacity: .9; }
152+
153+
.exception-summary { background: var(--background-error); border-bottom: 2px solid rgba(0, 0, 0, 0.1); border-top: 1px solid rgba(0, 0, 0, .3); flex: 0 0 auto; margin-bottom: 15px; }
154+
.exception-metadata { background: rgba(0, 0, 0, 0.1); padding: 7px 0; }
155+
.exception-metadata .container { display: flex; flex-direction: row; justify-content: space-between; }
156+
.exception-metadata h2, .exception-metadata h2 > a { color: rgba(255, 255, 255, 0.8); font-size: 13px; font-weight: 400; margin: 0; }
157+
.exception-http small { font-size: 13px; opacity: .7; }
158+
.exception-hierarchy { flex: 1; }
159+
.exception-hierarchy .icon { margin: 0 3px; opacity: .7; }
160+
.exception-hierarchy .icon svg { height: 13px; width: 13px; vertical-align: -2px; }
161+
162+
.exception-without-message .exception-message-wrapper { display: none; }
163+
.exception-message-wrapper .container { display: flex; align-items: flex-start; min-height: 70px; padding: 10px 15px 8px; }
164+
.exception-message { flex-grow: 1; }
165+
.exception-message, .exception-message a { color: #FFF; font-size: 21px; font-weight: 400; margin: 0; }
166+
.exception-message.long { font-size: 18px; }
167+
.exception-message a { border-bottom: 1px solid rgba(255, 255, 255, 0.5); font-size: inherit; text-decoration: none; }
168+
.exception-message a:hover { border-bottom-color: #ffffff; }
169+
170+
.exception-illustration { flex-basis: 111px; flex-shrink: 0; height: 66px; margin-left: 15px; opacity: .7; }
171+
172+
.trace + .trace { margin-top: 30px; }
173+
.trace-head { background-color: var(--base-2); padding: 10px; position: relative; }
174+
.trace-head .trace-class { color: var(--base-6); font-size: 18px; font-weight: bold; line-height: 1.3; margin: 0; position: relative; }
175+
.trace-head .trace-namespace { color: #999; display: block; font-size: 13px; }
176+
.trace-head .icon { position: absolute; right: 0; top: 0; }
177+
.trace-head .icon svg { height: 24px; width: 24px; }
178+
179+
.trace-details { background: var(--base-0); border: var(--border); box-shadow: 0px 0px 1px rgba(128, 128, 128, .2); margin: 1em 0; table-layout: fixed; }
180+
181+
.trace-message { font-size: 14px; font-weight: normal; margin: .5em 0 0; }
182+
183+
.trace-line { position: relative; padding-top: 8px; padding-bottom: 8px; }
184+
.trace-line + .trace-line { border-top: var(--border); }
185+
.trace-line:hover { background: var(--base-1); }
186+
.trace-line a { color: var(--base-6); }
187+
.trace-line .icon { opacity: .4; position: absolute; left: 10px; top: 11px; }
188+
.trace-line .icon svg { height: 16px; width: 16px; }
189+
.trace-line-header { padding-left: 36px; padding-right: 10px; }
190+
191+
.trace-file-path, .trace-file-path a { color: var(--base-6); font-size: 13px; }
192+
.trace-class { color: var(--color-error); }
193+
.trace-type { padding: 0 2px; }
194+
.trace-method { color: var(--color-error); font-weight: bold; }
195+
.trace-arguments { color: #777; font-weight: normal; padding-left: 2px; }
196+
197+
.trace-code { background: var(--base-0); font-size: 12px; margin: 10px 10px 2px 10px; padding: 10px; overflow-x: auto; white-space: nowrap; }
198+
.trace-code ol { margin: 0; float: left; }
199+
.trace-code li { color: #969896; margin: 0; padding-left: 10px; float: left; width: 100%; }
200+
.trace-code li + li { margin-top: 5px; }
201+
.trace-code li.selected { background: var(--trace-selected-background); margin-top: 2px; }
202+
.trace-code li code { color: var(--base-6); white-space: nowrap; }
203+
204+
.trace-as-text .stacktrace { line-height: 1.8; margin: 0 0 15px; white-space: pre-wrap; }
205+
206+
@media (min-width: 575px) {
207+
.hidden-xs-down { display: initial; }
208+
.help-link { margin-left: 30px; }
209+
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
.sf-reset .traces {
2+
padding-bottom: 14px;
3+
}
4+
.sf-reset .traces li {
5+
font-size: 12px;
6+
color: #868686;
7+
padding: 5px 4px;
8+
list-style-type: decimal;
9+
margin-left: 20px;
10+
}
11+
.sf-reset #logs .traces li.error {
12+
font-style: normal;
13+
color: #AA3333;
14+
background: #f9ecec;
15+
}
16+
.sf-reset #logs .traces li.warning {
17+
font-style: normal;
18+
background: #ffcc00;
19+
}
20+
/* fix for Opera not liking empty <li> */
21+
.sf-reset .traces li:after {
22+
content: "\00A0";
23+
}
24+
.sf-reset .trace {
25+
border: 1px solid #D3D3D3;
26+
padding: 10px;
27+
overflow: auto;
28+
margin: 10px 0 20px;
29+
}
30+
.sf-reset .block-exception {
31+
-moz-border-radius: 16px;
32+
-webkit-border-radius: 16px;
33+
border-radius: 16px;
34+
margin-bottom: 20px;
35+
background-color: #f6f6f6;
36+
border: 1px solid #dfdfdf;
37+
padding: 30px 28px;
38+
word-wrap: break-word;
39+
overflow: hidden;
40+
}
41+
.sf-reset .block-exception div {
42+
color: #313131;
43+
font-size: 10px;
44+
}
45+
.sf-reset .block-exception-detected .illustration-exception,
46+
.sf-reset .block-exception-detected .text-exception {
47+
float: left;
48+
}
49+
.sf-reset .block-exception-detected .illustration-exception {
50+
width: 152px;
51+
}
52+
.sf-reset .block-exception-detected .text-exception {
53+
width: 670px;
54+
padding: 30px 44px 24px 46px;
55+
position: relative;
56+
}
57+
.sf-reset .text-exception .open-quote,
58+
.sf-reset .text-exception .close-quote {
59+
font-family: Arial, Helvetica, sans-serif;
60+
position: absolute;
61+
color: #C9C9C9;
62+
font-size: 8em;
63+
}
64+
.sf-reset .open-quote {
65+
top: 0;
66+
left: 0;
67+
}
68+
.sf-reset .close-quote {
69+
bottom: -0.5em;
70+
right: 50px;
71+
}
72+
.sf-reset .block-exception p {
73+
font-family: Arial, Helvetica, sans-serif;
74+
}
75+
.sf-reset .block-exception p a,
76+
.sf-reset .block-exception p a:hover {
77+
color: #565656;
78+
}
79+
.sf-reset .logs h2 {
80+
float: left;
81+
width: 654px;
82+
}
83+
.sf-reset .error-count, .sf-reset .support {
84+
float: right;
85+
width: 170px;
86+
text-align: right;
87+
}
88+
.sf-reset .error-count span {
89+
display: inline-block;
90+
background-color: #aacd4e;
91+
-moz-border-radius: 6px;
92+
-webkit-border-radius: 6px;
93+
border-radius: 6px;
94+
padding: 4px;
95+
color: white;
96+
margin-right: 2px;
97+
font-size: 11px;
98+
font-weight: bold;
99+
}
100+
101+
.sf-reset .support a {
102+
display: inline-block;
103+
-moz-border-radius: 6px;
104+
-webkit-border-radius: 6px;
105+
border-radius: 6px;
106+
padding: 4px;
107+
color: #000000;
108+
margin-right: 2px;
109+
font-size: 11px;
110+
font-weight: bold;
111+
}
112+
113+
.sf-reset .toggle {
114+
vertical-align: middle;
115+
}
116+
.sf-reset .linked ul,
117+
.sf-reset .linked li {
118+
display: inline;
119+
}
120+
.sf-reset #output-content {
121+
color: #000;
122+
font-size: 12px;
123+
}
124+
.sf-reset #traces-text pre {
125+
white-space: pre;
126+
font-size: 12px;
127+
font-family: monospace;
128+
}
Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAgCAYAAAABtRhCAAADVUlEQVRIx82XX0jTURTHLYPyqZdefQx66CEo80+aYpoIkqzUikz6Z5klQoWUWYRIJYEUGpQ+lIr9U5dOTLdCtkmWZis3rbnC5fw/neYW002307mX/cZvP3/7o1PwwOdh95x7vnf39zvnd29AgBer2xO6DclAXiMqZAqxIiNIN/IYSUS2BPhjmGATchUxI+ADWiRhpWK7HKuHFVBFdmU5YvnI4grFGCaReF/EBH4KsZlGgj2JBTuCYBWRIYF8YoEOJ6wBt/gEs7mBbyOjQXruPLSdOgPCiEiPSUUHDoL8Ug5IUo9B/d5wrt+G7OAKNrODPuVdB6vRCIzN6SdBlpW9RIgk/1FeAXabzRlrUPVCS/JhbmwudztnGeeH9AyXBIwtmM3wLinZJZHifjHw2V+NBoRh+9ixQrbgbnaSIcl7cGea6hoXQbNe7za241oeO5Z0p42M4BV2EqP2D50wo+6HzvwC6C4sApNOR8cmOrtcnhtj2kYRyC9eBvXzKrBZrXSs72kFd1t3MoKVbMekQkEnSNKOO8fac3LpmK6l1TlGtsxmsdKFsecPYgwxst0cwROMYDXboSotg0WLBRqjY51jLYcENElXwW2XJKPydvoI2GN9T8rBtrAArYIUruBJXkFheCQYlCpQP6uk5dAQFQNaUROMSGVQFxLmkoQsxDJrhLbTZ+nvVsERME9MgPJRKV/58AsyomTSzE813WLFvWK++qI0xSfQl8k8Pg46sYRuv5t6dS+4RqxDwaa4BGjYH+NTQvKScIp9+YL/hoZh3jDtLRHtt2C3g6bmhX+CpsFBWg7ilDSPgj0lD2ncr5ev/BP8VvyAJhqVyZeUhPOrEhEFxgEtjft846Z/guQTNT89Q5P9flMLoth4F7808wKtWWKzAwNQHxrh/1vaid2F+XpYTSbQf1XA2McOmOpROnvpvMEA4tSjq1cW0sws2gCYxswY6TKkvzYnJq1NHZLnRU4BX+4U0uburvusu8Kv8iHY7qefkM4IFngJHEOUXmLEPgiGsI8YnlZILit3vSSLRTQe/MPIZva5pshNIEmyFQlCvruJKXPkCEfmePzkphXHdzZNQdoRI9KPlBAxlj/I8U97ERPS5bjGbWDFbEdqHVe5caTBeZZx2H/IMvzeN15yoQAAAABJRU5ErkJggg==

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy