-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.html
More file actions
184 lines (170 loc) · 9.09 KB
/
Copy pathindex.html
File metadata and controls
184 lines (170 loc) · 9.09 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
---
layout: default
---
<div class="screen" id="screen" tabindex="0"></div>
<div class="chips" id="chips">
<button class="chip" data-cmd="whoami">whoami</button>
<button class="chip" data-cmd="cat research.md">research</button>
<button class="chip" data-cmd="cat exploits.md">exploits</button>
<button class="chip" data-cmd="cat teaching.md">teaching</button>
<button class="chip" data-cmd="cat publications.md">publications</button>
<button class="chip" data-cmd="cat contact.md">contact</button>
<button class="chip" data-cmd="help">help</button>
</div>
<div class="promptline">
<span class="ps1" id="ps1"></span>
<input id="cmd" autocomplete="off" autocapitalize="off" spellcheck="false" aria-label="terminal command input" />
<span class="cursor"></span>
</div>
<script>
/* ---- identity (from _config.yml) ---- */
const USER = {{ site.shell.user | jsonify }};
const HOST = {{ site.shell.host | jsonify }};
const PATH = "~";
/* ---- posts, generated from your markdown by Jekyll ----
`cats` is the full category path, e.g. ["research","heap"].
Nesting is just a longer path; the engine below handles any depth. */
const POSTS = [{% for p in site.posts %}
{ title: {{ p.title | jsonify }}, url: {{ p.url | relative_url | jsonify }}, date: {{ p.date | date: "%Y-%m-%d" | jsonify }}, summary: {{ p.description | default: p.excerpt | strip_html | strip_newlines | truncate: 110 | jsonify }}, tags: {{ p.tags | jsonify }}, cats: {{ p.categories | jsonify }} },{% endfor %}
];
/* top-level sections, in the order you want them listed */
const TOP = ["research", "exploits", "teaching", "publications"];
/* ---- EDIT ME: static identity sections ---- */
const BANNER = String.raw`
_ __ _
___ (_)_ __ _____ _____ _ __ / _| | _____ __
/ _ \| | '_ \ / _ \ \ / / _ \ '__| |_| |/ _ \ \ /\ / /
| __/| | |_) | (_) \ V / __/ | | _| | (_) \ V V /
\___||_| .__/ \___/ \_/ \___|_| |_| |_|\___/ \_/\_/
|_| `;
const WHOAMI = [
`<span class="bright">${USER}</span> — security researcher & educator`,
``,
`I break things to understand them, write up what I find, and`,
`teach others to do the same. Memory corruption, binary`,
`exploitation, and web application security.`,
``,
`<span class="dim"># type `+"`help`"+` to see commands.</span>`,
];
const CONTACT = [
`<span class="bright">find me:</span>`, ``,
` github <a href="https://github.com/eipoverflow">github.com/eipoverflow</a>`,
` mastodon <a href="#">@eipoverflow@infosec.exchange</a>`,
` email <a href="mailto:you@example.com">you@example.com</a>`,
];
/* ---- engine (rarely needs editing) ---- */
const screen = document.getElementById("screen");
const input = document.getElementById("cmd");
const ps1El = document.getElementById("ps1");
const ps1 = () => `<span class="user">${USER}</span>@<span class="host">${HOST}</span>:<span class="path">${PATH}</span>$ `;
ps1El.innerHTML = ps1();
function print(html = "", cls = "") {
const d = document.createElement("div");
d.className = "line " + cls; d.innerHTML = html;
screen.appendChild(d); screen.scrollTop = screen.scrollHeight; return d;
}
const esc = s => s.replace(/[&<>]/g, c => ({"&":"&","<":"<",">":">"}[c]));
function echo(cmd){ print(`${ps1()}<span class="bright">${esc(cmd)}</span>`); }
/* --- category tree helpers (work at any nesting depth) --- */
const startsWith = (p, path) => path.every((seg, i) => p.cats[i] === seg);
const postsUnder = path => POSTS.filter(p => startsWith(p, path)); // incl. deeper
const postsHere = path => POSTS.filter(p => startsWith(p, path) && p.cats.length === path.length);
function subdirsOf(path) {
const s = new Set();
POSTS.forEach(p => { if (startsWith(p, path) && p.cats.length > path.length) s.add(p.cats[path.length]); });
return [...s].sort();
}
function postEntry(p) {
const tags = (p.tags||[]).map(t=>`<span class="tag">#${t}</span>`).join(" ");
print(`<div class="entry"><span class="bright">▸</span> <a href="${p.url}">${esc(p.title)}</a> <span class="meta">[${p.date}]</span>` +
`<br><span class="dim"> ${esc(p.summary||"")}</span> ${tags}</div>`);
}
/* list a path: show subdirectories (clickable) then posts in this dir */
function listPath(path) {
print(`<h2 class="sec">${path.join("/")}/</h2>`);
const subs = subdirsOf(path), here = postsHere(path);
subs.forEach(s => {
const full = path.concat(s), n = postsUnder(full).length;
print(`<div class="entry"><span class="bright">▸</span> <a href="#" data-cmd="cat ${full.join("/")}">${esc(s)}/</a> <span class="meta">[${n} post${n===1?"":"s"}]</span></div>`);
});
if (subs.length && here.length) print(`<span class="dim">—</span>`);
here.forEach(postEntry);
if (!subs.length && !here.length) print(`<span class="dim"># empty — drop a markdown file in _posts/</span>`);
}
const CMDS = {
help() {
print(`<span class="bright">available commands</span>`);
print(` <span class="bright">whoami</span> who is this`);
print(` <span class="bright">ls</span> [dir] list sections, or what's inside one`);
print(` <span class="bright">cat</span> <path> open a section, e.g. research or research/heap`);
print(` <span class="bright">theme</span> <name> green | amber`);
print(` <span class="bright">banner</span> · <span class="bright">clear</span> · <span class="bright">help</span>`);
print(``);
print(`<span class="dim"># sections nest like folders: cat exploits then cat exploits/iot</span>`);
print(`<span class="dim"># ↑/↓ history · or click the chips below.</span>`);
},
ls(a) {
const raw = (a[0]||"").replace(/^\/+|\/+$/g, "");
if (!raw) { print(`<span class="bright">${TOP.map(t=>t+"/").concat("contact.md").join(" ")}</span>`); return; }
const path = raw.split("/"), subs = subdirsOf(path), here = postsHere(path);
if (!subs.length && !here.length) { print(`ls: ${esc(raw)}: No such file or directory`, "warn"); return; }
const names = subs.map(s=>s+"/").concat(here.map(p=>p.title)).map(esc);
print(`<span class="bright">${names.join(" ")}</span>`);
},
whoami(){ WHOAMI.forEach(l=>print(l)); },
cat(a) {
const raw = (a[0]||"").replace(/\.md$/, "").replace(/^\/+|\/+$/g, "");
if (!raw) { print(`cat: missing operand. try: cat research`, "warn"); return; }
if (raw === "contact") { CONTACT.forEach(l=>print(l)); return; }
const path = raw.split("/");
const valid = TOP.includes(path[0]) && (path.length === 1 || postsUnder(path).length || subdirsOf(path).length);
valid ? listPath(path) : print(`cat: ${esc(raw)}: No such file or directory`, "warn");
},
theme(a) {
const t=(a[0]||"").toLowerCase();
if (t==="amber"||t==="green"){ window.__setTheme(t); print(`phosphor set to <span class="bright">${t}</span>`); }
else print(`usage: theme [green|amber]`,"warn");
},
banner(){ print(`<div class="banner">${BANNER}</div>`); },
clear(){ screen.innerHTML=""; },
};
const ALIAS = { research:["cat","research"], exploits:["cat","exploits"], teaching:["cat","teaching"],
contact:["cat","contact"], about:["whoami"], publications: ["cat", "publications"], dir:["ls"], "?":["help"] };
function run(raw){
const cmd=raw.trim(); if(!cmd) return; echo(cmd);
let [n,...a]=cmd.split(/\s+/); n=n.toLowerCase();
if (ALIAS[n]) [n,...a]=ALIAS[n];
(CMDS[n]) ? CMDS[n](a) : print(`${esc(n)}: command not found. type <span class="bright">help</span>`,"warn");
print("");
}
const hist=[]; let hi=-1;
input.addEventListener("keydown", e=>{
if (e.key==="Enter"){ const v=input.value; if(v.trim()){hist.push(v);hi=hist.length;} input.value=""; run(v); }
else if (e.key==="ArrowUp"){ if(hi>0){hi--;input.value=hist[hi];e.preventDefault();} }
else if (e.key==="ArrowDown"){ if(hi<hist.length-1){hi++;input.value=hist[hi];} else {hi=hist.length;input.value="";} }
else if (e.key==="l"&&e.ctrlKey){ e.preventDefault(); CMDS.clear(); }
});
document.getElementById("chips").addEventListener("click", e=>{
const b=e.target.closest(".chip"); if(!b) return; run(b.dataset.cmd); input.focus();
});
/* clicking a subdirectory link in the output runs its command */
screen.addEventListener("click", e=>{
const link=e.target.closest("[data-cmd]"); if(!link) return;
e.preventDefault(); run(link.dataset.cmd); input.focus();
});
document.querySelector(".crt").addEventListener("mousedown", e=>{
if (e.target.closest("a,.chip,#cmd,.tbtn")) return; setTimeout(()=>input.focus(),0);
});
(function boot(){
const reduce = matchMedia("(prefers-reduced-motion: reduce)").matches;
const lines = [
`<span class="dim">Last login: ${new Date().toUTCString()} on tty1</span>`,
`<div class="banner">${BANNER}</div>`,
`<span class="tagline">// security research · exploits · teaching</span>`,
`<span class="dim">type </span><span class="bright">help</span><span class="dim"> to begin, or click a command below.</span>`, ``,
];
if (reduce){ lines.forEach(l=>print(l)); input.focus(); return; }
let i=0; (function step(){ if(i>=lines.length){input.focus();return;} print(lines[i++]); setTimeout(step,200); })();
})();
input.focus();
</script>