1"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[48431],{40750:(e,t,s)=>{function r(e){return`${Math.floor(e/0x1000000)%256}.${Math.floor(e/65536)%256}.${Math.floor(e/256)%256}.${e%256}`}s.d(t,{fD:()=>r,vm:()=>n});let a=/^0[0-9]+$|^0[xX]/,i=/^[0-9]{1,3}$/;function n(e){let t=e.trim();if(""===t)return{ok:!1,reason:"empty",message:"Enter an IPv4 address, like 192.168.1.1."};if(/[^0-9a-fA-FxX.]/.test(t))return{ok:!1,reason:"invalid",message:"An IPv4 address uses only digits and dots, like 192.168.1.1."};let s=t.split(".");if(s.length>4)return{ok:!1,reason:"invalid",message:`An IPv4 address has four parts, and this has ${s.length}.`};let n=s.find(e=>a.test(e));if(void 0!==n&&4===s.length){let e=function(e){let t=0;for(let s of e){let e=function(e){let t=e,s=10;return(t.length>=2&&(t.startsWith("0x")||t.startsWith("0X"))?(t=t.slice(2),s=16):t.length>=2&&t.startsWith("0")&&(t=t.slice(1),s=8),""===t)?0:(16===s?/^[0-9a-fA-F]+$/:8===s?/^[0-7]+$/:/^[0-9]+$/).test(t)?parseInt(t,s):null}(s);if(null===e||e>255)return null;t=256*t+e}return t}(s),a=function(e){let t=0;for(let s of e){if(!/^[0-9]+$/.test(s))return null;let e=Number(s);if(e>255)return null;t=256*t+e}return t}(s);return{ok:!1,reason:"ambiguous",part:n,browser:null===e?null:r(e),plain:null===a?null:r(a),message:function(e,t,s,r){let a=/^0[xX]/.test(t)?`"${t}" is hex`:`a leading zero means octal, so "${t}" is octal`;return null===s?`${e} is ambiguous. In a browser ${a}, and "${t}" is not a legal number in that base, so a browser refuses the address outright while a plain reading gives ${r??"nothing usable"}. Write it without the leading zero.`:null===r?`${e} is ambiguous. In a browser ${a} and the address resolves to ${s}. Written without the leading zero it is not a valid address at all. Write the one you mean.`:s===r?`${e} has a leading zero, which means octal. Both readings land on ${r} here, but "010" style parts do not. Write it without the leading zero.`:`${e} is ambiguous. In a browser ${a} and the address resolves to ${s}, while this tool and most others would read ${r}. Write it without the leading zero.`}(t,n,null===e?null:r(e),null===a?null:r(a))}}if(s.length<4||s.some(e=>""===e))return{ok:!1,reason:"incomplete",message:"An IPv4 address has four parts, like 192.168.1.1."};let o=[];for(let e of s){if(!i.test(e))return{ok:!1,reason:"invalid",message:`"${e}" is not a number from 0 to 255.`};let t=Number(e);if(t>255)return{ok:!1,reason:"invalid",message:`Each part goes 0 to 255, so "${e}" is out of range.`};o.push(t)}let[l,d,h,c]=o;return{ok:!0,value:0x1000000*l+65536*d+256*h+c,octets:[l,d,h,c],canonical:`${l}.${d}.${h}.${c}`}}},48431:(e,t,s)=>{s.r(t),s.d(t,{default:()=>K});var r=s(45993),a=s(75837),i=s(77771),n=s(30845),o=s(35323),l=s(41945),d=s(73950),h=s(22925),c=s(73567);function u(e){let t=[],s=0;for(;s<e.length;){for(;s<e.length&&(" "===e[s]||" "===e[s]);)s++;if(s>=e.length)break;let r="",a=e[s];if('"'===a||"'"===a){for(s++;s<e.length&&e[s]!==a;)"\\"===e[s]&&s+1<e.length?(r+=e[s+1],s+=2):(r+=e[s],s++);s++}else for(;s<e.length&&" "!==e[s]&&" "!==e[s];)r+=e[s],s++;t.push(r)}return t}let p=(e,t="")=>void 0===e||""===e?t:e,f={rewriteengine:{meaning:e=>"on"===(e.args[0]??"").toLowerCase()?"Turns the rewrite engine on. Without this line every RewriteRule below is read and then ignored.":"Turns the rewrite engine off, so every RewriteRule below does nothing at all.",module:"mod_rewrite",override:"FileInfo"},rewritebase:{meaning:e=>`Tells mod_rewrite that this folder is ${p(e.args[0],"/")} when it builds a relative substitution.`,module:"mod_rewrite",override:"FileInfo"},rewritecond:{meaning:e=>`A test that has to pass before the next RewriteRule runs. It checks ${p(e.args[0])} against ${p(e.args[1])}.`,module:"mod_rewrite",override:"FileInfo"},rewriterule:{meaning:e=>{let t=p(e.args[1],"-");return"-"===t?`Matches ${p(e.args[0])} and leaves the URL alone, usually to block it or set a flag.`:`Matches ${p(e.args[0])} and turns it into ${t}.`},module:"mod_rewrite",override:"FileInfo"},redirect:{meaning:e=>`Sends anything starting with ${p(e.args[e.args.length-2])} to ${p(e.args[e.args.length-1])}. This is prefix matching, not a pattern, and it resolves before your RewriteRules.`,module:"mod_alias",override:"FileInfo"},redirectmatch:{meaning:e=>`Sends every URL matching ${p(e.args[e.args.length-2])} to ${p(e.args[e.args.length-1])}, before any RewriteRule gets a look.`,module:"mod_alias",override:"FileInfo"},errordocument:{meaning:e=>`Serves ${p(e.args[1])} whenever this folder returns a ${p(e.args[0])}.`,module:null,override:"FileInfo"},options:{meaning:e=>`Changes what the server allows in this folder: ${e.args.join(" ")}.`,module:null,override:"Options"},directoryindex:{meaning:e=>`When someone asks for the folder itself, Apache tries ${e.args.join(", then ")}.`,module:"mod_dir",override:"Indexes"},directoryslash:{meaning:e=>"off"===(e.args[0]??"").toLowerCase()?"Stops Apache adding its own trailing slash redirect to directory requests.":"Keeps the default behavior where a directory without a trailing slash gets a 301.",module:"mod_dir",override:"Indexes"},header:{meaning:e=>{let t="always"===(e.args[0]??"").toLowerCase(),s=p((t?e.args.slice(1):e.args)[1]);return t?`Sends ${s} on every response, including your 404 and 500 pages.`:`Sends ${s}, but only on successful responses. Error pages will not carry it.`},module:"mod_headers",override:"FileInfo"},requestheader:{meaning:()=>"Changes a header on the way in, before your application sees the request.",module:"mod_headers",override:"FileInfo"},expiresactive:{meaning:()=>"Switches on browser cache headers for this folder.",module:"mod_expires",override:"FileInfo"},expiresdefault:{meaning:e=>`Anything without its own rule is cached for ${p(e.args[0])}.`,module:"mod_expires",override:"FileInfo"},expiresbytype:{meaning:e=>`Files of type ${p(e.args[0])} are cached for ${p(e.args[1])}.`,module:"mod_expires",override:"FileInfo"},addoutputfilterbytype:{meaning:e=>`Compresses these types on the way out: ${e.args.slice(1).join(", ")}.`,module:"mod_deflate",override:"FileInfo"},addtype:{meaning:e=>`Tells browsers that ${e.args.slice(1).join(" and ")} files are ${p(e.args[0])}.`,module:"mod_mime",override:"FileInfo"},addhandler:{meaning:e=>`Runs ${p(e.args[0])} for ${e.args.slice(1).join(" and ")} files.`,module:"mod_mime",override:"FileInfo"}
1,adddefaultcharset:{meaning:e=>`Labels text responses as ${p(e.args[0])} when they do not say otherwise.`,module:null,override:"FileInfo"},authtype:{meaning:()=>"Sets the login style. Basic sends the password base64 encoded, which is not encryption, so this needs HTTPS.",module:"mod_auth_basic",override:"AuthConfig"},authname:{meaning:e=>`The text the browser shows in its login box: ${p(e.args[0])}.`,module:"mod_auth_basic",override:"AuthConfig"},authuserfile:{meaning:e=>`Where the password file lives. ${p(e.args[0])} has to be an absolute server path, and it belongs outside the web root.`,module:"mod_authn_file",override:"AuthConfig"},require:{meaning:e=>{let t=e.args.join(" ");return/^valid-user/i.test(t)?"Anyone in the password file gets in, nobody else does.":/^all denied/i.test(t)?"Blocks everyone. This is the Apache 2.4 way of denying access.":/^all granted/i.test(t)?"Allows everyone. This is the Apache 2.4 way of granting access.":`Access control, Apache 2.4 syntax: ${t}.`},module:null,override:"Limit"},order:{meaning:()=>"Apache 2.2 access control. On a 2.4 server this only works if mod_access_compat happens to be loaded.",module:"mod_access_compat",override:"Limit"},allow:{meaning:()=>"Apache 2.2 access control, replaced by Require in 2.4.",module:"mod_access_compat",override:"Limit"},deny:{meaning:()=>"Apache 2.2 access control, replaced by Require in 2.4.",module:"mod_access_compat",override:"Limit"},satisfy:{meaning:()=>"Apache 2.2 directive for mixing password and IP rules. In 2.4 that is done with RequireAll or RequireAny.",module:"mod_access_compat",override:"AuthConfig"},php_value:{meaning:e=>`Sets the PHP setting ${p(e.args[0])}. This only works under mod_php, and it returns a 500 under PHP-FPM.`,module:"mod_php",override:"All"},php_flag:{meaning:e=>`Sets the PHP flag ${p(e.args[0])}. Same catch as php_value, it needs mod_php.`,module:"mod_php",override:"All"},setenvif:{meaning:()=>"Sets an internal variable based on something in the request.",module:"mod_setenvif",override:"FileInfo"},fileetag:{meaning:e=>`Controls the ETag that goes out with each file: ${e.args.join(" ")}.`,module:null,override:"FileInfo"},serversignature:{meaning:()=>"Controls the Apache version line printed on generated error pages.",module:null,override:"All"}},m={ifmodule:"Everything inside only applies when that module is loaded. If it is missing, the block is skipped in silence rather than causing an error.",files:"Applies to files with this exact name.",filesmatch:"Applies to every file whose name matches this pattern.",directory:"This is illegal inside .htaccess and returns a 500 for the whole folder. It belongs in the main server config.",limit:"Applies only to the HTTP methods named here, which is why it is a poor way to block access.",limitexcept:"Applies to every method except the ones named here.",requireall:"Every Require inside has to pass.",requireany:"Any one Require inside is enough.",requirenone:"Access is denied if any Require inside matches.",if:"Applies only when this expression is true. Needs Apache 2.4.",ifversion:"Applies only on the Apache versions named. Needs mod_version."},g={ifmodule:"None",files:"All",filesmatch:"All",directory:"None",limit:"Limit",limitexcept:"Limit",requireall:"Limit",requireany:"Limit",requirenone:"Limit",if:"All",ifversion:"None"};function w(e){if("close"===e.kind)return{line:e.line,text:e.raw,meaning:`Closes the ${e.name} block.`,module:null,override:"None"};if("open"===e.kind)return{line:e.line,text:e.raw,meaning:m[e.lower]??`Opens a ${e.name} block. Everything inside applies only to what it selects.`,module:(e.lower,null),override:g[e.lower]??"All"};let t=f[e.lower];return t?{line:e.line,text:e.raw,meaning:t.meaning(e),module:t.module,override:t.override}:{line:e.line,text:e.raw,meaning:"Not a directive this tool knows. It may still be valid, so check it against the Apache docs before you rely on it.",module:null,override:"All"}}let y={host:"example.com",scheme:"http",proxyInFront:!1,fileExists:!1,dirExists:!1,userAgent:"Mozilla/5.0",referer:""},x={permanent:301,temp:302,seeother:303,gone:410};function v(e){let t=e.trim();if(!t)return"/";let s=/^[a-z][a-z0-9+.-]*:\/\//i.exec(t);if(s){let e=(t=t.slice(s[0].length)).indexOf("/");t=e<0?"/":t.slice(e)}return(t=(t.split("#")[0]??"/").split("?")[0]??"/").startsWith("/")||(t=`/${t}`),t||"/"}function b(e){return"https"===e.scheme&&!e.env.proxyInFront}function k(e,t,s,r){let a=e.replace(/%\{([^}]+)\}/g,(e,s)=>(function(e,t){let s=e.toUpperCase();if(s.startsWith("HTTP:")){let s=e.slice(5).toLowerCase();if("x-forwarded-proto"===s)return t.env.proxyInFront?t.scheme:"";return"host"===s?t.host:"user-agent"===s?t.env.userAgent:"referer"===s?t.env.referer:""}if(s.startsWith("ENV:"))return t.vars[e.slice(4)]??"";switch(s){case"HTTPS":return b(t)?"on":"off";case"SERVER_PORT":return b(t)?"443":"80";case"REQUEST_SCHEME":return b(t)?"https":"http";case"HTTP_HOST":case"SERVER_NAME":return t.host;case"REQUEST_URI":return t.url;case"REQUEST_FILENAME":case"SCRIPT_FILENAME":return`/var/www/html${t.url}`;case"DOCUMENT_ROOT":return"/var/www/html";case"HTTP_USER_AGENT":return t.env.userAgent;case"HTTP_REFERER":return t.env.referer;case"QUERY_STRING":default:return"";case"THE_REQUEST":return`GET ${t.url} HTTP/1.1`;case"REQUEST_METHOD":return"GET"}})(s,t));return(a=a.replace(/\$(\d)/g,(e,t)=>s[Number(t)]??"")).replace(/%(\d)/g,(e,t)=>r[Number(t)]??"")}function $(e,t){try{return new RegExp(e,t?"i":"")}catch{return null}}function T(e){return/^[a-z][a-z0-9+.-]*:\/\//i.test(e)}function R(e){let t=e.trim();if(!t||"^"===t||".*"===t||"^.*$"===t||"(.*)"===t||"^(.*)$"===t||(t=t.replace(/^\^/,"").replace(/\$$/,""),/[()[\]{}*+?|]/.test(t.replace(/\\[()[\]{}*+?|.]/g,""))))return null;let s=t.replace(/\\(.)/g,"$1");return!s||/[^A-Za-z0-9._/-]/.test(s)?null:s}let N=new Set(["^",".",".*","^.*",".*$","^.*$","(.*)","^(.*)$","^(.*)","(.*)$"]),A=["image/jpeg","image/jpg","image/png","image/gif","
1image/webp","image/avif","font/woff","font/woff2","application/zip","application/x-gzip","video/mp4","audio/mpeg","application/pdf"],j=["strict-transport-security","x-content-type-options","x-frame-options","referrer-policy","permissions-policy","content-security-policy"];function E(e,t){return e.conds.some(e=>t.test(e.test)||t.test(e.pattern))}var S=s(40750);let C={apache:"2.4",domain:"example.com",guards:!0,comments:!0,forceHttps:!0,behindProxy:!0,wwwPolicy:"strip",slash:"none",noIndexes:!0,errorPages:!0,error404:"/404.html",blockIps:"",blockBots:!1,hotlink:!1,caching:!0,compression:!0,securityHeaders:!0,hsts:!0,basicAuth:!1,authName:"Staging",authFile:"/home/user/secure/.htpasswd",protectFiles:!0,spaFallback:!1,mime:!0},P={wordpress:{forceHttps:!0,wwwPolicy:"strip",protectFiles:!0,noIndexes:!0,caching:!0,compression:!0,securityHeaders:!0,spaFallback:!1,basicAuth:!1,hotlink:!1,slash:"none"},spa:{forceHttps:!0,wwwPolicy:"strip",spaFallback:!0,caching:!0,compression:!0,securityHeaders:!0,mime:!0,protectFiles:!0,basicAuth:!1,slash:"none"}
1,static:{forceHttps:!0,wwwPolicy:"strip",spaFallback:!1,caching:!0,compression:!0,securityHeaders:!0,noIndexes:!0,basicAuth:!1,slash:"none"},staging:{forceHttps:!0,basicAuth:!0,noIndexes:!0,protectFiles:!0,caching:!1,securityHeaders:!0,hsts:!1,spaFallback:!1,wwwPolicy:"none"}},F={wordpress:"WordPress hardening",spa:"Single page app",static:"Plain static site",staging:"Staging lockdown"};function _(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function I(e){return e.replace(/["\\]/g,"").replace(/\s+/g," ").trim()}function H(e){return/\s/.test(e)?`"${e}"`:e}function L(e){let[t,s,...r]=e.split("/");if(r.length>0||void 0===t||""===t)return!1;if(t.includes(":")){let e;try{e=new URL(`http://[${t}]/`).hostname}catch{return!1}return!!e.startsWith("[")&&(void 0===s||/^\d{1,3}$/.test(s)&&128>=Number(s))}let a=t.replace(/\.$/,"").split(".");if(a.length<1||a.length>4)return!1;let i=[...a,...Array(4-a.length).fill("0")].join(".");return!!(0,S.vm)(i).ok&&(void 0===s||(/^\d{1,2}$/.test(s)?32>=Number(s):(0,S.vm)(s).ok))}function O(e){let t=e.split(/[\s,]+/).map(e=>e.trim()).filter(Boolean),s=t.filter(e=>!L(e)),r=t.filter(e=>L(e)),a=r.slice(0,40);return{ips:a,dropped:r.length-a.length,rejected:s}}class M{constructor(e){this.comments=e,this.lines=[]}add(e,t=""){this.lines.push(t+e)}note(e,t=""){this.comments&&this.lines.push(`${t}# ${e}`)}head(e){this.gap(),this.comments&&this.lines.push(`# ${e} ${"-".repeat(Math.max(4,72-e.length))}`)}gap(){let e=this.lines[this.lines.length-1];this.lines.length>0&&""!==e&&this.lines.push("")}text(){return`${this.lines.join("\n").replace(/\n{3,}/g,"\n\n").trim()} 2`}}function U(e,t){let s=0;return{get indent(){return s>0?" ":""},open(r){t&&(e.add(`<IfModule ${r}>`),s=1)},close(){t&&0!==s&&(e.add("</IfModule>"),s=0)}}}let q=(e,t,s)=>{"2.4"===e?s.add("Require all denied",t):(s.add("Order allow,deny",t),s.add("Deny from all",t))};function W(e,t){let s="is"===t?"RewriteCond %{HTTPS} =on":"RewriteCond %{HTTPS} !=on";if(!e)return[s];let r="is"===t?"RewriteCond %{HTTP:X-Forwarded-Proto} =https":"RewriteCond %{HTTP:X-Forwarded-Proto} !https";return"is"===t?[`${s} [OR]`,r]:[s,r]}var z=s(35339),B=s(19914),D=s(15662);let X=["wordpress","spa","static","staging"],Q=`RewriteEngine On 3 4RewriteCond %{HTTPS} off 5RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L] 6 7RewriteCond %{HTTP_HOST} ^www\\.(.+)$ [NC] 8RewriteRule ^(.*)$ https://%1/$1 [R=301,L] 9 10Order allow,deny 11Deny from 203.0.113.9 12 13<IfModule mod_headers.c> 14Header set X-Frame-Options "SAMEORIGIN" 15Header set X-XSS-Protection "1; mode=block" 16</IfModule> 17 18php_value upload_max_filesize 64M 19ErrorDocument 404 404.html 20`,G={error:"text-red-600 dark:text-red-400",warning:"text-amber-600 dark:text-amber-500",note:"text-sky-600 dark:text-sky-400"},J={error:"Breaks",warning:"Risky",note:"Note"},V=["domain","blockIps","authFile","authName","error404"];function Y(e){try{let s=JSON.parse(e);if(!s||"object"!=typeof s||Array.isArray(s))return null;let r={...C};for(let e of Object.keys(C)){var t;let a=s[e];t=C[e],null!=a&&(Array.isArray(t)?Array.isArray(a):!Array.isArray(a)&&typeof a==typeof t)&&(r[e]=a)}return r}catch{return null}}function K(){let[e,t]=(0,a.useState)("build"),[s,p]=(0,a.useState)(C),[f,m]=(0,a.useState)(""),[g,b]=(0,a.useState)(!1),[S,L]=(0,a.useState)("/about"),[K,Z]=(0,a.useState)("www"),[ee,et]=(0,a.useState)(!1),[es,er]=(0,a.useState)(!1),[ea,ei]=(0,a.useState)(!1),[en,eo]=(0,a.useState)(!1),[el,ed]=(0,a.useState)(!1),[eh,ec]=(0,a.useState)(!1),[eu,ep]=(0,a.useState)(""),ef=(0,a.useRef)(null);(0,a.useEffect)(()=>{let e=(0,h.qA)(window.location.search,window.location.hash),t=e.get("s"),s=e.get("w");if(!t&&!s)return;let r=t?Y(t):{...C};if(!r)return;let a=s?Y(s):null;if(!a)return void p(r);let i={...r};for(let e of V)i[e]=a[e];p(i)},[]);let em=(0,a.useMemo)(()=>(0,c.o)(s.domain),[s.domain]),eg=(0,a.useMemo)(()=>em.ok?function(e,t){let s="add"===e.wwwPolicy?`www.${t}`:t,r=new M(e.comments),{ips:a}=O(e.blockIps);if(r.note(`.htaccess for ${s}`),r.note(`Written for Apache ${e.apache}. Upload it to the folder it should apply to.`),r.note("Keep a copy of whatever was in this file before you replace it."),e.comments&&r.gap(),(e.noIndexes||e.errorPages)&&(r.head("Directory behavior"),e.noIndexes&&(r.note("No directory listings. A folder with no index file would otherwise show everything in it."),r.add("Options -Indexes"),r.add("DirectoryIndex index.html index.php")),e.errorPages)){let t=I(e.error404)||"/404.html",s=t.startsWith("/")?t:`/${t}`;r.gap(),r.note("The leading slash matters. Without it Apache prints the value as literal text."),r.add(`ErrorDocument 404 ${H(s)}`),r.add("ErrorDocument 403 /403.html")}if(e.protectFiles&&(r.head("Files nobody outside should be able to download"),r.note("Dotfiles first. A stray .env in the web root is one of the easiest ways to leak a database password."),r.add('<FilesMatch "^\\.(env|git|gitignore|gitattributes|htaccess|htpasswd|user\\.ini)$">'),q(e.apache," ",r),r.add("</FilesMatch>"),r.gap(),r.add('<FilesMatch "^(wp-config\\.php|composer\\.(json|lock)|package(-lock)?\\.json|yarn\\.lock|\\.DS_Store)$">'),q(e.apache," ",r),r.add("</FilesMatch>"),r.gap(),r.note("Backups and dumps left in the web root by accident."),r.add('<FilesMatch "\\.(sql|bak|old|orig|save|swp|log)$">'),q(e.apache," ",r),r.add("</FilesMatch>")),e.basicAuth&&(r.head("Password protection"),r.note("Deliberately not wrapped in IfModule. If the auth module is missing you want a loud"),r.note("error, not a staging site quietly open to the world."),r.note("Basic auth is base64, not encryption, so this needs HTTPS to be worth anything."),r.add("AuthType Basic"),r.add(`AuthName "${I(e.authName)||"Restricted"}"`),r.note("Absolute server path, and it belongs outside the web root. This is the mistake people make."),r.add(`AuthUserFile ${H(I(e.authFile)||"/home/user/secure/.htpasswd")}`),r.add("Require valid-user")),a.length)if(r.head("Blocked addresses"),"2.4"===e.apache){for(let e of(r.add("<RequireAll>"),r.add("Require all granted"," "),a))r.add(`Require not ip ${e}`," ");r.add("</RequireAll>")}else for(let e of(r.add("Order allow,deny"),r.add("Allow from all"),a))r.add(`Deny from ${e}`);if(e.forceHttps||"none"!==e.wwwPolicy||"none"!==e.slash||e.blockBots||e.hotlink||e.spaFallback||e.protectFiles){r.head("Redirects and rewrites");let a=U(r,e.guards);a.open("mod_rewrite.c");let i=a.indent;
20if(r.add("RewriteEngine On",i),r.note("Every pattern below is matched against the path with the leading slash already",i),r.note("removed, which is why they read like index.php and not /index.php.",i),e.protectFiles&&(r.gap(),r.note("Version control folders. Serving .git/config hands over your repository history.",i),r.add("RewriteRule (^|/)\\.(git|svn|hg|bzr)(/|$) - [F]",i)),e.blockBots&&(r.gap(),r.note("Aggressive crawlers that ignore robots.txt. Add or remove names as you need them.",i),r.add("RewriteCond %{HTTP_USER_AGENT} (ahrefsbot|semrushbot|mj12bot|dotbot|petalbot) [NC]",i),r.add("RewriteRule ^ - [F]",i)),e.forceHttps&&"none"!==e.wwwPolicy){for(let t of(r.gap(),r.note("One redirect, not two. This first rule only marks the request as already secure,",i),r.note(e.behindProxy?"whether TLS ends here or at a proxy in front. It never changes the URL.":"reading %{HTTPS} only, since nothing in front is setting a forwarded header. It never changes the URL.",i),W(e.behindProxy,"is")))r.add(t,i);r.add("RewriteRule ^ - [E=SECURE:1]",i),r.gap(),r.note(`Now one 301 fixes the scheme and the host together, so http://www.${t}/page`,i),r.note("lands on the canonical address in a single hop instead of two.",i),r.add("RewriteCond %{ENV:SECURE} !=1 [OR]",i),r.add(`RewriteCond %{HTTP_HOST} !^${_(s)}$ [NC]`,i),r.add(`RewriteRule ^ https://${s}%{REQUEST_URI} [R=301,L]`,i)}else if(e.forceHttps){for(let t of(r.gap(),e.behindProxy?(r.note("Both tests have to say insecure before this fires. Behind Cloudflare or any host",i),r.note("that ends TLS in front of Apache, %{HTTPS} is off on every request, so checking",i),r.note("it on its own redirects forever.",i)):(r.note("No proxy in front, so the plain check is enough. Add the X-Forwarded-Proto test",i),r.note("the day you put a CDN in front, or this will loop.",i)),W(e.behindProxy,"not")))r.add(t,i);r.add("RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]",i)}else if("none"!==e.wwwPolicy){let t;if(r.gap(),r.note(`Send every other hostname to ${s} so links and rankings point at one address.`,i),"2.2"===e.apache)r.note("Apache 2.2 has no REQUEST_SCHEME variable, so this redirect drops to http.",i),r.note("Turn Force HTTPS on to avoid that.",i),t="http";else if(e.behindProxy){for(let e of(r.note("%{REQUEST_SCHEME} is the scheme Apache is serving, and behind a proxy that ends",i),r.note("TLS that is http on every request. These two lines carry the scheme the VISITOR",i),r.note("used, so the canonical redirect never sends an https visitor back to http.",i),r.add("RewriteRule ^ - [E=PROTO:http]",i),W(!0,"is")))r.add(e,i);r.add("RewriteRule ^ - [E=PROTO:https]",i),r.gap(),t="%{ENV:PROTO}"}else t="%{REQUEST_SCHEME}";r.add(`RewriteCond %{HTTP_HOST} !^${_(s)}$ [NC]`,i),r.add(`RewriteRule ^ ${t}://${s}%{REQUEST_URI} [R=301,L]`,i)}"add"===e.slash?(r.gap(),r.note("Add a trailing slash, but only to paths that are not real files and have no extension.",i),r.add("RewriteCond %{REQUEST_FILENAME} !-f",i),r.add("RewriteCond %{REQUEST_URI} !\\.[a-zA-Z0-9]{1,6}$",i),r.add("RewriteCond %{REQUEST_URI} !/$",i),r.add("RewriteRule ^(.*)$ /$1/ [R=301,L]",i)):"remove"===e.slash&&(r.gap(),r.note("Strip the trailing slash. The !-d test keeps real directories working, and",i),r.note("DirectorySlash Off stops Apache adding its own slash straight back.",i),r.add("RewriteCond %{REQUEST_FILENAME} !-d",i),r.add("RewriteRule ^(.+)/$ /$1 [R=301,L]",i)),e.hotlink&&(r.gap(),r.note("Hotlink protection. The first condition allows an empty referrer, or direct visits",i),r.note("and some privacy browsers see broken images.",i),r.add("RewriteCond %{HTTP_REFERER} !^$",i),r.add(`RewriteCond %{HTTP_REFERER} !^https?://(www\\.)?${_(t)}/ [NC]`,i),r.add("RewriteRule \\.(jpe?g|png|gif|webp|avif|svg)$ - [F,NC]",i)),e.spaFallback&&(r.gap(),r.note("Send anything that is not a real file or folder to the app shell, so a deep link",i),r.note("survives a refresh. This is a rewrite, not a redirect, so the address bar stays put.",i),r.note("It has to be the last rewrite in the file or it swallows the redirects above.",i),r.add("RewriteCond %{REQUEST_FILENAME} !-f",i),r.add("RewriteCond %{REQUEST_FILENAME} !-d",i),r.add("RewriteRule . /index.html [L]",i)),a.close()}if("remove"===e.slash&&(r.gap(),r.add("DirectorySlash Off")),e.mime){r.head("File types");let t=U(r,e.guards);t.open("mod_mime.c");let s=t.indent;r.note("Older servers hand these back as plain downloads because they have never heard of them.",s),r.add("AddType image/avif .avif",s),r.add("AddType image/webp .webp",s),r.add("AddType font/woff2 .woff2",s
20),r.add("AddType application/manifest+json .webmanifest",s),t.close(),r.add("AddDefaultCharset UTF-8")}if(e.compression){r.head("Compression");let t=U(r,e.guards);t.open("mod_deflate.c");let s=t.indent;r.note("Text only. Images, fonts, video and zip files are already compressed, so putting",s),r.note("them through gzip burns processing time and saves nothing.",s),r.add("AddOutputFilterByType DEFLATE text/html text/plain text/css text/javascript application/javascript application/json application/xml image/svg+xml",s),t.close()}if(e.caching){r.head("Browser caching");let t=U(r,e.guards);t.open("mod_expires.c");let s=t.indent;r.add("ExpiresActive On",s),r.add('ExpiresDefault "access plus 1 month"',s),r.note("HTML gets nothing, so a fresh deploy shows up straight away.",s),r.add('ExpiresByType text/html "access plus 0 seconds"',s),r.add('ExpiresByType text/css "access plus 1 year"',s),r.add('ExpiresByType application/javascript "access plus 1 year"',s),r.add('ExpiresByType image/webp "access plus 1 year"',s),r.add('ExpiresByType image/avif "access plus 1 year"',s),r.add('ExpiresByType image/svg+xml "access plus 1 year"',s),r.add('ExpiresByType font/woff2 "access plus 1 year"',s),t.close(),r.gap(),r.note("A year is only safe on files whose name changes when the content does, such as"),r.note("app.9f2c1d.css. On a plain style.css your returning visitors keep the old one.")}if(e.securityHeaders||e.hsts&&e.forceHttps){r.head("Response headers");let t=U(r,e.guards);t.open("mod_headers.c");let s=t.indent;r.note("The always keyword is doing real work here. Plain Header set only applies to",s),r.note("successful responses, so your 404 and 500 pages would ship none of these.",s),e.securityHeaders&&(r.add('Header always set X-Content-Type-Options "nosniff"',s),r.add('Header always set Referrer-Policy "strict-origin-when-cross-origin"',s),r.add('Header always set X-Frame-Options "SAMEORIGIN"',s),r.add('Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()"',s),r.add("Header always unset X-Powered-By",s),r.note("X-XSS-Protection is deliberately absent. Every current browser dropped the filter",s),r.note("it controlled, and in older ones it caused problems of its own.",s)),e.hsts&&e.forceHttps&&(r.gap(),r.note("Browsers refuse plain http for this name for a year once they see this. Add preload",s),r.note("only when every subdomain is on HTTPS, because getting off that list takes months.",s),r.add('Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"',s)),t.close()}return r.text()}(s,em.host):"",[s,em]),ew="build"===e?eg:f,ey=(0,a.useMemo)(()=>(function(e){let t=65279===e.charCodeAt(0),s=t?e.slice(1):e,r=/\r\n/.test(s),a=s.split(/\r\n|\n|\r/),i=[],n=[],o=[],l=[];for(let e=0;e<a.length;e++){let t=e+1,s=a[e]??"",r=s.trimStart();if(""===r)continue;if(r.startsWith("#")){let e=/^#\s*(BEGIN|END)\s+(.+?)\s*$/i.exec(r);if(e){let s=(e[1]??"").toUpperCase(),r=e[2]??"";if("BEGIN"===s)o.push({name:r,start:t,end:null});else for(let e=o.length-1;e>=0;e--){let s=o[e];if(s&&null===s.end&&s.name.toLowerCase()===r.toLowerCase()){s.end=t;break}}}continue}let d=s,h=0;for(;/\\\s*$/.test(d)&&e+1<a.length&&h<200;)d=d.replace(/\\\s*$/," "),e++,h++,d+=(a[e]??"").trim();if(!(d=d.trim()))continue;if(d.startsWith("</")){let e=d.slice(2).replace(/>[\s\S]*$/,"").trim(),s=l.pop();s?s.tag.toLowerCase()!==e.toLowerCase()&&n.push({line:t,message:`</${e}> does not match the <${s.tag}> opened on line ${s.line}.`}):n.push({line:t,message:`</${e}> closes a block that was never opened. Apache returns a 500 for this whole directory.`}),i.push({line:t,name:e,lower:e.toLowerCase(),args:[],raw:d,path:l.map(e=>e.label),kind:"close"});continue}if(d.startsWith("<")){d.endsWith(">")||n.push({line:t,message:`<${d.slice(1).split(/\s/)[0]??""}> is missing its closing angle bracket.`});let e=u(d.replace(/^</,"").replace(/>\s*$/,"")),s=e.shift()??"",r=e.length?`${s} ${e.join(" ")}`:s;i.push({line:t,name:s,lower:s.toLowerCase(),args:e,raw:d,path:l.map(e=>e.label),kind:"open"}),l.push({tag:s,line:t,label:r});continue}let c=u(d),p=c.shift()??"";i.push({line:t,name:p,lower:p.toLowerCase(),args:c,raw:d,path:l.map(e=>e.label),kind:"directive"})}for(let e of l)n.push({line:e.line,message:`<${e.tag}> opened here is never closed. An unclosed block is one of the fastest ways to 500 a directory.`});
20return{directives:i,structural:n,hasBom:t,hasCrlf:r,markers:o,lineCount:a.length}})(ew),[ew]),ex=(0,a.useMemo)(()=>(function(e){let t=[],s=[],r=[],a=!1,i=!1,n=null,o=!1;for(let l of e.directives)if("directive"===l.kind){if("rewriteengine"===l.lower){i=!0,"on"===(l.args[0]??"").toLowerCase()&&(a=!0);continue}if("rewritebase"===l.lower){n=l.args[0]??null;continue}if("directoryslash"===l.lower){"off"===(l.args[0]??"").toLowerCase()&&(o=!0);continue}if("rewritecond"===l.lower){i=!0;let e=/^\[.*\]$/.test(l.args[2]??"")?(l.args[2]??"").slice(1,-1).split(",").map(e=>e.trim().toUpperCase()):[];r.push({line:l.line,test:l.args[0]??"",pattern:l.args[1]??"",flags:e,raw:l.raw});continue}if("rewriterule"===l.lower){i=!0;let{map:e,raw:s}=function(e){let t={},s=/\[([^\]]*)\]\s*$/.exec(e.trim());if(!s)return{map:t,raw:""};for(let e of(s[1]??"").split(",")){let s=e.trim();if(!s)continue;let r=s.indexOf("=");r>0?t[s.slice(0,r).toUpperCase()]=s.slice(r+1):t[s.toUpperCase()]=""}return{map:t,raw:s[1]??""}}(l.raw);t.push({line:l.line,pattern:l.args[0]??"",substitution:l.args[1]??"-",flags:e,flagText:s,conds:r,guard:l.path,raw:l.raw}),r=[];continue}if("redirect"===l.lower||"redirectmatch"===l.lower){let e=[...l.args],t=302,r=(e[0]??"").toLowerCase();/^\d{3}$/.test(r)?(t=Number.parseInt(r,10),e.shift()):void 0!==x[r]&&(t=x[r],e.shift()),s.push({line:l.line,kind:"redirect"===l.lower?"Redirect":"RedirectMatch",status:t,from:e[0]??"",to:e[1]??"",raw:l.raw});continue}r.length&&"rewritecond"!==l.lower&&(r=[])}return{engineOn:a,hasRewriteDirectives:i,rules:t,aliases:s,base:n,directorySlashOff:o}})(ey),[ey]),ev=(0,a.useMemo)(()=>(function(e,t,s){let r=[],a=(e,t,s,a)=>r.push({line:e,level:t,title:s,detail:a});for(let t of(e.hasBom&&a(1,"error","The file starts with a byte order mark","Windows editors add three invisible bytes at the top when they save as UTF-8 with BOM. Apache reads them as part of the first directive and returns a 500 for the whole folder. Save as UTF-8 without BOM, or use the download button here, which writes a clean file."),e.hasCrlf&&a(1,"warning","Windows line endings","The lines end with CRLF. Most builds of Apache cope, but a quoted value can pick up the stray carriage return and behave in ways that are very hard to spot. The download here uses plain LF endings."),e.structural))a(t.line,"error","Unbalanced block",t.message);for(let t of e.directives)"open"===t.kind&&"directory"===t.lower&&a(t.line,"error","<Directory> cannot be used in .htaccess","This one is an instant 500 for the folder. .htaccess already applies to the directory it sits in, so drop the wrapper and keep the directives inside it."),"open"===t.kind&&"virtualhost"===t.lower&&a(t.line,"error","<VirtualHost> cannot be used in .htaccess","It belongs in the main server config. Apache returns a 500 when it finds this here.");let i=e.directives.filter(e=>"directive"===e.kind&&["order","allow","deny","satisfy"].includes(e.lower)),n=e.directives.filter(e=>"directive"===e.kind&&"require"===e.lower);if("2.4"===s&&i.length){let e=i[0];a(e?e.line:1,"error","Apache 2.2 access syntax on a 2.4 server",`Order, Allow from and Deny from were replaced in Apache 2.4 by Require. On 2.4 they either throw "Invalid command" and 500 the directory, or, if mod_access_compat happens to be loaded, they quietly work today and stop working the day your host drops it. Replace them with Require all denied, Require all granted or Require ip. Found on line${i.length>1?"s":""} ${i.map(e=>e.line).join(", ")}.`)}if("2.2"===s&&n.length){let e=n[0];a(e?e.line:1,"error","Apache 2.4 access syntax on a 2.2 server","Require all granted and Require ip do not exist in 2.2 and will throw an error. Use Order allow,deny with Allow from and Deny from instead.")}for(let e of(i.length&&n.length&&a(n[0]?.line??1,"warning","Both access syntaxes in one file","The 2.2 and 2.4 styles are mixed here. Whichever one your server does not understand decides whether the folder works, so pick the version your host actually runs and delete the other."),t.rules.length>0&&!t.engineOn&&a(t.rules[0]?.line??1,"error","RewriteEngine On is missing","Apache reads every RewriteRule below and skips all of them without a word in the error log. This is the answer nine times out of ten when someone says the file is being ignored."),t.rules))/^\^?\//.test(e.pattern)&&a(e.line,"error","This pattern can never match",`In .htaccess the path is matched with the leading slash already removed, so ${e.pattern} is compared against something like index.php rather than /index.php. Drop the slash and write it as ${e.pattern.replace(/^(\^?)\//,"$1")}.`),void 0!==e.flags.P&&a(e.line,"warning","[P] needs mod_proxy","Proxying from .htaccess requires mod_proxy loaded and ProxyRequests off, and many shared hosts refuse it outright. The request simulator on this page stops at this rule rather than guessing what the other server does."),void 0!==e.flags.R&&void 0===e.flags.L&&void 0===e.flags.END&&a(e.line,"warning","A redirect without [L]","Apache keeps evaluating the rules below this one before it sends the redirect, so a later rule can still change the target. Write it as [R=301,L].");for(let e of t.rules){let t=e.conds.find(e=>/%\{HTTPS\}/i.test(e.test));if(!t)continue;let s=t.pattern.trim();(s.startsWith("!")||/^\^?off\$?$/i.test(s))&&(E(e,/X-Forwarded-Proto|ENV:/i)?t.flags.includes("OR")&&a(t.line,"error","The proxy check is joined with [OR], which still loops","With [OR] the redirect fires when either test says insecure, and behind a proxy %{HTTPS} always says insecure. The two tests have to be ANDed, so remove the [OR] and let both conditions apply."):a(t.line,"warning","This force-HTTPS rule loops behind a proxy","Behind Cloudflare, a load balancer or any host that ends TLS in front of Apache, %{HTTPS} is off on every request even though the visitor is already on https. The rule fires, the browser comes back over https, %{HTTPS} is still off, and the redirect never stops. Add a second condition on %{HTTP:X-Forwarded-Proto} so both have to say insecure before the redirect fires."),/^!?\^?off\$?$/i.test(s)&&a(t.line,"note","%{HTTPS} is a string, not a flag","It holds the literal text on or off, so the reliable test is !=on rather than off. They look equivalent and behave differently on a few builds."))}let o=null;for(let e of t.rules){let t=e.guard.join(">");if(o&&o.guard.join(">")===t){a(e.line,"warning","This rule never runs",`Line ${o.line} matches every request, has no conditions and ends with [L], so Apache stops there and nothing below it in the same block is ever reached.`);continue}0===e.conds.length&&N.has(e.pattern.trim())&&(void 0!==e.flags.L||void 0!==e.flags.END)&&void 0===e.flags.R&&(o=e)}for(let e of t.rules){if(e.conds.length>0||"-"===e.substitution||void 0!==e.flags.R||/^[a-z]+:\/\//i.test(e.substitution)||!
20/\$\d/.test(e.substitution)||!N.has(e.pattern.trim()))continue;let t=e.substitution.replace(/^\//,"").replace(/\$\d/g,"").replace(/\/+$/,"");t&&a(e.line,"error","This rule feeds itself and never stops",`The pattern matches everything, and the substitution puts ${t} in front of whatever matched. In .htaccess the whole ruleset runs again after each rewrite, so the path grows every pass until Apache gives up with "Request exceeded the limit of 10 internal redirects". Add a condition such as RewriteCond %{REQUEST_FILENAME} !-f, or narrow the pattern so the rewritten path stops matching.`)}for(let e=0;e<t.rules.length;e++){let s=t.rules[e];if(!s)continue;let r=R(s.pattern);if(r)for(let i=0;i<e;i++){let e,n=t.rules[i];if(n&&!(n.conds.length>0)&&n.guard.join(">")===s.guard.join(">")&&(void 0!==n.flags.L||void 0!==n.flags.R)&&!N.has(n.pattern.trim())){try{e=new RegExp(n.pattern,void 0!==n.flags.NC?"i":"")}catch{continue}if(e.test(r)){a(s.line,"warning","An earlier rule already caught this path",`Line ${n.line} matches ${r} too, and it stops there, so this rule never gets the request. Move the specific rule above the broad one.`);break}}}}t.aliases.length>0&&t.rules.length>
200&&a(t.aliases[0]?.line??1,"warning","Redirect resolves before your RewriteRules","mod_alias runs in an earlier phase than per-directory mod_rewrite, so a Redirect line takes the request no matter where it sits in the file. Mixing the two is how a rule that looks first ends up second. Convert these to RewriteRule if the order matters.");let l=t.rules.filter(e=>void 0!==e.flags.R),d=l.find(e=>E(e,/%\{HTTPS\}|X-Forwarded-Proto/i)),h=l.find(e=>E(e,/HTTP_HOST/i)&&/www\\?\./i.test(e.conds.map(e=>e.pattern).join(" ")));for(let t of(d&&h&&d.line!==h.line&&a(h.line,"warning","Every visitor takes two redirects",`The HTTPS rule on line ${d.line} and the www rule on line ${h.line} are separate, so a request to the plain http www address gets a 301 to https, then a second 301 to drop the www. One combined rule does it in a single hop, which is faster for the visitor and cleaner for search engines.`),e.directives)){if("directive"!==t.kind||"header"!==t.lower)continue;let e="always"===(t.args[0]??"").toLowerCase(),s=e?t.args.slice(1):t.args,r=(s[0]??"").toLowerCase(),i=(s[1]??"").toLowerCase();!e&&"set"===r&&j.includes(i)&&a(t.line,"warning",`${s[1]} is missing the always keyword`,"Plain Header set only applies to successful responses, so this header disappears on your 404 and 500 pages, which are exactly the pages an attacker will aim for. Write it as Header always set."),"strict-transport-security"===i&&/preload/i.test(s.slice(2).join(" "))&&a(t.line,"warning","HSTS preload is close to permanent","Once the domain is on the browser preload list, getting it off takes months and every subdomain has to serve valid HTTPS the whole time. If one internal subdomain is still on http, it becomes unreachable. Ship the header without preload for a
20while first."),"x-xss-protection"===i&&a(t.line,"note","X-XSS-Protection is deprecated","Every modern browser has dropped the filter this header controlled, and in older ones it introduced its own bugs. Chrome, Edge and Firefox all ignore it now. Remove the line, or send 0 if a scanner insists on seeing it. Content-Security-Policy is what actually does this job.")}for(let t of e.directives)if("directive"===t.kind){if(("php_value"===t.lower||"php_flag"===t.lower)&&a(t.line,"error","php_value only works under mod_php","Most shared hosts now run PHP through FPM or LiteSpeed, and on those this line returns a 500 for the whole folder. Put the setting in a .user.ini file in the same directory instead, or use your control panel PHP settings."),"authuserfile"!==t.lower||(t.args[0]??"").startsWith("/")||a(t.line,"error","AuthUserFile needs an absolute path","A relative path here fails and the login box either never appears or never accepts anything. Use the full server path, and put the password file outside the web root so nobody can download it."),"errordocument"===t.lower){let e=t.args[1]??"";/^https?:\/\//i.test(e)?a(t.line,"warning","An external error page changes the status code",`Pointing ErrorDocument at a full URL makes Apache send a 302 to that address instead of the ${t.args[0]??"error"} you meant, so search engines see a redirect where they should see an error. Use a local path starting with a slash.`):!e||e.startsWith("/")||e.startsWith('"')||a(t.line,"warning","This error page is being treated as text",`Without a leading slash Apache treats ${e} as a literal message to print, not a file to serve. Write it as /${e}.`)}if("options"===t.lower&&t.args.some(e=>/^\+?indexes$/i.test(e))&&a(t.line,"warning","Directory listing is switched on","Any folder without an index file will show its full contents to anyone who asks for it, backups and stray SQL dumps included. Options -Indexes is almost always what you want."),"addoutputfilterbytype"===t.lower){let e=t.args.filter(e=>A.includes(e.toLowerCase()));e.length&&a(t.line,"note","These types are already compressed",`${e.join(", ")} gain nothing from gzip and cost processing time on every request. Compress text formats only.`)}if("expiresbytype"===t.lower&&/text\/html/i.test(t.args[0]??"")){let e=t.args[1]??"";/(month|year|week)/i.test(e)&&a(t.line,"warning","HTML is being cached for a long time",`Returning visitors will keep the old page until ${e} runs out, and there is no way to push an update to them in the meantime. Cache HTML for seconds, and save the long times for files whose names change when the content does.`)}}let c=new Set;for(let t of e.directives)"open"===t.kind&&"ifmodule"===t.lower&&c.add((t.args[0]??"").toLowerCase());for(let[t,s,r]of[[/^rewrite/,"mod_rewrite.c","the rewrite rules"],[/^header$/,"mod_headers.c","the header lines"],[/^expires/,"mod_expires.c","the cache headers"],[/^addoutputfilterbytype$/,"mod_deflate.c","the compression lines"]]){let i=e.directives.find(e=>"directive"===e.kind&&t.test(e.lower));!(!i||c.has(s))&&(i.path.some(e=>e.toLowerCase().includes(s))||a(i.line,"note",`No <IfModule ${s}> around ${r}`,'If the host has not loaded that module, Apache throws "Invalid command" and returns a 500 for the folder. Wrapping the block makes it skip quietly instead. The trade is that a missing module then fails in silence, which is why "my rules do nothing" is a more common complaint than "my site is down".'))}let u=new Set(["ifmodule","rewriteengine","rewritebase","rewriterule","rewritecond"]);for(let t of e.markers){if(!/wordpress/i.test(t.name))continue;let s=e.directives.filter(e=>null!==t.end&&e.line>t.start&&e.line<t.end);(s.filter(e=>!u.has(e.lower)).length>0||s.length>10)&&a(t.start,"warning","Your rules are inside the WordPress markers","WordPress rewrites everything between # BEGIN WordPress and # END WordPress every time permalinks are saved, and it does it without asking. Anything custom in there disappears. Move your own rules above the BEGIN line.")}let p={error:0,warning:1,note:2};return r.sort((e,t)=>p[e.level]-p[t.level]||e.line-t.line)})(ey,ex,s.apache),[ey,ex,s.apache]),eb=(0,a.useMemo)(()=>ey.directives.map(w),[ey]),ek=(0,a.useMemo)(()=>(function(e,t){let s=new Set(["/"]);for(let t of e.aliases)t.from.startsWith("/")&&s.add(t.from);for(let t of e.rules){let e=R(t.pattern);e&&s.add(`/${e}`)}let r=new Set;for(let e of t.directives)if("filesmatch"===e.lower||"files"===e.lower){let t=R(e.args[0]??"");t&&r.add(t)}for(let e of[...r].slice(0,2))s.add(`/${e}`);return s.size<4&&(s.add("/about"),s.add("/style.css")),[...s].filter(e=>e.length<=60).slice(0,6)})(ex,ey),[ex,ey]),e$=em.ok?em.host:"example.com",eT=(0,a.useMemo)(()=>({...y,host:"www"===K?`www.${e$}`:e$,scheme:ee?"https":"http",proxyInFront:es,fileExists:ea,dirExists:en}),[e$,K,ee,es,ea,en]),eR=(0,a.useMemo)(()=>(function(e,t,s){let r=[],a=[],i={},n=v(t),o=s.host,l=s.scheme,d=0,h=0,c="ok",u="",p=e=>{if(!T(e))return n=v(e),!0;let t=function(e){let t=/^[a-z][a-z0-9+.-]*:\/\/([^/?#]+)/i.exec(e);return(t?.[1]??"").toLowerCase()}(e);return t&&!function(e,t){let s=e=>e.toLowerCase().replace(/:\d+$/,"").replace(/^www\./,"");return s(e)===s(t)}(t,o)?(c="offsite",u=e,!1):(t&&(o=t),l=/^https:/i.test(e)?"https":"http",n=function(e){if(!T(e))return v(e);let t=/^[a-z][a-z0-9+.-]*:\/\/[^/?#]*(\/[^?#]*)?/i.exec(e);return v(t?.[1]??"/")}(e),!0)};if(!e.engineOn&&e.rules.length>0)return{steps:[],finalUrl:n,hops:0,rounds:0,verdict:"ignored",headline:"None of the RewriteRules run, because RewriteEngine On is missing.",notes:["Apache reads the rules and skips every one of them without a warning, which is why this looks like the file is being ignored entirely."]};e:for(;;){if(++h>10){c="loop";break}let t=!1;for(let s of e.aliases){let e=function(e,t){if("Redirect"===e.kind){if(!e.from.startsWith("/"))return null;let s=e.from.endsWith("/")?e.from:`${e.from}/`;if(t!==e.from&&!t.startsWith(s))return null;let r=t.slice(e.from.length);return{target:`${e.to}${r}`,how:"prefix match"}}let s=$(e.from,!1);if(!s)return null;let r=s.exec(t);return r?{target:e.to.replace(/\$(\d)/g,(e,t)=>r[Number(t)]??""),how:"regex match"}:null}(s,n);if(!e)continue;let a=e.target;if(r.push({round:h,line:s.line,source:"mod_alias",rule:s.raw,action:`${s.kind} fired on a ${e.how} and sent a ${s.status}.`,from:n,to:a,status:s.status}),d++,t=!0,!p(a))break e;break}if(t){if(d>8){c="loop";break}continue}if(!e.engineOn)break;let f={url:n,host:o,scheme:l,env:s,vars:i},m=!1,g=!1,w=0,y=0;for(;w<e.rules.length;){let t=e.rules[w];if(!t)break;
20if(y>0){y--,w++;continue}f.url=n;let s=n.replace(/^\//,""),o=void 0!==t.flags.NC,l=t.pattern,x=!1;l.startsWith("!")&&(x=!0,l=l.slice(1));let b=$(l,o),R=[],N=!1;if(b){let e=b.exec(s);N=null!==e,e&&(R=Array.from(e,e=>e??""))}else a.push(`Line ${t.line} has a pattern this simulator cannot read, so the rule was skipped.`);x&&(N=!!b&&!N,R=[]);let A=N;if(A){let e=function(e,t,s){if(0===e.length)return{ok:!0,caps:[],notes:[]};let r=!0,a=!1,i=!1,n=[],o=[];for(let l of e){let e=function(e,t,s,r){let a=k(e.test,t,s,r),i=e.pattern,n=!1;i.startsWith("!")&&(n=!0,i=i.slice(1));let o=e.flags.includes("NC")||e.flags.includes("NOCASE"),l=!1,d=[];if("-f"===i||"-s"===i||"-F"===i)l=t.env.fileExists;else if("-d"===i)l=t.env.dirExists;else if("-l"===i||"-x"===i||"-U"===i)l=!1;else if(i.startsWith(">=")||i.startsWith("<=")){let e=i.slice(2);l=i.startsWith(">=")?a>=e:a<=e}else if(i.startsWith(">")||i.startsWith("<")){let e=i.slice(1);l=i.startsWith(">")?a>e:a<e}else if(i.startsWith("="))l=a===i.slice(1);else{let t=$(i,o);if(!t)return{ok:!1,caps:[],note:`Line ${e.line} has a pattern this simulator cannot read, so the condition was treated as false.`};let s=t.exec(a);l=null!==s,s&&(d=Array.from(s,e=>e??""))}return{ok:n?!l:l,caps:n?[]:d}}(l,t,s,n);if(e.note&&o.push(e.note),e.ok&&e.caps.length&&(n=e.caps),a=i&&a||e.ok,l.flags.includes("OR")||l.flags.includes("ORNEXT")){i=!0;continue}r=r&&a,i=!1,a=!1}return i&&(r=r&&a),{ok:r,caps:n,notes:o}}(t.conds,f,R);for(let t of e.notes)a.includes(t)||a.push(t);if((A=e.ok)&&void 0!==t.flags.E){let[e,...s]=t.flags.E.split(":");e&&(i[e]=s.join(":")||"1")}if(A){let s=function(e,t,s,r,a,i){let n={round:i,line:e.line,source:"mod_rewrite",rule:e.raw,from:a};if(void 0!==e.flags.F)return{step:{...n,action:"Matched, and [F] answers 403 Forbidden right there.",to:a,status:403},nextUrl:a,redirect:!1,terminal:"forbidden"};if(void 0!==e.flags.G)return{step:{...n,action:"Matched, and [G] answers 410 Gone right there.",to:a,status:410},nextUrl:a,redirect:!1,terminal:"gone"};if("-"===e.substitution){let t=void 0!==e.flags.E?` It set the ${e.flags.E.split(":")[0]} variable.`:"";return{step:{...n,action:`Matched, and the dash means the URL is left alone.${t}`,to:a,status:null},nextUrl:a,redirect:!1,terminal:null}}let o=k(e.substitution,t,s,r),l=void 0!==e.flags.R,d=l&&Number.parseInt(e.flags.R||"302",10)||302,h=l||T(o);if(void 0!==e.flags.P)return{step:{...n,action:"Matched, and [P] proxies the request to another server instead of redirecting.",to:o,status:null},nextUrl:o,redirect:!1,terminal:"proxy"};if(h){let e=T(o)?o:`${t.scheme}://${t.host}${v(o)}`;return{step:{...n,action:`Matched, and sent the browser a ${d} to a new address.`,to:e,status:d},nextUrl:e,redirect:!0,terminal:null}}let c=v(o);return{step:{...n,action:"Matched, and rewrote the path internally. The address bar does not change.",to:c,status:null},nextUrl:c,redirect:!1,terminal:null}}(t,f,R,e.caps,n,h);if(r.push(s.step),"forbidden"===s.terminal){c="forbidden";break e}if("gone"===s.terminal){c="gone";break e}if("proxy"===s.terminal){a.push("A [P] rule needs mod_proxy and hands the request to another server, so this simulator stops there rather than guessing what that server does."),u=s.nextUrl;break e}if(s.redirect){if(d++,void 0===t.flags.L&&void 0===t.flags.END){let e="A rule with [R] but no [L] keeps running the rules below it before the redirect is sent, which is a common source of surprise. Add L.";a.includes(e)||a.push(e)}if(!p(s.nextUrl))break e;if(d>8){c="loop";break e}continue e}if(s.nextUrl!==n&&(n=s.nextUrl,m=!0),void 0!==t.flags.END){g=!0;break}if(void 0!==t.flags.L)break;void 0!==t.flags.S&&(y=Number.parseInt(t.flags.S,10)||0),w++;continue}}if(void 0!==t.flags.C){let t=w+1;for(;t<e.rules.length;){let s=e.rules[t-1];if(t++,!s||void 0===s.flags.C)break}w=t;continue}w++}if(g||!m)break}"ok"===c&&s.dirExists&&!n.endsWith("/")&&!e.directorySlashOff&&(r.push({round:h,line:0,source:"mod_dir",rule:"DirectorySlash On (Apache default)",action:"The path is a real directory with no trailing slash, so mod_dir sends its own 301.",from:n,to:`${n}/`,status:301}),n=`${n}/`,d++);let f=u||(d>0?`${l}://${o}${n}`:n),m=function(e,t,s,r){switch(e){case"loop":{let e=s.slice(-3).map(e=>e.line?`line ${e.line}`:"mod_dir");return`Redirect loop. The request never settles, and the rules still firing at the end are ${e.join(", ")}.`}case"forbidden":return"Blocked with a 403 before anything is served.";case"gone":return"Answered with 410 Gone.";case"offsite":return`Sent off this site to ${r}.`;case"ignored":return"The rewrite rules never run.";default:if(0===t)return`Served as ${r} with no redirect.`;if(1===t)return`One redirect, ending at ${r}.`;return`${t} redirects before it lands on ${r}, and that can be one.`}}(c,d,r,f);return"ok"===c&&d>=2&&r.filter(e=>301===e.status).length>=2&&a.push(`This path takes ${d} redirects before it settles. Every one of them is a full round trip for the visitor and a diluted signal for search engines, and a single combined rule does the same job in one.`),0===e.rules.length&&0===e.aliases.length&&a.push("There are no redirect or rewrite rules in this file, so every request lands where it was asked for."),{steps:r,finalUrl:f,hops:d,rounds:h,verdict:"ok"===c&&d>0?"redirect":c,headline:m,notes:a}})(ex,S,eT),[ex,S,eT]),eN=O(s.blockIps),eA=ev.filter(e=>"error"===e.level).length,ej=ev.filter(e=>"warning"===e.level).length,eE=(e,t)=>p(s=>({...s,[e]:t})),eS=(0,a.useCallback)(e=>{if(e.size>524288)return void ep("That file is over 512 KB, which is far too big for an .htaccess, so nothing was loaded.");(0,B.G)(e).then(s=>{m(s.text),t("check");let r=s.text.split(/\r\n|\n|\r/).length;ep(""!==s.note?`Loaded ${e.name||".htaccess"}, ${r} lines. ${s.note}`:`Loaded ${e.name||".htaccess"}, ${r} lines.`)}).catch(e=>ep(e instanceof Error&&e.message.includes("UTF-32")?e.message:"That file could not be read as text."))},[]),eC=(0,a.useMemo)(()=>JSON.stringify(s),[s]),eP=(0,a.useCallback)(()=>(0,h.CZ)([{key:"s",value:eC.length>3500?null:function(e){let t={...e};for(let e of V)delete t[e];return JSON.stringify(t)}(s)},{key:"w",value:eC.length>3500?null:function(e){let t={};for(let s of V)t[s]=e[s];return JSON.stringify(t)}(s),personal:"your domain, the addresses you block, the password file you point at and your 404 path"}]),[s,eC.length]),eF=eC.length>3500,e_=(0,a.useMemo)(()=>eh&&"check"===e&&f.trim()?function(e,t){let s=e.length,r=t.length,a=Array.from({length:s+1},()=>Array(r+1).fill(0));for(let i=s-1;i>=0;i--)for(let s=r-1;s>=0;s--){let r=a[i],n=a[i+1];r&&n&&(r[s]=e[i]===t[s]?(n[s+1]??0)+1:Math.max(n[s]??0,r[s+1]??0))}let i=[],n=0,o=0;for(;n<s&&o<r;)e[n]===t[o]?(i.push({tag:" ",text:e[n]??""}),n++,o++):(a[n+1]?.[o]??0)>=(a[n]?.[o+1]??0)?(i.push({tag:"-",text:e[n]??""}),n++):(i.push({tag:"+",text:t[o]??""}),o++);for(;n<s;)i.push({tag:"-",text:e[n++]??""});for(;o<r;)i.push({tag:"+",text:t[o++]??""});return i}(f.split(/\r\n|\n|\r/).slice(0,400),eg.split(/\r\n|\n|\r/).slice(0,400)):null,[eh,e,f,eg]),eI=(0,a.useMemo)(()=>e_?Math.max(f.split(/\r\n|\n|\r/).length,eg.split(/\r\n|\n|\r/).length)-400:0,[e_,f,eg]),eH=(e,t,s,a)=>(0,r.jsx)(i.$,{type:"button",variant:e?"default":"outline",size:"sm","aria-pressed":e,title:a,onClick:s,children:t},t);return(0,r.jsxs)("div",{className:"space-y-5",children:[(0,r.jsxs)("div",{className:"flex flex-wrap items-center gap-2",children:[(0,r.jsx)(i.$,{type:"button",variant:"build"===e?"default":"outline",size:"sm","aria-pressed":"build"===e,onClick:()=>t("build"),children:"Build a file"}),(0,r.jsx)(i.$,{type:"button",variant:"check"===e?"default":"outline",size:"sm","aria-pressed":"check"===e,onClick:()=>t("check"),children:"Check the file I already have"}),(0,r.jsx)("span",{className:"text-xs text-muted-foreground",children:"Both modes run the same checks and the same request walk. Nothing you paste leaves your browser."})]}),"check"===e?(0,r.jsxs)("div",{className:"space-y-2",children:[(0,r.jsxs)("div",{onDragOver:e=>{e.preventDefault(),b(!0)},onDragLeave:()=>b(!1),onDrop:e=>{e.preventDefault(),b(!1);let t=e.dataTransfer.files?.[0];t&&eS(t)},className:`rounded-lg border-2 border-dashed p-2 transition-colors ${g?"border-primary bg-muted/50":"border-muted"}`,children:[(0,r.jsx)(o.J,{htmlFor:"ht-paste",className:"px-1 text-xs text-muted-foreground",children:"Paste your .htaccess here, or drag the file into this box, or use the button below to choose it"}),(0,r.jsx)(l.T,{id:"ht-paste",value:f,onChange:e=>m(e.target.value),placeholder:"RewriteEngine On RewriteCond %{HTTPS} off RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]",className:"min-h-[200px] border-0 font-mono text-xs shadow-none focus-visible:ring-0"})]}),(0,r.jsxs)("div",{className:"flex flex-wrap items-center gap-2",children:[(0,r.jsx)("input",{ref:ef,type:"file",accept:".htaccess,.txt,text/plain",className:"hidden",onChange:e=>{let t=e.target.files?.[0];t&&eS(t),e.target.value=""}}),(0,r.jsx)(i.$,{type:"button",variant:"outline",size:"sm",onClick:()=>ef.current?.click(),children:"Choose a file"}),(0,r.jsx)(i.$,{type:"button",variant:"ghost",size:"sm",onClick:()=>m(Q),children:"Load a broken example"}),(0,r.jsx)(i.$,{type:"button",variant:"ghost",size:"sm",onClick:()=>m(""),children:"Clear"}),eu&&(0,r.jsx)("span",{className:"text-xs text-muted-foreground",children:eu})]})]}):(0,r.jsxs)(r.Fragment,{children:[(0,r.jsxs)("div",{className:"space-y-2 rounded-lg border p-3",children:[(0,r.jsx)("div",{className:"text-sm font-medium",children:"Start from a common setup"}),(0,r.jsxs)("div",{className:"flex flex-wrap gap-2",children:[X.map(e=>(0,r.jsx)(i.$,{type:"button",variant:"outline",size:"sm",onClick:()=>p(t=>({...t,...P[e]})),children:F[e]},e)),(0,r.jsx)(i.$,{type:"button",variant:"ghost",size:"sm",onClick:()=>p(C),children:"Reset"})]})]}),(0,r.jsxs)("div",{className:"space-y-4 rounded-lg border p-4",children:[(0,r.jsxs)("div",{className:"grid gap-3 sm:grid-cols-2",children:[(0,r.jsxs)("div",{className:"space-y-1",children:[(0,r.jsx)(o.J,{htmlFor:"ht-domain",children:"Your domain"}),(0,r.jsx)(n.p,{id:"ht-domain",value:s.domain,onChange:e=>eE("domain",e.target.value),placeholder:"example.com",className:"font-mono","aria-invalid":!em.ok,"aria-describedby":"ht-domain-note"}),(0,r.jsx)("p",{id:"ht-domain-note",className:"text-xs",children:em.ok?em.punycoded?(0,r.jsxs)("span",{className:"text-muted-foreground",children:["An international name has to be written in punycode inside a RewriteCond, so this file uses ",(0,r.jsx)("span",{className:"font-mono",children:em.host}),". That is the form your browser actually sends."]}):(0,r.jsx)("span",{className:"text-muted-foreground",children:"Paste the address or type the bare name. The scheme, the port, the path and a leading www are all taken off."}):(0,r.jsx)("span",{className:"text-destructive",children:em.message})})]}),(0,r.jsxs)("div",{className:"space-y-1",children:[(0,r.jsx)(o.J,{htmlFor:"ht-apac
20he",children:"Apache version on your host"}),(0,r.jsx)("div",{id:"ht-apache",className:"flex gap-2 pt-1",children:["2.4","2.2"].map(e=>(0,r.jsxs)(i.$,{type:"button",variant:s.apache===e?"default":"outline",size:"sm","aria-pressed":s.apache===e,onClick:()=>eE("apache",e),children:["Apache ",e]},e))})]})]}),(0,r.jsx)("p",{className:"text-xs text-muted-foreground",children:"The version changes the access control syntax. Apache 2.4 uses Require, 2.2 uses Order and Deny from, and putting 2.2 syntax on a 2.4 server throws Invalid command and takes the folder down with it. If you are not sure, 2.4 is the safe assumption on anything set up in the last decade."}),(0,r.jsxs)("div",{className:"grid gap-3 sm:grid-cols-2",children:[(0,r.jsxs)("div",{className:"space-y-1",children:[(0,r.jsx)(o.J,{htmlFor:"ht-www",children:"Canonical hostname"}),(0,r.jsx)("div",{id:"ht-www",className:"flex flex-wrap gap-2 pt-1",children:[["strip",`Force ${e$}`],["add",`Force www.${e$}`],["none","Leave it alone"]].map(([e,t])=>eH(s.wwwPolicy===e,t,()=>eE("wwwPolicy",e)))})]}),(0,r.jsxs)("div",{className:"space-y-1",children:[(0,r.jsx)(o.J,{htmlFor:"ht-slash",children:"Trailing slash"}),(0,r.jsx)("div",{id:"ht-slash",className:"flex flex-wrap gap-2 pt-1",children:[["none","No rule"],["add","Always add"],["remove","Always remove"]].map(([e,t])=>eH(s.slash===e,t,()=>eE("slash",e)))})]})]}),(0,r.jsxs)("div",{className:"flex flex-wrap gap-2",children:[eH(s.forceHttps,"Force HTTPS",()=>eE("forceHttps",!s.forceHttps)),eH(s.behindProxy,"Behind a CDN or proxy",()=>eE("behindProxy",!s.behindProxy),"Cloudflare and most load balancers end TLS in front of Apache, which makes the naive HTTPS rule loop"),eH(s.noIndexes,"No directory listing",()=>eE("noIndexes",!s.noIndexes)),eH(s.errorPages,"Custom error pages",()=>eE("errorPages",!s.errorPages)),eH(s.protectFiles,"Protect config files",()=>eE("protectFiles",!s.protectFiles)),eH(s.caching,"Browser caching",()=>eE("caching",!s.caching)),eH(s.compression,"Compression",()=>eE("compression",!s.compression)),eH(s.securityHeaders,"Security headers",()=>eE("securityHeaders",!s.securityHeaders)),eH(s.hsts,"HSTS",()=>eE("hsts",!s.hsts),"Needs Force HTTPS on"),eH(s.spaFallback,"Single page app fallback",()=>eE("spaFallback",!s.spaFallback)),eH(s.hotlink,"Hotlink protection",()=>eE("hotlink",!s.hotlink)),eH(s.blockBots,"Block aggressive crawlers",()=>eE("blockBots",!s.blockBots)),eH(s.basicAuth,"Password protect",()=>eE("basicAuth",!s.basicAuth)),eH(s.mime,"Modern file types",()=>eE("mime",!s.mime)),eH(s.guards,"Wrap in IfModule",()=>eE("guards",!s.guards),"On, a missing module is skipped in silence. Off, it throws an error you can actually see"),eH(s.comments,"Explain each block",()=>eE("comments",!s.comments))]}),s.errorPages&&(0,r.jsxs)("div",{className:"space-y-1 sm:max-w-[260px]",children:[(0,r.jsx)(o.J,{htmlFor:"ht-404",children:"Your 404 page"}),(0,r.jsx)(n.p,{id:"ht-404",value:s.error404,onChange:e=>eE("error404",e.target.value),placeholder:"/404.html",className:"font-mono"})]}),s.basicAuth&&(0,r.jsxs)("div",{className:"grid gap-3 sm:grid-cols-2",children:[(0,r.jsxs)("div",{className:"space-y-1",children:[(0,r.jsx)(o.J,{htmlFor:"ht-authname",children:"Login box message"}),(0,r.jsx)(n.p,{id:"ht-authname",value:s.authName,onChange:e=>eE("authName",e.target.value),placeholder:"Staging"})]}),(0,r.jsxs)("div",{className:"space-y-1",children:[(0,r.jsx)(o.J,{htmlFor:"ht-authfile",children:"Password file, absolute path"}),(0,r.jsx)(n.p,{id:"ht-authfile",value:s.authFile,onChange:e=>eE("authFile",e.target.value),placeholder:"/home/user/secure/.htpasswd",className:"font-mono"})]})]}),(0,r.jsxs)("div",{className:"space-y-1",children:[(0,r.jsx)(o.J,{htmlFor:"ht-ips",children:"Block these addresses, one per line"}),(0,r.jsx)(l.T,{id:"ht-ips",value:s.blockIps,onChange:e=>eE("blockIps",e.target.value),placeholder:"203.0.113.9 198.51.100.0/24",className:"min-h-[64px] font-mono text-xs"}),eN.rejected.length>0&&(0,r.jsxs)("p",{className:"text-xs text-amber-600 dark:text-amber-500",children:[eN.rejected.length," of these are not addresses Apache can match, so they were left out of the file:"," ",(0,r.jsx)("span",{className:"font-mono",children:eN.rejected.slice(0,8).join(", ")}),eN.rejected.length>8?` and ${eN.rejected.length-8} more`:"",". One bad entry makes Apache reject the whole directive and answer the folder with a 500, so a typo here would take the site down instead of blocking anybody."]}),eN.dropped>
200&&(0,r.jsxs)("p",{className:"text-xs text-amber-600 dark:text-amber-500",children:["Only the first 40 addresses are used, so ",eN.dropped," more were left out. A block list this long belongs in the server config or a firewall, because .htaccess is re-read on every request."]})]})]})]}),(0,r.jsxs)("div",{className:"space-y-3 rounded-lg border p-4",children:[(0,r.jsxs)("div",{className:"flex flex-wrap items-center gap-2",children:[(0,r.jsx)("span",{className:"text-sm font-medium",children:"What this file will do to your site"}),"check"===e&&["2.4","2.2"].map(e=>(0,r.jsxs)(i.$,{type:"button",variant:s.apache===e?"default":"outline",size:"sm","aria-pressed":s.apache===e,title:"The access control checks depend on which Apache your host runs",onClick:()=>eE("apache",e),children:["Apache ",e]},e)),(0,r.jsx)("span",{className:"text-xs text-muted-foreground",children:""===ew.trim()?"Nothing to check yet.":0===ev.length?"No problems found in the checks this tool can run.":`${eA} that will break something, ${ej} risky, ${ev.length-eA-ej} worth knowing.`})]}),ev.length>0&&(0,r.jsx)("ul",{className:"space-y-2.5",children:ev.slice(0,30).map((e,t)=>(0,r.jsxs)("li",{className:"text-sm",children:[(0,r.jsxs)("div",{className:"flex flex-wrap items-baseline gap-2",children:[(0,r.jsx)("span",{className:`text-xs font-semibold uppercase tracking-wide ${G[e.level]}`,children:J[e.level]}),e.line>0&&(0,r.jsxs)("span",{className:"font-mono text-xs text-muted-foreground",children:["line ",e.line]}),(0,r.jsx)("span",{className:"font-medium",children:e.title})]}),(0,r.jsx)("p",{className:"text-muted-foreground",children:e.detail})]},`${e.line}-${t}`))}),ev.length>30&&(0,r.jsxs)("p",{className:"text-xs text-muted-foreground",children:["Showing the first 30 of ",ev.length,". Fix these and the rest usually go with them."]})]}),(0,r.jsxs)("div",{className:"space-y-3 rounded-lg border p-4",children:[(0,r.jsxs)("div",{children:[(0,r.jsx)("div",{className:"text-sm font-medium",children:"Walk a request through it"}),(0,r.jsx)("p",{className:"text-xs text-muted-foreground",children:"mod_alias resolves first, then mod_rewrite runs from the top and starts again after every rewrite, the way it really does in a .htaccess file."})]}),(0,r.jsxs)("div",{className:"flex flex-wrap items-center gap-2",children:[(0,r.jsx)(n.p,{"aria-label":"Path to walk through the rules",value:S,onChange:e=>L(e.target.value),placeholder:"/about",className:"max-w-[260px] font-mono"}),ek.map(e=>(0,r.jsx)(i.$,{type:"button",variant:"ghost",size:"sm",onClick:()=>L(e),children:e},e))]}),(0,r.jsxs)("div",{className:"flex flex-wrap gap-2",children:[(0,r.jsxs)(i.$,{type:"button",variant:"outline",size:"sm",title:"Swap between the www and the bare hostname to see what a canonical redirect costs",onClick:()=>Z("www"===K?"canonical":"www"),children:["Host: ",eT.host]}),eH(ee,"Visitor is on https",()=>et(!ee)),eH(es,"TLS ends at a proxy",()=>er(!es),"Cloudflare and similar. This is the setup where %{HTTPS} says off even on an https request"),eH(ea,"Path is a real file",()=>ei(!ea)),eH(en,"Path is a real folder",()=>eo(!en))]}),(0,r.jsxs)("div",{className:"space-y-2 rounded-md bg-muted/50 p-3 text-sm",children:[(0,r.jsxs)("div",{className:"font-mono text-xs text-muted-foreground",children:["GET ",ee?"https":"http","
20://",eT.host,S.trim()||"/"]}),(0,r.jsx)("div",{className:"font-medium",children:eR.headline}),eR.steps.length>0&&(0,r.jsx)("ol",{className:"space-y-1.5",children:eR.steps.map((e,t)=>(0,r.jsxs)("li",{className:"border-l-2 border-muted-foreground/30 pl-3",children:[(0,r.jsxs)("div",{className:"font-mono text-xs",children:[e.line>0?`line ${e.line}`:e.source," ","\xb7"," ",e.rule]}),(0,r.jsx)("div",{className:"text-xs text-muted-foreground",children:e.action}),(0,r.jsxs)("div",{className:"font-mono text-xs",children:[e.from," ","â"," ",e.to]})]},t))}),eR.notes.map((e,t)=>(0,r.jsx)("p",{className:"text-xs text-muted-foreground",children:e},t))]})]}),(0,r.jsxs)("div",{className:"space-y-2 rounded-lg border p-4",children:[(0,r.jsxs)("div",{className:"flex flex-wrap items-center gap-2",children:[(0,r.jsx)(i.$,{type:"button",variant:"outline",size:"sm",onClick:()=>ed(!el),children:el?"Hide the line by line":"Explain it line by line"}),"check"===e&&f.trim()&&(0,r.jsx)(i.$,{type:"button",variant:"outline",size:"sm",onClick:()=>ec(!eh),children:eh?"Hide the comparison":"Compare with what the form would build"}),(0,r.jsxs)("span",{className:"text-xs text-muted-foreground",children:[ey.directives.length," directives, ",ey.lineCount," lines."]})]}),el&&eb.length>0&&(0,r.jsxs)("div",{className:"space-y-2",children:[eb.slice(0,120).map((e,t)=>(0,r.jsxs)("div",{className:"border-l-2 border-muted pl-3 text-sm",children:[(0,r.jsxs)("div",{className:"font-mono text-xs",children:[(0,r.jsx)("span",{className:"text-muted-foreground",children:e.line})," ",e.text]}),(0,r.jsx)("div",{className:"text-muted-foreground",children:e.meaning}),(0,r.jsxs)("div",{className:"text-xs text-muted-foreground",children:[e.module?`Needs ${e.module}. `:"","None"!==e.override?`Your host has to allow AllowOverride ${e.override}.`:"No AllowOverride class of its own."]})]},`${e.line}-${t}`)),eb.length>120&&(0,r.jsx)("p",{className:"text-xs text-muted-foreground",children:"Showing the first 120 directives. A .htaccess this long is slowing every request down and belongs in the server config."})]}),e_&&eI>0&&(0,r.jsxs)("p",{className:"rounded-md border border-amber-500/40 bg-amber-500/10 px-3 py-2 text-sm text-amber-700 dark:text-amber-400",children:["Only the first ",400," lines of each side were compared, so ",eI," further"," ",1===eI?"line was":"lines were"," not checked at all. Matching above does not mean matching below."]}),e_&&(0,r.jsx)("div",{className:"max-h-[360px] overflow-auto rounded-md bg-muted/50 p-3 font-mono text-xs",children:e_.map((e,t)=>(0,r.jsxs)("div",{className:"-"===e.tag?"text-red-600 dark:text-red-400":"+"===e.tag?"text-emerald-600 dark:text-emerald-400":"text-muted-foreground",children:[e.tag," ",e.text||"\xa0"]},t))}),eh&&e_&&(0,r.jsx)("p",{className:"text-xs text-muted-foreground",children:"Red lines are yours, green lines are what the form above would write. Neither side is applied anywhere until you download it and upload it yourself."})]}),(0,r.jsxs)("div",{className:"space-y-2",children:["build"===e&&!em.ok&&(0,r.jsxs)("div",{className:"space-y-1 rounded-lg border border-destructive/50 p-3",children:[(0,r.jsx)("div",{className:"text-sm font-medium text-destructive",children:"No file yet"}),(0,r.jsx)("p",{className:"text-sm text-muted-foreground",children:em.message}),(0,r.jsx)("p",{className:"text-sm text-muted-foreground",children:"Nothing is written until the domain reads cleanly, because every rule below the canonical redirect is built around that one name."})]}),(0,r.jsx)(o.J,{htmlFor:"ht-out",children:".htaccess"}),(0,r.jsx)(l.T,{id:"ht-out",readOnly:!0,"aria-label":"The generated htaccess file",value:ew,className:"min-h-[320px] font-mono text-xs"}),(0,r.jsxs)("div",{className:"flex flex-wrap gap-2",children:[(0,r.jsx)(D.i,{value:ew,label:"Copy",copiedLabel:"Copied!",variant:"outline",size:"sm"}),(0,r.jsx)(i.$,{type:"button",variant:"outline",size:"sm",onClick:()=>{let e=new Blob([ew.replace(/\r\n/g,"\n").replace(/^\uFEFF/,"")],{type:"text/plain;charset=utf-8"});(0,z.WN)(e,".htaccess")},children:"Download .htaccess"})]}),(0,r.jsx)("p",{className:"text-xs text-muted-foreground",children:"The download is a real file, UTF-8 with no byte order mark and plain LF line endings, because a file saved the Windows way can take a directory down on its own. Windows Explorer will not let you name a file with a leading dot, so if you rebuild it by hand, save it as htaccess.txt and rename it after upload. Most FTP clients and cPanel hide dotfiles until you turn on show hidden files."})]}),eF&&(0,r.jsxs)("p",{className:"text-sm text-muted-foreground",children:["Your settings are over ",3500," characters, so the share link below cannot carry them and opens on the starter setup instead. Copy the file above and send that."]}),(0,r.jsx)(d.ToolActions,{share:eP,title:"htaccess Generator",embedCarriesPersonal:!1})]})}},73567:(e,t,s)=>{s.d(t,{o:()=>i});let r=/^[a-z][a-z0-9+.-]*:\/\//i,a=/^[a-z0-9_](?:[a-z0-9_-]*[a-z0-9_])?$/i;function i(e,t={}){let s,n=e.trim();if(""===n)return{ok:!1,reason:"empty",message:"Enter a domain, like example.com."};let o=r.test(n)?n:`http://${n.replace(/^\/+/,"")}`;try{s=new URL(o).hostname}catch{return{ok:!1,reason:"unreadable",message:`"${n}" is not a name a browser can resolve, so there is no ASCII form to put in the config. Check it for a space, a stray character, or an xn-- part that is not val
20id punycode.`}}let l=(t.keepWww?s:s.replace(/^www\./,"")).replace(/\.$/,"");if(""===l)return{ok:!1,reason:"unusable",message:`"${n}" has no host in it once the scheme and the path are taken off.`};let d=l.split("."),h=d.find(e=>!a.test(e)||e.length>63);if(void 0!==h){let e=""===h?"an empty part between two dots":`"${h}"`;return{ok:!1,reason:"unusable",message:`${e} is not a usable part of a domain name, so this would generate a config pointing somewhere real servers cannot match. A part holds letters, digits and hyphens, and cannot start or end with a hyphen.`}}return{ok:!0,host:l,punycoded:d.some(e=>/^xn--/i.test(e))&&!/xn--/i.test(n),typed:n}}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.