<!doctype html>
<html lang="en">
	<head>
		<meta charset="utf-8">
		<title>CodeMirror nginx-renewed mode</title>
		<link
			rel="stylesheet"
			href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.62.2/codemirror.min.css"
			integrity="sha512-xIf9AdJauwKIVtrVRZ0i4nHP61Ogx9fSRAkCLecmE2dL/U8ioWpDvFCAy4dcfecN72HHB9+7FfQj3aiO68aaaw=="
			crossorigin="anonymous"
			referrerpolicy="no-referrer" />
		<link
			rel="stylesheet"
			href="https://xavierog.github.io/codemirror-mode-pcre/src/pcre.css">
		<style>
			.CodeMirror {
				border-top: 1px solid black;
				border-bottom: 1px solid black;
				height: 100%;
			}
		</style>
	</head>
	<body>
		<h1>CodeMirror nginx-renewed mode</h1>
		<p>
			This is a <a href="https://codemirror.net/">CodeMirror</a> mode that brings
			syntax highlighting for nginx configuration files.
		</p>
		<p>
			<strong>Features:</strong>
			<ul>
				<li>recognize more than 870 nginx directives, including those from OpenResty and nginx-plus;</li>
				<li>unrecognized directives remain accepted (they are shown in italic)</li>
				<li>highlight directives that are recognized but should not occur in the current scope;</li>
				<li>highlight regular expressions -- requires <em>codemirror-mode-pcre</em><br>
				supported in: locations, maps, if conditions, rewrite statements and some proxy_* directives</li>
				<li>highlight variables in strings -- requires <em>codemirror-mode-variables</em>;</li>
				<li>highlight IP addresses -- requires <em>codemirror-mode-ipaddr</em>;<br>
				supported in: geo, set_real_ip_from</li>
				<li>
					highlight Lua code -- requires <a href="https://codemirror.net/mode/lua/index.html">CodeMirror's Lua mode</a>; without it, strings and comments are still highlighted;<br>
					supported in: blocks (<code>*_by_lua_block</code>)<br>
					not supported in: strings (<code>*_by_lua</code>)<br>
				</li>
			</ul>
		</p>
		<p>
			<strong>MIME types defined:</strong>
			<ul>
				<li>text/x-nginx-conf</li>
			</ul>
		</p>
		<p>
			<strong>Options:</strong>
			<ul>
				<li>initial_scope: string; default value: "main"</li>
				<li>check_directive_scope: boolean; default value: true</li>
			</ul>
		</p>
		<p>
			<strong>MIME types used to invoke nested modes:</strong>
			<ul>
				<li>text/x-regex</li>
				<li>text/x-variables</li>
				<li>text/x-ip-address</li>
				<li>text/x-lua</li>
			</ul>
		</p>
		<h2 id="h2direct">Direct use</h2>
		<p>
			By default, codemirror-mode-nginx-renewed:
			<ol>
				<li>assumes the textarea reflects nginx's main scope (i.e. the same scope as nginx.conf);</li>
				<li>checks whether each directive is allowed in the current scope.</li>
			</ol>
			The textareas below showcase codemirror-mode-nginx-renewed's capabilities.
		</p>
		<p>
			Useful commands:
			<ul>
				<li>Ctrl+d: toggle comments</li>
				<li>Ctrl+]: indent</li>
				<li>Ctrl+[: unindent</li>
				<li>Tab: auto-indent the current line or selection</li>
				<li>Shift+Tab: if something is selected, indent it by one indent unit; otherwise, insert a tab character</li>
			</ul>
		</p>
		<textarea data-mime="text/x-nginx-conf">
user       www www;
worker_processes  5;
error_log  logs/error.log;
pid        logs/nginx.pid;
worker_rlimit_nofile 8192;
events {
	worker_connections  4096;  ## Default: 1024
}
http {
	types {
		text/html                             html htm shtml;
		text/xml                              xml rss;
		image/gif                             gif;
	}
	map "${http_user_agent}" $mobile {
		default "${http_user_agent}";
		hostnames;
		volatile;
		"Opera Mini" 1;
		"~(?:Opera Mini)" 1;
		"~*(?:Opera Mini)" 1;
		include "/path/to/map/file.conf";
	}
	geo $country {
		default        ZZ;
		include        conf/geo.conf;
		delete         127.0.0.0/16;
		proxy          192.168.100.0/24;
		proxy          2001:0db8::/32;
		127.0.0.0/24   US;
		127.0.0.1/32   RU;
		10.1.0.0/16    RU;
		192.168.1.0/24 UK;
		1.2.3.4        TEST;
		2001:0db8::    TEST;
		2001:0db8::/32 TEST;
	}
	geo $dummy_range {
		ranges;
		1.2.3.4-1.2.3.6    "IPv4 range";
		# nginx's IP ranges are IPv4-only; IPv6 ranges yield "[emerg] invalid network"
		2001:0db8::-2001:0db8:ffff:ffff:ffff:ffff:ffff:ffff "IPv6 range";
	}
	set_real_ip_from 10.20.30.40;
	set_real_ip_from 10.20.30.0/24;
	set_real_ip_from 2001:0db8::1234;
	set_real_ip_from 2001:0db8::/64;
	set_real_ip_from trustme.company.com;
	real_ip_header X-Forwarded-For;
	real_ip_recursive on;
	upstream bunch_of_servers {
		server server00001:80 resolve;
		server server00002:80 resolve;
		server server00003:80 resolve;
		server server00004:80 resolve;
		server server00005:80 resolve;
	}
	server {
		server_name "nginx.syntax-highlighting.demo.example";
		server_name "~^nginx[^.]+\.syntax-highlighting\.demo\.example$";
		# Examples from http://nginx.org/en/docs/http/server_names.html:
		server_name example.org  www.example.org;
		server_name *.example.org;
		server_name mail.*;
		server_name ~^(?<user>.+)\.example\.net$;
		server_name ~^www\d+\.example\.net$;
		server_name "~^(?<name>\w+\d{1,3})\.example\.net$";
		server_name ~^(www\.)?(?<domain>.+)$;
		server_name ~^(www\.)?(.+)$;
		server_name example.org  www.example.org  "";
		# Locations, using double-quotes, single-quotes and unquoted strings:
		location  =  "/exact-match" {}         location  =  '/exact-match' {}         location  =  /exact-match {}
		location     "/prefix-match-wi-re" {}  location     '/prefix-match-wi-re' {}  location     /prefix-match-wi-re {}
		location ^~  "/prefix-match-wo-re" {}  location ^~  '/prefix-match-wo-re' {}  location ^~  /prefix-match-wo-re {}
		location  ~  "(cs|regex|match)" {}     location  ~  '(cs|regex|match)' {}     location  ~  (cs|regex|match) {}
		location  ~* "(ci|regex|match)" {}     location  ~* '(ci|regex|match)' {}     location  ~* (ci|regex|match) {}
		# Conditions, using double-quotes, single-quotes and unquoted strings:
		if ($variable  =  "${text}/x-variables") {}  if ($variable  =  '${text}/x-variables') {}  if ($variable  =  ${text}/x-variables) {}
		if ($variable !=  "${text}/x-variables") {}  if ($variable !=  '${text}/x-variables') {}  if ($variable !=  ${text}/x-variables) {}
		if ($variable  ~  "^text/(?x:-[regex])") {}  if ($variable  ~  '^text/(?x:-[regex])') {}  if ($variable  ~  ^text/(?x:-[regex])) {}
		if ($variable  ~* "^text/(?x:-[regex])") {}  if ($variable  ~* '^text/(?x:-[regex])') {}  if ($variable  ~* ^text/(?x:-[regex])) {}
		if ($variable !~  "^text/(?x:-[regex])") {}  if ($variable !~  '^text/(?x:-[regex])') {}  if ($variable !~  ^text/(?x:-[regex])) {}
		if ($variable !~* "^text/(?x:-[regex])") {}  if ($variable !~* '^text/(?x:-[regex])') {}  if ($variable !~* ^text/(?x:-[regex])) {}
		if ( -f "${text}/x-variables") {}            if ( -f '${text}/x-variables') {}            if ( -f ${text}/x-variables) {}
		if (!-f "${text}/x-variables") {}            if (!-f '${text}/x-variables') {}            if (!-f ${text}/x-variables) {}
		if ( -d "${text}/x-variables") {}            if ( -d '${text}/x-variables') {}            if ( -d ${text}/x-variables) {}
		if (!-d "${text}/x-variables") {}            if (!-d '${text}/x-variables') {}            if (!-d ${text}/x-variables) {}
		if ( -e "${text}/x-variables") {}            if ( -e '${text}/x-variables') {}            if ( -e ${text}/x-variables) {}
		if (!-e "${text}/x-variables") {}            if (!-e '${text}/x-variables') {}            if (!-e ${text}/x-variables) {}
		if ( -x "${text}/x-variables") {}            if ( -x '${text}/x-variables') {}            if ( -x ${text}/x-variables) {}
		if (!-x "${text}/x-variables") {}            if (!-x '${text}/x-variables') {}            if (!-x ${text}/x-variables) {}
		# without forgetting the simplest case:
		if ($variable) {}
		# Unquoted strings in conditions:
		if (                                    # condition comment
			$variable !~* ^text/(?x:-[regex])   # condition comment
		)                                       # condition comment
		# post-condition, pre-scope comment
		{   # scope comment
			# scope comment
		}   # post-scope comment
		# Rewrites:
		rewrite "^/foo(?<tail>.*)" "/bar${tail}";
		rewrite '^/foo(?<tail>.*)' '/bar${tail}' break;
		rewrite  ^/foo(?<tail>.*)   /bar${tail}  last;
		# Other mod_rewrite directives:
		rewrite_log on;
		uninitialized_variable_warn off;
		break;
		location / {
			limit_except GET PROPFIND LOCK {
				deny all;
			}
			proxy_pass http://bunch_of_servers;
			proxy_redirect off;
			proxy_redirect default;
			proxy_redirect http://$proxy_host:8000/ /;
			proxy_redirect ~^(http://[^:]+):\d+(/.+)$ $1$2;
			proxy_redirect ~*/user/([^/]+)/(.+)$      http://$1.example.com/$2;
			location /foo {
				proxy_cookie_domain off;
				proxy_cookie_domain localhost example.org;
				proxy_cookie_domain www.$host $host;
				proxy_cookie_domain "~\.(?<domain_and_tld>[a-z]+\.[a-z]+)$" "${domain_and_tld}";
				location /foo/bar {
					proxy_cookie_path off;
					proxy_cookie_path /two/ /;
					proxy_cookie_path $uri "/some${uri}";
					proxy_cookie_path "~*^/user/(?<username>[^/]+)" "/u/${username}";
					location /foo/bar/baz {
						proxy_cookie_flags off;
						proxy_cookie_flags one httponly;
						proxy_cookie_flags ${my_cookie} nohttponly;
						proxy_cookie_flags ${app_name}_session secure "samesite=${my_map}" httponly;
						proxy_cookie_flags ~^(?i:alpha_cookie|cookie_beta)$ nosecure samesite=strict;
					}
				}
			}
		}
		# Enforce HTTPS for domain.tld subdomains:
		set $scheme_and_domain "${map_real_scheme}://${host}";
		if ($scheme_and_domain ~ "^http://.*\.domain\.tld\.?$") {
			return 308 "https://${host}${request_uri}";
		}
		location /redirect { return "https://somewhere.else/"; }
		location /close { return 444; }
		location /prefix {
			root /path/to/document/root/;
		}
		location ~ "(?x) # Enable PCRE extended mode
			^
			/user
			/(?<action>login|logout|profile) # Also include 'profile' because...
			/(?<tail>.*)
		"
		{
			if ($tail ~ "^(some|[^/]+/really|compl(?:ex|icated)|stuff|t?here)$") {
				return 307 "${scheme}://${host}/somewhere/else/${tail}${is_args}${args}";
			}
			# ...
		}
	}
}</textarea>
<p>
	This textarea is a copy of <a href="https://github.com/openresty/lua-nginx-module#synopsis">lua-nginx-module's synopsis</a>:
</p>
<textarea data-mime="text/x-nginx-conf" data-scope="main/http">
# set search paths for pure Lua external libraries (';;' is the default path):
lua_package_path '/foo/bar/?.lua;/blah/?.lua;;';
# set search paths for Lua external libraries written in C (can also use ';;'):
lua_package_cpath '/bar/baz/?.so;/blah/blah/?.so;;';
server {
	location /lua_content {
		# MIME type determined by default_type:
		default_type 'text/plain';
		content_by_lua_block {
			ngx.say('Hello,world!')
		}
	}
	location /nginx_var {
		# MIME type determined by default_type:
		default_type 'text/plain';
		# try access /nginx_var?a=hello,world
		content_by_lua_block {
			ngx.say(ngx.var.arg_a)
		}
	}
	location = /request_body {
		client_max_body_size 50k;
		client_body_buffer_size 50k;
		content_by_lua_block {
			ngx.req.read_body()  -- explicitly read the req body
			local data = ngx.req.get_body_data()
			if data then
				ngx.say("body data:")
				ngx.print(data)
				return
			end
			-- body may get buffered in a temp file:
			local file = ngx.req.get_body_file()
			if file then
				ngx.say("body is in file ", file)
			else
				ngx.say("no body found")
			end
		}
	}
	# transparent non-blocking I/O in Lua via subrequests
	# (well, a better way is to use cosockets)
	location = /lua {
		# MIME type determined by default_type:
		default_type 'text/plain';
		content_by_lua_block {
			local res = ngx.location.capture("/some_other_location")
			if res then
				ngx.say("status: ", res.status)
				ngx.say("body:")
				ngx.print(res.body)
			end
		}
	}
	location = /foo {
		rewrite_by_lua_block {
			res = ngx.location.capture("/memc",
				{ args = { cmd = "incr", key = ngx.var.uri } }
			)
		}
		proxy_pass http://blah.blah.com;
	}
	location = /mixed {
		rewrite_by_lua_file /path/to/rewrite.lua;
		access_by_lua_file /path/to/access.lua;
		content_by_lua_file /path/to/content.lua;
	}
	# use nginx var in code path
	# CAUTION: contents in nginx var must be carefully filtered,
	# otherwise there'll be great security risk!
	location ~ ^/app/([-_a-zA-Z0-9/]+) {
		set $path $1;
		content_by_lua_file /path/to/lua/app/root/$path.lua;
	}
	location / {
		client_max_body_size 100k;
		client_body_buffer_size 100k;
		access_by_lua_block {
			-- check the client IP address is in our black list
			if ngx.var.remote_addr == "132.5.72.3" then
				ngx.exit(ngx.HTTP_FORBIDDEN)
			end
			-- check if the URI contains bad words
			if ngx.var.uri and
				string.match(ngx.var.request_body, "evil")
				-- Alteration to test double square bracket strings:
				page = [==[
<HTML>
<HEAD>
<TITLE>An HTML Page</TITLE>
</HEAD>
<BODY>
<A HREF="http://www.lua.org">Lua</A>
[[a text between double brackets]]
</BODY>
</HTML>]==]
			then
				return ngx.redirect("/terms_of_use.html")
			end
			-- tests passed
		}
		# proxy_pass/fastcgi_pass/etc settings
	}
}</textarea>
		<h2 id="h2customgeneric">Custom use without a specific scope</h2>
		<p>
			Sometimes, it is hard, if not impossible, to predict the scope of an nginx snippet.
			Typical cases:
		</p>
		<ul>
			<li>include files, which may contain directives suited for various scopes;</li>
			<li>the output of "nginx -T", which may combine many files and thus many scopes.</li>
		</ul>
		<p>
			To deal with these situations, state "check_directive_scope: false" when configuring codemirror-mode-nginx-renewed.
		</p>
		<textarea data-mime="text/x-nginx-conf" data-scope="unknown">
# configuration file /etc/nginx/nginx.conf:
worker_rlimit_nofile 8192; # main
# configuration file /etc/nginx/event.conf:
worker_connections  4096; # events
# configuration file /etc/nginx/include/enforce_org_tld.conf:
uninitialized_variable_warn off; # http, server or location?
if ($host ~* "^(?<domain_head>.+)\.net$") { # if in server or if in location?
	return 308 "${scheme}://${domain_head}.org${request_uri}";
}</textarea>
		<h2 id="h2customspecific">Custom use with specific scopes</h2>
		<p>
			By playing with the "initial_scope" setting, it is possible to change the nginx scope of the textarea.
			initial_scope defaults to "main" and can take various values reflecting the desired scope.
			Examples:
			<ul>
				<li>main/events</li>
				<li>main/http/server/location/limit_except</li>
				<li>main/http/server/location/if</li>
				<li>main/http/server/location/types</li>
				<li>main/http/server/if</li>
				<li>main/mail/server</li>
				<li>main/stream/upstream</li>
			</ul>
			Other examples and demonstrations follow.
		</p>
		<h3 id="h3mainhttpmap">main/http/map</h3>
		<textarea data-mime="text/x-nginx-conf" data-scope="main/http/map">
# Everything in this textarea is considered inside an HTTP map:
"~*^[a-m]" "alphabet-1st-half";
"~*^[n-z]" "alphabet-2nd-half";
"~*^[0-9]" "start-with-digit";
default "other";</textarea>
		<h3 id="h3mainhttpgeo">main/http/geo</h3>
		<textarea data-mime="text/x-nginx-conf" data-scope="main/http/geo">
# Everything in this textarea is considered inside an HTTP geo:
  0.0.0.0/1 ipv4-1st-half;
128.0.0.0/1 ipv4-2nd-half;
   0000::/1 ipv6-1st-half;
   8000::/1 ipv6-2nd-half;
    default no-clue;</textarea>
		<h3 id="h3mainhttptypes">main/http/types</h3>
		<textarea data-mime="text/x-nginx-conf" data-scope="main/http/types">
# Everything in this textarea is considered inside a types {} directive:
application/octet-stream bin exe dll;
application/octet-stream deb;
application/octet-stream dmg;</textarea>
		<h3 id="h3mainhttpserver">main/http/server</h3>
		<textarea data-mime="text/x-nginx-conf" data-scope="main/http/server">
# Everything in this textarea is considered inside an HTTP server:
listen 80;
listen 443 ssl;
ssl_certificate     "/path/to/myapp.mycompany.com.cert.pem";
ssl_certificate_key "/path/to/myapp.mycompany.com.key.pem";
server_name myapp.mycompany.com;
if ($real_scheme = "http") {
	return 308 "https://${server_name}${request_uri}";
}
location / {
	return 200 "Under construction";
}
# proxy_pass cannot be used directly in server, which is why it is shown in bold red:
proxy_pass https://target_upstream;</textarea>
		<h3 id="h3mainhttpserverlocation">main/http/server/location</h3>
		<textarea data-mime="text/x-nginx-conf" data-scope="main/http/server/location">
# Everything in this textarea is considered inside an HTTP location:
rewrite "^/prefix(?<tail>.*)" "${tail}" break;
proxy_pass https://target_upstream;
proxy_set_header "X-Forwarded-Prefix" "/prefix";
# listen does not make sense in location, which is why it is shown in bold red:
listen 42;</textarea>
		<h3 id="mainhttpupstream">main/http/upstream</h3>
		<textarea data-mime="text/x-nginx-conf" data-scope="main/http/upstream">
# Everything in this textarea is considered inside an HTTP upstream:
zone target_upstream_zone 64k;
server mybackend1.mycompany.com:443 resolve;
server mybackend2.mycompany.com:443 resolve;
server mybackend3.mycompany.com:443 resolve backup;
sticky cookie myappsession path=/;
# proxy_ssl_name cannot be set  directly in upstream, which is why it is shown in bold red:
proxy_ssl_name mybackendX.mycompany.com;</textarea>
		<script
			src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.62.2/codemirror.min.js"
			integrity="sha512-6Q5cHfb86ZJ3qWx47Pw7P5CN1/pXcBMmz3G0bXLIQ67wOtRF7brCaK5QQLPz2CWLBqjWRNH+/bV5MwwWxFGxww=="
			crossorigin="anonymous"
			referrerpolicy="no-referrer">
		</script>
		<script
			src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.62.2/addon/comment/comment.min.js"
			integrity="sha512-U0udq4Tvwzb+OiL6f7aak/n07mlIutTl+pEPtKoK0oOTF4Q9SkLOQYvKpIaCIM8CKTufd7szgeN9VDcgXDVvWg=="
			crossorigin="anonymous"
			referrerpolicy="no-referrer">
		</script>
		<script
			src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.62.2/mode/lua/lua.min.js"
			integrity="sha512-MXR/wu8WxkFikybMYGuaR9O0SgRrcSReZUNuherC0XZ7SJN/db3W+qQCh+4rAiBBeNk/yd/NdnQd/s2nO4q4fA=="
			crossorigin="anonymous"
			referrerpolicy="no-referrer">
		</script>
		<script src="https://xavierog.github.io/codemirror-mode-pcre/src/pcre.js"></script>
		<script src="https://xavierog.github.io/codemirror-mode-ipaddr/src/ipaddr.js"></script>
		<script src="https://xavierog.github.io/codemirror-mode-variables/src/variables.js"></script>
		<script src="src/nginx-renewed.js"></script>
		<script>
			function demo(textarea) {
				var mime = textarea.attributes['data-mime'];
				if (!mime) return;
				var scope = textarea.attributes['data-scope'];
				scope = scope ? scope.value : false;
				var mode = { name: mime.value };
				if (scope === 'unknown') mode.check_directive_scope = false;
				else if (scope) mode.initial_scope = scope;
				var conf = {
					lineNumbers: true,
					indentWithTabs: true,
					indentUnit: 4,
					showCursorWhenSelecting: true,
					extraKeys: {
						'Ctrl-D': 'toggleComment',
					},
					mode: mode,
				};
				CodeMirror.fromTextArea(textarea, conf);
			}
			var i, textareas = document.getElementsByTagName('textarea');
			for (i = 0; i < textareas.length; ++ i) demo(textareas[i]);
		</script>
	</body>
</html>