1use combine::{eof, many, many1, Parser};
2use combine::{choice, position};
3use combine::combinator::{opaque, no_partial, FnOpaque};
4use combine::error::StreamError;
5use combine::easy::Error;
6
7use ast::{self, Main, Directive, Item};
8use error::ParseError;
9use helpers::{semi, ident, text, string};
10use position::Pos;
11use tokenizer::{TokenStream, Token};
12use value::Value;
13
14use access;
15use core;
16use gzip;
17use headers;
18use proxy;
19use rewrite;
20use log;
21use real_ip;
22
23
24pub enum Code {
25 Redirect(u32),
26 Normal(u32),
27}
28
29pub fn bool<'a>() -> impl Parser<Output=bool, Input=TokenStream<'a>> {
30 choice((
31 ident("on").map(|_| true),
32 ident("off").map(|_| false),
33 ))
34}
35
36pub fn value<'a>() -> impl Parser<Output=Value, Input=TokenStream<'a>> {
37 (position(), string())
38 .and_then(|(p, v)| Value::parse(p, v))
39}
40
41pub fn worker_processes<'a>()
42 -> impl Parser<Output=Item, Input=TokenStream<'a>>
43{
44 use ast::WorkerProcesses;
45 ident("worker_processes")
46 .with(choice((
47 ident("auto").map(|_| WorkerProcesses::Auto),
48 string().and_then(|s| s.value.parse().map(WorkerProcesses::Exact)),
49 )))
50 .skip(semi())
51 .map(Item::WorkerProcesses)
52}
53
54pub fn server_name<'a>() -> impl Parser<Output=Item, Input=TokenStream<'a>> {
55 use ast::ServerName::*;
56 ident("server_name")
57 .with(many1(
58 string().map(|t| {
59 if t.value.starts_with("~") {
60 Regex(t.value[1..].to_string())
61 } else if t.value.starts_with("*.") {
62 StarSuffix(t.value[2..].to_string())
63 } else if t.value.ends_with(".*") {
64 StarPrefix(t.value[..t.value.len()-2].to_string())
65 } else if t.value.starts_with(".") {
66 Suffix(t.value[1..].to_string())
67 } else {
68 Exact(t.value.to_string())
69 }
70 })
71 ))
72 .skip(semi())
73 .map(Item::ServerName)
74}
75
76
77pub fn map<'a>() -> impl Parser<Output=Item, Input=TokenStream<'a>> {
78 use tokenizer::Kind::{BlockStart, BlockEnd};
79 use helpers::kind;
80 enum Tok {
81 Hostnames,
82 Volatile,
83 Pattern(String, Value),
84 Default(Value),
85 Include(String),
86 }
87 ident("map")
88 .with(value())
89 .and(string().and_then(|t| {
90 let ch1 = t.value.chars().nth(0).unwrap_or(' ');
91 let ch2 = t.value.chars().nth(1).unwrap_or(' ');
92 if ch1 == '$' && matches!(ch2, 'a'...'z' | 'A'...'Z' | '_') &&
93 t.value[2..].chars()
94 .all(|x| matches!(x, 'a'...'z' | 'A'...'Z' | '0'...'9' | '_'))
95 {
96 Ok(t.value[1..].to_string())
97 } else {
98 Err(Error::unexpected_message("invalid variable"))
99 }
100 }))
101 .skip(kind(BlockStart))
102 .and(many(choice((
103 ident("hostnames").map(|_| Tok::Hostnames),
104 ident("volatile").map(|_| Tok::Volatile),
105 ident("default").with(value()).map(|v| Tok::Default(v)),
106 ident("include").with(raw()).map(|v| Tok::Include(v)),
107 raw().and(value()).map(|(s, v)| Tok::Pattern(s, v)),
108 )).skip(semi())))
109 .skip(kind(BlockEnd))
110 .map(|((expression, variable), vec): ((_, _), Vec<Tok>)| {
111 let mut res = ::ast::Map {
112 variable, expression,
113 default: None,
114 hostnames: false,
115 volatile: false,
116 includes: Vec::new(),
117 patterns: Vec::new(),
118 };
119 for val in vec {
120 match val {
121 Tok::Hostnames => res.hostnames = true,
122 Tok::Volatile => res.volatile = true,
123 Tok::Default(v) => res.default = Some(v),
124 Tok::Include(path) => res.includes.push(path),
125 Tok::Pattern(x, targ) => {
126 use ast::MapPattern::*;
127 let mut s = &x[..];
128 if s.starts_with('~') {
129 res.patterns.push((Regex(s[1..].to_string()), targ));
130 continue;
131 } else if s.starts_with('\\') {
132 s = &s[1..];
133 }
134 let pat = if res.hostnames {
135 if s.starts_with("*.") {
136 StarSuffix(s[2..].to_string())
137 } else if s.ends_with(".*") {
138 StarPrefix(s[..s.len()-2].to_string())
139 } else if s.starts_with(".") {
140 Suffix(s[1..].to_string())
141 } else {
142 Exact(s.to_string())
143 }
144 } else {
145 Exact(s.to_string())
146 };
147 res.patterns.push((pat, targ));
148 }
149 }
150 }
151 Item::Map(res)
152 })
153}
154
155pub fn block<'a>()
156 -> FnOpaque<TokenStream<'a>, ((Pos, Pos), Vec<Directive>)>
157{
158 use tokenizer::Kind::{BlockStart, BlockEnd};
159 use helpers::kind;
160 opaque(|f| {
161 f(&mut no_partial((
162 position(),
163 kind(BlockStart)
164 .with(many(directive()))
165 .skip(kind(BlockEnd)),
166 position(),
167 ))
168 .map(|(s, dirs, e)| ((s, e), dirs)))
169 })
170}
171
172pub fn raw<'a>() -> impl Parser<Output=String, Input=TokenStream<'a>> {
174 string().and_then(|t| Ok::<_, Error<_, _>>(t.value.to_string()))
177}
178
179pub fn location<'a>() -> impl Parser<Output=Item, Input=TokenStream<'a>> {
180 use ast::LocationPattern::*;
181 ident("location").with(choice((
182 text("=").with(raw().map(Exact)),
183 text("^~").with(raw().map(FinalPrefix)),
184 text("~").with(raw().map(Regex)),
185 text("~*").with(raw().map(RegexInsensitive)),
186 raw()
187 .map(|v| if v.starts_with('*') {
188 Named(v)
189 } else {
190 Prefix(v)
191 }),
192 ))).and(block())
193 .map(|(pattern, (position, directives))| {
194 Item::Location(ast::Location { pattern, position, directives })
195 })
196}
197
198impl Code {
199 pub fn parse<'x, 'y>(code_str: &'x str)
200 -> Result<Code, Error<Token<'y>, Token<'y>>>
201 {
202 let code = code_str.parse::<u32>()?;
203 match code {
204 301 | 302 | 303 | 307 | 308 => Ok(Code::Redirect(code)),
205 200...599 => Ok(Code::Normal(code)),
206 _ => return Err(Error::unexpected_message(
207 format!("invalid response code {}", code))),
208 }
209 }
210 pub fn as_code(&self) -> u32 {
211 match *self {
212 Code::Redirect(code) => code,
213 Code::Normal(code) => code,
214 }
215 }
216}
217
218
219pub fn try_files<'a>() -> impl Parser<Output=Item, Input=TokenStream<'a>> {
220 use ast::TryFilesLastOption::*;
221 use ast::Item::TryFiles;
222 use value::Item::*;
223
224 ident("try_files")
225 .with(many1(value()))
226 .skip(semi())
227 .and_then(|mut v: Vec<_>| -> Result<_, Error<_, _>> {
228 let last = v.pop().unwrap();
229 let last = match &last.data[..] {
230 [Literal(x)] if x.starts_with("=") => {
231 Code(self::Code::parse(&x[1..])?.as_code())
232 }
233 [Literal(x)] if x.starts_with("@") => {
234 NamedLocation(x[1..].to_string())
235 }
236 _ => Uri(last.clone()),
237 };
238 Ok(TryFiles(::ast::TryFiles {
239 options: v,
240 last_option: last,
241 }))
242 })
243}
244
245
246pub fn openresty<'a>() -> impl Parser<Output=Item, Input=TokenStream<'a>> {
247 use ast::Item::*;
248 choice((
249 ident("rewrite_by_lua_file").with(value()).skip(semi())
250 .map(Item::RewriteByLuaFile),
251 ident("balancer_by_lua_file").with(value()).skip(semi())
252 .map(BalancerByLuaFile),
253 ident("access_by_lua_file").with(value()).skip(semi())
254 .map(AccessByLuaFile),
255 ident("header_filter_by_lua_file").with(value()).skip(semi())
256 .map(HeaderFilterByLuaFile),
257 ident("content_by_lua_file").with(value()).skip(semi())
258 .map(ContentByLuaFile),
259 ident("body_filter_by_lua_file").with(value()).skip(semi())
260 .map(BodyFilterByLuaFile),
261 ident("log_by_lua_file").with(value()).skip(semi())
262 .map(LogByLuaFile),
263 ident("lua_need_request_body").with(value()).skip(semi())
264 .map(LuaNeedRequestBody),
265 ident("ssl_certificate_by_lua_file").with(value()).skip(semi())
266 .map(SslCertificateByLuaFile),
267 ident("ssl_session_fetch_by_lua_file").with(value()).skip(semi())
268 .map(SslSessionFetchByLuaFile),
269 ident("ssl_session_store_by_lua_file").with(value()).skip(semi())
270 .map(SslSessionStoreByLuaFile),
271 ))
272}
273
274pub fn directive<'a>() -> impl Parser<Output=Directive, Input=TokenStream<'a>>
275{
276 position()
277 .and(choice((
278 ident("daemon").with(bool()).skip(semi())
279 .map(Item::Daemon),
280 ident("master_process").with(bool()).skip(semi())
281 .map(Item::MasterProcess),
282 worker_processes(),
283 ident("http").with(block())
284 .map(|(position, directives)| ast::Http { position, directives })
285 .map(Item::Http),
286 ident("server").with(block())
287 .map(|(position, directives)| ast::Server { position, directives })
288 .map(Item::Server),
289 rewrite::directives(),
290 try_files(),
291 ident("include").with(value()).skip(semi()).map(Item::Include),
292 ident("ssl_certificate").with(value()).skip(semi())
293 .map(Item::SslCertificate),
294 ident("ssl_certificate_key").with(value()).skip(semi())
295 .map(Item::SslCertificateKey),
296 location(),
297 headers::directives(),
298 server_name(),
299 map(),
300 ident("client_max_body_size").with(value()).skip(semi())
301 .map(Item::ClientMaxBodySize),
302 proxy::directives(),
303 gzip::directives(),
304 core::directives(),
305 access::directives(),
306 log::directives(),
307 real_ip::directives(),
308 openresty(),
309 ident("empty_gif").skip(semi()).map(|_| Item::EmptyGif),
311 ident("index").with(many(value())).skip(semi())
312 .map(Item::Index),
313 )))
314 .map(|(pos, dir)| Directive {
315 position: pos,
316 item: dir,
317 })
318}
319
320
321pub fn parse_main(s: &str) -> Result<Main, ParseError> {
327 parse_directives(s).map(|directives| Main { directives })
328}
329
330pub fn parse_directives(s: &str) -> Result<Vec<Directive>, ParseError> {
334 let mut tokens = TokenStream::new(s);
335 let (doc, _) = many1(directive())
336 .skip(eof())
337 .parse_stream(&mut tokens)
338 .map_err(|e| e.into_inner().error)?;
339 Ok(doc)
340}