diff --git a/src/pentesting-web/h2c-smuggling.md b/src/pentesting-web/h2c-smuggling.md
index a5cc80ee2..36d2080ba 100644
--- a/src/pentesting-web/h2c-smuggling.md
+++ b/src/pentesting-web/h2c-smuggling.md
@@ -6,15 +6,15 @@
#### HTTP2 Over Cleartext (H2C)
-H2C, ili **http2 preko čistog teksta**, odstupa od norme prolaznih HTTP veza tako što nadograđuje standardnu HTTP **vezu na trajnu**. Ova nadograđena veza koristi http2 binarni protokol za kontinuiranu komunikaciju, za razliku od jedinstvene prirode zahteva čistog HTTP-a.
+H2C, ili **http2 preko čistog teksta**, odstupa od norme prolaznih HTTP veza tako što unapređuje standardnu HTTP **vezu u trajnu**. Ova unapređena veza koristi http2 binarni protokol za kontinuiranu komunikaciju, za razliku od jedinstvene prirode zahteva čistog HTTP-a.
-Suština problema sa krijumčarenjem nastaje upotrebom **obrnute proxy**. Obično, obrnuta proxy obrađuje i prosleđuje HTTP zahteve ka backend-u, vraćajući odgovor backend-a nakon toga. Međutim, kada je `Connection: Upgrade` zaglavlje prisutno u HTTP zahtevu (što se obično vidi sa websocket vezama), obrnuta **proxy održava trajnu vezu** između klijenta i servera, olakšavajući kontinuiranu razmenu potrebnu za određene protokole. Za H2C veze, pridržavanje RFC zahteva prisustvo tri specifična zaglavlja:
+Suština problema sa krijumčarenjem nastaje upotrebom **obrnute proxy**. Obično, obrnuta proxy obrađuje i prosleđuje HTTP zahteve ka backend-u, vraćajući odgovor backend-a nakon toga. Međutim, kada je `Connection: Upgrade` zaglavlje prisutno u HTTP zahtevu (što se obično vidi sa websocket vezama), obrnuta **proxy održava trajnu vezu** između klijenta i servera, olakšavajući kontinuiranu razmenu potrebnu za određene protokole. Za H2C veze, pridržavanje RFC-a zahteva prisustvo tri specifična zaglavlja:
```
Upgrade: h2c
HTTP2-Settings: AAMAAABkAARAAAAAAAIAAAAA
Connection: Upgrade, HTTP2-Settings
```
-Vulnerabilnost nastaje kada, nakon nadogradnje veze, obrnuti proxy prestane da upravlja pojedinačnim zahtevima, pretpostavljajući da je njegov posao usmeravanja završen nakon uspostavljanja veze. Iskorišćavanje H2C Smuggling-a omogućava zaobilaženje pravila obrnute proxy primenjenih tokom obrade zahteva, kao što su usmeravanje zasnovano na putanji, autentifikacija i WAF obrada, pod pretpostavkom da je H2C veza uspešno uspostavljena.
+Vulnerabilnost nastaje kada, nakon nadogradnje veze, obrnuti proxy prestane da upravlja pojedinačnim zahtevima, pretpostavljajući da je njegov posao usmeravanja završen nakon uspostavljanja veze. Iskorišćavanje H2C Smuggling-a omogućava zaobilaženje pravila obrnute proxy koje se primenjuju tokom obrade zahteva, kao što su usmeravanje zasnovano na putanji, autentifikacija i WAF obrada, pod pretpostavkom da je H2C veza uspešno uspostavljena.
#### Vulnerable Proxies
@@ -40,7 +40,7 @@ S druge strane, ove usluge ne prosleđuju inherentno oba zaglavlja tokom proxy-p
Važno je napomenuti da ne svi serveri inherentno prosleđuju zaglavlja potrebna za usklađenu H2C nadogradnju. Kao takvi, serveri poput AWS ALB/CLB, NGINX i Apache Traffic Server, među ostalima, prirodno blokiraju H2C veze. Ipak, vredi testirati sa neusklađenom varijantom `Connection: Upgrade`, koja isključuje `HTTP2-Settings` vrednost iz `Connection` zaglavlja, jer neki backend-ovi možda neće biti u skladu sa standardima.
> [!CAUTION]
-> Bez obzira na specifičnu **putanju** određenu u `proxy_pass` URL-u (npr., `http://backend:9999/socket.io`), uspostavljena veza podrazumevano se prebacuje na `http://backend:9999`. Ovo omogućava interakciju sa bilo kojom putanjom unutar tog internog krajnjeg tačke, koristeći ovu tehniku. Kao rezultat, specifikacija putanje u `proxy_pass` URL-u ne ograničava pristup.
+> Bez obzira na specifičnu **putanju** određenu u `proxy_pass` URL-u (npr., `http://backend:9999/socket.io`), uspostavljena veza podrazumevano se vraća na `http://backend:9999`. Ovo omogućava interakciju sa bilo kojom putanjom unutar tog internog krajnjeg tačke, koristeći ovu tehniku. Kao rezultat, specifikacija putanje u `proxy_pass` URL-u ne ograničava pristup.
Alati [**h2csmuggler by BishopFox**](https://github.com/BishopFox/h2csmuggler) i [**h2csmuggler by assetnote**](https://github.com/assetnote/h2csmuggler) olakšavaju pokušaje da se **zaobiđu zaštite koje nameće proxy** uspostavljanjem H2C veze, čime se omogućava pristup resursima zaštićenim proxy-jem.
@@ -48,7 +48,7 @@ Za dodatne informacije o ovoj vulnerabilnosti, posebno u vezi sa NGINX-om, pogle
## Websocket Smuggling
-Websocket smuggling, za razliku od kreiranja HTTP2 tunela do krajnje tačke dostupne putem proxy-ja, uspostavlja Websocket tunel kako bi zaobišao potencijalna ograničenja proxy-ja i omogućio direktnu komunikaciju sa krajnjom tačkom.
+Websocket smuggling, za razliku od kreiranja HTTP2 tunela do krajnje tačke dostupne putem proxy-ja, uspostavlja Websocket tunel kako bi zaobišao potencijalna ograničenja proxy-ja i olakšao direktnu komunikaciju sa krajnjom tačkom.
### Scenario 1
@@ -58,7 +58,7 @@ U ovom scenariju, backend koji nudi javni WebSocket API zajedno sa nedostupnim i
2. Backend odgovara sa status kodom `426`, ukazujući na pogrešnu verziju protokola u `Sec-WebSocket-Version` zaglavlju. Obrnuti proxy, zanemarujući status odgovora backend-a, pretpostavlja spremnost za WebSocket komunikaciju i prosleđuje odgovor klijentu.
3. Kao rezultat, obrnuti proxy je zavaravan da veruje da je WebSocket veza uspostavljena između klijenta i backend-a, dok je u stvarnosti backend odbio Upgrade zahtev. Ipak, proxy održava otvorenu TCP ili TLS vezu između klijenta i backend-a, omogućavajući klijentu neograničen pristup privatnom REST API-ju putem ove veze.
-Pogođeni obrnuti proxy-ji uključuju Varnish, koji je odbio da reši problem, i Envoy proxy verziju 1.8.0 ili stariju, dok su novije verzije izmenile mehanizam nadogradnje. Drugi proxy-ji takođe mogu biti podložni.
+Pogođeni obrnuti proxy-ji uključuju Varnish, koji je odbio da reši problem, i Envoy proxy verziju 1.8.0 ili stariju, dok su novije verzije promenile mehanizam nadogradnje. Drugi proxy-ji takođe mogu biti podložni.

diff --git a/theme/elasticlunr.min.js b/theme/elasticlunr.min.js
new file mode 100644
index 000000000..32cb1bce1
--- /dev/null
+++ b/theme/elasticlunr.min.js
@@ -0,0 +1,10 @@
+/**
+ * elasticlunr - http://weixsong.github.io
+ * Lightweight full-text search engine in Javascript for browser search and offline search. - 0.9.5
+ *
+ * Copyright (C) 2016 Oliver Nightingale
+ * Copyright (C) 2016 Wei Song
+ * MIT Licensed
+ * @license
+ */
+!function(){function e(e){if(null===e||"object"!=typeof e)return e;var t=e.constructor();for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n]);return t}var t=function(e){var n=new t.Index;return n.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),e&&e.call(n,n),n};t.version="0.9.5",lunr=t,t.utils={},t.utils.warn=function(e){return function(t){e.console&&console.warn&&console.warn(t)}}(this),t.utils.toString=function(e){return void 0===e||null===e?"":e.toString()},t.EventEmitter=function(){this.events={}},t.EventEmitter.prototype.addListener=function(){var e=Array.prototype.slice.call(arguments),t=e.pop(),n=e;if("function"!=typeof t)throw new TypeError("last argument must be a function");n.forEach(function(e){this.hasHandler(e)||(this.events[e]=[]),this.events[e].push(t)},this)},t.EventEmitter.prototype.removeListener=function(e,t){if(this.hasHandler(e)){var n=this.events[e].indexOf(t);-1!==n&&(this.events[e].splice(n,1),0==this.events[e].length&&delete this.events[e])}},t.EventEmitter.prototype.emit=function(e){if(this.hasHandler(e)){var t=Array.prototype.slice.call(arguments,1);this.events[e].forEach(function(e){e.apply(void 0,t)},this)}},t.EventEmitter.prototype.hasHandler=function(e){return e in this.events},t.tokenizer=function(e){if(!arguments.length||null===e||void 0===e)return[];if(Array.isArray(e)){var n=e.filter(function(e){return null===e||void 0===e?!1:!0});n=n.map(function(e){return t.utils.toString(e).toLowerCase()});var i=[];return n.forEach(function(e){var n=e.split(t.tokenizer.seperator);i=i.concat(n)},this),i}return e.toString().trim().toLowerCase().split(t.tokenizer.seperator)},t.tokenizer.defaultSeperator=/[\s\-]+/,t.tokenizer.seperator=t.tokenizer.defaultSeperator,t.tokenizer.setSeperator=function(e){null!==e&&void 0!==e&&"object"==typeof e&&(t.tokenizer.seperator=e)},t.tokenizer.resetSeperator=function(){t.tokenizer.seperator=t.tokenizer.defaultSeperator},t.tokenizer.getSeperator=function(){return t.tokenizer.seperator},t.Pipeline=function(){this._queue=[]},t.Pipeline.registeredFunctions={},t.Pipeline.registerFunction=function(e,n){n in t.Pipeline.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+n),e.label=n,t.Pipeline.registeredFunctions[n]=e},t.Pipeline.getRegisteredFunction=function(e){return e in t.Pipeline.registeredFunctions!=!0?null:t.Pipeline.registeredFunctions[e]},t.Pipeline.warnIfFunctionNotRegistered=function(e){var n=e.label&&e.label in this.registeredFunctions;n||t.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",e)},t.Pipeline.load=function(e){var n=new t.Pipeline;return e.forEach(function(e){var i=t.Pipeline.getRegisteredFunction(e);if(!i)throw new Error("Cannot load un-registered function: "+e);n.add(i)}),n},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(e){t.Pipeline.warnIfFunctionNotRegistered(e),this._queue.push(e)},this)},t.Pipeline.prototype.after=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i+1,0,n)},t.Pipeline.prototype.before=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i,0,n)},t.Pipeline.prototype.remove=function(e){var t=this._queue.indexOf(e);-1!==t&&this._queue.splice(t,1)},t.Pipeline.prototype.run=function(e){for(var t=[],n=e.length,i=this._queue.length,o=0;n>o;o++){for(var r=e[o],s=0;i>s&&(r=this._queue[s](r,o,e),void 0!==r&&null!==r);s++);void 0!==r&&null!==r&&t.push(r)}return t},t.Pipeline.prototype.reset=function(){this._queue=[]},t.Pipeline.prototype.get=function(){return this._queue},t.Pipeline.prototype.toJSON=function(){return this._queue.map(function(e){return t.Pipeline.warnIfFunctionNotRegistered(e),e.label})},t.Index=function(){this._fields=[],this._ref="id",this.pipeline=new t.Pipeline,this.documentStore=new t.DocumentStore,this.index={},this.eventEmitter=new t.EventEmitter,this._idfCache={},this.on("add","remove","update",function(){this._idfCache={}}.bind(this))},t.Index.prototype.on=function(){var e=Array.prototype.slice.call(arguments);return this.eventEmitter.addListener.apply(this.eventEmitter,e)},t.Index.prototype.off=function(e,t){return this.eventEmitter.removeListener(e,t)},t.Index.load=function(e){e.version!==t.version&&t.utils.warn("version mismatch: current "+t.version+" importing "+e.version);var n=new this;n._fields=e.fields,n._ref=e.ref,n.documentStore=t.DocumentStore.load(e.documentStore),n.pipeline=t.Pipeline.load(e.pipeline),n.index={};for(var i in e.index)n.index[i]=t.InvertedIndex.load(e.index[i]);return n},t.Index.prototype.addField=function(e){return this._fields.push(e),this.index[e]=new t.InvertedIndex,this},t.Index.prototype.setRef=function(e){return this._ref=e,this},t.Index.prototype.saveDocument=function(e){return this.documentStore=new t.DocumentStore(e),this},t.Index.prototype.addDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.addDoc(i,e),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));this.documentStore.addFieldLength(i,n,o.length);var r={};o.forEach(function(e){e in r?r[e]+=1:r[e]=1},this);for(var s in r){var u=r[s];u=Math.sqrt(u),this.index[n].addToken(s,{ref:i,tf:u})}},this),n&&this.eventEmitter.emit("add",e,this)}},t.Index.prototype.removeDocByRef=function(e){if(e&&this.documentStore.isDocStored()!==!1&&this.documentStore.hasDoc(e)){var t=this.documentStore.getDoc(e);this.removeDoc(t,!1)}},t.Index.prototype.removeDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.hasDoc(i)&&(this.documentStore.removeDoc(i),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));o.forEach(function(e){this.index[n].removeToken(e,i)},this)},this),n&&this.eventEmitter.emit("remove",e,this))}},t.Index.prototype.updateDoc=function(e,t){var t=void 0===t?!0:t;this.removeDocByRef(e[this._ref],!1),this.addDoc(e,!1),t&&this.eventEmitter.emit("update",e,this)},t.Index.prototype.idf=function(e,t){var n="@"+t+"/"+e;if(Object.prototype.hasOwnProperty.call(this._idfCache,n))return this._idfCache[n];var i=this.index[t].getDocFreq(e),o=1+Math.log(this.documentStore.length/(i+1));return this._idfCache[n]=o,o},t.Index.prototype.getFields=function(){return this._fields.slice()},t.Index.prototype.search=function(e,n){if(!e)return[];var i=null;null!=n&&(i=JSON.stringify(n));var o=new t.Configuration(i,this.getFields()).get(),r=this.pipeline.run(t.tokenizer(e)),s={};for(var u in o){var a=this.fieldSearch(r,u,o),l=o[u].boost;for(var d in a)a[d]=a[d]*l;for(var d in a)d in s?s[d]+=a[d]:s[d]=a[d]}var c=[];for(var d in s)c.push({ref:d,score:s[d]});return c.sort(function(e,t){return t.score-e.score}),c},t.Index.prototype.fieldSearch=function(e,t,n){var i=n[t].bool,o=n[t].expand,r=n[t].boost,s=null,u={};return 0!==r?(e.forEach(function(e){var n=[e];1==o&&(n=this.index[t].expandToken(e));var r={};n.forEach(function(n){var o=this.index[t].getDocs(n),a=this.idf(n,t);if(s&&"AND"==i){var l={};for(var d in s)d in o&&(l[d]=o[d]);o=l}n==e&&this.fieldSearchStats(u,n,o);for(var d in o){var c=this.index[t].getTermFrequency(n,d),f=this.documentStore.getFieldLength(d,t),h=1;0!=f&&(h=1/Math.sqrt(f));var p=1;n!=e&&(p=.15*(1-(n.length-e.length)/n.length));var v=c*a*h*p;d in r?r[d]+=v:r[d]=v}},this),s=this.mergeScores(s,r,i)},this),s=this.coordNorm(s,u,e.length)):void 0},t.Index.prototype.mergeScores=function(e,t,n){if(!e)return t;if("AND"==n){var i={};for(var o in t)o in e&&(i[o]=e[o]+t[o]);return i}for(var o in t)o in e?e[o]+=t[o]:e[o]=t[o];return e},t.Index.prototype.fieldSearchStats=function(e,t,n){for(var i in n)i in e?e[i].push(t):e[i]=[t]},t.Index.prototype.coordNorm=function(e,t,n){for(var i in e)if(i in t){var o=t[i].length;e[i]=e[i]*o/n}return e},t.Index.prototype.toJSON=function(){var e={};return this._fields.forEach(function(t){e[t]=this.index[t].toJSON()},this),{version:t.version,fields:this._fields,ref:this._ref,documentStore:this.documentStore.toJSON(),index:e,pipeline:this.pipeline.toJSON()}},t.Index.prototype.use=function(e){var t=Array.prototype.slice.call(arguments,1);t.unshift(this),e.apply(this,t)},t.DocumentStore=function(e){this._save=null===e||void 0===e?!0:e,this.docs={},this.docInfo={},this.length=0},t.DocumentStore.load=function(e){var t=new this;return t.length=e.length,t.docs=e.docs,t.docInfo=e.docInfo,t._save=e.save,t},t.DocumentStore.prototype.isDocStored=function(){return this._save},t.DocumentStore.prototype.addDoc=function(t,n){this.hasDoc(t)||this.length++,this.docs[t]=this._save===!0?e(n):null},t.DocumentStore.prototype.getDoc=function(e){return this.hasDoc(e)===!1?null:this.docs[e]},t.DocumentStore.prototype.hasDoc=function(e){return e in this.docs},t.DocumentStore.prototype.removeDoc=function(e){this.hasDoc(e)&&(delete this.docs[e],delete this.docInfo[e],this.length--)},t.DocumentStore.prototype.addFieldLength=function(e,t,n){null!==e&&void 0!==e&&0!=this.hasDoc(e)&&(this.docInfo[e]||(this.docInfo[e]={}),this.docInfo[e][t]=n)},t.DocumentStore.prototype.updateFieldLength=function(e,t,n){null!==e&&void 0!==e&&0!=this.hasDoc(e)&&this.addFieldLength(e,t,n)},t.DocumentStore.prototype.getFieldLength=function(e,t){return null===e||void 0===e?0:e in this.docs&&t in this.docInfo[e]?this.docInfo[e][t]:0},t.DocumentStore.prototype.toJSON=function(){return{docs:this.docs,docInfo:this.docInfo,length:this.length,save:this._save}},t.stemmer=function(){var e={ational:"ate",tional:"tion",enci:"ence",anci:"ance",izer:"ize",bli:"ble",alli:"al",entli:"ent",eli:"e",ousli:"ous",ization:"ize",ation:"ate",ator:"ate",alism:"al",iveness:"ive",fulness:"ful",ousness:"ous",aliti:"al",iviti:"ive",biliti:"ble",logi:"log"},t={icate:"ic",ative:"",alize:"al",iciti:"ic",ical:"ic",ful:"",ness:""},n="[^aeiou]",i="[aeiouy]",o=n+"[^aeiouy]*",r=i+"[aeiou]*",s="^("+o+")?"+r+o,u="^("+o+")?"+r+o+"("+r+")?$",a="^("+o+")?"+r+o+r+o,l="^("+o+")?"+i,d=new RegExp(s),c=new RegExp(a),f=new RegExp(u),h=new RegExp(l),p=/^(.+?)(ss|i)es$/,v=/^(.+?)([^s])s$/,g=/^(.+?)eed$/,m=/^(.+?)(ed|ing)$/,y=/.$/,S=/(at|bl|iz)$/,x=new RegExp("([^aeiouylsz])\\1$"),w=new RegExp("^"+o+i+"[^aeiouwxy]$"),I=/^(.+?[^aeiou])y$/,b=/^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/,E=/^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/,D=/^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/,F=/^(.+?)(s|t)(ion)$/,_=/^(.+?)e$/,P=/ll$/,k=new RegExp("^"+o+i+"[^aeiouwxy]$"),z=function(n){var i,o,r,s,u,a,l;if(n.length<3)return n;if(r=n.substr(0,1),"y"==r&&(n=r.toUpperCase()+n.substr(1)),s=p,u=v,s.test(n)?n=n.replace(s,"$1$2"):u.test(n)&&(n=n.replace(u,"$1$2")),s=g,u=m,s.test(n)){var z=s.exec(n);s=d,s.test(z[1])&&(s=y,n=n.replace(s,""))}else if(u.test(n)){var z=u.exec(n);i=z[1],u=h,u.test(i)&&(n=i,u=S,a=x,l=w,u.test(n)?n+="e":a.test(n)?(s=y,n=n.replace(s,"")):l.test(n)&&(n+="e"))}if(s=I,s.test(n)){var z=s.exec(n);i=z[1],n=i+"i"}if(s=b,s.test(n)){var z=s.exec(n);i=z[1],o=z[2],s=d,s.test(i)&&(n=i+e[o])}if(s=E,s.test(n)){var z=s.exec(n);i=z[1],o=z[2],s=d,s.test(i)&&(n=i+t[o])}if(s=D,u=F,s.test(n)){var z=s.exec(n);i=z[1],s=c,s.test(i)&&(n=i)}else if(u.test(n)){var z=u.exec(n);i=z[1]+z[2],u=c,u.test(i)&&(n=i)}if(s=_,s.test(n)){var z=s.exec(n);i=z[1],s=c,u=f,a=k,(s.test(i)||u.test(i)&&!a.test(i))&&(n=i)}return s=P,u=c,s.test(n)&&u.test(n)&&(s=y,n=n.replace(s,"")),"y"==r&&(n=r.toLowerCase()+n.substr(1)),n};return z}(),t.Pipeline.registerFunction(t.stemmer,"stemmer"),t.stopWordFilter=function(e){return e&&t.stopWordFilter.stopWords[e]!==!0?e:void 0},t.clearStopWords=function(){t.stopWordFilter.stopWords={}},t.addStopWords=function(e){null!=e&&Array.isArray(e)!==!1&&e.forEach(function(e){t.stopWordFilter.stopWords[e]=!0},this)},t.resetStopWords=function(){t.stopWordFilter.stopWords=t.defaultStopWords},t.defaultStopWords={"":!0,a:!0,able:!0,about:!0,across:!0,after:!0,all:!0,almost:!0,also:!0,am:!0,among:!0,an:!0,and:!0,any:!0,are:!0,as:!0,at:!0,be:!0,because:!0,been:!0,but:!0,by:!0,can:!0,cannot:!0,could:!0,dear:!0,did:!0,"do":!0,does:!0,either:!0,"else":!0,ever:!0,every:!0,"for":!0,from:!0,get:!0,got:!0,had:!0,has:!0,have:!0,he:!0,her:!0,hers:!0,him:!0,his:!0,how:!0,however:!0,i:!0,"if":!0,"in":!0,into:!0,is:!0,it:!0,its:!0,just:!0,least:!0,let:!0,like:!0,likely:!0,may:!0,me:!0,might:!0,most:!0,must:!0,my:!0,neither:!0,no:!0,nor:!0,not:!0,of:!0,off:!0,often:!0,on:!0,only:!0,or:!0,other:!0,our:!0,own:!0,rather:!0,said:!0,say:!0,says:!0,she:!0,should:!0,since:!0,so:!0,some:!0,than:!0,that:!0,the:!0,their:!0,them:!0,then:!0,there:!0,these:!0,they:!0,"this":!0,tis:!0,to:!0,too:!0,twas:!0,us:!0,wants:!0,was:!0,we:!0,were:!0,what:!0,when:!0,where:!0,which:!0,"while":!0,who:!0,whom:!0,why:!0,will:!0,"with":!0,would:!0,yet:!0,you:!0,your:!0},t.stopWordFilter.stopWords=t.defaultStopWords,t.Pipeline.registerFunction(t.stopWordFilter,"stopWordFilter"),t.trimmer=function(e){if(null===e||void 0===e)throw new Error("token should not be undefined");return e.replace(/^\W+/,"").replace(/\W+$/,"")},t.Pipeline.registerFunction(t.trimmer,"trimmer"),t.InvertedIndex=function(){this.root={docs:{},df:0}},t.InvertedIndex.load=function(e){var t=new this;return t.root=e.root,t},t.InvertedIndex.prototype.addToken=function(e,t,n){for(var n=n||this.root,i=0;i<=e.length-1;){var o=e[i];o in n||(n[o]={docs:{},df:0}),i+=1,n=n[o]}var r=t.ref;n.docs[r]?n.docs[r]={tf:t.tf}:(n.docs[r]={tf:t.tf},n.df+=1)},t.InvertedIndex.prototype.hasToken=function(e){if(!e)return!1;for(var t=this.root,n=0;n0&&t.push(e);for(var i in n)"docs"!==i&&"df"!==i&&this.expandToken(e+i,t,n[i]);return t},t.InvertedIndex.prototype.toJSON=function(){return{root:this.root}},t.Configuration=function(e,n){var e=e||"";if(void 0==n||null==n)throw new Error("fields should not be null");this.config={};var i;try{i=JSON.parse(e),this.buildUserConfig(i,n)}catch(o){t.utils.warn("user configuration parse failed, will use default configuration"),this.buildDefaultConfig(n)}},t.Configuration.prototype.buildDefaultConfig=function(e){this.reset(),e.forEach(function(e){this.config[e]={boost:1,bool:"OR",expand:!1}},this)},t.Configuration.prototype.buildUserConfig=function(e,n){var i="OR",o=!1;if(this.reset(),"bool"in e&&(i=e.bool||i),"expand"in e&&(o=e.expand||o),"fields"in e)for(var r in e.fields)if(n.indexOf(r)>-1){var s=e.fields[r],u=o;void 0!=s.expand&&(u=s.expand),this.config[r]={boost:s.boost||0===s.boost?s.boost:1,bool:s.bool||i,expand:u}}else t.utils.warn("field name in user configuration not found in index instance fields");else this.addAllFields2UserConfig(i,o,n)},t.Configuration.prototype.addAllFields2UserConfig=function(e,t,n){n.forEach(function(n){this.config[n]={boost:1,bool:e,expand:t}},this)},t.Configuration.prototype.get=function(){return this.config},t.Configuration.prototype.reset=function(){this.config={}},lunr.SortedSet=function(){this.length=0,this.elements=[]},lunr.SortedSet.load=function(e){var t=new this;return t.elements=e,t.length=e.length,t},lunr.SortedSet.prototype.add=function(){var e,t;for(e=0;e1;){if(r===e)return o;e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o]}return r===e?o:-1},lunr.SortedSet.prototype.locationFor=function(e){for(var t=0,n=this.elements.length,i=n-t,o=t+Math.floor(i/2),r=this.elements[o];i>1;)e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o];return r>e?o:e>r?o+1:void 0},lunr.SortedSet.prototype.intersect=function(e){for(var t=new lunr.SortedSet,n=0,i=0,o=this.length,r=e.length,s=this.elements,u=e.elements;;){if(n>o-1||i>r-1)break;s[n]!==u[i]?s[n]u[i]&&i++:(t.add(s[n]),n++,i++)}return t},lunr.SortedSet.prototype.clone=function(){var e=new lunr.SortedSet;return e.elements=this.toArray(),e.length=e.elements.length,e},lunr.SortedSet.prototype.union=function(e){var t,n,i;this.length>=e.length?(t=this,n=e):(t=e,n=this),i=t.clone();for(var o=0,r=n.toArray();o {
+ const WRAPPER = document.getElementById('search-wrapper');
+ const TOGGLE = document.getElementById('search-toggle');
+ const INPUT = document.getElementById('searchbar');
+ const LIST = document.getElementById('searchresults');
+ const HOTKEY = 83; // “s”
+ let worker, debounce;
-/* ────────────────────────────────────────────────────────────────
- search.js
- ─────────────────────────────────────────────────────────────── */
-
-"use strict";
-window.search = window.search || {};
-(function search(search) {
- // Search functionality
- //
- // You can use !hasFocus() to prevent keyhandling in your key
- // event handlers while the user is typing their search.
-
- if (!Mark || !elasticlunr) {
- return;
+ function startWorker() {
+ if (worker) return;
+ worker = new Worker('/search-worker.js', { type:'module' });
+ worker.onmessage = ({data}) => {
+ LIST.innerHTML = data.slice(0,30).map(h =>
+ `${h.doc.title}`
+ ).join('');
+ };
}
-
- //IE 11 Compatibility from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith
- if (!String.prototype.startsWith) {
- String.prototype.startsWith = function(search, pos) {
- return this.substr(!pos || pos < 0 ? 0 : +pos, search.length) === search;
- };
+
+ async function openUI() {
+ WRAPPER.classList.remove('hidden');
+ INPUT.focus();
+ startWorker(); // fetches CDN/GitHub in parallel
}
-
- var search_wrap = document.getElementById('search-wrapper'),
- search_modal = document.getElementById('search-modal'),
- searchbar = document.getElementById('searchbar'),
- searchbar_outer = document.getElementById('searchbar-outer'),
- searchresults = document.getElementById('searchresults'),
- searchresults_outer = document.getElementById('searchresults-outer'),
- searchresults_header = document.getElementById('searchresults-header'),
- searchicon = document.getElementById('search-toggle'),
- content = document.getElementById('content'),
-
- searchindex = null,
- doc_urls = [],
- results_options = {
- teaser_word_count: 30,
- limit_results: 30,
- },
- search_options = {
- bool: "AND",
- expand: true,
- fields: {
- title: {boost: 1},
- body: {boost: 1},
- breadcrumbs: {boost: 0}
- }
- },
- mark_exclude = [],
- marker = new Mark(content),
- current_searchterm = "",
- URL_SEARCH_PARAM = 'search',
- URL_MARK_PARAM = 'highlight',
- teaser_count = 0,
-
- SEARCH_HOTKEY_KEYCODE = 83,
- ESCAPE_KEYCODE = 27,
- DOWN_KEYCODE = 40,
- UP_KEYCODE = 38,
- SELECT_KEYCODE = 13;
-
- function hasFocus() {
- return searchbar === document.activeElement;
- }
-
- function removeChildren(elem) {
- while (elem.firstChild) {
- elem.removeChild(elem.firstChild);
- }
- }
-
- // Helper to parse a url into its building blocks.
- function parseURL(url) {
- var a = document.createElement('a');
- a.href = url;
- return {
- source: url,
- protocol: a.protocol.replace(':',''),
- host: a.hostname,
- port: a.port,
- params: (function(){
- var ret = {};
- var seg = a.search.replace(/^\?/,'').split('&');
- var len = seg.length, i = 0, s;
- for (;i': '>',
- '"': '"',
- "'": '''
- };
- var repl = function(c) { return MAP[c]; };
- return function(s) {
- return s.replace(/[&<>'"]/g, repl);
- };
- })();
-
- function formatSearchMetric(count, searchterm) {
- if (count == 1) {
- return count + " search result for '" + searchterm + "':";
- } else if (count == 0) {
- return "No search results for '" + searchterm + "'.";
- } else {
- return count + " search results for '" + searchterm + "':";
- }
- }
-
- function formatSearchResult(result, searchterms) {
- var teaser = makeTeaser(escapeHTML(result.doc.body), searchterms);
- teaser_count++;
-
- // The ?URL_MARK_PARAM= parameter belongs inbetween the page and the #heading-anchor
- var url = doc_urls[result.ref].split("#");
- if (url.length == 1) { // no anchor found
- url.push("");
- }
-
- // encodeURIComponent escapes all chars that could allow an XSS except
- // for '. Due to that we also manually replace ' with its url-encoded
- // representation (%27).
- var searchterms = encodeURIComponent(searchterms.join(" ")).replace(/\'/g, "%27");
-
- return '' + result.doc.breadcrumbs
- + ''
- + teaser + '' + '';
- }
-
- function makeTeaser(body, searchterms) {
- // The strategy is as follows:
- // First, assign a value to each word in the document:
- // Words that correspond to search terms (stemmer aware): 40
- // Normal words: 2
- // First word in a sentence: 8
- // Then use a sliding window with a constant number of words and count the
- // sum of the values of the words within the window. Then use the window that got the
- // maximum sum. If there are multiple maximas, then get the last one.
- // Enclose the terms in .
- var stemmed_searchterms = searchterms.map(function(w) {
- return elasticlunr.stemmer(w.toLowerCase());
- });
- var searchterm_weight = 40;
- var weighted = []; // contains elements of ["word", weight, index_in_document]
- // split in sentences, then words
- var sentences = body.toLowerCase().split('. ');
- var index = 0;
- var value = 0;
- var searchterm_found = false;
- for (var sentenceindex in sentences) {
- var words = sentences[sentenceindex].split(' ');
- value = 8;
- for (var wordindex in words) {
- var word = words[wordindex];
- if (word.length > 0) {
- for (var searchtermindex in stemmed_searchterms) {
- if (elasticlunr.stemmer(word).startsWith(stemmed_searchterms[searchtermindex])) {
- value = searchterm_weight;
- searchterm_found = true;
- }
- };
- weighted.push([word, value, index]);
- value = 2;
- }
- index += word.length;
- index += 1; // ' ' or '.' if last word in sentence
- };
- index += 1; // because we split at a two-char boundary '. '
- };
-
- if (weighted.length == 0) {
- return body;
- }
-
- var window_weight = [];
- var window_size = Math.min(weighted.length, results_options.teaser_word_count);
-
- var cur_sum = 0;
- for (var wordindex = 0; wordindex < window_size; wordindex++) {
- cur_sum += weighted[wordindex][1];
- };
- window_weight.push(cur_sum);
- for (var wordindex = 0; wordindex < weighted.length - window_size; wordindex++) {
- cur_sum -= weighted[wordindex][1];
- cur_sum += weighted[wordindex + window_size][1];
- window_weight.push(cur_sum);
- };
-
- if (searchterm_found) {
- var max_sum = 0;
- var max_sum_window_index = 0;
- // backwards
- for (var i = window_weight.length - 1; i >= 0; i--) {
- if (window_weight[i] > max_sum) {
- max_sum = window_weight[i];
- max_sum_window_index = i;
- }
- };
- } else {
- max_sum_window_index = 0;
- }
-
- // add around searchterms
- var teaser_split = [];
- var index = weighted[max_sum_window_index][2];
- for (var i = max_sum_window_index; i < max_sum_window_index+window_size; i++) {
- var word = weighted[i];
- if (index < word[2]) {
- // missing text from index to start of `word`
- teaser_split.push(body.substring(index, word[2]));
- index = word[2];
- }
- if (word[1] == searchterm_weight) {
- teaser_split.push("")
- }
- index = word[2] + word[0].length;
- teaser_split.push(body.substring(word[2], index));
- if (word[1] == searchterm_weight) {
- teaser_split.push("")
- }
- };
-
- return teaser_split.join('');
- }
-
- function init(config) {
- results_options = config.results_options;
- search_options = config.search_options;
- searchbar_outer = config.searchbar_outer;
- doc_urls = config.doc_urls;
- searchindex = elasticlunr.Index.load(config.index);
-
- // Set up events
- searchicon.addEventListener('click', function(e) { searchIconClickHandler(); }, false);
- search_wrap.addEventListener('click', function(e) { searchIconClickHandler(); }, false);
- search_modal.addEventListener('click', function(e) { e.stopPropagation(); }, false);
- searchbar.addEventListener('keyup', function(e) { searchbarKeyUpHandler(); }, false);
- document.addEventListener('keydown', function(e) { globalKeyHandler(e); }, false);
- // If the user uses the browser buttons, do the same as if a reload happened
- window.onpopstate = function(e) { doSearchOrMarkFromUrl(); };
- // Suppress "submit" events so the page doesn't reload when the user presses Enter
- document.addEventListener('submit', function(e) { e.preventDefault(); }, false);
-
- // If reloaded, do the search or mark again, depending on the current url parameters
- doSearchOrMarkFromUrl();
- }
-
- function unfocusSearchbar() {
- // hacky, but just focusing a div only works once
- var tmp = document.createElement('input');
- tmp.setAttribute('style', 'position: absolute; opacity: 0;');
- searchicon.appendChild(tmp);
- tmp.focus();
- tmp.remove();
- }
-
- // On reload or browser history backwards/forwards events, parse the url and do search or mark
- function doSearchOrMarkFromUrl() {
- // Check current URL for search request
- var url = parseURL(window.location.href);
- if (url.params.hasOwnProperty(URL_SEARCH_PARAM)
- && url.params[URL_SEARCH_PARAM] != "") {
- showSearch(true);
- searchbar.value = decodeURIComponent(
- (url.params[URL_SEARCH_PARAM]+'').replace(/\+/g, '%20'));
- searchbarKeyUpHandler(); // -> doSearch()
- } else {
- showSearch(false);
- }
-
- if (url.params.hasOwnProperty(URL_MARK_PARAM)) {
- var words = decodeURIComponent(url.params[URL_MARK_PARAM]).split(' ');
- marker.mark(words, {
- exclude: mark_exclude
- });
-
- var markers = document.querySelectorAll("mark");
- function hide() {
- for (var i = 0; i < markers.length; i++) {
- markers[i].classList.add("fade-out");
- window.setTimeout(function(e) { marker.unmark(); }, 300);
- }
- }
- for (var i = 0; i < markers.length; i++) {
- markers[i].addEventListener('click', hide);
- }
- }
- }
-
- // Eventhandler for keyevents on `document`
- function globalKeyHandler(e) {
- if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey || e.target.type === 'textarea' || e.target.type === 'text' || !hasFocus() && /^(?:input|select|textarea)$/i.test(e.target.nodeName)) { return; }
-
- if (e.keyCode === ESCAPE_KEYCODE) {
- e.preventDefault();
- searchbar.classList.remove("active");
- setSearchUrlParameters("",
- (searchbar.value.trim() !== "") ? "push" : "replace");
- if (hasFocus()) {
- unfocusSearchbar();
- }
- showSearch(false);
- marker.unmark();
- } else if (!hasFocus() && e.keyCode === SEARCH_HOTKEY_KEYCODE) {
- e.preventDefault();
- showSearch(true);
- window.scrollTo(0, 0);
- searchbar.select();
- } else if (hasFocus() && e.keyCode === DOWN_KEYCODE) {
- e.preventDefault();
- unfocusSearchbar();
- searchresults.firstElementChild.classList.add("focus");
- } else if (!hasFocus() && (e.keyCode === DOWN_KEYCODE
- || e.keyCode === UP_KEYCODE
- || e.keyCode === SELECT_KEYCODE)) {
- // not `:focus` because browser does annoying scrolling
- var focused = searchresults.querySelector("li.focus");
- if (!focused) return;
- e.preventDefault();
- if (e.keyCode === DOWN_KEYCODE) {
- var next = focused.nextElementSibling;
- if (next) {
- focused.classList.remove("focus");
- next.classList.add("focus");
- }
- } else if (e.keyCode === UP_KEYCODE) {
- focused.classList.remove("focus");
- var prev = focused.previousElementSibling;
- if (prev) {
- prev.classList.add("focus");
- } else {
- searchbar.select();
- }
- } else { // SELECT_KEYCODE
- window.location.assign(focused.querySelector('a'));
- }
- }
- }
-
- function showSearch(yes) {
- if (yes) {
- search_wrap.classList.remove('hidden');
- searchicon.setAttribute('aria-expanded', 'true');
- } else {
- search_wrap.classList.add('hidden');
- searchicon.setAttribute('aria-expanded', 'false');
- var results = searchresults.children;
- for (var i = 0; i < results.length; i++) {
- results[i].classList.remove("focus");
- }
- }
- }
-
- function showResults(yes) {
- if (yes) {
- searchresults_outer.classList.remove('hidden');
- } else {
- searchresults_outer.classList.add('hidden');
- }
- }
-
- // Eventhandler for search icon
- function searchIconClickHandler() {
- if (search_wrap.classList.contains('hidden')) {
- showSearch(true);
- window.scrollTo(0, 0);
- searchbar.select();
- } else {
- showSearch(false);
- }
- }
-
- // Eventhandler for keyevents while the searchbar is focused
- function searchbarKeyUpHandler() {
- var searchterm = searchbar.value.trim();
- if (searchterm != "") {
- searchbar.classList.add("active");
- doSearch(searchterm);
- } else {
- searchbar.classList.remove("active");
- showResults(false);
- removeChildren(searchresults);
- }
-
- setSearchUrlParameters(searchterm, "push_if_new_search_else_replace");
-
- // Remove marks
- marker.unmark();
- }
-
- // Update current url with ?URL_SEARCH_PARAM= parameter, remove ?URL_MARK_PARAM and #heading-anchor .
- // `action` can be one of "push", "replace", "push_if_new_search_else_replace"
- // and replaces or pushes a new browser history item.
- // "push_if_new_search_else_replace" pushes if there is no `?URL_SEARCH_PARAM=abc` yet.
- function setSearchUrlParameters(searchterm, action) {
- var url = parseURL(window.location.href);
- var first_search = ! url.params.hasOwnProperty(URL_SEARCH_PARAM);
- if (searchterm != "" || action == "push_if_new_search_else_replace") {
- url.params[URL_SEARCH_PARAM] = searchterm;
- delete url.params[URL_MARK_PARAM];
- url.hash = "";
- } else {
- delete url.params[URL_MARK_PARAM];
- delete url.params[URL_SEARCH_PARAM];
- }
- // A new search will also add a new history item, so the user can go back
- // to the page prior to searching. A updated search term will only replace
- // the url.
- if (action == "push" || (action == "push_if_new_search_else_replace" && first_search) ) {
- history.pushState({}, document.title, renderURL(url));
- } else if (action == "replace" || (action == "push_if_new_search_else_replace" && !first_search) ) {
- history.replaceState({}, document.title, renderURL(url));
- }
- }
-
- function doSearch(searchterm) {
-
- // Don't search the same twice
- if (current_searchterm == searchterm) { return; }
- else { current_searchterm = searchterm; }
-
- if (searchindex == null) { return; }
-
- // Do the actual search
- var results = searchindex.search(searchterm, search_options);
- var resultcount = Math.min(results.length, results_options.limit_results);
-
- // Display search metrics
- searchresults_header.innerText = formatSearchMetric(resultcount, searchterm);
-
- // Clear and insert results
- var searchterms = searchterm.split(' ');
- removeChildren(searchresults);
- for(var i = 0; i < resultcount ; i++){
- var resultElem = document.createElement('li');
- resultElem.innerHTML = formatSearchResult(results[i], searchterms);
- searchresults.appendChild(resultElem);
- }
-
- // Display results
- showResults(true);
- }
-
- (async function loadSearchIndex(lang = window.lang || "en") {
- const branch = lang === "en" ? "master" : lang;
- const rawUrl =
- `https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/refs/heads/${branch}/searchindex.js`;
- const localJs = "/searchindex.js";
- const TIMEOUT_MS = 10_000;
-
- const injectScript = (src) =>
- new Promise((resolve, reject) => {
- const s = document.createElement("script");
- s.src = src;
- s.onload = () => resolve(src);
- s.onerror = (e) => reject(e);
- document.head.appendChild(s);
- });
-
- try {
- /* 1 — download raw JS from GitHub */
- const controller = new AbortController();
- const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
-
- const res = await fetch(rawUrl, { signal: controller.signal });
- clearTimeout(timer);
- if (!res.ok) throw new Error(`HTTP ${res.status}`);
-
- /* 2 — wrap in a Blob so the browser sees application/javascript */
- const code = await res.text();
- const blobUrl = URL.createObjectURL(
- new Blob([code], { type: "application/javascript" })
- );
-
- /* 3 — execute it */
- await injectScript(blobUrl);
-
- /* ───────────── PATCH ─────────────
- heavy parsing now deferred to idle time
- */
- requestIdleCallback(() => init(window.search));
- return; // ✔ UI remains responsive
- } catch (eRemote) {
- console.warn("Remote JS failed →", eRemote);
- }
-
- /* ───────── fallback: local copy ───────── */
- try {
- await injectScript(localJs);
-
- /* ───────────── PATCH ───────────── */
- requestIdleCallback(() => init(window.search));
- return;
- } catch (eLocal) {
- console.error("Local JS failed →", eLocal);
- }
- })();
-
- // Exported functions
- search.hasFocus = hasFocus;
-})(window.search);
\ No newline at end of file
+
+ TOGGLE.addEventListener('click', openUI);
+ document.addEventListener('keydown', e => {
+ if (!e.metaKey && !e.ctrlKey && !e.altKey && e.keyCode === HOTKEY) {
+ e.preventDefault(); openUI();
+ }
+ });
+
+ INPUT.addEventListener('input', e => {
+ clearTimeout(debounce);
+ debounce = setTimeout(() => {
+ worker?.postMessage(e.target.value.trim());
+ }, 120); // small debounce keeps typing smooth
+ });
+ })();
+
\ No newline at end of file
diff --git a/theme/search-worker.js b/theme/search-worker.js
new file mode 100644
index 000000000..8bbbb4b88
--- /dev/null
+++ b/theme/search-worker.js
@@ -0,0 +1,40 @@
+/* search-worker.js ------------------------------------------------------- */
+/* Make code written for window work in a worker: */
+self.window = self;
+
+////////////////////////////////////////////////////////////////////////////
+// 1. elasticlunr.min.js : CDN first → local fallback
+////////////////////////////////////////////////////////////////////////////
+try {
+ importScripts('https://cdn.jsdelivr.net/npm/elasticlunr@0.9.5/elasticlunr.min.js');
+} catch (e) {
+ importScripts('/elasticlunr.min.js'); // ship this with your site
+}
+
+////////////////////////////////////////////////////////////////////////////
+// 2. searchindex.js : GitHub Raw first → local fallback
+// We fetch → wrap in a Blob({type:'application/javascript'}) to bypass
+// GitHub’s text/plain + nosniff MIME blocking.
+////////////////////////////////////////////////////////////////////////////
+try {
+ const res = await fetch(
+ 'https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/refs/heads/master/searchindex.js',
+ {mode: 'cors'}
+ );
+ if (!res.ok) throw new Error(res.status);
+ const blobUrl = URL.createObjectURL(
+ new Blob([await res.text()], { type:'application/javascript' })
+ );
+ importScripts(blobUrl); // correct MIME, runs once
+} catch (e) {
+ importScripts('/searchindex.js'); // offline fallback
+}
+
+////////////////////////////////////////////////////////////////////////////
+// 3. Build the index once and answer queries
+////////////////////////////////////////////////////////////////////////////
+const idx = elasticlunr.Index.load(self.search.index);
+
+self.onmessage = ({data: q}) => {
+ postMessage(idx.search(q, { bool:'AND', expand:true }));
+};