├── .busted ├── .luacheckrc ├── LICENSE.md ├── NEWS ├── README.md ├── lpeg_patterns-scm-0.rockspec ├── lpeg_patterns ├── IPv4.lua ├── IPv6.lua ├── core.lua ├── email.lua ├── http.lua ├── http │ ├── alpn.lua │ ├── alternate.lua │ ├── authentication.lua │ ├── caching.lua │ ├── conditional.lua │ ├── cookie.lua │ ├── core.lua │ ├── disposition.lua │ ├── expect_ct.lua │ ├── forwarded.lua │ ├── frameoptions.lua │ ├── hoba.lua │ ├── link.lua │ ├── origin.lua │ ├── parameters.lua │ ├── pkp.lua │ ├── range.lua │ ├── referrer_policy.lua │ ├── semantics.lua │ ├── slug.lua │ ├── sts.lua │ ├── util.lua │ ├── webdav.lua │ └── websocket.lua ├── language.lua ├── phone.lua ├── uri.lua └── util.lua └── spec ├── IPv6_spec.lua ├── email_spec.lua ├── http_alpn_spec.lua ├── http_cookie_spec.lua ├── http_disposition_spec.lua ├── http_expect_ct_spec.lua ├── http_frameoptions_spec.lua ├── http_link_spec.lua ├── http_origin_spec.lua ├── http_pkp_spec.lua ├── http_slug_spec.lua ├── http_spec.lua ├── http_sts_spec.lua ├── http_websocket_spec.lua ├── language_spec.lua ├── phone_spec.lua └── uri_spec.lua /.busted: -------------------------------------------------------------------------------- 1 | return { 2 | default = { 3 | lpath = "./?.lua"; 4 | }; 5 | } 6 | -------------------------------------------------------------------------------- /.luacheckrc: -------------------------------------------------------------------------------- 1 | std = "min" 2 | files["spec"] = {std = "+busted"} 3 | max_line_length = false 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2012-2016 Daurnimator 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NEWS: -------------------------------------------------------------------------------- 1 | UNRELEASED 2 | 3 | - http: split into multiple modules 4 | - http.alpn: verify that protocol_id meets unique encoding criteria 5 | 6 | 7 | 0.5 - 2018-07-15 8 | 9 | - http: Cache-Control directives are case-normalised and grouped into pairs 10 | - http: Strict_Transport_Security now returns a table and doesn't match on duplicates 11 | - http: Public_Key_Pins capture format and validation 12 | - http: New Expect_CT and Referrer_Policy patterns 13 | 14 | 15 | 0.4 - 2016-11-23 16 | 17 | - Reduce memory usage by refactoring IPv6 pattern 18 | Also fixes lpeg 0.10 compatability 19 | - http: case-normalise cookie attribute names 20 | - http: fix captures of Via header 21 | - http: fixes some whitespace rules 22 | - uri: fix missing case-normalisation for percent encoded characters in hostnames 23 | - uri: export IP_literal and sub_delims patterns 24 | 25 | 26 | 0.3 - 2016-08-21 27 | 28 | - New http module 29 | Includes parsers for almost every HTTP header. 30 | Expect this API to be unstable, it's a significant amount of new code 31 | - New language module that parses language codes e.g. "zh-Hans-CN" 32 | - New email.mailbox pattern (name + email like: "Bob ") 33 | - New uri.absolute_uri pattern that does not allow fragments 34 | - Expose some previously internal uri patterns 35 | - Fix: Don't percent decode in URIs when it could introduce ambiguity (thanks @torhve) 36 | 37 | 38 | 0.2 - 2015-12-14 39 | 40 | - Fixed parsing of IPv6 addresses (thanks Sean Conner) 41 | - IPv6 zone support 42 | - Stricter uri matching (scheme is now compulsory) 43 | - "reference" (i.e. relative) URI matching 44 | 45 | 46 | 0.1 - 2015-01-29 47 | 48 | - First release 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A collection of [LPEG](http://www.inf.puc-rio.br/~roberto/lpeg/lpeg.html) patterns 2 | 3 | ## Use cases 4 | 5 | - Strict validation of user input 6 | - Searching free-form input 7 | 8 | 9 | ## Modules 10 | 11 | ### `core` 12 | 13 | A small module implementing commonly used rules from [RFC-5234 appendix B.1](https://tools.ietf.org/html/rfc5234#appendix-B.1) 14 | 15 | - `ALPHA` (pattern) 16 | - `BIT` (pattern) 17 | - `CHAR` (pattern) 18 | - `CR` (pattern) 19 | - `CRLF` (pattern) 20 | - `CTL` (pattern) 21 | - `DIGIT` (pattern) 22 | - `DQUOTE` (pattern) 23 | - `HEXDIG` (pattern) 24 | - `HTAB` (pattern) 25 | - `LF` (pattern) 26 | - `LWSP` (pattern) 27 | - `OCTET` (pattern) 28 | - `SP` (pattern) 29 | - `VCHAR` (pattern) 30 | - `WSP` (pattern) 31 | 32 | 33 | ### `IPv4` 34 | 35 | - `IPv4address` (pattern): parses an IPv4 address in dotted decimal notation. on success, returns addresses as an IPv4 object 36 | - `IPv4_methods` (table): 37 | - `unpack` (function): the IPv4 address as a series of 4 8 bit numbers 38 | - `binary` (function): the IPv4 address as a 4 byte binary string 39 | - `IPv4_mt` (table): metatable given to IPv4 objects 40 | - `__index` (table): `IPv4_methods` 41 | - `__tostring` (function): returns the IPv4 address in dotted decimal notation 42 | 43 | IPv4 "dotted decimal notation" in this document refers to "strict" form (see [RFC-6943 section 3.1.1](https://tools.ietf.org/html/rfc6943#section-3.1.1)) unless otherwise noted. 44 | 45 | 46 | ### `IPv6` 47 | 48 | - `IPv6address` (pattern): parses an IPv6 address 49 | - `IPv6addrz` (pattern): parses an IPv6 address with optional "ZoneID" (see [RFC-6874](https://tools.ietf.org/html/rfc6874)) 50 | - `IPv6_methods` (table): methods available on IPv6 objects 51 | - `unpack` (function): the IPv6 address as a series of 8 16bit numbers, optionally followed by zoneid 52 | - `binary` (function): the IPv6 address as a 16 byte binary string 53 | - `setzoneid` (function): set the zoneid of this IPv6 address 54 | - `IPv6_mt` (table): metatable given to IPv6 objects 55 | - `__tostring` (function): will return the IPv6 address as a valid IPv6 string 56 | 57 | 58 | ### `uri` 59 | 60 | Parses URIs as described in [RFC-3986](https://tools.ietf.org/html/rfc3986). 61 | 62 | - `uri` (pattern): on success, returns a table with fields: (similar to [luasocket](http://w3.impa.br/~diego/software/luasocket/url.html#parse)) 63 | - `scheme` 64 | - `userinfo` 65 | - `host` 66 | - `port` 67 | - `path` 68 | - `query` 69 | - `fragment` 70 | - `absolute_uri` (pattern): similar to `uri`, but does not permit fragments 71 | - `uri_reference` (pattern): similar to `uri`, but permits relative URIs 72 | - `relative_part` (pattern): matches a relative uri not including query and fragment; data is held in named group captures `"userinfo"`, `"host"`, `"port"`, `"path"` 73 | - `scheme` (pattern): matches the scheme portion of a URI 74 | - `userinfo` (pattern): matches the userinfo portion of a URI 75 | - `host` (pattern): matches the host portion of a URI 76 | - `IP_literal` (pattern): matches an IP based host portion of a URI. Capture is an [IPv4](#IPv4), [IPv6](#IPv6) or IPvFuture object 77 | - `port` (pattern): matches the port portion of a URI 78 | - `authority` (pattern): matches the authority portion of a URI; data is held in named group captures of `"userinfo"`, `"host"`, `"port"` 79 | - `path` (pattern): matches the path portion of a URI. Captures `nil` for the empty path. 80 | - `segment` (pattern): matches a path segment (a piece of a path without a `/`) 81 | - `query` (pattern): matches the query portion of a URI 82 | - `fragment` (pattern): matches the fragment portion of a URI 83 | - `sane_uri` (pattern): a variant that shouldn't match things that people would not normally consider URIs. 84 | e.g. uris without a hostname 85 | - `sane_host` (pattern): a variant that shouldn't match things that people would not normally consider valid hosts. 86 | - `sane_authority` (pattern): a variant that shouldn't match things that people would not normally consider valid hosts. 87 | - `pct_encoded` (pattern): matches a percent encoded octet, produces a capture of the normalised form. 88 | - `sub_delims` (pattern): the set of subcomponent delimeters 89 | 90 | 91 | ### `email` 92 | 93 | - `mailbox` (pattern): the mailbox format: matches either `name_addr` or an addr-spec. 94 | - `name_addr` (pattern): the name and address format i.e. `Display Name` 95 | Has captures of the local_part and the domain. Captures the display name in the named capture `"display"` 96 | - `email` (pattern): also known as an "addr-spec"; follows [RFC-5322 section 3.4.1](http://tools.ietf.org/html/rfc5322#section-3.4.1) 97 | Has captures of the local_part and the domain 98 | Be careful trying to reconstruct the email address from the captures; you may need escaping 99 | - `local_part` (pattern): the bit before the `@` in an email address 100 | - `domain` (pattern): the bit after the `@` in an email address 101 | - `email_nocfws` (pattern): a variant that doesn't allow for comments or folding whitespace 102 | - `local_part_nocfws` (pattern): the bit before the `@` in an email address; no comments or folding whitespace allowed. 103 | - `domain_nocfws` (pattern): the bit after the `@` in an email address; no comments or folding whitespace allowed. 104 | 105 | 106 | ### `http` 107 | 108 | These patterns should be considered to have non stable APIs. 109 | 110 | #### [RFC 4918](https://tools.ietf.org/html/rfc4918) 111 | 112 | - `DAV` (pattern) 113 | - `Depth` (pattern) 114 | - `Destination` (pattern) 115 | - `If` (pattern) 116 | - `Lock_Token` (pattern) 117 | - `Overwrite` (pattern) 118 | - `TimeOut` (pattern) 119 | 120 | 121 | #### [RFC 5023](https://tools.ietf.org/html/rfc5023) 122 | 123 | - `SLUG` (pattern) 124 | 125 | 126 | #### [RFC 5323](https://tools.ietf.org/html/rfc5323) 127 | 128 | - `DASL` (pattern) 129 | 130 | 131 | #### [RFC 5789](https://tools.ietf.org/html/rfc5789) 132 | 133 | - `Accept_Patch` (pattern) 134 | 135 | 136 | #### [RFC 5988](https://tools.ietf.org/html/rfc5988) 137 | 138 | - `Link` (pattern) 139 | 140 | 141 | #### [RFC 6265](https://tools.ietf.org/html/rfc6265) 142 | 143 | - `Set_Cookie` (pattern) 144 | - `Cookie` (pattern) 145 | 146 | 147 | #### [RFC 6266](https://tools.ietf.org/html/rfc6266) 148 | 149 | - `Content_Disposition` (pattern) 150 | 151 | 152 | #### [RFC 6454](https://tools.ietf.org/html/rfc6454) 153 | 154 | - `Origin` (pattern) 155 | 156 | 157 | #### [RFC 6455](https://tools.ietf.org/html/rfc6455) 158 | 159 | - `Sec_WebSocket_Accept` (pattern) 160 | - `Sec_WebSocket_Key` (pattern) 161 | - `Sec_WebSocket_Extensions` (pattern) 162 | - `Sec_WebSocket_Protocol_Client` (pattern) 163 | - `Sec_WebSocket_Protocol_Server` (pattern) 164 | - `Sec_WebSocket_Version_Client` (pattern) 165 | - `Sec_WebSocket_Version_Server` (pattern) 166 | 167 | 168 | #### [RFC 6638](https://tools.ietf.org/html/rfc6638) 169 | 170 | - `Schedule_Reply` (pattern) 171 | - `Schedule_Tag` (pattern) 172 | - `If_Schedule_Tag_Match` (pattern) 173 | 174 | 175 | #### [RFC 6797](https://tools.ietf.org/html/rfc6797) 176 | 177 | - `Strict_Transport_Security` (pattern) 178 | 179 | 180 | #### [RFC 7034](https://tools.ietf.org/html/rfc7034) 181 | 182 | - `X_Frame_Options` (pattern) 183 | 184 | 185 | #### [RFC 7089](https://tools.ietf.org/html/rfc7089) 186 | 187 | - `Accept_Datetime` (pattern) 188 | - `Memento_Datetime` (pattern) 189 | 190 | 191 | #### [RFC 7230](https://tools.ietf.org/html/rfc7230) 192 | 193 | - `request_line` (pattern) 194 | - `field_name` (pattern) 195 | - `field_value` (pattern) 196 | - `header_field` (pattern) 197 | - `OWS` (pattern) 198 | - `RWS` (pattern) 199 | - `BWS` (pattern) 200 | - `token` (pattern) 201 | - `qdtext` (pattern) 202 | - `quoted_string` (pattern) 203 | - `comment` (pattern) 204 | - `Content_Length` (pattern) 205 | - `Transfer_Encoding` (pattern) 206 | - `chunk_ext` (pattern) 207 | - `TE` (pattern) 208 | - `Trailer` (pattern) 209 | - `request_target` (pattern) 210 | - `Host` (pattern) 211 | - `Via` (pattern): captures are a list of tables with fields `.protocol`, `.by` and `.comment` 212 | - `Connection` (pattern) 213 | - `Upgrade` (pattern): captures are a list of strings containing *protocol* or *protocol/version* 214 | 215 | 216 | #### [RFC 7231](https://tools.ietf.org/html/rfc7231) 217 | 218 | - `IMF_fixdate` (pattern) 219 | - `Content_Encoding` (pattern) 220 | - `Content_Type` (pattern) 221 | - `Content_Language` (pattern) 222 | - `Content_Location` (pattern) 223 | - `Expect` (pattern) 224 | - `Max_Forwards` (pattern) 225 | - `Accept` (pattern) 226 | - `Accept_Charset` (pattern) 227 | - `Accept_Encoding` (pattern) 228 | - `Accept_Language` (pattern) 229 | - `From` (pattern) 230 | - `Referer` (pattern) 231 | - `User_Agent` (pattern) 232 | - `Date` (pattern): capture is a table in the same format as used by [`os.time`](http://www.lua.org/manual/5.3/manual.html#pdf-os.time) 233 | - `Location` (pattern) 234 | - `Retry_After` (pattern): capture is either a table describing an absolute time in the same format as used by [`os.time`](http://www.lua.org/manual/5.3/manual.html#pdf-os.time), or a relative time as a number of seconds 235 | - `Vary` (pattern) 236 | - `Allow` (pattern) 237 | - `Server` (pattern) 238 | 239 | 240 | #### [RFC 7232](https://tools.ietf.org/html/rfc7232) 241 | 242 | - `Last_Modified` (pattern): capture is a table in the same format as used by [`os.time`](http://www.lua.org/manual/5.3/manual.html#pdf-os.time) 243 | - `ETag` (pattern) 244 | - `If_Match` (pattern) 245 | - `If_None_Match` (pattern) 246 | - `If_Modified_Since` (pattern): capture is a table in the same format as used by [`os.time`](http://www.lua.org/manual/5.3/manual.html#pdf-os.time) 247 | - `If_Unmodified_Since` (pattern): capture is a table in the same format as used by [`os.time`](http://www.lua.org/manual/5.3/manual.html#pdf-os.time) 248 | 249 | 250 | #### [RFC 7233](https://tools.ietf.org/html/rfc7233) 251 | 252 | - `Accept_Ranges` (pattern) 253 | - `Range` (pattern) 254 | - `If_Range` (pattern): capture is either an `entity_tag` or a table in the same format as used by [`os.time`](http://www.lua.org/manual/5.3/manual.html#pdf-os.time) 255 | - `Content_Range` (pattern) 256 | 257 | 258 | #### [RFC 7234](https://tools.ietf.org/html/rfc7234) 259 | 260 | - `Age` (pattern) 261 | - `Cache_Control` (pattern): captures are grouped into key/value pairs (where a directive with no value has a value of `true`) 262 | - `Expires` (pattern): capture is a table in the same format as used by [`os.time`](http://www.lua.org/manual/5.3/manual.html#pdf-os.time) 263 | - `Pragma` (pattern) 264 | - `Warning` (pattern) 265 | 266 | 267 | #### [RFC 7235](https://tools.ietf.org/html/rfc7235) 268 | 269 | - `WWW_Authenticate` (pattern) 270 | - `Authorization` (pattern) 271 | - `Proxy_Authenticate` (pattern) 272 | - `Proxy_Authorization` (pattern) 273 | 274 | 275 | #### [RFC 7239](https://tools.ietf.org/html/rfc7239) 276 | 277 | - `Forwarded` (pattern) 278 | 279 | 280 | #### [RFC 7469](https://tools.ietf.org/html/rfc7469) 281 | 282 | - `Public_Key_Pins` (pattern) 283 | - `Public_Key_Pins_Report_Only` (pattern) 284 | 285 | 286 | #### [RFC 7486](https://tools.ietf.org/html/rfc7486) 287 | 288 | - `Hobareg` (pattern) 289 | 290 | 291 | #### [RFC 7615](https://tools.ietf.org/html/rfc7615) 292 | 293 | - `Authentication_Info` (pattern) 294 | - `Proxy_Authentication_Info` (pattern) 295 | 296 | 297 | #### [RFC 7639](https://tools.ietf.org/html/rfc7639) 298 | 299 | - `ALPN` (pattern) 300 | 301 | 302 | #### [RFC 7809](https://tools.ietf.org/html/rfc7809) 303 | 304 | - `CalDAV_Timezones` (pattern) 305 | 306 | 307 | #### [RFC 7838](https://tools.ietf.org/html/rfc7838) 308 | 309 | - `Alt_Svc` (pattern) 310 | - `Alt_Used` (pattern) 311 | 312 | 313 | #### [Expect-CT Extension for HTTP](https://tools.ietf.org/html/draft-ietf-httpbis-expect-ct-06) 314 | 315 | - `Expect_CT` (pattern) 316 | 317 | 318 | #### [Referrer-Policy header](https://www.w3.org/TR/referrer-policy/#referrer-policy-header) 319 | 320 | - `Referrer_Policy` (pattern) 321 | 322 | 323 | ### `phone` 324 | 325 | - `phone` (pattern): includes detailed checking for: 326 | - USA phone numbers using the [NANP](https://en.wikipedia.org/wiki/North_American_Numbering_Plan) 327 | 328 | 329 | ### `language` 330 | 331 | Patterns for definitions from [RFC-4646 Section 2.1](https://tools.ietf.org/html/rfc4646#section-2.1) 332 | 333 | - `langtag` (pattern): Capture is a table with the language tag decomposed into components: 334 | - `language` 335 | - `extlang` (optional) 336 | - `script` (optional) 337 | - `region` (optional) 338 | - `variant` (optional): an array 339 | - `extension` (optional): a dictionary from singleton to value 340 | - `privateuse` (optional): an array 341 | - `privateuse` (pattern): captures an array 342 | - `Language_Tag` (pattern): captures the whole language tag 343 | -------------------------------------------------------------------------------- /lpeg_patterns-scm-0.rockspec: -------------------------------------------------------------------------------- 1 | package = "lpeg_patterns" 2 | version = "scm-0" 3 | 4 | description= { 5 | summary = "a collection of LPEG patterns"; 6 | license = "MIT"; 7 | } 8 | 9 | dependencies = { 10 | "lua"; 11 | "lpeg"; 12 | } 13 | 14 | source = { 15 | url = "git://github.com/daurnimator/lpeg_patterns.git"; 16 | } 17 | 18 | build = { 19 | type = "builtin"; 20 | modules = { 21 | ["lpeg_patterns.util"] = "lpeg_patterns/util.lua"; 22 | ["lpeg_patterns.core"] = "lpeg_patterns/core.lua"; 23 | ["lpeg_patterns.IPv4"] = "lpeg_patterns/IPv4.lua"; 24 | ["lpeg_patterns.IPv6"] = "lpeg_patterns/IPv6.lua"; 25 | ["lpeg_patterns.uri"] = "lpeg_patterns/uri.lua"; 26 | ["lpeg_patterns.email"] = "lpeg_patterns/email.lua"; 27 | ["lpeg_patterns.http"] = "lpeg_patterns/http.lua"; 28 | ["lpeg_patterns.http.alpn"] = "lpeg_patterns/http/alpn.lua"; 29 | ["lpeg_patterns.http.alternate"] = "lpeg_patterns/http/alternate.lua"; 30 | ["lpeg_patterns.http.authentication"] = "lpeg_patterns/http/authentication.lua"; 31 | ["lpeg_patterns.http.caching"] = "lpeg_patterns/http/caching.lua"; 32 | ["lpeg_patterns.http.conditional"] = "lpeg_patterns/http/conditional.lua"; 33 | ["lpeg_patterns.http.cookie"] = "lpeg_patterns/http/cookie.lua"; 34 | ["lpeg_patterns.http.core"] = "lpeg_patterns/http/core.lua"; 35 | ["lpeg_patterns.http.disposition"] = "lpeg_patterns/http/disposition.lua"; 36 | ["lpeg_patterns.http.expect_ct"] = "lpeg_patterns/http/expect_ct.lua"; 37 | ["lpeg_patterns.http.forwarded"] = "lpeg_patterns/http/forwarded.lua"; 38 | ["lpeg_patterns.http.frameoptions"] = "lpeg_patterns/http/frameoptions.lua"; 39 | ["lpeg_patterns.http.hoba"] = "lpeg_patterns/http/hoba.lua"; 40 | ["lpeg_patterns.http.link"] = "lpeg_patterns/http/link.lua"; 41 | ["lpeg_patterns.http.origin"] = "lpeg_patterns/http/origin.lua"; 42 | ["lpeg_patterns.http.parameters"] = "lpeg_patterns/http/parameters.lua"; 43 | ["lpeg_patterns.http.pkp"] = "lpeg_patterns/http/pkp.lua"; 44 | ["lpeg_patterns.http.range"] = "lpeg_patterns/http/range.lua"; 45 | ["lpeg_patterns.http.referrer_policy"] = "lpeg_patterns/http/referrer_policy.lua"; 46 | ["lpeg_patterns.http.semantics"] = "lpeg_patterns/http/semantics.lua"; 47 | ["lpeg_patterns.http.slug"] = "lpeg_patterns/http/slug.lua"; 48 | ["lpeg_patterns.http.sts"] = "lpeg_patterns/http/sts.lua"; 49 | ["lpeg_patterns.http.util"] = "lpeg_patterns/http/util.lua"; 50 | ["lpeg_patterns.http.webdav"] = "lpeg_patterns/http/webdav.lua"; 51 | ["lpeg_patterns.http.websocket"] = "lpeg_patterns/http/websocket.lua"; 52 | ["lpeg_patterns.phone"] = "lpeg_patterns/phone.lua"; 53 | ["lpeg_patterns.language"] = "lpeg_patterns/language.lua"; 54 | }; 55 | } 56 | -------------------------------------------------------------------------------- /lpeg_patterns/IPv4.lua: -------------------------------------------------------------------------------- 1 | -- IPv4 2 | 3 | local lpeg = require "lpeg" 4 | local P = lpeg.P 5 | local R = lpeg.R 6 | local Cg = lpeg.Cg 7 | 8 | local core = require "lpeg_patterns.core" 9 | local DIGIT = core.DIGIT 10 | 11 | local dec_octet = ( 12 | P"1" * DIGIT * DIGIT 13 | + P"2" * (R"04"*DIGIT + P"5"*R"05") 14 | + DIGIT * DIGIT^-1 15 | ) / tonumber 16 | 17 | local IPv4_methods = {} 18 | local IPv4_mt = { 19 | __name = "lpeg_patterns.IPv4"; 20 | __index = IPv4_methods; 21 | } 22 | 23 | local function new_IPv4 ( o1 , o2 , o3 , o4 ) 24 | return setmetatable({o1, o2, o3, o4}, IPv4_mt) 25 | end 26 | 27 | function IPv4_methods:unpack() 28 | return self[1], self[2], self[3], self[4] 29 | end 30 | 31 | function IPv4_methods:binary() 32 | return string.char(self:unpack()) 33 | end 34 | 35 | function IPv4_mt:__tostring ( ) 36 | return string.format("%d.%d.%d.%d", self:unpack()) 37 | end 38 | 39 | local IPv4address = Cg ( dec_octet * P"." * dec_octet * P"." * dec_octet * P"." * dec_octet ) / new_IPv4 40 | 41 | return { 42 | IPv4_methods = IPv4_methods; 43 | IPv4_mt = IPv4_mt; 44 | IPv4address = IPv4address; 45 | } 46 | -------------------------------------------------------------------------------- /lpeg_patterns/IPv6.lua: -------------------------------------------------------------------------------- 1 | -- IPv6 2 | 3 | local unpack = table.unpack or unpack -- luacheck: ignore 113 143 4 | 5 | local lpeg = require "lpeg" 6 | local P = lpeg.P 7 | local V = lpeg.V 8 | local Cc = lpeg.Cc 9 | local Cg = lpeg.Cg 10 | 11 | local util = require "lpeg_patterns.util" 12 | 13 | local core = require "lpeg_patterns.core" 14 | local HEXDIG = core.HEXDIG 15 | 16 | local IPv4address = require "lpeg_patterns.IPv4".IPv4address 17 | 18 | local IPv6_methods = {} 19 | local IPv6_mt = { 20 | __name = "lpeg_patterns.IPv6"; 21 | __index = IPv6_methods; 22 | } 23 | 24 | local function new_IPv6(o1, o2, o3, o4, o5, o6, o7, o8, zoneid) 25 | return setmetatable({ 26 | o1, o2, o3, o4, o5, o6, o7, o8, 27 | zoneid = zoneid; 28 | }, IPv6_mt) 29 | end 30 | 31 | function IPv6_methods:unpack() 32 | return self[1], self[2], self[3], self[4], self[5], self[6], self[7], self[8], self.zoneid 33 | end 34 | 35 | function IPv6_methods:binary() 36 | local t = {} 37 | for i=1, 8 do 38 | local lo = self[i] % 256 39 | t[i*2-1] = (self[i] - lo) / 256 40 | t[i*2] = lo 41 | end 42 | -- TODO: append zoneid. 43 | -- In a struct sockaddr_in6 it is the numeric index of the scope, so need to lookup? 44 | return string.char(unpack(t, 1, 16)) 45 | end 46 | 47 | function IPv6_methods:setzoneid(zoneid) 48 | self.zoneid = zoneid 49 | end 50 | 51 | function IPv6_mt:__tostring() 52 | local fmt_str 53 | if self.zoneid then 54 | fmt_str = "%x:%x:%x:%x:%x:%x:%x:%x%%%s" 55 | else 56 | fmt_str = "%x:%x:%x:%x:%x:%x:%x:%x" 57 | end 58 | return string.format(fmt_str, self:unpack()) 59 | end 60 | 61 | -- RFC 3986 Section 3.2.2 62 | -- This is written as a grammar to reduce memory usage 63 | local raw_IPv6address = Cg(P{ 64 | h16 = HEXDIG * HEXDIG^-3 / util.read_hex; 65 | h16c = V"h16" * P":"; 66 | ls32 = ( V"h16c" * V"h16" ) + IPv4address / function ( ipv4 ) 67 | local o1, o2, o3, o4 = ipv4:unpack() 68 | return o1*2^8 + o2 , o3*2^8 + o4 69 | end; 70 | 71 | mh16c_1 = V"h16c"; 72 | mh16c_2 = V"h16c" * V"h16c"; 73 | mh16c_3 = V"h16c" * V"h16c" * V"h16c"; 74 | mh16c_4 = V"h16c" * V"h16c" * V"h16c" * V"h16c"; 75 | mh16c_5 = V"h16c" * V"h16c" * V"h16c" * V"h16c" * V"h16c"; 76 | mh16c_6 = V"h16c" * V"h16c" * V"h16c" * V"h16c" * V"h16c" * V"h16c"; 77 | 78 | mcc_1 = P"::" * Cc(0); 79 | mcc_2 = P"::" * Cc(0, 0); 80 | mcc_3 = P"::" * Cc(0, 0, 0); 81 | mcc_4 = P"::" * Cc(0, 0, 0, 0); 82 | mcc_5 = P"::" * Cc(0, 0, 0, 0, 0); 83 | mcc_6 = P"::" * Cc(0, 0, 0, 0, 0, 0); 84 | mcc_7 = P"::" * Cc(0, 0, 0, 0, 0, 0, 0); 85 | mcc_8 = P"::" * Cc(0, 0, 0, 0, 0, 0, 0, 0); 86 | 87 | mh16_1 = V"h16"; 88 | mh16_2 = V"mh16c_1" * V"h16"; 89 | mh16_3 = V"mh16c_2" * V"h16"; 90 | mh16_4 = V"mh16c_3" * V"h16"; 91 | mh16_5 = V"mh16c_4" * V"h16"; 92 | mh16_6 = V"mh16c_5" * V"h16"; 93 | mh16_7 = V"mh16c_6" * V"h16"; 94 | 95 | V"mh16c_6" * V"ls32" 96 | + V"mcc_1" * V"mh16c_5" * V"ls32" 97 | + V"mcc_2" * V"mh16c_4" * V"ls32" 98 | + V"h16" * V"mcc_1" * V"mh16c_4" * V"ls32" 99 | + V"mcc_3" * V"mh16c_3" * V"ls32" 100 | + V"h16" * V"mcc_2" * V"mh16c_3" * V"ls32" 101 | + V"mh16_2" * V"mcc_1" * V"mh16c_3" * V"ls32" 102 | + V"mcc_4" * V"mh16c_2" * V"ls32" 103 | + V"h16" * V"mcc_3" * V"mh16c_2" * V"ls32" 104 | + V"mh16_2" * V"mcc_2" * V"mh16c_2" * V"ls32" 105 | + V"mh16_3" * V"mcc_1" * V"mh16c_2" * V"ls32" 106 | + V"mcc_5" * V"h16c" * V"ls32" 107 | + V"h16" * V"mcc_4" * V"h16c" * V"ls32" 108 | + V"mh16_2" * V"mcc_3" * V"h16c" * V"ls32" 109 | + V"mh16_3" * V"mcc_2" * V"h16c" * V"ls32" 110 | + V"mh16_4" * V"mcc_1" * V"h16c" * V"ls32" 111 | + V"mcc_6" * V"ls32" 112 | + V"h16" * V"mcc_5" * V"ls32" 113 | + V"mh16_2" * V"mcc_4" * V"ls32" 114 | + V"mh16_3" * V"mcc_3" * V"ls32" 115 | + V"mh16_4" * V"mcc_2" * V"ls32" 116 | + V"mh16_5" * V"mcc_1" * V"ls32" 117 | + V"mcc_7" * V"h16" 118 | + V"h16" * V"mcc_6" * V"h16" 119 | + V"mh16_2" * V"mcc_5" * V"h16" 120 | + V"mh16_3" * V"mcc_4" * V"h16" 121 | + V"mh16_4" * V"mcc_3" * V"h16" 122 | + V"mh16_5" * V"mcc_2" * V"h16" 123 | + V"mh16_6" * V"mcc_1" * V"h16" 124 | + V"mcc_8" 125 | + V"mh16_1" * V"mcc_7" 126 | + V"mh16_2" * V"mcc_6" 127 | + V"mh16_3" * V"mcc_5" 128 | + V"mh16_4" * V"mcc_4" 129 | + V"mh16_5" * V"mcc_3" 130 | + V"mh16_6" * V"mcc_2" 131 | + V"mh16_7" * V"mcc_1" 132 | }) 133 | 134 | local IPv6address = raw_IPv6address / new_IPv6 135 | 136 | local ZoneID = P(1)^1 -- ZoneIDs can be any character 137 | local IPv6addrz = raw_IPv6address * (P"%" * ZoneID)^-1 / new_IPv6 138 | 139 | return { 140 | IPv6_methods = IPv6_methods; 141 | IPv6_mt = IPv6_mt; 142 | IPv6address = IPv6address; 143 | IPv6addrz = IPv6addrz; 144 | } 145 | -------------------------------------------------------------------------------- /lpeg_patterns/core.lua: -------------------------------------------------------------------------------- 1 | -- Core Rules 2 | -- https://tools.ietf.org/html/rfc5234#appendix-B.1 3 | 4 | local lpeg = require "lpeg" 5 | 6 | local P = lpeg.P 7 | local R = lpeg.R 8 | local S = lpeg.S 9 | 10 | local _M = { } 11 | 12 | _M.ALPHA = R("AZ","az") 13 | _M.BIT = S"01" 14 | _M.CHAR = R"\1\127" 15 | _M.CR = P"\r" 16 | _M.CRLF = P"\r\n" 17 | _M.CTL = R"\0\31" + P"\127" 18 | _M.DIGIT = R"09" 19 | _M.DQUOTE= P'"' 20 | _M.HEXDIG= _M.DIGIT + S"ABCDEFabcdef" 21 | _M.HTAB = P"\t" 22 | _M.LF = P"\n" 23 | _M.OCTET = P(1) 24 | _M.SP = P" " 25 | _M.VCHAR = R"\33\126" 26 | _M.WSP = S" \t" 27 | 28 | _M.LWSP = (_M.WSP + _M.CRLF*_M.WSP)^0 29 | 30 | return _M 31 | -------------------------------------------------------------------------------- /lpeg_patterns/email.lua: -------------------------------------------------------------------------------- 1 | -- Email Addresses 2 | -- RFC 5322 Section 3.4.1 3 | 4 | local lpeg = require "lpeg" 5 | local P = lpeg.P 6 | local R = lpeg.R 7 | local S = lpeg.S 8 | local V = lpeg.V 9 | local C = lpeg.C 10 | local Cg = lpeg.Cg 11 | local Ct = lpeg.Ct 12 | local Cs = lpeg.Cs 13 | 14 | local core = require "lpeg_patterns.core" 15 | local CHAR = core.CHAR 16 | local CRLF = core.CRLF 17 | local CTL = core.CTL 18 | local DQUOTE = core.DQUOTE 19 | local WSP = core.WSP 20 | local VCHAR = core.VCHAR 21 | 22 | local obs_NO_WS_CTL = R("\1\8", "\11\12", "\14\31") + P"\127" 23 | 24 | local obs_qp = Cg(P"\\" * C(P"\0" + obs_NO_WS_CTL + core.LF + core.CR)) 25 | local quoted_pair = Cg(P"\\" * C(VCHAR + WSP)) + obs_qp 26 | 27 | -- Folding White Space 28 | local FWS = (WSP^0 * CRLF)^-1 * WSP^1 / " " -- Fold whitespace into a single " " 29 | 30 | -- Comments 31 | local ctext = R"\33\39" + R"\42\91" + R"\93\126" 32 | local comment = P { 33 | V"comment" ; 34 | ccontent = ctext + quoted_pair + V"comment" ; 35 | comment = P"("* (FWS^-1 * V"ccontent")^0 * FWS^-1 * P")"; 36 | } 37 | local CFWS = ((FWS^-1 * comment)^1 * FWS^-1 + FWS ) / function() end 38 | 39 | -- Atom 40 | local specials = S[=[()<>@,;:\".[]]=] 41 | local atext = CHAR-specials-P" "-CTL 42 | local atom = CFWS^-1 * C(atext^1) * CFWS^-1 43 | local dot_atom_text = C(atext^1 * ( P"." * atext^1 )^0) 44 | local dot_atom = CFWS^-1 * dot_atom_text * CFWS^-1 45 | 46 | -- Quoted Strings 47 | local qtext = S"\33"+R("\35\91","\93\126") 48 | local qcontent = qtext + quoted_pair 49 | local quoted_string_text = DQUOTE * Cs((FWS^-1 * qcontent)^0 * FWS^-1) * DQUOTE 50 | local quoted_string = CFWS^-1 * quoted_string_text * CFWS^-1 51 | 52 | -- Miscellaneous Tokens 53 | local word = atom + quoted_string 54 | local obs_phrase = C(word * (word + P"." + CFWS)^0 / function() end) 55 | local phrase = obs_phrase -- obs_phrase is more broad than `word^1`, it's really the same but allows "." 56 | 57 | -- Addr-spec 58 | local obs_dtext = obs_NO_WS_CTL + quoted_pair 59 | local dtext = R("\33\90", "\94\126") + obs_dtext 60 | local domain_literal_text = P"[" * Cs((FWS^-1 * dtext)^0 * FWS^-1) * P"]" 61 | 62 | local domain_text = dot_atom_text + domain_literal_text 63 | local local_part_text = dot_atom_text + quoted_string_text 64 | local addr_spec_text = local_part_text * P"@" * domain_text 65 | 66 | local domain_literal = CFWS^-1 * domain_literal_text * CFWS^-1 67 | local obs_domain = Ct(atom * (C"." * atom)^0) / table.concat 68 | local domain = obs_domain + dot_atom + domain_literal 69 | local obs_local_part = Ct(word * (C"." * word)^0) / table.concat 70 | local local_part = obs_local_part + dot_atom + quoted_string 71 | local addr_spec = local_part * P"@" * domain 72 | 73 | local display_name = phrase 74 | local obs_domain_list = (CFWS + P",")^0 * P"@" * domain 75 | * (P"," * CFWS^-1 * (P"@" * domain)^-1)^0 76 | local obs_route = Cg(Ct(obs_domain_list) * P":", "route") 77 | local obs_angle_addr = CFWS^-1 * P"<" * obs_route * addr_spec * P">" * CFWS^-1 78 | local angle_addr = CFWS^-1 * P"<" * addr_spec * P">" * CFWS^-1 79 | + obs_angle_addr 80 | local name_addr = Cg(display_name, "display")^-1 * angle_addr 81 | local mailbox = name_addr + addr_spec 82 | 83 | return { 84 | local_part = local_part; 85 | domain = domain; 86 | email = addr_spec; 87 | name_addr = name_addr; 88 | mailbox = mailbox; 89 | 90 | -- A variant that does not allow comments or folding whitespace 91 | local_part_nocfws = local_part_text; 92 | domain_nocfws = domain_text; 93 | email_nocfws = addr_spec_text; 94 | } 95 | -------------------------------------------------------------------------------- /lpeg_patterns/http.lua: -------------------------------------------------------------------------------- 1 | -- HTTP related patterns 2 | 3 | local _M = {} 4 | 5 | -- RFC 7230 6 | local http_core = require "lpeg_patterns.http.core" 7 | _M.OWS = http_core.OWS 8 | _M.RWS = http_core.RWS 9 | _M.BWS = http_core.BWS 10 | 11 | _M.chunk_ext = http_core.chunk_ext 12 | _M.comment = http_core.comment 13 | _M.field_name = http_core.field_name 14 | _M.field_value = http_core.field_value 15 | _M.header_field = http_core.header_field 16 | _M.qdtext = http_core.qdtext 17 | _M.quoted_string = http_core.quoted_string 18 | _M.request_line = http_core.request_line 19 | _M.request_target = http_core.request_target 20 | _M.token = http_core.token 21 | 22 | _M.Connection = http_core.Connection 23 | _M.Content_Length = http_core.Content_Length 24 | _M.Host = http_core.Host 25 | _M.TE = http_core.TE 26 | _M.Trailer = http_core.Trailer 27 | _M.Transfer_Encoding = http_core.Transfer_Encoding 28 | _M.Upgrade = http_core.Upgrade 29 | _M.Via = http_core.Via 30 | 31 | -- RFC 7231 32 | local http_semantics = require "lpeg_patterns.http.semantics" 33 | 34 | _M.IMF_fixdate = http_semantics.IMF_fixdate 35 | 36 | _M.Accept = http_semantics.Accept 37 | _M.Accept_Charset = http_semantics.Accept_Charset 38 | _M.Accept_Encoding = http_semantics.Accept_Encoding 39 | _M.Accept_Language = http_semantics.Accept_Language 40 | _M.Allow = http_semantics.Allow 41 | _M.Content_Encoding = http_semantics.Content_Encoding 42 | _M.Content_Language = http_semantics.Content_Language 43 | _M.Content_Location = http_semantics.Content_Location 44 | _M.Content_Type = http_semantics.Content_Type 45 | _M.Date = http_semantics.Date 46 | _M.Expect = http_semantics.Expect 47 | _M.From = http_semantics.From 48 | _M.Location = http_semantics.Location 49 | _M.Max_Forwards = http_semantics.Max_Forwards 50 | _M.Referer = http_semantics.Referer 51 | _M.Retry_After = http_semantics.Retry_After 52 | _M.Server = http_semantics.Server 53 | _M.User_Agent = http_semantics.User_Agent 54 | _M.Vary = http_semantics.Vary 55 | 56 | -- RFC 7232 57 | local http_conditional = require "lpeg_patterns.http.conditional" 58 | _M.ETag = http_conditional.ETag 59 | _M.If_Match = http_conditional.If_Match 60 | _M.If_Modified_Since = http_conditional.If_Modified_Since 61 | _M.If_None_Match = http_conditional.If_None_Match 62 | _M.If_Unmodified_Since = http_conditional.If_Unmodified_Since 63 | _M.Last_Modified = http_conditional.Last_Modified 64 | 65 | -- RFC 7233 66 | local http_range = require "lpeg_patterns.http.range" 67 | _M.Accept_Ranges = http_range.Accept_Ranges 68 | _M.Range = http_range.Range 69 | _M.If_Range = http_range.If_Range 70 | _M.Content_Range = http_range.Content_Range 71 | 72 | -- RFC 7234 73 | local http_caching = require "lpeg_patterns.http.caching" 74 | _M.Age = http_caching.Age 75 | _M.Cache_Control = http_caching.Cache_Control 76 | _M.Expires = http_caching.Expires 77 | _M.Pragma = http_caching.Pragma 78 | _M.Warning = http_caching.Warning 79 | 80 | -- RFC 7235 81 | local http_authentication = require "lpeg_patterns.http.authentication" 82 | _M.WWW_Authenticate = http_authentication.WWW_Authenticate 83 | _M.Authorization = http_authentication.Authorization 84 | _M.Proxy_Authenticate = http_authentication.Proxy_Authenticate 85 | _M.Proxy_Authorization = http_authentication.Proxy_Authorization 86 | 87 | -- WebDav 88 | local http_webdav = require "lpeg_patterns.http.webdav" 89 | _M.CalDAV_Timezones = http_webdav.CalDAV_Timezones 90 | _M.DASL = http_webdav.DASL 91 | _M.DAV = http_webdav.DAV 92 | _M.Depth = http_webdav.Depth 93 | _M.Destination = http_webdav.Destination 94 | _M.If = http_webdav.If 95 | _M.If_Schedule_Tag_Match = http_webdav.If_Schedule_Tag_Match 96 | _M.Lock_Token = http_webdav.Lock_Token 97 | _M.Overwrite = http_webdav.Overwrite 98 | _M.Schedule_Reply = http_webdav.Schedule_Reply 99 | _M.Schedule_Tag = http_webdav.Schedule_Tag 100 | _M.TimeOut = http_webdav.TimeOut 101 | 102 | -- RFC 5023 103 | _M.SLUG = require "lpeg_patterns.http.slug".SLUG 104 | 105 | -- RFC 5789 106 | _M.Accept_Patch = http_core.comma_sep_trim(http_semantics.media_type, 1) 107 | 108 | -- RFC 5988 109 | _M.Link = require "lpeg_patterns.http.link".Link 110 | 111 | -- RFC 6265 112 | local http_cookie = require "lpeg_patterns.http.cookie" 113 | _M.Cookie = http_cookie.Cookie 114 | _M.Set_Cookie = http_cookie.Set_Cookie 115 | 116 | -- RFC 6266 117 | _M.Content_Disposition = require "lpeg_patterns.http.disposition".Content_Disposition 118 | 119 | -- RFC 6454 120 | _M.Origin = require "lpeg_patterns.http.origin".Origin 121 | 122 | -- RFC 6455 123 | local http_websocket = require "lpeg_patterns.http.websocket" 124 | _M.Sec_WebSocket_Accept = http_websocket.Sec_WebSocket_Accept 125 | _M.Sec_WebSocket_Key = http_websocket.Sec_WebSocket_Key 126 | _M.Sec_WebSocket_Extensions = http_websocket.Sec_WebSocket_Extensions 127 | _M.Sec_WebSocket_Protocol_Client = http_websocket.Sec_WebSocket_Protocol_Client 128 | _M.Sec_WebSocket_Protocol_Server = http_websocket.Sec_WebSocket_Protocol_Server 129 | _M.Sec_WebSocket_Version_Client = http_websocket.Sec_WebSocket_Version_Client 130 | _M.Sec_WebSocket_Version_Server = http_websocket.Sec_WebSocket_Version_Server 131 | 132 | -- RFC 6797 133 | _M.Strict_Transport_Security = require "lpeg_patterns.http.sts".Strict_Transport_Security 134 | 135 | -- RFC 7034 136 | _M.X_Frame_Options = require "lpeg_patterns.http.frameoptions".X_Frame_Options 137 | 138 | -- RFC 7089 139 | _M.Accept_Datetime = http_semantics.IMF_fixdate 140 | _M.Memento_Datetime = http_semantics.IMF_fixdate 141 | 142 | -- RFC 7239 143 | _M.Forwarded = require "lpeg_patterns.http.forwarded".Forwarded 144 | 145 | -- RFC 7469 146 | local http_pkp = require "lpeg_patterns.http.pkp" 147 | _M.Public_Key_Pins = http_pkp.Public_Key_Pins 148 | _M.Public_Key_Pins_Report_Only = http_pkp.Public_Key_Pins_Report_Only 149 | 150 | -- RFC 7486 151 | _M.Hobareg = require "lpeg_patterns.http.hoba".Hobareg 152 | 153 | -- RFC 7615 154 | _M.Authentication_Info = http_authentication.Authentication_Info 155 | _M.Proxy_Authentication_Info = http_authentication.Proxy_Authentication_Info 156 | 157 | -- RFC 7639 158 | _M.ALPN = require "lpeg_patterns.http.alpn".ALPN 159 | 160 | -- RFC 7838 161 | local http_alternate = require "lpeg_patterns.http.alternate" 162 | _M.Alt_Svc = http_alternate.Alt_Svc 163 | _M.Alt_Used = http_alternate.Alt_Used 164 | 165 | -- https://tools.ietf.org/html/draft-ietf-httpbis-expect-ct-06#section-2.1 166 | _M.Expect_CT = require "lpeg_patterns.http.expect_ct".Expect_CT 167 | 168 | -- https://www.w3.org/TR/referrer-policy/#referrer-policy-header 169 | _M.Referrer_Policy = require "lpeg_patterns.http.referrer_policy".Referrer_Policy 170 | 171 | return _M 172 | -------------------------------------------------------------------------------- /lpeg_patterns/http/alpn.lua: -------------------------------------------------------------------------------- 1 | -- RFC 7639 2 | 3 | local lpeg = require "lpeg" 4 | local http_core = require "lpeg_patterns.http.core" 5 | local util = require "lpeg_patterns.util" 6 | 7 | local Cmt = lpeg.Cmt 8 | local Cs = lpeg.Cs 9 | local P = lpeg.P 10 | local R = lpeg.R 11 | 12 | --[[ protocol-id is a percent-encoded ALPN protocol name 13 | - Octets in the ALPN protocol MUST NOT be percent-encoded if they 14 | are valid token characters except "%". 15 | - When using percent-encoding, uppercase hex digits MUST be used. 16 | ]] 17 | 18 | local valid_chars = http_core.tchar - P"%" 19 | local upper_hex = R("09", "AF") 20 | local percent_char = P"%" * (upper_hex * upper_hex / util.read_hex) / string.char 21 | local percent_encoded = Cmt(percent_char, function(_, _, c) 22 | -- check that decoded character would not have been allowed unescaped 23 | if not valid_chars:match(c) then 24 | return true, c 25 | end 26 | end) 27 | local percent_replace = Cs((valid_chars + percent_encoded)^0) 28 | 29 | local protocol_id = percent_replace 30 | 31 | local ALPN = http_core.comma_sep_trim(protocol_id, 1) 32 | 33 | return { 34 | protocol_id = protocol_id; 35 | ALPN = ALPN; 36 | } 37 | -------------------------------------------------------------------------------- /lpeg_patterns/http/alternate.lua: -------------------------------------------------------------------------------- 1 | -- RFC 7838 2 | -- HTTP Alternative Services 3 | 4 | local lpeg = require "lpeg" 5 | local http_alpn = require "lpeg_patterns.http.alpn" 6 | local http_core = require "lpeg_patterns.http.core" 7 | local http_semantics = require "lpeg_patterns.http.semantics" 8 | local uri = require "lpeg_patterns.uri" 9 | 10 | local C = lpeg.C 11 | local P = lpeg.P 12 | 13 | local clear = C"clear" -- case-sensitive 14 | local alt_authority = http_core.quoted_string -- containing [ uri_host ] ":" port 15 | local alternative = http_alpn.protocol_id * P"=" * alt_authority 16 | local alt_value = alternative * (http_core.OWS * P";" * http_core.OWS * http_semantics.parameter)^0 17 | local Alt_Svc = clear + http_core.comma_sep_trim(alt_value, 1) 18 | local Alt_Used = uri.host * (P":" * uri.port)^-1 19 | 20 | return { 21 | Alt_Svc = Alt_Svc; 22 | Alt_Used = Alt_Used; 23 | } 24 | -------------------------------------------------------------------------------- /lpeg_patterns/http/authentication.lua: -------------------------------------------------------------------------------- 1 | local lpeg = require "lpeg" 2 | local core = require "lpeg_patterns.core" 3 | local http_core = require "lpeg_patterns.http.core" 4 | 5 | local C = lpeg.C 6 | local Cf = lpeg.Cf 7 | local Cg = lpeg.Cg 8 | local Ct = lpeg.Ct 9 | local P = lpeg.P 10 | 11 | -- RFC 7235 Section 2 12 | local auth_scheme = http_core.token 13 | local auth_param = Cg(http_core.token / string.lower * http_core.BWS * P"=" * http_core.BWS * (http_core.token + http_core.quoted_string)) 14 | local token68 = C((core.ALPHA + core.DIGIT + P"-" + P"." + P"_" + P"~" + P"+" + P"/" )^1 * (P"=")^0) 15 | -- TODO: each parameter name MUST only occur once per challenge 16 | local challenge = auth_scheme * (core.SP^1 * (Cf(Ct(true) * http_core.comma_sep(auth_param), rawset) + token68))^-1 17 | local credentials = challenge 18 | 19 | -- RFC 7235 Section 4 20 | local WWW_Authenticate = http_core.comma_sep_trim(Ct(challenge), 1) 21 | local Authorization = credentials 22 | local Proxy_Authenticate = WWW_Authenticate 23 | local Proxy_Authorization = Authorization 24 | 25 | -- RFC 7615 26 | local Authentication_Info = http_core.comma_sep_trim(auth_param) 27 | local Proxy_Authentication_Info = http_core.comma_sep_trim(auth_param) 28 | 29 | return { 30 | Authentication_Info = Authentication_Info; 31 | Authorization = Authorization; 32 | Proxy_Authenticate = Proxy_Authenticate; 33 | Proxy_Authentication_Info = Proxy_Authentication_Info; 34 | Proxy_Authorization = Proxy_Authorization; 35 | WWW_Authenticate = WWW_Authenticate; 36 | } 37 | -------------------------------------------------------------------------------- /lpeg_patterns/http/caching.lua: -------------------------------------------------------------------------------- 1 | -- RFC 7234 2 | -- Hypertext Transfer Protocol (HTTP/1.1): Caching 3 | 4 | local lpeg = require "lpeg" 5 | local core = require "lpeg_patterns.core" 6 | local http_core = require "lpeg_patterns.http.core" 7 | local http_semantics = require "lpeg_patterns.http.semantics" 8 | local uri = require "lpeg_patterns.uri" 9 | 10 | local Cc = lpeg.Cc 11 | local Cg = lpeg.Cg 12 | local P = lpeg.P 13 | 14 | -- RFC 7234 Section 1.2.1 15 | local delta_seconds = core.DIGIT^1 / tonumber 16 | 17 | -- RFC 7234 Section 5.1 18 | local Age = delta_seconds 19 | 20 | -- RFC 7234 Section 5.2 21 | local cache_directive = http_core.token / string.lower * ((P"=" * (http_core.token + http_core.quoted_string)) + Cc(true)) 22 | local Cache_Control = http_core.comma_sep_trim(Cg(cache_directive), 1) 23 | 24 | -- RFC 7234 Section 5.3 25 | local Expires = http_semantics.HTTP_date 26 | 27 | -- RFC 7234 Section 5.4 28 | local extension_pragma = http_core.token * (P"=" * (http_core.token + http_core.quoted_string))^-1 29 | local pragma_directive = "no_cache" + extension_pragma 30 | local Pragma = http_core.comma_sep_trim(pragma_directive, 1) 31 | 32 | -- RFC 7234 Section 5.5 33 | local warn_code = core.DIGIT * core.DIGIT * core.DIGIT 34 | local warn_agent = (uri.host * (P":" * uri.port)^-1) + http_core.pseudonym 35 | local warn_text = http_core.quoted_string 36 | local warn_date = core.DQUOTE * http_semantics.HTTP_date * core.DQUOTE 37 | local warning_value = warn_code * core.SP * warn_agent * core.SP * warn_text * (core.SP * warn_date)^-1 38 | local Warning = http_core.comma_sep_trim(warning_value, 1) 39 | 40 | return { 41 | Age = Age; 42 | Cache_Control = Cache_Control; 43 | Expires = Expires; 44 | Pragma = Pragma; 45 | Warning = Warning; 46 | } 47 | -------------------------------------------------------------------------------- /lpeg_patterns/http/conditional.lua: -------------------------------------------------------------------------------- 1 | -- RFC 7232 2 | -- Hypertext Transfer Protocol (HTTP/1.1): Conditional Requests 3 | 4 | local lpeg = require "lpeg" 5 | local core = require "lpeg_patterns.core" 6 | local http_core = require "lpeg_patterns.http.core" 7 | local http_semantics = require "lpeg_patterns.http.semantics" 8 | 9 | local C = lpeg.C 10 | local Cc = lpeg.Cc 11 | local Cg = lpeg.Cg 12 | local P = lpeg.P 13 | local R = lpeg.R 14 | 15 | -- RFC 7232 Section 2.2 16 | local Last_Modified = http_semantics.HTTP_date 17 | 18 | -- RFC 7232 Section 2.3 19 | local weak = P"W/" -- case sensitive 20 | local etagc = P"\33" + R"\35\115" + http_core.obs_text 21 | local opaque_tag = core.DQUOTE * etagc^0 * core.DQUOTE 22 | local entity_tag = Cg(weak*Cc(true) + Cc(false), "weak") * C(opaque_tag) 23 | local ETag = entity_tag 24 | 25 | -- RFC 7232 Section 3.1 26 | local If_Match = P"*" + http_core.comma_sep(entity_tag, 1) 27 | 28 | -- RFC 7232 Section 3.2 29 | local If_None_Match = P"*" + http_core.comma_sep(entity_tag, 1) 30 | 31 | -- RFC 7232 Section 3.3 32 | local If_Modified_Since = http_semantics.HTTP_date 33 | 34 | -- RFC 7232 Section 3.4 35 | local If_Unmodified_Since = http_semantics.HTTP_date 36 | 37 | return { 38 | entity_tag = entity_tag; 39 | opaque_tag = opaque_tag; 40 | 41 | Last_Modified = Last_Modified; 42 | ETag = ETag; 43 | If_Match = If_Match; 44 | If_None_Match = If_None_Match; 45 | If_Modified_Since = If_Modified_Since; 46 | If_Unmodified_Since = If_Unmodified_Since; 47 | } 48 | -------------------------------------------------------------------------------- /lpeg_patterns/http/cookie.lua: -------------------------------------------------------------------------------- 1 | -- RFC 6265 2 | 3 | local lpeg = require "lpeg" 4 | local core = require "lpeg_patterns.core" 5 | local http_core = require "lpeg_patterns.http.core" 6 | 7 | local C = lpeg.C 8 | local Cc = lpeg.Cc 9 | local Cf = lpeg.Cf 10 | local Cg = lpeg.Cg 11 | local Ct = lpeg.Ct 12 | local P = lpeg.P 13 | local R = lpeg.R 14 | local S = lpeg.S 15 | 16 | local cookie_name = http_core.token 17 | local cookie_octet = S"!" + R("\35\43", "\45\58", "\60\91", "\93\126") 18 | local cookie_value = core.DQUOTE * C(cookie_octet^0) * core.DQUOTE + C(cookie_octet^0) 19 | local cookie_pair = cookie_name * http_core.BWS * P"=" * http_core.BWS * cookie_value * http_core.BWS 20 | 21 | local ext_char = core.CHAR - core.CTL - S";" 22 | ext_char = ext_char - core.WSP + core.WSP * #(core.WSP^0 * ext_char) -- No trailing whitespace 23 | -- Complexity is to make sure whitespace before an `=` isn't captured 24 | local extension_av = ((ext_char - S"=" - core.WSP) + core.WSP^1 * #(1-S"="))^0 / string.lower 25 | * http_core.BWS * P"=" * http_core.BWS * C(ext_char^0) 26 | + (ext_char)^0 / string.lower * Cc(true) 27 | local cookie_av = extension_av 28 | local set_cookie_string = cookie_pair * Cf(Ct(true) * (P";" * http_core.OWS * Cg(cookie_av))^0, rawset) 29 | local Set_Cookie = set_cookie_string 30 | 31 | local cookie_string = Cf(Ct(true) * Cg(cookie_pair) * (P";" * http_core.OWS * Cg(cookie_pair))^0, rawset) 32 | local Cookie = cookie_string 33 | 34 | return { 35 | Cookie = Cookie; 36 | Set_Cookie = Set_Cookie; 37 | } 38 | -------------------------------------------------------------------------------- /lpeg_patterns/http/core.lua: -------------------------------------------------------------------------------- 1 | -- RFC 7230 2 | -- Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing 3 | 4 | local lpeg = require "lpeg" 5 | local core = require "lpeg_patterns.core" 6 | local uri = require "lpeg_patterns.uri" 7 | local util = require "lpeg_patterns.util" 8 | 9 | local C = lpeg.C 10 | local Cc = lpeg.Cc 11 | local Cf = lpeg.Cf 12 | local Cg = lpeg.Cg 13 | local Cs = lpeg.Cs 14 | local Ct = lpeg.Ct 15 | local P = lpeg.P 16 | local R = lpeg.R 17 | local S = lpeg.S 18 | local V = lpeg.V 19 | 20 | -- RFC 7230 Section 3.2.3 21 | local OWS = (core.SP + core.HTAB)^0 22 | local RWS = (core.SP + core.HTAB)^1 23 | local BWS = OWS 24 | 25 | -- Analogue to RFC 7230 Section 7's ABNF extension of '#' 26 | -- Also documented as `#rule` under RFC 2616 Section 2.1 27 | local sep = OWS * lpeg.P "," * OWS 28 | local optional_sep = (lpeg.P"," + core.SP + core.HTAB)^0 29 | local function comma_sep(element, min, max) 30 | local extra = sep * optional_sep * element 31 | local patt = element 32 | if min then 33 | for _=2, min do 34 | patt = patt * extra 35 | end 36 | else 37 | min = 0 38 | patt = patt^-1 39 | end 40 | if max then 41 | local more = max-min-1 42 | patt = patt * extra^-more 43 | else 44 | patt = patt * extra^0 45 | end 46 | return patt 47 | end 48 | -- allows leading + trailing 49 | local function comma_sep_trim(...) 50 | return optional_sep * comma_sep(...) * optional_sep 51 | end 52 | 53 | -- RFC 7230 Section 2.6 54 | local HTTP_name = P"HTTP" 55 | local HTTP_version = HTTP_name * P"/" * (core.DIGIT * P"." * core.DIGIT / util.safe_tonumber) 56 | 57 | -- RFC 7230 Section 2.7 58 | local absolute_path = (P"/" * uri.segment )^1 59 | local partial_uri = Ct(uri.relative_part * (P"?" * uri.query)^-1) 60 | 61 | -- RFC 7230 Section 3.2.6 62 | local tchar = S "!#$%&'*+-.^_`|~" + core.DIGIT + core.ALPHA 63 | local token = C(tchar^1) 64 | local obs_text = R("\128\255") 65 | local qdtext = core.HTAB + core.SP + P"\33" + R("\35\91", "\93\126") + obs_text 66 | local quoted_pair = Cs(P"\\" * C(core.HTAB + core.SP + core.VCHAR + obs_text) / "%1") 67 | local quoted_string = core.DQUOTE * Cs((qdtext + quoted_pair)^0) * core.DQUOTE 68 | 69 | local ctext = core.HTAB + core.SP + R("\33\39", "\42\91", "\93\126") + obs_text 70 | local comment = P { P"(" * ( ctext + quoted_pair + V(1) )^0 * P")" } 71 | 72 | -- RFC 7230 Section 3.2 73 | local field_name = token / string.lower -- case insensitive 74 | local field_vchar = core.VCHAR + obs_text 75 | local field_content = field_vchar * (( core.SP + core.HTAB )^1 * field_vchar)^-1 76 | local obs_fold = ( core.SP + core.HTAB )^0 * core.CRLF * ( core.SP + core.HTAB )^1 / " " 77 | -- field_value is not correct, see Errata: https://www.rfc-editor.org/errata_search.php?rfc=7230&eid=4189 78 | local field_value = Cs((field_content + obs_fold)^0) 79 | local header_field = field_name * P":" * OWS * field_value * OWS 80 | 81 | -- RFC 7230 Section 3.3.2 82 | local Content_Length = core.DIGIT^1 83 | 84 | -- RFC 7230 Section 4 85 | -- See https://www.rfc-editor.org/errata_search.php?rfc=7230&eid=4683 86 | local transfer_parameter = (token - S"qQ" * BWS * P"=") * BWS * P"=" * BWS * ( token + quoted_string ) 87 | local transfer_extension = Cf(Ct(token / string.lower) -- case insensitive 88 | * ( OWS * P";" * OWS * Cg(transfer_parameter) )^0, rawset) 89 | local transfer_coding = transfer_extension 90 | 91 | -- RFC 7230 Section 3.3.1 92 | local Transfer_Encoding = comma_sep_trim(transfer_coding, 1) 93 | 94 | -- RFC 7230 Section 4.1.1 95 | local chunk_ext_name = token 96 | local chunk_ext_val = token + quoted_string 97 | -- See https://www.rfc-editor.org/errata_search.php?rfc=7230&eid=4667 98 | local chunk_ext = ( P";" * chunk_ext_name * ( P"=" * chunk_ext_val)^-1 )^0 99 | 100 | -- RFC 7230 Section 4.3 101 | local rank = (P"0" * ((P"." * core.DIGIT^-3) / util.safe_tonumber + Cc(0)) + P"1" * ("." * (P"0")^-3)^-1) * Cc(1) 102 | local t_ranking = OWS * P";" * OWS * S"qQ" * P"=" * rank -- q is case insensitive 103 | local t_codings = (transfer_coding * t_ranking^-1) / function(t, q) 104 | if q then 105 | t["q"] = q 106 | end 107 | return t 108 | end 109 | local TE = comma_sep_trim(t_codings) 110 | 111 | -- RFC 7230 Section 4.4 112 | local Trailer = comma_sep_trim(field_name, 1) 113 | 114 | -- RFC 7230 Section 5.3 115 | local origin_form = Cs(absolute_path * (P"?" * uri.query)^-1) 116 | local absolute_form = util.no_rich_capture(uri.absolute_uri) 117 | local authority_form = util.no_rich_capture(uri.authority) 118 | local asterisk_form = C"*" 119 | local request_target = asterisk_form + origin_form + absolute_form + authority_form 120 | 121 | -- RFC 7230 Section 3.1.1 122 | local method = token 123 | local request_line = method * core.SP * request_target * core.SP * HTTP_version * core.CRLF 124 | 125 | -- RFC 7230 Section 5.4 126 | local Host = uri.host * (P":" * uri.port)^-1 127 | 128 | -- RFC 7230 Section 6.7 129 | local protocol_name = token 130 | local protocol_version = token 131 | local protocol = protocol_name * (P"/" * protocol_version)^-1 / "%0" 132 | local Upgrade = comma_sep_trim(protocol) 133 | 134 | -- RFC 7230 Section 5.7.1 135 | local received_protocol = (protocol_name * P"/" + Cc("HTTP")) * protocol_version / "%1/%2" 136 | local pseudonym = token 137 | -- workaround for https://lists.w3.org/Archives/Public/ietf-http-wg/2016OctDec/0527.html 138 | local received_by = uri.host * ((P":" * uri.port) + -lpeg.B(",")) / "%0" + pseudonym 139 | local Via = comma_sep_trim(Ct( 140 | Cg(received_protocol, "protocol") 141 | * RWS * Cg(received_by, "by") 142 | * (RWS * Cg(comment, "comment"))^-1 143 | ), 1) 144 | 145 | -- RFC 7230 Section 6.1 146 | local connection_option = token / string.lower -- case insensitive 147 | local Connection = comma_sep_trim(connection_option) 148 | 149 | return { 150 | comma_sep = comma_sep; 151 | comma_sep_trim = comma_sep_trim; 152 | 153 | OWS = OWS; 154 | RWS = RWS; 155 | BWS = BWS; 156 | 157 | chunk_ext = chunk_ext; 158 | comment = comment; 159 | field_name = field_name; 160 | field_value = field_value; 161 | header_field = header_field; 162 | method = method; 163 | obs_text = obs_text; 164 | partial_uri = partial_uri; 165 | pseudonym = pseudonym; 166 | qdtext = qdtext; 167 | quoted_string = quoted_string; 168 | rank = rank; 169 | request_line = request_line; 170 | request_target = request_target; 171 | t_ranking = t_ranking; 172 | tchar = tchar; 173 | token = token; 174 | 175 | Connection = Connection; 176 | Content_Length = Content_Length; 177 | Host = Host; 178 | TE = TE; 179 | Trailer = Trailer; 180 | Transfer_Encoding = Transfer_Encoding; 181 | Upgrade = Upgrade; 182 | Via = Via; 183 | } 184 | -------------------------------------------------------------------------------- /lpeg_patterns/http/disposition.lua: -------------------------------------------------------------------------------- 1 | -- RFC 6266 2 | -- Use of the Content-Disposition Header Field in the 3 | -- Hypertext Transfer Protocol (HTTP) 4 | 5 | local lpeg = require "lpeg" 6 | local http_core = require "lpeg_patterns.http.core" 7 | local http_parameters = require "lpeg_patterns.http.parameters" 8 | 9 | local C = lpeg.C 10 | local Cf = lpeg.Cf 11 | local Cg = lpeg.Cg 12 | local Ct = lpeg.Ct 13 | local P = lpeg.P 14 | 15 | local disp_ext_type = http_core.token / string.lower 16 | local disposition_type = disp_ext_type 17 | -- can't use 'token' here as we need to not include the "*" at the end 18 | local ext_token = C((http_core.tchar-P"*"*(-http_core.tchar))^1) * P"*" 19 | local value = http_core.token + http_core.quoted_string 20 | local disp_ext_parm = ext_token * http_core.OWS * P"=" * http_core.OWS * http_parameters.ext_value 21 | + http_core.token * http_core.OWS * P"=" * http_core.OWS * value 22 | local disposition_parm = disp_ext_parm 23 | local Content_Disposition = disposition_type * Cf(Ct(true) * (http_core.OWS * P";" * http_core.OWS * Cg(disposition_parm))^0, rawset) 24 | 25 | return { 26 | Content_Disposition = Content_Disposition; 27 | } 28 | -------------------------------------------------------------------------------- /lpeg_patterns/http/expect_ct.lua: -------------------------------------------------------------------------------- 1 | -- https://tools.ietf.org/html/draft-ietf-httpbis-expect-ct-06#section-2.1 2 | 3 | local http_core = require "lpeg_patterns.http.core" 4 | local http_utils = require "lpeg_patterns.http.util" 5 | 6 | local expect_ct_directive = http_utils.directive 7 | local Expect_CT = http_utils.no_dup(http_core.comma_sep_trim(expect_ct_directive)) 8 | 9 | return { 10 | Expect_CT = Expect_CT; 11 | } 12 | -------------------------------------------------------------------------------- /lpeg_patterns/http/forwarded.lua: -------------------------------------------------------------------------------- 1 | -- RFC 7239 2 | -- Forwarded HTTP Extension 3 | 4 | local lpeg = require "lpeg" 5 | local http_core = require "lpeg_patterns.http.core" 6 | 7 | local P = lpeg.P 8 | 9 | -- RFC 7239 Section 4 10 | local value = http_core.token + http_core.quoted_string 11 | local forwarded_pair = http_core.token * P"=" * value 12 | local forwarded_element = forwarded_pair^-1 * (P";" * forwarded_pair^-1)^0 13 | local Forwarded = http_core.comma_sep_trim(forwarded_element) 14 | 15 | return { 16 | Forwarded = Forwarded; 17 | } 18 | -------------------------------------------------------------------------------- /lpeg_patterns/http/frameoptions.lua: -------------------------------------------------------------------------------- 1 | -- RFC 7034 2 | 3 | local lpeg = require "lpeg" 4 | local http_core = require "lpeg_patterns.http.core" 5 | local util = require "lpeg_patterns.util" 6 | 7 | local case_insensitive = util.case_insensitive 8 | local Cc = lpeg.Cc 9 | 10 | local X_Frame_Options = case_insensitive "deny" * Cc("deny") 11 | + case_insensitive "sameorigin" * Cc("sameorigin") 12 | + case_insensitive "allow-from" * http_core.RWS * require "lpeg_patterns.http.origin".serialized_origin 13 | 14 | return { 15 | X_Frame_Options = X_Frame_Options; 16 | } 17 | -------------------------------------------------------------------------------- /lpeg_patterns/http/hoba.lua: -------------------------------------------------------------------------------- 1 | -- RFC 7486 2 | -- HTTP Origin-Bound Authentication (HOBA) 3 | 4 | local lpeg = require "lpeg" 5 | 6 | local C = lpeg.C 7 | 8 | local Hobareg = C"regok" + C"reginwork" 9 | 10 | return { 11 | Hobareg = Hobareg; 12 | } 13 | -------------------------------------------------------------------------------- /lpeg_patterns/http/link.lua: -------------------------------------------------------------------------------- 1 | -- RFC 5988 2 | 3 | local lpeg = require "lpeg" 4 | local core = require "lpeg_patterns.core" 5 | local http_core = require "lpeg_patterns.http.core" 6 | local http_parameters = require "lpeg_patterns.http.parameters" 7 | local uri = require "lpeg_patterns.uri" 8 | 9 | local Cf = lpeg.Cf 10 | local Cg = lpeg.Cg 11 | local Ct = lpeg.Ct 12 | local P = lpeg.P 13 | local S = lpeg.S 14 | 15 | local ptokenchar = S"!#$%&'()*+-./:<=>?@[]^_`{|}~" + core.DIGIT + core.ALPHA 16 | local ptoken = ptokenchar^1 17 | local ext_name_star = http_parameters.parmname * P"*" 18 | local link_extension = ext_name_star * P"=" * http_parameters.ext_value 19 | + http_parameters.parmname * (P"=" * (ptoken + http_core.quoted_string))^-1 20 | -- See https://www.rfc-editor.org/errata_search.php?rfc=5988&eid=3158 21 | local link_param = link_extension 22 | local link_value = Cf(Ct(P"<" * uri.uri_reference * P">") * (http_core.OWS * P";" * http_core.OWS * Cg(link_param))^0, rawset) 23 | -- TODO: handle multiple ext_value variants... 24 | -- e.g. server might provide one title in english, one in chinese, client should be able to pick which one to display 25 | 26 | local Link = http_core.comma_sep_trim(link_value) 27 | 28 | return { 29 | Link = Link; 30 | } 31 | -------------------------------------------------------------------------------- /lpeg_patterns/http/origin.lua: -------------------------------------------------------------------------------- 1 | -- RFC 6454 2 | 3 | local lpeg = require "lpeg" 4 | local core = require "lpeg_patterns.core" 5 | local http_core = require "lpeg_patterns.http.core" 6 | local uri = require "lpeg_patterns.uri" 7 | 8 | local C = lpeg.C 9 | local P = lpeg.P 10 | 11 | -- discard captures from scheme, host, port and just get whole string 12 | local serialized_origin = C(uri.scheme * P"://" * uri.host * (P":" * uri.port)^-1/function() end) 13 | local origin_list = serialized_origin * (core.SP * serialized_origin)^0 14 | local origin_list_or_null = P"null" + origin_list 15 | local Origin = http_core.OWS * origin_list_or_null * http_core.OWS 16 | 17 | return { 18 | serialized_origin = serialized_origin; 19 | Origin = Origin; 20 | } 21 | -------------------------------------------------------------------------------- /lpeg_patterns/http/parameters.lua: -------------------------------------------------------------------------------- 1 | -- RFC 5987 2 | -- Character Set and Language Encoding for 3 | -- Hypertext Transfer Protocol (HTTP) Header Field Parameters 4 | 5 | local lpeg = require "lpeg" 6 | local core = require "lpeg_patterns.core" 7 | local language = require "lpeg_patterns.language" 8 | local util = require "lpeg_patterns.util" 9 | 10 | local C = lpeg.C 11 | local Cg = lpeg.Cg 12 | local Cs = lpeg.Cs 13 | local P = lpeg.P 14 | local S = lpeg.S 15 | 16 | local attr_char = core.ALPHA + core.DIGIT + S"!#$&+-.^_`|~" 17 | -- can't use uri.pct_encoded, as it doesn't decode all characters 18 | local pct_encoded = P"%" * (core.HEXDIG * core.HEXDIG / util.read_hex) / string.char 19 | local value_chars = Cs((pct_encoded + attr_char)^0) 20 | local parmname = C(attr_char^1) 21 | -- ext-value uses charset from RFC 5987 22 | local mime_charsetc = core.ALPHA + core.DIGIT + S"!#$%&+-^_`{}~" 23 | local mime_charset = C(mime_charsetc^1) 24 | local ext_value = Cg(mime_charset, "charset") * P"'" * Cg(language.Language_Tag, "language")^-1 * P"'" * value_chars 25 | 26 | return { 27 | ext_value = ext_value; 28 | parmname = parmname; 29 | } 30 | -------------------------------------------------------------------------------- /lpeg_patterns/http/pkp.lua: -------------------------------------------------------------------------------- 1 | local lpeg = require "lpeg" 2 | local http_core = require "lpeg_patterns.http.core" 3 | local http_utils = require "lpeg_patterns.http.util" 4 | 5 | local Cmt = lpeg.Cmt 6 | local P = lpeg.P 7 | 8 | -- RFC 7469 9 | local Public_Key_Directives = http_utils.directive * (http_core.OWS * P";" * http_core.OWS * http_utils.directive)^0 10 | local function pkp_cmt(pins, t, k, v, ...) 11 | -- duplicates are allowed if the directive name starts with "pin-" 12 | local pin_name = k:match("^pin%-(.+)") 13 | if pin_name then 14 | local hashes = pins[pin_name] 15 | if hashes then 16 | hashes[#hashes+1] = v 17 | else 18 | hashes = {v} 19 | pins[pin_name] = hashes 20 | end 21 | else 22 | local old = t[k] 23 | if old then 24 | return false 25 | end 26 | t[k] = v 27 | end 28 | if ... then 29 | return pkp_cmt(pins, t, ...) 30 | else 31 | return true 32 | end 33 | end 34 | local Public_Key_Pins = Cmt(Public_Key_Directives, function(_, _, ...) 35 | local pins = {} 36 | local t = {} 37 | local ok = pkp_cmt(pins, t, ...) 38 | if ok and t["max-age"] then 39 | return true, pins, t 40 | end 41 | end) 42 | local Public_Key_Pins_Report_Only = Cmt(Public_Key_Directives, function(_, _, ...) 43 | local pins = {} 44 | local t = {} 45 | local ok = pkp_cmt(pins, t, ...) 46 | if ok then 47 | return true, pins, t 48 | end 49 | end) 50 | 51 | return { 52 | Public_Key_Pins = Public_Key_Pins; 53 | Public_Key_Pins_Report_Only = Public_Key_Pins_Report_Only; 54 | } 55 | -------------------------------------------------------------------------------- /lpeg_patterns/http/range.lua: -------------------------------------------------------------------------------- 1 | -- RFC 7233 2 | -- Hypertext Transfer Protocol (HTTP/1.1): Range Requests 3 | 4 | local lpeg = require "lpeg" 5 | local core = require "lpeg_patterns.core" 6 | local http_conditional = require "lpeg_patterns.http.conditional" 7 | local http_core = require "lpeg_patterns.http.core" 8 | local http_semantics = require "lpeg_patterns.http.semantics" 9 | 10 | local C = lpeg.C 11 | local Cc = lpeg.Cc 12 | local P = lpeg.P 13 | 14 | local bytes_unit = P"bytes" 15 | local other_range_unit = http_core.token 16 | local range_unit = C(bytes_unit) + other_range_unit 17 | 18 | local first_byte_pos = core.DIGIT^1 / tonumber 19 | local last_byte_pos = core.DIGIT^1 / tonumber 20 | local byte_range_spec = first_byte_pos * P"-" * last_byte_pos^-1 21 | local suffix_length = core.DIGIT^1 / tonumber 22 | local suffix_byte_range_spec = Cc(nil) * P"-" * suffix_length 23 | local byte_range_set = http_core.comma_sep(byte_range_spec + suffix_byte_range_spec, 1) 24 | local byte_ranges_specifier = bytes_unit * P"=" * byte_range_set 25 | 26 | -- RFC 7233 Section 2.3 27 | local acceptable_ranges = http_core.comma_sep_trim(range_unit, 1) + P"none" 28 | local Accept_Ranges = acceptable_ranges 29 | 30 | -- RFC 7233 Section 3.1 31 | local other_range_set = core.VCHAR^1 32 | local other_ranges_specifier = other_range_unit * P"=" * other_range_set 33 | local Range = byte_ranges_specifier + other_ranges_specifier 34 | 35 | -- RFC 7233 Section 3.2 36 | local If_Range = http_conditional.entity_tag + http_semantics.HTTP_date 37 | 38 | -- RFC 7233 Section 4.2 39 | local complete_length = core.DIGIT^1 / tonumber 40 | local unsatisfied_range = P"*/" * complete_length 41 | local byte_range = first_byte_pos * P"-" * last_byte_pos 42 | local byte_range_resp = byte_range * P"/" * (complete_length + P"*") 43 | local byte_content_range = bytes_unit * core.SP * (byte_range_resp + unsatisfied_range) 44 | local other_range_resp = core.CHAR^0 45 | local other_content_range = other_range_unit * core.SP * other_range_resp 46 | local Content_Range = byte_content_range + other_content_range 47 | 48 | return { 49 | Accept_Ranges = Accept_Ranges; 50 | Range = Range; 51 | If_Range = If_Range; 52 | Content_Range = Content_Range; 53 | } 54 | -------------------------------------------------------------------------------- /lpeg_patterns/http/referrer_policy.lua: -------------------------------------------------------------------------------- 1 | -- https://www.w3.org/TR/referrer-policy/#referrer-policy-header 2 | 3 | local lpeg = require "lpeg" 4 | local http_core = require "lpeg_patterns.http.core" 5 | 6 | local C = lpeg.C 7 | 8 | local policy_token = C"no-referrer" 9 | + C"no-referrer-when-downgrade" 10 | + C"strict-origin" 11 | + C"strict-origin-when-cross-origin" 12 | + C"same-origin" 13 | + C"origin" 14 | + C"origin-when-cross-origin" 15 | + C"unsafe-url" 16 | local Referrer_Policy = http_core.comma_sep_trim(policy_token, 1) 17 | 18 | return { 19 | Referrer_Policy = Referrer_Policy; 20 | } 21 | -------------------------------------------------------------------------------- /lpeg_patterns/http/semantics.lua: -------------------------------------------------------------------------------- 1 | -- RFC 7231 2 | -- Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content 3 | 4 | local lpeg = require "lpeg" 5 | local core = require "lpeg_patterns.core" 6 | local http_core = require "lpeg_patterns.http.core" 7 | local email = require "lpeg_patterns.email" 8 | local language = require "lpeg_patterns.language" 9 | local uri = require "lpeg_patterns.uri" 10 | 11 | local Cc = lpeg.Cc 12 | local Cf = lpeg.Cf 13 | local Cg = lpeg.Cg 14 | local Ct = lpeg.Ct 15 | local P = lpeg.P 16 | local S = lpeg.S 17 | 18 | 19 | -- RFC 7231 Section 3.1.1 20 | local content_coding = http_core.token / string.lower -- case insensitive 21 | local Content_Encoding = http_core.comma_sep_trim(content_coding, 1) 22 | 23 | -- RFC 7231 Section 3.1.2 24 | local type = http_core.token / string.lower -- case insensitive 25 | local subtype = http_core.token / string.lower -- case insensitive 26 | local parameter = http_core.token / string.lower -- case insensitive 27 | * P"=" * (http_core.token + http_core.quoted_string) 28 | local media_type = Cg(type, "type") * P"/" * Cg(subtype, "subtype") 29 | * Cg(Cf(Ct(true) * (http_core.OWS * P";" * http_core.OWS * Cg(parameter))^0, rawset), "parameters") 30 | local charset = http_core.token / string.lower -- case insensitive 31 | local Content_Type = Ct(media_type) 32 | 33 | -- RFC 7231 Section 3.1.3 34 | local Content_Language = http_core.comma_sep_trim(language.Language_Tag, 1) 35 | 36 | -- RFC 7231 Section 3.1.4.2 37 | local Content_Location = uri.absolute_uri + http_core.partial_uri 38 | 39 | -- RFC 7231 Section 5.1.1 40 | local Expect = P"100-"*S"cC"*S"oO"*S"nN"*S"tT"*S"iI"*S"nN"*S"uU"*S"eE" * Cc("100-continue") 41 | 42 | -- RFC 7231 Section 5.1.2 43 | local Max_Forwards = core.DIGIT^1 / tonumber 44 | 45 | -- RFC 7231 Section 5.3.1 46 | -- local qvalue = http_core.rank -- luacheck: ignore 211 47 | local weight = http_core.t_ranking 48 | 49 | -- RFC 7231 Section 5.3.2 50 | local media_range = (P"*/*" 51 | + (Cg(type, "type") * P"/*") 52 | + (Cg(type, "type") * P"/" * Cg(subtype, "subtype")) 53 | ) * Cg(Cf(Ct(true) * (http_core.OWS * ";" * http_core.OWS * Cg(parameter) - weight)^0, rawset), "parameters") 54 | local accept_ext = http_core.OWS * P";" * http_core.OWS * http_core.token * (P"=" * (http_core.token + http_core.quoted_string))^-1 55 | local accept_params = Cg(weight, "q") * Cg(Cf(Ct(true) * Cg(accept_ext)^0, rawset), "extensions") 56 | local Accept = http_core.comma_sep_trim(Ct(media_range * (accept_params+Cg(Ct(true), "extensions")))) 57 | 58 | -- RFC 7231 Section 5.3.3 59 | local Accept_Charset = http_core.comma_sep_trim((charset + P"*") * weight^-1, 1) 60 | 61 | -- RFC 7231 Section 5.3.4 62 | local codings = content_coding + "*" 63 | local Accept_Encoding = http_core.comma_sep_trim(codings * weight^-1) 64 | 65 | -- RFC 4647 Section 2.1 66 | local alphanum = core.ALPHA + core.DIGIT 67 | local language_range = (core.ALPHA * core.ALPHA^-7 * (P"-" * alphanum * alphanum^-7)^0) + P"*" 68 | -- RFC 7231 Section 5.3.5 69 | local Accept_Language = http_core.comma_sep_trim(language_range * weight^-1, 1) 70 | 71 | -- RFC 7231 Section 5.5.1 72 | local From = email.mailbox 73 | 74 | -- RFC 7231 Section 5.5.2 75 | local Referer = uri.absolute_uri + http_core.partial_uri 76 | 77 | -- RFC 7231 Section 5.5.3 78 | local product_version = http_core.token 79 | local product = http_core.token * (P"/" * product_version)^-1 80 | local User_Agent = product * (http_core.RWS * (product + http_core.comment))^0 81 | 82 | -- RFC 7231 Section 7.1.1.1 83 | -- Uses os.date field names 84 | local day_name = Cg(P"Mon"*Cc(2) 85 | + P"Tue"*Cc(3) 86 | + P"Wed"*Cc(4) 87 | + P"Thu"*Cc(5) 88 | + P"Fri"*Cc(6) 89 | + P"Sat"*Cc(7) 90 | + P"Sun"*Cc(1), "wday") 91 | local day = Cg(core.DIGIT * core.DIGIT / tonumber, "day") 92 | local month = Cg(P"Jan"*Cc(1) 93 | + P"Feb"*Cc(2) 94 | + P"Mar"*Cc(3) 95 | + P"Apr"*Cc(4) 96 | + P"May"*Cc(5) 97 | + P"Jun"*Cc(6) 98 | + P"Jul"*Cc(7) 99 | + P"Aug"*Cc(8) 100 | + P"Sep"*Cc(9) 101 | + P"Oct"*Cc(10) 102 | + P"Nov"*Cc(11) 103 | + P"Dec"*Cc(12), "month") 104 | local year = Cg(core.DIGIT * core.DIGIT * core.DIGIT * core.DIGIT / tonumber, "year") 105 | local date1 = day * core.SP * month * core.SP * year 106 | 107 | local GMT = P"GMT" 108 | 109 | local minute = Cg(core.DIGIT * core.DIGIT / tonumber, "min") 110 | local second = Cg(core.DIGIT * core.DIGIT / tonumber, "sec") 111 | local hour = Cg(core.DIGIT * core.DIGIT / tonumber, "hour") 112 | -- XXX only match 00:00:00 - 23:59:60 (leap second)? 113 | 114 | local time_of_day = hour * P":" * minute * P":" * second 115 | local IMF_fixdate = Ct(day_name * P"," * core.SP * date1 * core.SP * time_of_day * core.SP * GMT) 116 | 117 | local date2 do 118 | local year_barrier = 70 119 | local twodayyear = Cg(core.DIGIT * core.DIGIT / function(y) 120 | y = tonumber(y, 10) 121 | if y < year_barrier then 122 | return 2000+y 123 | else 124 | return 1900+y 125 | end 126 | end, "year") 127 | date2 = day * P"-" * month * P"-" * twodayyear 128 | end 129 | local day_name_l = Cg(P"Monday"*Cc(2) 130 | + P"Tuesday"*Cc(3) 131 | + P"Wednesday"*Cc(4) 132 | + P"Thursday"*Cc(5) 133 | + P"Friday"*Cc(6) 134 | + P"Saturday"*Cc(7) 135 | + P"Sunday"*Cc(1), "wday") 136 | local rfc850_date = Ct(day_name_l * P"," * core.SP * date2 * core.SP * time_of_day * core.SP * GMT) 137 | 138 | local date3 = month * core.SP * (day + Cg(core.SP * core.DIGIT / tonumber, "day")) 139 | local asctime_date = Ct(day_name * core.SP * date3 * core.SP * time_of_day * core.SP * year) 140 | local obs_date = rfc850_date + asctime_date 141 | 142 | local HTTP_date = IMF_fixdate + obs_date 143 | local Date = HTTP_date 144 | 145 | -- RFC 7231 Section 7.1.2 146 | local Location = uri.uri_reference 147 | 148 | -- RFC 7231 Section 7.1.3 149 | local delay_seconds = core.DIGIT^1 / tonumber 150 | local Retry_After = HTTP_date + delay_seconds 151 | 152 | -- RFC 7231 Section 7.1.4 153 | local Vary = P"*" + http_core.comma_sep(http_core.field_name, 1) 154 | 155 | -- RFC 7231 Section 7.4.1 156 | local Allow = http_core.comma_sep_trim(http_core.method) 157 | 158 | -- RFC 7231 Section 7.4.2 159 | local Server = product * (http_core.RWS * (product + http_core.comment))^0 160 | 161 | return { 162 | HTTP_date = HTTP_date; 163 | IMF_fixdate = IMF_fixdate; 164 | media_type = media_type; 165 | parameter = parameter; 166 | 167 | Accept = Accept; 168 | Accept_Charset = Accept_Charset; 169 | Accept_Encoding = Accept_Encoding; 170 | Accept_Language = Accept_Language; 171 | Allow = Allow; 172 | Content_Encoding = Content_Encoding; 173 | Content_Language = Content_Language; 174 | Content_Location = Content_Location; 175 | Content_Type = Content_Type; 176 | Date = Date; 177 | Expect = Expect; 178 | From = From; 179 | Location = Location; 180 | Max_Forwards = Max_Forwards; 181 | Referer = Referer; 182 | Retry_After = Retry_After; 183 | Server = Server; 184 | User_Agent = User_Agent; 185 | Vary = Vary; 186 | } 187 | -------------------------------------------------------------------------------- /lpeg_patterns/http/slug.lua: -------------------------------------------------------------------------------- 1 | -- RFC 5023 2 | 3 | local lpeg = require "lpeg" 4 | local core = require "lpeg_patterns.core" 5 | local http_core = require "lpeg_patterns.http.core" 6 | local util = require "lpeg_patterns.util" 7 | 8 | local Cs = lpeg.Cs 9 | local P = lpeg.P 10 | local R = lpeg.R 11 | 12 | local slugtext = http_core.RWS / " " 13 | + P"%" * (core.HEXDIG * core.HEXDIG / util.read_hex) / string.char 14 | + R"\32\126" 15 | 16 | local SLUG = Cs(slugtext^0) 17 | 18 | return { 19 | SLUG = SLUG; 20 | } 21 | -------------------------------------------------------------------------------- /lpeg_patterns/http/sts.lua: -------------------------------------------------------------------------------- 1 | -- RFC 6797 2 | 3 | local lpeg = require "lpeg" 4 | local http_core = require "lpeg_patterns.http.core" 5 | local http_utils = require "lpeg_patterns.http.util" 6 | 7 | local P = lpeg.P 8 | 9 | local Strict_Transport_Security = http_utils.no_dup(http_utils.directive^-1 * (http_core.OWS * P";" * http_core.OWS * http_utils.directive^-1)^0) 10 | 11 | return { 12 | Strict_Transport_Security = Strict_Transport_Security; 13 | } 14 | -------------------------------------------------------------------------------- /lpeg_patterns/http/util.lua: -------------------------------------------------------------------------------- 1 | -- This is a private module containing utility functions shared by various http parsers 2 | 3 | local lpeg = require "lpeg" 4 | local http_core = require "lpeg_patterns.http.core" 5 | 6 | local P = lpeg.P 7 | local Cc = lpeg.Cc 8 | local Cg = lpeg.Cg 9 | local Ct = lpeg.Ct 10 | local Cmt = lpeg.Cmt 11 | 12 | local directive_name = http_core.token / string.lower 13 | local directive_value = http_core.token + http_core.quoted_string 14 | local directive = Cg(directive_name * ((http_core.OWS * P"=" * http_core.OWS * directive_value) + Cc(true))) 15 | 16 | -- Helper function that doesn't match if there are duplicate keys 17 | local function no_dup_cmt(s, i, t, name, value, ...) 18 | local old = t[name] 19 | if old then 20 | return false 21 | end 22 | t[name] = value 23 | if ... then 24 | return no_dup_cmt(s, i, t, ...) 25 | elseif t["max-age"] then -- max-age is required 26 | return true, t 27 | end 28 | -- else return nil 29 | end 30 | 31 | local function no_dup(patt) 32 | return Cmt(Ct(true) * patt, no_dup_cmt) 33 | end 34 | 35 | return { 36 | directive = directive; 37 | no_dup = no_dup; 38 | } 39 | -------------------------------------------------------------------------------- /lpeg_patterns/http/webdav.lua: -------------------------------------------------------------------------------- 1 | -- WebDAV 2 | 3 | local lpeg = require "lpeg" 4 | local core = require "lpeg_patterns.core" 5 | local http_conditional = require "lpeg_patterns.http.conditional" 6 | local http_core = require "lpeg_patterns.http.core" 7 | local uri = require "lpeg_patterns.uri" 8 | local util = require "lpeg_patterns.util" 9 | 10 | local case_insensitive = util.case_insensitive 11 | 12 | local Cc = lpeg.Cc 13 | local P = lpeg.P 14 | local S = lpeg.S 15 | 16 | local T_F = S"Tt" * Cc(true) + S"Ff" * Cc(false) 17 | 18 | -- RFC 4918 19 | local Coded_URL = P"<" * uri.absolute_uri * P">" 20 | local extend = Coded_URL + http_core.token 21 | local compliance_class = P"1" + P"2" + P"3" + extend 22 | local DAV = http_core.comma_sep_trim(compliance_class) 23 | local Depth = P"0" * Cc(0) 24 | + P"1" * Cc(1) 25 | + case_insensitive "infinity" * Cc(math.huge) 26 | local Simple_ref = uri.absolute_uri + http_core.partial_uri 27 | local Destination = Simple_ref 28 | local State_token = Coded_URL 29 | local Condition = (case_insensitive("not") * Cc("not"))^-1 30 | * http_core.OWS * (State_token + P"[" * http_conditional.entity_tag * P"]") 31 | local List = P"(" * http_core.OWS * (Condition * http_core.OWS)^1 * P")" 32 | local No_tag_list = List 33 | local Resource_Tag = P"<" * Simple_ref * P">" 34 | local Tagged_list = Resource_Tag * http_core.OWS * (List * http_core.OWS)^1 35 | local If = (Tagged_list * http_core.OWS)^1 + (No_tag_list * http_core.OWS)^1 36 | local Lock_Token = Coded_URL 37 | local Overwrite = T_F 38 | local DAVTimeOutVal = core.DIGIT^1 / tonumber 39 | local TimeType = case_insensitive "Second-" * DAVTimeOutVal 40 | + case_insensitive "Infinite" * Cc(math.huge) 41 | local TimeOut = http_core.comma_sep_trim(TimeType) 42 | 43 | -- RFC 5323 44 | local DASL = http_core.comma_sep_trim(Coded_URL, 1) 45 | 46 | -- RFC 6638 47 | local Schedule_Reply = T_F 48 | local Schedule_Tag = http_conditional.opaque_tag 49 | local If_Schedule_Tag_Match = http_conditional.opaque_tag 50 | 51 | -- RFC 7809 52 | local CalDAV_Timezones = T_F 53 | 54 | return { 55 | CalDAV_Timezones = CalDAV_Timezones; 56 | DASL = DASL; 57 | DAV = DAV; 58 | Depth = Depth; 59 | Destination = Destination; 60 | If = If; 61 | If_Schedule_Tag_Match = If_Schedule_Tag_Match; 62 | Lock_Token = Lock_Token; 63 | Overwrite = Overwrite; 64 | Schedule_Reply = Schedule_Reply; 65 | Schedule_Tag = Schedule_Tag; 66 | TimeOut = TimeOut; 67 | } 68 | -------------------------------------------------------------------------------- /lpeg_patterns/http/websocket.lua: -------------------------------------------------------------------------------- 1 | local lpeg = require "lpeg" 2 | local core = require "lpeg_patterns.core" 3 | local http_core = require "lpeg_patterns.http.core" 4 | 5 | local Cc = lpeg.Cc 6 | local Cf = lpeg.Cf 7 | local Cg = lpeg.Cg 8 | local Ct = lpeg.Ct 9 | local Cmt = lpeg.Cmt 10 | local P = lpeg.P 11 | local S = lpeg.S 12 | 13 | -- RFC 6455 14 | local base64_character = core.ALPHA + core.DIGIT + S"+/" 15 | local base64_data = base64_character * base64_character * base64_character * base64_character 16 | local base64_padding = base64_character * base64_character * P"==" 17 | + base64_character * base64_character * base64_character * P"=" 18 | local base64_value_non_empty = (base64_data^1 * base64_padding^-1) + base64_padding 19 | local Sec_WebSocket_Accept = base64_value_non_empty 20 | local Sec_WebSocket_Key = base64_value_non_empty 21 | local registered_token = http_core.token 22 | local extension_token = registered_token 23 | local extension_param do 24 | local EOF = P(-1) 25 | local token_then_EOF = Cc(true) * http_core.token * EOF 26 | -- the quoted-string must be a valid token 27 | local quoted_token = Cmt(http_core.quoted_string, function(_, _, q) 28 | return token_then_EOF:match(q) 29 | end) 30 | extension_param = http_core.token * ((P"=" * (http_core.token + quoted_token)) + Cc(true)) 31 | end 32 | local extension = extension_token * Cg(Cf(Ct(true) * (P";" * Cg(extension_param))^0, rawset), "parameters") 33 | local extension_list = http_core.comma_sep_trim(Ct(extension)) 34 | local Sec_WebSocket_Extensions = extension_list 35 | local Sec_WebSocket_Protocol_Client = http_core.comma_sep_trim(http_core.token) 36 | local Sec_WebSocket_Protocol_Server = http_core.token 37 | local NZDIGIT = S"123456789" 38 | -- Limited to 0-255 range, with no leading zeros 39 | local version = ( 40 | P"2" * (S"01234" * core.DIGIT + P"5" * S"012345") 41 | + (P"1") * core.DIGIT * core.DIGIT 42 | + NZDIGIT * core.DIGIT^-1 43 | ) / tonumber 44 | local Sec_WebSocket_Version_Client = version 45 | local Sec_WebSocket_Version_Server = http_core.comma_sep_trim(version) 46 | 47 | return { 48 | Sec_WebSocket_Accept = Sec_WebSocket_Accept; 49 | Sec_WebSocket_Key = Sec_WebSocket_Key; 50 | Sec_WebSocket_Extensions = Sec_WebSocket_Extensions; 51 | Sec_WebSocket_Protocol_Client = Sec_WebSocket_Protocol_Client; 52 | Sec_WebSocket_Protocol_Server = Sec_WebSocket_Protocol_Server; 53 | Sec_WebSocket_Version_Client = Sec_WebSocket_Version_Client; 54 | Sec_WebSocket_Version_Server = Sec_WebSocket_Version_Server; 55 | } 56 | -------------------------------------------------------------------------------- /lpeg_patterns/language.lua: -------------------------------------------------------------------------------- 1 | -- RFC 5646 Section 2.1 2 | 3 | local lpeg = require "lpeg" 4 | local core = require "lpeg_patterns.core" 5 | 6 | local C = lpeg.C 7 | local P = lpeg.P 8 | local R = lpeg.R 9 | local Cg = lpeg.Cg 10 | local Ct = lpeg.Ct 11 | local Cmt = lpeg.Cmt 12 | 13 | local M = {} 14 | 15 | local alphanum = core.ALPHA + core.DIGIT 16 | 17 | local extlang = core.ALPHA * core.ALPHA * core.ALPHA * -#alphanum 18 | * (P"-" * core.ALPHA * core.ALPHA * core.ALPHA * -#alphanum)^-2 19 | 20 | local language = Cg(core.ALPHA * core.ALPHA * core.ALPHA * core.ALPHA * core.ALPHA * core.ALPHA^-3, "language") 21 | + Cg(core.ALPHA * core.ALPHA * core.ALPHA * core.ALPHA, "language") 22 | + Cg(core.ALPHA * core.ALPHA * core.ALPHA^-1, "language") * (P"-" * Cg(extlang, "extlang"))^-1 23 | 24 | local script = core.ALPHA * core.ALPHA * core.ALPHA * core.ALPHA 25 | * -#alphanum -- Prevent intepretation of a 'variant' 26 | 27 | local region = ( 28 | core.ALPHA * core.ALPHA 29 | + core.DIGIT * core.DIGIT * core.DIGIT 30 | ) * -#alphanum -- Prevent intepretation of a 'variant' 31 | 32 | local variant = core.DIGIT * alphanum * alphanum * alphanum 33 | + alphanum * alphanum * alphanum * alphanum * alphanum * alphanum^-3 34 | 35 | local singleton = core.DIGIT + R("AW", "YZ", "aw", "yz") 36 | 37 | local extension = C(singleton) * Ct((P"-" * (alphanum*alphanum*alphanum^-6 / string.lower))^1) 38 | 39 | M.privateuse = P"x" * Ct((P"-" * C(alphanum*alphanum^-7))^1) 40 | 41 | M.langtag = language 42 | * (P"-" * Cg(script, "script"))^-1 43 | * (P"-" * Cg(region, "region"))^-1 44 | * Cg(Ct((P"-" * C(variant))^1), "variant")^-1 45 | * Cg(Cmt(Ct((P"-" * Ct(extension))^1), function(_, _, c) 46 | -- Can't use a fold with rawset as we want the pattern to not match if there is a duplicate extension 47 | local r = {} 48 | for _, v in ipairs(c) do 49 | local a, b = v[1], v[2] 50 | if r[a] then 51 | -- duplicate extension 52 | return false 53 | end 54 | r[a] = b 55 | end 56 | return true, r 57 | end), "extension")^-1 58 | * (P"-" * Cg(M.privateuse, "privateuse"))^-1 59 | 60 | local irregular = P"en-GB-oed" 61 | + P"i-ami" 62 | + P"i-bnn" 63 | + P"i-default" 64 | + P"i-enochian" 65 | + P"i-hak" 66 | + P"i-klingon" 67 | + P"i-lux" 68 | + P"i-mingo" 69 | + P"i-navajo" 70 | + P"i-pwn" 71 | + P"i-tao" 72 | + P"i-tay" 73 | + P"i-tsu" 74 | + P"sgn-BE-FR" 75 | + P"sgn-BE-NL" 76 | + P"sgn-CH-DE" 77 | 78 | M.Language_Tag = C((M.langtag 79 | + M.privateuse 80 | + irregular) / function() end) -- capture the whole tag. throws away decomposition 81 | 82 | return M 83 | -------------------------------------------------------------------------------- /lpeg_patterns/phone.lua: -------------------------------------------------------------------------------- 1 | -- Phone numbers 2 | 3 | local lpeg = require "lpeg" 4 | local P = lpeg.P 5 | local R = lpeg.R 6 | local S = lpeg.S 7 | 8 | local digit = R"09" 9 | local seperator = S"- ,." 10 | local function optional_parens(patt) 11 | return P"(" * patt * P")" + patt 12 | end 13 | 14 | local _M = {} 15 | 16 | local extension = P"e" * (P"xt")^-1 * seperator^-1 * digit^1 17 | local optional_extension = (seperator^-1 * extension)^-1 18 | 19 | _M.Australia = ( 20 | -- Normal landlines 21 | optional_parens((P"0")^-1*S"2378") * seperator^-1 * digit*digit*digit*digit * seperator^-1 * digit*digit*digit*digit 22 | -- Mobile numbers 23 | + (optional_parens(P"0"*S"45"*digit*digit) + S"45"*digit*digit) 24 | * seperator^-1 * digit*digit*digit * seperator^-1 * digit*digit*digit 25 | -- Local rate calls 26 | + P"1300" * seperator^-1 * digit*digit*digit * seperator^-1 * digit*digit*digit 27 | -- 1345 is only used for back-to-base monitored alarm systems 28 | + P"1345" * seperator^-1 * digit*digit * seperator^-1 * digit*digit 29 | + P"13" * seperator^-1 * digit*digit * seperator^-1 * digit*digit 30 | + (P"0")^-1*P"198" * seperator^-1 * digit*digit*digit * seperator^-1 * digit*digit*digit -- data calls 31 | -- Free calls 32 | + P"1800" * seperator^-1 * digit*digit*digit * seperator^-1 * digit*digit*digit 33 | + P"180" * seperator^-1 * digit*digit*digit*digit 34 | ) * optional_extension 35 | 36 | local NPA = (digit-S"01")*digit*digit 37 | local NXX = ((digit-S"01")*(digit-P"9")-P"37"-P"96")*digit-P(1)*P"11" 38 | local USSubscriber = digit*digit*digit*digit 39 | _M.USA = ((P"1" * seperator^-1)^-1 * optional_parens(NPA) * seperator^-1)^-1 40 | * NXX * seperator^-1 * USSubscriber * optional_extension 41 | 42 | local international = ( 43 | P"1" * seperator^-1 * #(-P"1") * _M.USA 44 | + P"61" * seperator^-1 * #(digit-P"0") * _M.Australia 45 | -- Other countries we haven't made specific patterns for yet 46 | +(P"20"+P"212"+P"213"+P"216"+P"218"+P"220"+P"221" 47 | +P"222"+P"223"+P"224"+P"225"+P"226"+P"227"+P"228"+P"229" 48 | +P"230"+P"231"+P"232"+P"233"+P"234"+P"235"+P"236"+P"237" 49 | +P"238"+P"239"+P"240"+P"241"+P"242"+P"243"+P"244"+P"245" 50 | +P"246"+P"247"+P"248"+P"249"+P"250"+P"251"+P"252"+P"253" 51 | +P"254"+P"255"+P"256"+P"257"+P"258"+P"260"+P"261"+P"262" 52 | +P"263"+P"264"+P"265"+P"266"+P"267"+P"268"+P"269"+P"27" 53 | +P"290"+P"291"+P"297"+P"298"+P"299"+P"30" +P"31" +P"32" 54 | +P"33" +P"34" +P"350"+P"351"+P"352"+P"353"+P"354"+P"355" 55 | +P"356"+P"357"+P"358"+P"359"+P"36" +P"370"+P"371"+P"372" 56 | +P"373"+P"374"+P"375"+P"376"+P"377"+P"378"+P"380"+P"381" 57 | +P"385"+P"386"+P"387"+P"389"+P"39" +P"40" +P"41" +P"420" 58 | +P"421"+P"423"+P"43" +P"44" +P"45" +P"46" +P"47" +P"48" 59 | +P"49" +P"500"+P"501"+P"502"+P"503"+P"504"+P"505"+P"506" 60 | +P"507"+P"508"+P"509"+P"51" +P"52" +P"53" +P"54" +P"55" 61 | +P"56" +P"57" +P"58" +P"590"+P"591"+P"592"+P"593"+P"594" 62 | +P"595"+P"596"+P"597"+P"598"+P"599"+P"60" +P"62" 63 | +P"63" +P"64" +P"65" +P"66" +P"670"+P"672"+P"673"+P"674" 64 | +P"675"+P"676"+P"677"+P"678"+P"679"+P"680"+P"681"+P"682" 65 | +P"683"+P"684"+P"685"+P"686"+P"687"+P"688"+P"689"+P"690" 66 | +P"691"+P"692"+P"7" +P"808"+P"81" +P"82" +P"84" +P"850" 67 | +P"852"+P"853"+P"855"+P"856"+P"86" +P"870"+P"871"+P"872" 68 | +P"873"+P"874"+P"878"+P"880"+P"881"+P"886"+P"90" +P"91" 69 | +P"92" +P"93" +P"94" +P"95" +P"960"+P"961"+P"962"+P"963" 70 | +P"964"+P"965"+P"966"+P"967"+P"968"+P"970"+P"971"+P"972" 71 | +P"973"+P"974"+P"975"+P"976"+P"977"+P"98" +P"992"+P"993" 72 | +P"994"+P"995"+P"996"+P"998" ) * (seperator^-1*digit)^6 -- At least 6 digits 73 | ) 74 | 75 | _M.phone = P"+" * seperator^-1 * international 76 | 77 | return _M 78 | -------------------------------------------------------------------------------- /lpeg_patterns/uri.lua: -------------------------------------------------------------------------------- 1 | -- URI 2 | -- RFC 3986 3 | 4 | local lpeg = require "lpeg" 5 | local P = lpeg.P 6 | local S = lpeg.S 7 | local C = lpeg.C 8 | local Cc = lpeg.Cc 9 | local Cg = lpeg.Cg 10 | local Cs = lpeg.Cs 11 | local Ct = lpeg.Ct 12 | 13 | local util = require "lpeg_patterns.util" 14 | 15 | local core = require "lpeg_patterns.core" 16 | local ALPHA = core.ALPHA 17 | local DIGIT = core.DIGIT 18 | local HEXDIG = core.HEXDIG 19 | 20 | local IPv4address = require "lpeg_patterns.IPv4".IPv4address 21 | local IPv6address = require "lpeg_patterns.IPv6".IPv6address 22 | 23 | local _M = {} 24 | 25 | _M.sub_delims = S"!$&'()*+,;=" -- 2.2 26 | local unreserved = ALPHA + DIGIT + S"-._~" -- 2.3 27 | _M.pct_encoded = P"%" * (HEXDIG * HEXDIG / util.read_hex) / function(n) 28 | local c = string.char(n) 29 | if unreserved:match(c) then 30 | -- always decode unreserved characters (2.3) 31 | return c 32 | else 33 | -- normalise to upper-case (6.2.2.1) 34 | return string.format("%%%02X", n) 35 | end 36 | end -- 2.1 37 | 38 | _M.scheme = ALPHA * (ALPHA + DIGIT + S"+-.")^0 / string.lower -- 3.1 39 | 40 | _M.userinfo = Cs((unreserved + _M.pct_encoded + _M.sub_delims + P":")^0) -- 3.2.1 41 | 42 | -- Host 3.2.2 43 | 44 | local IPvFuture_mt = { 45 | __name = "lpeg_patterns.IPvFuture"; 46 | } 47 | function IPvFuture_mt:__tostring() 48 | return string.format("v%x.%s", self.version, self.string) 49 | end 50 | local function new_IPvFuture(version, string) 51 | return setmetatable({version=version, string=string}, IPvFuture_mt) 52 | end 53 | local IPvFuture = S"vV" * (HEXDIG^1/util.read_hex) * P"." * C((unreserved+_M.sub_delims+P":")^1) / new_IPvFuture 54 | 55 | -- RFC 6874 56 | local ZoneID = Cs((unreserved + _M.pct_encoded)^1) 57 | local IPv6addrz = IPv6address * (P"%25" * ZoneID)^-1 / function(IPv6, zoneid) 58 | IPv6:setzoneid(zoneid) 59 | return IPv6 60 | end 61 | 62 | _M.IP_literal = P"[" * (IPv6addrz + IPvFuture) * P"]" 63 | local IP_host = (_M.IP_literal + IPv4address) / tostring 64 | local reg_name = Cs(( 65 | unreserved / string.lower 66 | + _M.pct_encoded / function(s) return s:sub(1,1) == "%" and s or string.lower(s) end 67 | + _M.sub_delims 68 | )^1) + Cc(nil) 69 | _M.host = IP_host + reg_name 70 | 71 | _M.port = DIGIT^0 / tonumber -- 3.2.3 72 | 73 | -- Path 3.3 74 | local pchar = unreserved + _M.pct_encoded + _M.sub_delims + S":@" 75 | local segment = pchar^0 76 | _M.segment = Cs(segment) 77 | local segment_nz = pchar^1 78 | local segment_nz_nc = (pchar - P":")^1 79 | 80 | -- an empty path is nil instead of the empty string 81 | local path_empty = Cc(nil) 82 | local path_abempty = Cs((P"/" * segment)^1) + path_empty 83 | local path_rootless = Cs(segment_nz * (P"/" * segment)^0) 84 | local path_noscheme = Cs(segment_nz_nc * (P"/" * segment)^0) 85 | local path_absolute = Cs(P"/" * (segment_nz * (P"/" * segment)^0)^-1) 86 | 87 | _M.query = Cs( ( pchar + S"/?" )^0 ) -- 3.4 88 | _M.fragment = _M.query -- 3.5 89 | 90 | -- Put together with named captures 91 | _M.authority = ( Cg(_M.userinfo, "userinfo") * P"@" )^-1 92 | * Cg(_M.host, "host") 93 | * ( P":" * Cg(_M.port, "port") )^-1 94 | 95 | local hier_part = P"//" * _M.authority * Cg (path_abempty, "path") 96 | + Cg(path_absolute + path_rootless + path_empty, "path") 97 | 98 | _M.absolute_uri = Ct ( 99 | ( Cg(_M.scheme, "scheme") * P":" ) 100 | * hier_part 101 | * ( P"?" * Cg(_M.query, "query"))^-1 102 | ) 103 | 104 | _M.uri = Ct ( 105 | ( Cg(_M.scheme, "scheme") * P":" ) 106 | * hier_part 107 | * ( P"?" * Cg(_M.query, "query"))^-1 108 | * ( P"#" * Cg(_M.fragment, "fragment"))^-1 109 | ) 110 | 111 | _M.relative_part = P"//" * _M.authority * Cg(path_abempty, "path") 112 | + Cg(path_absolute + path_noscheme + path_empty, "path") 113 | 114 | local relative_ref = Ct ( 115 | _M.relative_part 116 | * ( P"?" * Cg(_M.query, "query"))^-1 117 | * ( P"#" * Cg(_M.fragment, "fragment"))^-1 118 | ) 119 | _M.uri_reference = _M.uri + relative_ref 120 | 121 | _M.path = path_abempty + path_absolute + path_noscheme + path_rootless + path_empty 122 | 123 | -- Create a slightly more sane host pattern 124 | -- scheme is optional 125 | -- the "//" isn't required 126 | -- if missing, the host needs to at least have a "." and end in two alpha characters 127 | -- an authority is always required 128 | local sane_host_char = unreserved / string.lower 129 | local hostsegment = (sane_host_char - P".")^1 130 | local dns_entry = Cs ( ( hostsegment * P"." )^1 * ALPHA^2 ) 131 | _M.sane_host = IP_host + dns_entry 132 | _M.sane_authority = ( Cg(_M.userinfo, "userinfo") * P"@" )^-1 133 | * Cg(_M.sane_host, "host") 134 | * ( P":" * Cg(_M.port, "port") )^-1 135 | local sane_hier_part = (P"//")^-1 * _M.sane_authority * Cg(path_absolute + path_empty, "path") 136 | _M.sane_uri = Ct ( 137 | ( Cg(_M.scheme, "scheme") * P":" )^-1 138 | * sane_hier_part 139 | * ( P"?" * Cg(_M.query, "query"))^-1 140 | * ( P"#" * Cg(_M.fragment, "fragment"))^-1 141 | ) 142 | 143 | return _M 144 | -------------------------------------------------------------------------------- /lpeg_patterns/util.lua: -------------------------------------------------------------------------------- 1 | local lpeg = require "lpeg" 2 | local C = lpeg.C 3 | local P = lpeg.P 4 | local S = lpeg.S 5 | 6 | local function case_insensitive(str) 7 | local patt = P(true) 8 | for i=1, #str do 9 | local c = str:sub(i, i) 10 | patt = patt * S(c:upper() .. c:lower()) 11 | end 12 | return patt 13 | end 14 | 15 | local function no_rich_capture(patt) 16 | return C(patt) / function(a) return a end 17 | end 18 | 19 | local function read_hex(hex_num) 20 | return tonumber(hex_num, 16) 21 | end 22 | 23 | local safe_tonumber do -- locale independent tonumber function 24 | local tolocale 25 | local function updatelocale() 26 | local decpoint = string.format("%f", 0.5):match "[^05]+" 27 | if decpoint == "." then 28 | tolocale = function(str) 29 | return str 30 | end 31 | else 32 | tolocale = function(str) 33 | str = str:gsub("%.", decpoint, 1) 34 | return str 35 | end 36 | end 37 | end 38 | updatelocale() 39 | safe_tonumber = function(str) 40 | local num = tonumber(tolocale(str)) 41 | if num then 42 | return num 43 | else 44 | updatelocale() 45 | return tonumber(tolocale(str)) 46 | end 47 | end 48 | end 49 | 50 | return { 51 | case_insensitive = case_insensitive; 52 | no_rich_capture = no_rich_capture; 53 | read_hex = read_hex; 54 | safe_tonumber = safe_tonumber; 55 | } 56 | -------------------------------------------------------------------------------- /spec/IPv6_spec.lua: -------------------------------------------------------------------------------- 1 | local lpeg = require "lpeg" 2 | 3 | describe("IPv6 Addresses", function() 4 | local IPv6address = require "lpeg_patterns.IPv6".IPv6address 5 | local IPv6address_only = IPv6address * lpeg.P(-1) 6 | it("Addresses are parsed correctly", function() 7 | local function same(str, ...) 8 | local addr = IPv6address_only:match(str) 9 | assert(addr, "Could not parse " .. str) 10 | assert.same({...}, {addr:unpack()}) 11 | end 12 | same("::", 0,0,0,0,0,0,0,0) 13 | same("::0.0.0.0", 0,0,0,0,0,0,0,0) 14 | same("::0:0.0.0.0", 0,0,0,0,0,0,0,0) 15 | same("0::0.0.0.0", 0,0,0,0,0,0,0,0) 16 | same("::1", 0,0,0,0,0,0,0,1) 17 | same("ff02::1", 0xff02,0,0,0,0,0,0,1) 18 | same("2001:0db8:85a3:0042:1000:8a2e:0370:7334", 19 | 0x2001, 0x0db8, 0x85a3, 0x0042, 0x1000, 0x8a2e, 0x0370, 0x7334) 20 | same("::FFFF:204.152.189.116", 0, 0, 0, 0, 0, 0xFFFF, 204*256+152, 189*256+116) 21 | end) 22 | it("Non-addresses fail parsing", function() 23 | assert.falsy(IPv6address_only:match"") 24 | assert.falsy(IPv6address_only:match"not an ip") 25 | assert.falsy(IPv6address_only:match"::x") 26 | assert.falsy(IPv6address_only:match"x::") 27 | assert.falsy(IPv6address_only:match":::") 28 | assert.falsy(IPv6address_only:match":1::") 29 | -- Two :: 30 | assert.falsy(IPv6address_only:match"1234::5678::") 31 | -- Invalid IPv4 32 | assert.falsy(IPv6address_only:match"::FFFF:0.0.0") 33 | assert.falsy(IPv6address_only:match"::FFFF:0.999.0.0") 34 | end) 35 | end) 36 | -------------------------------------------------------------------------------- /spec/email_spec.lua: -------------------------------------------------------------------------------- 1 | local lpeg = require "lpeg" 2 | local EOF = lpeg.P(-1) 3 | 4 | describe("email Addresses", function() 5 | local email = lpeg.Ct(require "lpeg_patterns.email".email) * EOF 6 | it("Pass valid addresses", function() 7 | assert.same({"localpart", "example.com"}, email:match "localpart@example.com") 8 | end) 9 | it("Deny invalid addresses", function() 10 | assert.falsy(email:match "not an address") 11 | end) 12 | it("Handle unusual localpart", function() 13 | assert.same({"foo.bar", "example.com"}, email:match "foo.bar@example.com") 14 | assert.same({"foo+", "example.com"}, email:match "foo+@example.com") 15 | assert.same({"foo+bar", "example.com"}, email:match "foo+bar@example.com") 16 | assert.same({"!#$%&'*+-/=?^_`{}|~", "example.com"}, email:match "!#$%&'*+-/=?^_`{}|~@example.com") 17 | assert.same({[[quoted]], "example.com"}, email:match [["quoted"@example.com]]) 18 | assert.same({[[quoted string]], "example.com"}, email:match [["quoted string"@example.com]]) 19 | assert.same({[[quoted@symbol]], "example.com"}, email:match [["quoted@symbol"@example.com]]) 20 | assert.same({[=[very.(),:;<>[]".VERY."very@\ "very".unusual]=], "example.com"}, 21 | email:match [=["very.(),:;<>[]\".VERY.\"very@\\ \"very\".unusual"@example.com]=]) 22 | end) 23 | it("folds whitespace", function() 24 | assert.same({"localpart ", "example.com"}, email:match [["localpart "@example.com]]) 25 | assert.same({"localpart ", "example.com"}, email:match [["localpart "@example.com]]) 26 | assert.same({" localpart ", "example.com"}, email:match [[" localpart "@example.com]]) 27 | assert.same({" localpart and again ", "example.com"}, email:match [[" localpart and again "@example.com]]) 28 | 29 | assert.same({"localpart", "example.com "}, email:match [=[localpart@[example.com ]]=]) 30 | assert.same({"localpart", "example.com "}, email:match [=[localpart@[example.com ]]=]) 31 | assert.same({"localpart", " example.com"}, email:match [=[localpart@[ example.com]]=]) 32 | assert.same({"localpart", " example.com "}, email:match [=[localpart@[ example.com ]]=]) 33 | assert.same({"localpart", " example with whitespace "}, email:match [=[localpart@[ example with whitespace ]]=]) 34 | end) 35 | it("Ignore invalid localpart", function() 36 | assert.falsy(email:match "@example.com") 37 | assert.falsy(email:match ".@example.com") 38 | assert.falsy(email:match "foobar.@example.com") 39 | assert.falsy(email:match "@foo@example.com") 40 | assert.falsy(email:match "foo@bar@example.com") 41 | -- quoted strings must be dot separated, or the only element making up the local-pat 42 | assert.falsy(email:match [[just"not"right@example.com]]) 43 | assert.falsy(email:match "\127@example.com") 44 | end) 45 | it("Handle unusual hosts", function() 46 | assert.same({"localpart", "host_name"}, email:match "localpart@host_name") 47 | assert.same({"localpart", "127.0.0.1"}, email:match "localpart@[127.0.0.1]") 48 | assert.same({"localpart", "IPv6:2001::d1"}, email:match "localpart@[IPv6:2001::d1]") 49 | assert.same({"localpart", "::1"}, email:match "localpart@[::1]") 50 | end) 51 | it("Handle comments", function() 52 | assert.same({"localpart", "example.com"}, email:match "(comment)localpart@example.com") 53 | assert.same({"localpart", "example.com"}, email:match "localpart(comment)@example.com") 54 | assert.same({"quoted", "example.com"}, email:match "(comment)\"quoted\"@example.com") 55 | assert.same({"quoted", "example.com"}, email:match "\"quoted\"(comment)@example.com") 56 | assert.same({"localpart", "example.com"}, email:match "localpart@(comment)example.com") 57 | assert.same({"localpart", "example.com"}, email:match "localpart@example.com(comment)") 58 | end) 59 | it("Handle escaped items in quotes", function() 60 | assert.same({"escape d", "example.com"}, email:match [["escape\ d"(comment)@example.com]]) 61 | assert.same({"escape\"d", "example.com"}, email:match [["escape\"d"(comment)@example.com]]) 62 | -- tests obs-qp 63 | assert.same({"escape\0d", "example.com"}, email:match "\"escape\\\0d\"@example.com") 64 | end) 65 | it("processes obs-dtext", function() 66 | assert.same({"localpart", "escape d"}, email:match "localpart@[escape\\ d]") 67 | end) 68 | it("processes obs-local-part", function() 69 | -- obs-local-part allows whitespace between atoms 70 | assert.same({"local.part", "example.com"}, email:match [[local .part@example.com]]) 71 | -- obs-local-part allows individually quoted atoms 72 | assert.same({"local.part", "example.com"}, email:match [["local".part@example.com]]) 73 | end) 74 | it("processes obs-domain", function() 75 | -- obs-domain allows whitespace between atoms 76 | assert.same({"localpart", "example.com"}, email:match [[localpart@example .com]]) 77 | end) 78 | it("Examples from RFC 3696 Section 3", function() 79 | -- Note: Look at errata 246, the followup 3563 and the followup to the followup 4002 80 | -- not only did the RFC author get some of these wrong, so did the RFC errata verifiers 81 | assert.same({"Abc@def", "example.com"}, email:match [["Abc\@def"@example.com]]) 82 | assert.same({"Abc@def", "example.com"}, email:match [["Abc@def"@example.com]]) 83 | assert.same({"Fred Bloggs", "example.com"}, email:match [["Fred\ Bloggs"@example.com]]) 84 | assert.same({"Fred Bloggs", "example.com"}, email:match [["Fred Bloggs"@example.com]]) 85 | assert.same({[[Joe.\Blow]], "example.com"}, email:match [["Joe.\\Blow"@example.com]]) 86 | assert.same({[[Joe.Blow]], "example.com"}, email:match [["Joe.\Blow"@example.com]]) 87 | assert.same({"Abc@def", "example.com"}, email:match [["Abc@def"@example.com]]) 88 | assert.same({"Fred Bloggs", "example.com"}, email:match [["Fred Bloggs"@example.com]]) 89 | assert.same({"user+mailbox", "example.com"}, email:match [[user+mailbox@example.com]]) 90 | assert.same({"customer/department", "example.com"}, email:match [[customer/department@example.com]]) 91 | assert.same({"$A12345", "example.com"}, email:match [[$A12345@example.com]]) 92 | assert.same({"!def!xyz%abc", "example.com"}, email:match [[!def!xyz%abc@example.com]]) 93 | assert.same({"_somename", "example.com"}, email:match [[_somename@example.com]]) 94 | end) 95 | end) 96 | describe("email nocfws variants", function() 97 | local email_nocfws = lpeg.Ct(require "lpeg_patterns.email".email_nocfws) * EOF 98 | it("Pass valid addresses", function() 99 | assert.same({"localpart", "example.com"}, email_nocfws:match "localpart@example.com") 100 | end) 101 | it("Deny invalid addresses", function() 102 | assert.falsy(email_nocfws:match "not an address") 103 | end) 104 | it("Handle unusual localpart", function() 105 | assert.same({"foo.bar", "example.com"}, email_nocfws:match "foo.bar@example.com") 106 | assert.same({"foo+", "example.com"}, email_nocfws:match "foo+@example.com") 107 | assert.same({"foo+bar", "example.com"}, email_nocfws:match "foo+bar@example.com") 108 | assert.same({"!#$%&'*+-/=?^_`{}|~", "example.com"}, email_nocfws:match "!#$%&'*+-/=?^_`{}|~@example.com") 109 | assert.same({[[quoted]], "example.com"}, email_nocfws:match [["quoted"@example.com]]) 110 | assert.same({[[quoted string]], "example.com"}, email_nocfws:match [["quoted string"@example.com]]) 111 | assert.same({[[quoted@symbol]], "example.com"}, email_nocfws:match [["quoted@symbol"@example.com]]) 112 | assert.same({[=[very.(),:;<>[]".VERY."very@\ "very".unusual]=], "example.com"}, 113 | email_nocfws:match [=["very.(),:;<>[]\".VERY.\"very@\\ \"very\".unusual"@example.com]=]) 114 | end) 115 | it("Ignore invalid localpart", function() 116 | assert.falsy(email_nocfws:match "@example.com") 117 | assert.falsy(email_nocfws:match ".@example.com") 118 | assert.falsy(email_nocfws:match "foobar.@example.com") 119 | assert.falsy(email_nocfws:match "@foo@example.com") 120 | assert.falsy(email_nocfws:match "foo@bar@example.com") 121 | -- quoted strings must be dot separated, or the only element making up the local-pat 122 | assert.falsy(email_nocfws:match [[just"not"right@example.com]]) 123 | assert.falsy(email_nocfws:match "\127@example.com") 124 | end) 125 | it("Handle unusual hosts", function() 126 | assert.same({"localpart", "host_name"}, email_nocfws:match "localpart@host_name") 127 | assert.same({"localpart", "127.0.0.1"}, email_nocfws:match "localpart@[127.0.0.1]") 128 | assert.same({"localpart", "IPv6:2001::d1"}, email_nocfws:match "localpart@[IPv6:2001::d1]") 129 | assert.same({"localpart", "::1"}, email_nocfws:match "localpart@[::1]") 130 | end) 131 | it("Doesn't allow comments", function() 132 | assert.falsy(email_nocfws:match "(comment)localpart@example.com") 133 | assert.falsy(email_nocfws:match "localpart(comment)@example.com") 134 | assert.falsy(email_nocfws:match "(comment)\"quoted\"@example.com") 135 | assert.falsy(email_nocfws:match "\"quoted\"(comment)@example.com") 136 | assert.falsy(email_nocfws:match "localpart@example.com(comment)") 137 | assert.falsy(email_nocfws:match "localpart@example.com(comment)") 138 | end) 139 | end) 140 | describe("mailbox", function() 141 | local mailbox = lpeg.Ct(require "lpeg_patterns.email".mailbox) * EOF 142 | it("matches an addr-spec", function() 143 | assert.same({"foo", "example.com"}, mailbox:match "foo@example.com") 144 | end) 145 | it("matches a name-addr", function() 146 | assert.same({"foo", "example.com"}, mailbox:match "") 147 | assert.same({"foo", "example.com", display = "Foo"}, mailbox:match "Foo") 148 | assert.same({"foo", "example.com", display = "Foo "}, mailbox:match "Foo ") 149 | assert.same({"foo", "example.com", display = [["Foo"]]}, mailbox:match [["Foo"]]) 150 | assert.same({"foo", "example.com", display = "Old.Style.With.Dots"}, 151 | mailbox:match "Old.Style.With.Dots") 152 | assert.same({"foo", "example.com", display = "Multiple Words"}, 153 | mailbox:match "Multiple Words") 154 | end) 155 | it("matches a old school name-addr", function() 156 | assert.same({"foo", "example.com", route = {"wow", "such", "domains"}}, 157 | mailbox:match "<@wow,@such,,@domains:foo@example.com>") 158 | end) 159 | end) 160 | -------------------------------------------------------------------------------- /spec/http_alpn_spec.lua: -------------------------------------------------------------------------------- 1 | describe("lpeg_patterns.http.alpn", function() 2 | local http_alpn = require "lpeg_patterns.http.alpn" 3 | local lpeg = require "lpeg" 4 | local EOF = lpeg.P(-1) 5 | local protocol_id = http_alpn.protocol_id * EOF 6 | it("unescapes an ALPN protocol id correctly", function() 7 | assert.same("foo", protocol_id:match("foo")) 8 | -- percent encoded chars 9 | assert.same(" ", protocol_id:match("%20")) -- space 10 | assert.same("%", protocol_id:match("%25")) -- % 11 | end) 12 | it("must not decode to character that didn't need to be escaped", function() 13 | assert.same(nil, protocol_id:match("%41")) -- a 14 | assert.same(nil, protocol_id:match("%26")) -- & 15 | end) 16 | it("must be 2 digit hex", function() 17 | assert.same(nil, protocol_id:match("%2")) 18 | end) 19 | it("must be uppercase hex", function() 20 | assert.same(nil, protocol_id:match("%1a")) 21 | end) 22 | end) 23 | -------------------------------------------------------------------------------- /spec/http_cookie_spec.lua: -------------------------------------------------------------------------------- 1 | describe("lpeg_patterns.http.cookie", function() 2 | local http_cookie = require "lpeg_patterns.http.cookie" 3 | local lpeg = require "lpeg" 4 | local EOF = lpeg.P(-1) 5 | it("Parses a Set-Cookie header", function() 6 | local Set_Cookie = lpeg.Ct(http_cookie.Set_Cookie) * EOF 7 | assert.same({"SID", "31d4d96e407aad42", {}}, Set_Cookie:match"SID=31d4d96e407aad42") 8 | assert.same({"SID", "", {}}, Set_Cookie:match"SID=") 9 | assert.same({"SID", "31d4d96e407aad42", {path="/"; domain="example.com"}}, 10 | Set_Cookie:match"SID=31d4d96e407aad42; Path=/; Domain=example.com") 11 | assert.same({"SID", "31d4d96e407aad42", { 12 | path = "/"; 13 | domain = "example.com"; 14 | secure = true; 15 | expires = "Sun Nov 6 08:49:37 1994"; 16 | }}, Set_Cookie:match"SID=31d4d96e407aad42; Path=/; Domain=example.com; Secure; Expires=Sun Nov 6 08:49:37 1994") 17 | -- Space before '=' 18 | assert.same({"SID", "31d4d96e407aad42", {path = "/";}}, Set_Cookie:match"SID=31d4d96e407aad42; Path =/") 19 | -- Quoted cookie value 20 | assert.same({"SID", "31d4d96e407aad42", {path = "/";}}, Set_Cookie:match[[SID="31d4d96e407aad42"; Path=/]]) 21 | -- Crazy whitespace 22 | assert.same({"SID", "31d4d96e407aad42", {path = "/";}}, Set_Cookie:match"SID = 31d4d96e407aad42 ; Path = /") 23 | assert.same({"SID", "31d4d96e407aad42", {["foo bar"] = true;}}, 24 | Set_Cookie:match"SID = 31d4d96e407aad42 ; foo bar") 25 | end) 26 | it("Parses a Cookie header", function() 27 | local Cookie = http_cookie.Cookie * EOF 28 | assert.same({SID = "31d4d96e407aad42"}, Cookie:match"SID=31d4d96e407aad42") 29 | assert.same({SID = "31d4d96e407aad42"}, Cookie:match"SID = 31d4d96e407aad42") 30 | assert.same({SID = "31d4d96e407aad42", lang = "en-US"}, Cookie:match"SID=31d4d96e407aad42; lang=en-US") 31 | end) 32 | end) 33 | -------------------------------------------------------------------------------- /spec/http_disposition_spec.lua: -------------------------------------------------------------------------------- 1 | describe("lpeg_patterns.http.disposition", function() 2 | local http_disposition = require "lpeg_patterns.http.disposition" 3 | local lpeg = require "lpeg" 4 | local EOF = lpeg.P(-1) 5 | it("Parses a Content-Disposition header", function() 6 | local Content_Disposition = lpeg.Ct(http_disposition.Content_Disposition) * EOF 7 | assert.same({"foo", {}}, Content_Disposition:match"foo") 8 | assert.same({"foo", {filename="example"}}, Content_Disposition:match"foo; filename=example") 9 | assert.same({"foo", {filename="example"}}, Content_Disposition:match"foo; filename*=UTF-8''example") 10 | end) 11 | end) 12 | -------------------------------------------------------------------------------- /spec/http_expect_ct_spec.lua: -------------------------------------------------------------------------------- 1 | describe("lpeg_patterns.http.expect_ct", function() 2 | local http_expect_ct = require "lpeg_patterns.http.expect_ct" 3 | local lpeg = require "lpeg" 4 | local EOF = lpeg.P(-1) 5 | it("Parses a Expect-Ct header", function() 6 | -- Examples from draft-ietf-httpbis-expect-ct-06 2.1.4 7 | local sts_patt = http_expect_ct.Expect_CT * EOF 8 | assert.same({["max-age"] = "86400", enforce = true}, sts_patt:match("max-age=86400, enforce")) 9 | assert.same({ 10 | ["max-age"] = "86400"; 11 | ["report-uri"] = "https://foo.example/report"; 12 | }, sts_patt:match([[max-age=86400,report-uri="https://foo.example/report"]])) 13 | -- max-age is required 14 | assert.same(nil, sts_patt:match("foo=0")) 15 | -- Should fail to parse when duplicate field given 16 | assert.same(nil, sts_patt:match("max-age086400, foo=0, foo=1")) 17 | end) 18 | end) 19 | -------------------------------------------------------------------------------- /spec/http_frameoptions_spec.lua: -------------------------------------------------------------------------------- 1 | describe("lpeg_patterns.http.frameoptions", function() 2 | local http_frameoptions = require "lpeg_patterns.http.frameoptions" 3 | local lpeg = require "lpeg" 4 | local EOF = lpeg.P(-1) 5 | it("Parses an X-Frame-Options header", function() 6 | local X_Frame_Options = lpeg.Ct(http_frameoptions.X_Frame_Options) * EOF 7 | assert.same({"deny"}, X_Frame_Options:match("deny")) 8 | assert.same({"deny"}, X_Frame_Options:match("DENY")) 9 | assert.same({"deny"}, X_Frame_Options:match("dEnY")) 10 | assert.same({"http://example.com"}, X_Frame_Options:match("Allow-From http://example.com")) 11 | end) 12 | end) 13 | -------------------------------------------------------------------------------- /spec/http_link_spec.lua: -------------------------------------------------------------------------------- 1 | describe("lpeg_patterns.http.link", function() 2 | local http_link = require "lpeg_patterns.http.link" 3 | local lpeg = require "lpeg" 4 | local EOF = lpeg.P(-1) 5 | it("Parses a Link header", function() 6 | local Link = lpeg.Ct(http_link.Link) * EOF 7 | assert.same({{{host="example.com"}}}, Link:match"") 8 | assert.same({ 9 | { 10 | {scheme = "http"; host = "example.com"; path = "/TheBook/chapter2";}; 11 | rel = "previous"; 12 | title="previous chapter" 13 | }}, 14 | Link:match[[; rel="previous"; title="previous chapter"]]) 15 | assert.same({{{path = "/"}, rel = "http://example.net/foo"}}, 16 | Link:match[[; rel="http://example.net/foo"]]) 17 | assert.same({ 18 | {{path = "/TheBook/chapter2"}, rel = "previous", title = "letztes Kapitel"}; 19 | {{path = "/TheBook/chapter4"}, rel = "next", title = "nächstes Kapitel"}; 20 | }, 21 | Link:match[[; rel="previous"; title*=UTF-8'de'letztes%20Kapitel, ; rel="next"; title*=UTF-8'de'n%c3%a4chstes%20Kapitel]]) 22 | assert.same({{{scheme = "http"; host = "example.org"; path = "/"}, rel = "start http://example.net/relation/other"}}, 23 | Link:match[[; rel="start http://example.net/relation/other"]]) 24 | end) 25 | end) 26 | -------------------------------------------------------------------------------- /spec/http_origin_spec.lua: -------------------------------------------------------------------------------- 1 | describe("lpeg_patterns.http.origin", function() 2 | local http_origin = require "lpeg_patterns.http.origin" 3 | local lpeg = require "lpeg" 4 | local EOF = lpeg.P(-1) 5 | it("Parses an Origin header", function() 6 | local Origin = lpeg.Ct(http_origin.Origin) * EOF 7 | assert.same({}, Origin:match("null")) 8 | assert.same({"http://example.com"}, Origin:match("http://example.com")) 9 | assert.same({"http://example.com", "https://foo.org"}, Origin:match("http://example.com https://foo.org")) 10 | end) 11 | end) 12 | -------------------------------------------------------------------------------- /spec/http_pkp_spec.lua: -------------------------------------------------------------------------------- 1 | describe("lpeg_patterns.http.pkp", function() 2 | local http_pkp = require "lpeg_patterns.http.pkp" 3 | local lpeg = require "lpeg" 4 | local EOF = lpeg.P(-1) 5 | it("Parses a HPKP header", function() 6 | -- Example from RFC 7469 2.1.5 7 | local pkp_patt = lpeg.Ct(http_pkp.Public_Key_Pins) * EOF 8 | assert.same({ 9 | { 10 | sha256 = { 11 | "d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM="; 12 | "E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g="; 13 | }; 14 | }, { 15 | ["max-age"] = "3000"; 16 | } 17 | }, pkp_patt:match([[max-age=3000; pin-sha256="d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM="; pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g="]])) 18 | 19 | -- max-age is compulsory 20 | assert.same(nil, pkp_patt:match([[pin-sha256="d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM="; pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g="]])) 21 | end) 22 | it("Parses a HPKP Report header", function() 23 | -- Example from RFC 7469 2.1.5 24 | local pkp_patt = lpeg.Ct(http_pkp.Public_Key_Pins_Report_Only) * EOF 25 | assert.same({ 26 | { 27 | sha256 = { 28 | "d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM="; 29 | "E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g="; 30 | }; 31 | }, { 32 | ["max-age"] = "3000"; 33 | } 34 | }, pkp_patt:match([[max-age=3000; pin-sha256="d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM="; pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g="]])) 35 | -- max-age isn't compulsory 36 | assert.same({ 37 | { 38 | sha256 = { 39 | "d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM="; 40 | "E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g="; 41 | }; 42 | }, { 43 | } 44 | }, pkp_patt:match([[pin-sha256="d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM="; pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g="]])) 45 | end) 46 | end) 47 | -------------------------------------------------------------------------------- /spec/http_slug_spec.lua: -------------------------------------------------------------------------------- 1 | describe("lpeg_patterns.http.slug", function() 2 | local http_slug = require "lpeg_patterns.http.slug" 3 | local lpeg = require "lpeg" 4 | local EOF = lpeg.P(-1) 5 | it("Parses a SLUG header", function() 6 | local SLUG = http_slug.SLUG * EOF 7 | assert.same("foo", SLUG:match("foo")) 8 | assert.same("foo bar", SLUG:match("foo bar")) 9 | assert.same("foo bar", SLUG:match("foo bar")) 10 | assert.same("foo bar", SLUG:match("foo %20 bar")) 11 | end) 12 | end) 13 | -------------------------------------------------------------------------------- /spec/http_spec.lua: -------------------------------------------------------------------------------- 1 | describe("http patterns", function() 2 | local http = require "lpeg_patterns.http" 3 | local lpeg = require "lpeg" 4 | local EOF = lpeg.P(-1) 5 | it("Splits a request line", function() 6 | local request_line = lpeg.Ct(http.request_line) * EOF 7 | assert.same({"GET", "/", 1.0}, request_line:match("GET / HTTP/1.0\r\n")) 8 | assert.same({"GET", "http://foo.com/", 1.0}, request_line:match("GET http://foo.com/ HTTP/1.0\r\n")) 9 | assert.same({"OPTIONS", "*", 1.1}, request_line:match("OPTIONS * HTTP/1.1\r\n")) 10 | end) 11 | it("Splits an Upgrade header", function() 12 | local Upgrade = lpeg.Ct(http.Upgrade) * EOF 13 | assert.same({"Foo"}, Upgrade:match("Foo")) 14 | assert.same({"WebSocket"}, Upgrade:match("WebSocket")) 15 | assert.same({"HTTP/2.0", "SHTTP/1.3", "IRC/6.9", "RTA/x11"}, Upgrade:match("HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11")) 16 | end) 17 | it("Splits a Via header", function() 18 | local Via = lpeg.Ct(http.Via) * EOF 19 | assert.same({{protocol="HTTP/1.0", by="fred"}}, Via:match("1.0 fred")) 20 | assert.same({{protocol="HTTP/1.0", by="fred"}}, Via:match("HTTP/1.0 fred")) 21 | assert.same({{protocol="Other/myversion", by="fred"}}, Via:match("Other/myversion fred")) 22 | assert.same({{protocol="HTTP/1.1", by="p.example.net"}}, Via:match("1.1 p.example.net")) 23 | assert.same({ 24 | {protocol="HTTP/1.0", by="fred"}, 25 | {protocol="HTTP/1.1", by="p.example.net"} 26 | }, Via:match("1.0 fred, 1.1 p.example.net")) 27 | assert.same({ 28 | {protocol="HTTP/1.0", by="my.host:80"}, 29 | {protocol="HTTP/1.1", by="my.other.host"} 30 | }, Via:match("1.0 my.host:80, 1.1 my.other.host")) 31 | assert.same({ 32 | {protocol="HTTP/1.0", by="fred"}, 33 | {protocol="HTTP/1.1", by="p.example.net"} 34 | }, Via:match(",,,1.0 fred , ,,, 1.1 p.example.net,,,")) 35 | end) 36 | it("Handles folding whitespace in field_value", function() 37 | local field_value = http.field_value * EOF 38 | assert.same("Foo", field_value:match("Foo")) 39 | -- doesn't remove repeated whitespace 40 | assert.same("Foo Bar", field_value:match("Foo Bar")) 41 | -- unfolds whitespace broken over multiple lines 42 | assert.same("Foo Bar", field_value:match("Foo\r\n Bar")) 43 | assert.same("Foo Bar", field_value:match("Foo \r\n Bar")) 44 | end) 45 | it("Splits a Connection header", function() 46 | local Connection = lpeg.Ct(http.Connection) * EOF 47 | assert.same({}, Connection:match(" ")) 48 | assert.same({}, Connection:match(",")) 49 | assert.same({}, Connection:match(", ,")) 50 | assert.same({"foo"}, Connection:match("foo")) 51 | assert.same({"foo"}, Connection:match(" foo")) 52 | assert.same({"foo"}, Connection:match(" foo,,,")) 53 | assert.same({"foo"}, Connection:match(",, , foo ")) 54 | assert.same({"foo", "bar"}, Connection:match("foo,bar")) 55 | assert.same({"foo", "bar"}, Connection:match("foo, bar")) 56 | assert.same({"foo", "bar"}, Connection:match("foo , bar")) 57 | assert.same({"foo", "bar"}, Connection:match("foo\t, bar")) 58 | assert.same({"foo", "bar"}, Connection:match("foo,,, ,bar")) 59 | end) 60 | it("Parses a Transfer-Encoding header", function() 61 | local Transfer_Encoding = lpeg.Ct(http.Transfer_Encoding) * EOF 62 | assert.falsy(Transfer_Encoding:match("")) -- doesn't allow empty 63 | assert.same({{"foo"}}, Transfer_Encoding:match("foo")) 64 | assert.same({{"foo"}, {"bar"}}, Transfer_Encoding:match("foo, bar")) 65 | assert.same({{"foo", someext = "bar"}}, Transfer_Encoding:match("foo;someext=bar")) 66 | assert.same({{"foo", someext = "bar", another = "qux"}}, Transfer_Encoding:match("foo;someext=bar;another=\"qux\"")) 67 | -- q not allowed 68 | assert.falsy(Transfer_Encoding:match("foo;q=0.5")) 69 | -- check transfer parameters starting with q (but not q) are allowed 70 | assert.same({{"foo", queen = "foo"}}, Transfer_Encoding:match("foo;queen=foo")) 71 | end) 72 | it("Parses a TE header", function() 73 | local TE = lpeg.Ct(http.TE) * EOF 74 | assert.same({}, TE:match("")) -- allows empty 75 | assert.same({{"foo"}}, TE:match("foo")) 76 | assert.same({{"foo"}, {"bar"}}, TE:match("foo, bar")) 77 | assert.same({{"foo", q=0.5}}, TE:match("foo;q=0.5")) 78 | assert.same({{"foo", someext = "foo", q=0.5}}, TE:match("foo;someext=foo;q=0.5")) 79 | end) 80 | it("Splits a Trailer header", function() 81 | local Trailer = lpeg.Ct(http.Trailer) * EOF 82 | assert.falsy(Trailer:match(" ")) 83 | assert.falsy(Trailer:match(",")) 84 | assert.falsy(Trailer:match(", ,")) 85 | assert.same({"foo"}, Trailer:match("foo")) 86 | assert.same({"foo"}, Trailer:match(" foo")) 87 | assert.same({"foo"}, Trailer:match(" foo,,,")) 88 | assert.same({"foo"}, Trailer:match(",, , foo ")) 89 | assert.same({"foo", "bar"}, Trailer:match("foo,bar")) 90 | assert.same({"foo", "bar"}, Trailer:match("foo, bar")) 91 | assert.same({"foo", "bar"}, Trailer:match("foo , bar")) 92 | assert.same({"foo", "bar"}, Trailer:match("foo\t, bar")) 93 | assert.same({"foo", "bar"}, Trailer:match("foo,,, ,bar")) 94 | end) 95 | it("Parses a Content-Type header", function() 96 | local Content_Type = http.Content_Type * EOF 97 | assert.same({ type = "foo", subtype = "bar", parameters = {}}, 98 | Content_Type:match("foo/bar")) 99 | assert.same({ type = "foo", subtype = "bar", parameters = {param="value"}}, 100 | Content_Type:match("foo/bar;param=value")) 101 | -- Examples from RFC7231 3.1.1.1. 102 | assert.same({ type = "text", subtype = "html", parameters = {charset="utf-8"}}, 103 | Content_Type:match([[text/html;charset=utf-8]])) 104 | -- assert.same({ type = "text", subtype = "html", parameters = {charset="utf-8"}}, 105 | -- Content_Type:match([[text/html;charset=UTF-8]])) 106 | assert.same({ type = "text", subtype = "html", parameters = {charset="utf-8"}}, 107 | Content_Type:match([[Text/HTML;Charset="utf-8"]])) 108 | assert.same({ type = "text", subtype = "html", parameters = {charset="utf-8"}}, 109 | Content_Type:match([[text/html; charset="utf-8"]])) 110 | end) 111 | it("Parses an Accept header", function() 112 | local Accept = lpeg.Ct(http.Accept) * EOF 113 | assert.same({{type = "foo", subtype = "bar", parameters = {}, q = nil, extensions = {}}}, Accept:match("foo/bar")) 114 | assert.same({ 115 | {type = "audio", subtype = nil, parameters = {}, q = 0.2, extensions = {}}; 116 | {type = "audio", subtype = "basic", parameters = {}, q = nil, extensions = {}}; 117 | }, Accept:match("audio/*; q=0.2, audio/basic")) 118 | assert.same({ 119 | {type = "text", subtype = "plain", parameters = {}, q = 0.5, extensions = {}}; 120 | {type = "text", subtype = "html", parameters = {}, q = nil, extensions = {}}; 121 | {type = "text", subtype = "x-dvi", parameters = {}, q = 0.8, extensions = {}}; 122 | {type = "text", subtype = "x-c", parameters = {}, q = nil, extensions = {}}; 123 | }, Accept:match("text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c")) 124 | assert.same({ 125 | {type = "text", subtype = nil, parameters = {}, extensions = {}}; 126 | {type = "text", subtype = "plain", parameters = {}, extensions = {}}; 127 | {type = "text", subtype = "plain", parameters = {format = "flowed"}, extensions = {}}; 128 | {type = nil, subtype = nil, parameters = {}, extensions = {}}; 129 | }, Accept:match("text/*, text/plain, text/plain;format=flowed, */*")) 130 | assert.same({ 131 | {type = "text", subtype = nil, parameters = {}, q = 0.3, extensions = {}}; 132 | {type = "text", subtype = "html", parameters = {}, q = 0.7, extensions = {}}; 133 | {type = "text", subtype = "html", parameters = {level = "1"}, q = nil, extensions = {}}; 134 | {type = "text", subtype = "html", parameters = {level = "2"}, q = 0.4, extensions = {}}; 135 | {type = nil, subtype = nil, parameters = {}, q = 0.5, extensions = {}}; 136 | }, Accept:match("text/*;q=0.3, text/html;q=0.7, text/html;level=1,text/html;level=2;q=0.4, */*;q=0.5")) 137 | end) 138 | it("Matches the 3 date formats", function() 139 | local Date = http.Date * EOF 140 | local example_time = { 141 | year = 1994; 142 | month = 11; 143 | day = 6; 144 | hour = 8; 145 | min = 49; 146 | sec = 37; 147 | wday = 1; 148 | } 149 | assert.same(example_time, Date:match"Sun, 06 Nov 1994 08:49:37 GMT") 150 | assert.same(example_time, Date:match"Sunday, 06-Nov-94 08:49:37 GMT") 151 | assert.same(example_time, Date:match"Sun Nov 6 08:49:37 1994") 152 | end) 153 | it("Parses a Cache-Control header", function() 154 | local cc_patt = lpeg.Cf(lpeg.Ct(true) * http.Cache_Control, rawset) * EOF 155 | assert.same({public = true}, cc_patt:match("public")) 156 | assert.same({["no-cache"] = true}, cc_patt:match("no-cache")) 157 | assert.same({["max-age"] = "31536000"}, cc_patt:match("max-age=31536000")) 158 | assert.same({["max-age"] = "31536000", immutable = true}, cc_patt:match("max-age=31536000, immutable")) 159 | -- leading/trailing whitespace 160 | assert.same({public = true}, cc_patt:match(" public ")) 161 | assert.same({["max-age"] = "31536000", immutable = true}, cc_patt:match(" max-age=31536000 , immutable ")) 162 | end) 163 | it("Parses an WWW_Authenticate header", function() 164 | local WWW_Authenticate = lpeg.Ct(http.WWW_Authenticate) * EOF 165 | assert.same({{"Newauth"}}, WWW_Authenticate:match"Newauth") 166 | assert.same({{"Newauth", {realm = "apps"}}}, WWW_Authenticate:match[[Newauth realm="apps"]]) 167 | assert.same({{"Newauth", {realm = "apps"}}}, WWW_Authenticate:match[[Newauth ReaLm="apps"]]) 168 | assert.same({{"Newauth"}, {"Basic"}}, WWW_Authenticate:match"Newauth, Basic") 169 | assert.same({{"Newauth", {realm = "apps", type="1", title="Login to \"apps\""}}, {"Basic", {realm="simple"}}}, 170 | WWW_Authenticate:match[[Newauth realm="apps", type=1, title="Login to \"apps\"", Basic realm="simple"]]) 171 | end) 172 | end) 173 | -------------------------------------------------------------------------------- /spec/http_sts_spec.lua: -------------------------------------------------------------------------------- 1 | describe("lpeg_patterns.http.sts", function() 2 | local http_sts = require "lpeg_patterns.http.sts" 3 | local lpeg = require "lpeg" 4 | local EOF = lpeg.P(-1) 5 | it("Parses a Strict-Transport-Security header", function() 6 | local sts_patt = http_sts.Strict_Transport_Security * EOF 7 | assert.same({["max-age"] = "0"}, sts_patt:match("max-age=0")) 8 | assert.same({["max-age"] = "0"}, sts_patt:match("max-age = 0")) 9 | assert.same({["max-age"] = "0"}, sts_patt:match("Max-Age=0")) 10 | assert.same({["max-age"] = "0"; includesubdomains = true}, sts_patt:match("max-age=0;includeSubdomains")) 11 | assert.same({["max-age"] = "0"; includesubdomains = true}, sts_patt:match("max-age=0 ; includeSubdomains")) 12 | -- max-age is required 13 | assert.same(nil, sts_patt:match("foo=0")) 14 | -- Should fail to parse when duplicate field given. 15 | assert.same(nil, sts_patt:match("max-age=42; foo=0; foo=1")) 16 | end) 17 | end) 18 | -------------------------------------------------------------------------------- /spec/http_websocket_spec.lua: -------------------------------------------------------------------------------- 1 | describe("lpeg_patterns.http.websocket", function() 2 | local http_websocket = require "lpeg_patterns.http.websocket" 3 | local lpeg = require "lpeg" 4 | local EOF = lpeg.P(-1) 5 | it("Parses a Sec-WebSocket-Extensions header", function() 6 | local Sec_WebSocket_Extensions = lpeg.Ct(http_websocket.Sec_WebSocket_Extensions) * EOF 7 | assert.same({{"foo", parameters = {}}}, 8 | Sec_WebSocket_Extensions:match"foo") 9 | assert.same({{"foo", parameters = {}}, {"bar", parameters = {}}}, 10 | Sec_WebSocket_Extensions:match"foo, bar") 11 | assert.same({{"foo", parameters = {hello = true; world = "extension"}}, {"bar", parameters = {}}}, 12 | Sec_WebSocket_Extensions:match"foo;hello;world=extension, bar") 13 | assert.same({{"foo", parameters = {hello = true; world = "extension"}}, {"bar", parameters = {}}}, 14 | Sec_WebSocket_Extensions:match"foo;hello;world=\"extension\", bar") 15 | -- quoted strings must be valid tokens 16 | assert.falsy(Sec_WebSocket_Extensions:match"foo;hello;world=\"exte\\\"nsion\", bar") 17 | end) 18 | it("Parses a Sec_WebSocket-Version-Client header", function() 19 | local Sec_WebSocket_Version_Client = http_websocket.Sec_WebSocket_Version_Client * EOF 20 | assert.same(1, Sec_WebSocket_Version_Client:match"1") 21 | assert.same(100, Sec_WebSocket_Version_Client:match"100") 22 | assert.same(255, Sec_WebSocket_Version_Client:match"255") 23 | assert.falsy(Sec_WebSocket_Version_Client:match"0") 24 | assert.falsy(Sec_WebSocket_Version_Client:match"256") 25 | assert.falsy(Sec_WebSocket_Version_Client:match"1.2") 26 | assert.falsy(Sec_WebSocket_Version_Client:match"090") 27 | end) 28 | end) 29 | -------------------------------------------------------------------------------- /spec/language_spec.lua: -------------------------------------------------------------------------------- 1 | local lpeg = require "lpeg" 2 | local EOF = lpeg.P(-1) 3 | describe("language tags", function() 4 | local language = require "lpeg_patterns.language" 5 | local langtag = lpeg.Ct(language.langtag) * EOF 6 | local Language_Tag = language.Language_Tag * EOF 7 | describe("examples from RFC 5646 Appendix A", function() 8 | it("Parses Simple language subtag", function() 9 | -- German 10 | assert.same({language = "de"}, langtag:match "de") 11 | -- French 12 | assert.same({language = "fr"}, langtag:match "fr") 13 | -- Japanese 14 | assert.same({language = "ja"}, langtag:match "ja") 15 | -- example of a grandfathered tag 16 | assert.truthy(Language_Tag:match "i-enochian") 17 | end) 18 | it("Parses Language subtag plus Script subtag", function() 19 | -- Chinese written using the Traditional Chinese script 20 | assert.same({language = "zh"; script = "Hant"}, 21 | langtag:match "zh-Hant") 22 | -- Chinese written using the Simplified Chinese script 23 | assert.same({language = "zh"; script = "Hans"}, 24 | langtag:match "zh-Hans") 25 | -- Serbian written using the Cyrillic script 26 | assert.same({language = "sr"; script = "Cyrl"}, 27 | langtag:match "sr-Cyrl") 28 | -- Serbian written using the Latin script 29 | assert.same({language = "sr"; script = "Latn"}, 30 | langtag:match "sr-Latn") 31 | end) 32 | it("Parses Extended language subtags and their primary language subtag counterparts", function() 33 | -- Chinese, Mandarin, Simplified script, as used in China 34 | assert.same({language = "zh"; extlang = "cmn", script = "Hans"; region = "CN"}, 35 | langtag:match "zh-cmn-Hans-CN") 36 | -- Mandarin Chinese, Simplified script, as used in China 37 | assert.same({language = "cmn"; script = "Hans"; region = "CN"}, 38 | langtag:match "cmn-Hans-CN") 39 | -- Chinese, Cantonese, as used in Hong Kong SAR 40 | assert.same({language = "zh"; extlang = "yue"; region = "HK"}, 41 | langtag:match "zh-yue-HK") 42 | -- Cantonese Chinese, as used in Hong Kong SAR 43 | assert.same({language = "yue"; region = "HK"}, 44 | langtag:match "yue-HK") 45 | end) 46 | it("Parses Language-Script-Region", function() 47 | -- Chinese written using the Simplified script as used in mainland China 48 | assert.same({language = "zh"; script = "Hans"; region = "CN"}, 49 | langtag:match "zh-Hans-CN") 50 | -- Serbian written using the Latin script as used in Serbia 51 | assert.same({language = "sr"; script = "Latn"; region = "RS"}, 52 | langtag:match "sr-Latn-RS") 53 | end) 54 | it("Parses Language-Variant", function() 55 | -- Resian dialect of Slovenian 56 | assert.same({language = "sl"; variant = {"rozaj"}}, 57 | langtag:match "sl-rozaj") 58 | -- San Giorgio dialect of Resian dialect of Slovenian 59 | assert.same({language = "sl"; variant = {"rozaj", "biske"}}, 60 | langtag:match "sl-rozaj-biske") 61 | -- Nadiza dialect of Slovenian 62 | assert.same({language = "sl"; variant = {"nedis"}}, 63 | langtag:match "sl-nedis") 64 | end) 65 | it("Parses Language-Region-Variant", function() 66 | -- German as used in Switzerland using the 1901 variant [orthography] 67 | assert.same({language = "de"; region = "CH"; variant = {"1901"}}, 68 | langtag:match "de-CH-1901") 69 | -- Slovenian as used in Italy, Nadiza dialect 70 | assert.same({language = "sl"; region = "IT"; variant = {"nedis"}}, 71 | langtag:match "sl-IT-nedis") 72 | end) 73 | it("Parses Language-Script-Region-Variant", function() 74 | -- Eastern Armenian written in Latin script, as used in Italy 75 | assert.same({language = "hy"; script = "Latn"; region = "IT"; variant = {"arevela"}}, 76 | langtag:match "hy-Latn-IT-arevela") 77 | end) 78 | it("Parses Language-Region", function() 79 | -- German for Germany 80 | assert.same({language = "de"; region = "DE"}, 81 | langtag:match "de-DE") 82 | -- English as used in the United States 83 | assert.same({language = "en"; region = "US"}, 84 | langtag:match "en-US") 85 | -- Spanish appropriate for the Latin America and Caribbean region using the UN region code 86 | assert.same({language = "es"; region = "419"}, 87 | langtag:match "es-419") 88 | end) 89 | it("Parses private use subtags", function() 90 | assert.same({language = "de"; region = "CH"; privateuse = {"phonebk"}}, 91 | langtag:match "de-CH-x-phonebk") 92 | assert.same({language = "az"; script = "Arab"; privateuse = {"AZE", "derbend"}}, 93 | langtag:match "az-Arab-x-AZE-derbend") 94 | end) 95 | it("Parses private use registry values", function() 96 | assert.truthy(Language_Tag:match "x-whatever") -- private use using the singleton 'x' 97 | -- all private tags 98 | assert.same({language = "qaa"; script = "Qaaa"; region = "QM"; privateuse = {"southern"}}, 99 | langtag:match "qaa-Qaaa-QM-x-southern") 100 | -- German, with a private script 101 | assert.same({language = "de"; script = "Qaaa"}, 102 | langtag:match "de-Qaaa") 103 | -- Serbian, Latin script, private region 104 | assert.same({language = "sr"; script = "Latn"; region = "QM"}, 105 | langtag:match "sr-Latn-QM") 106 | -- Serbian, private script, for Serbia 107 | assert.same({language = "sr"; script = "Qaaa"; region = "RS"}, 108 | langtag:match "sr-Qaaa-RS") 109 | end) 110 | it("Parses tags that use extensions", function() 111 | assert.same({language = "en"; region = "US"; extension = { u = {"islamcal"}}}, 112 | langtag:match "en-US-u-islamcal") 113 | assert.same({language = "zh"; region = "CN"; extension = { a = {"myext"}}; privateuse = {"private"}}, 114 | langtag:match "zh-CN-a-myext-x-private") 115 | assert.same({language = "en"; extension = { a = {"myext"}, b = {"another"}}}, 116 | langtag:match "en-a-myext-b-another") 117 | end) 118 | it("Rejects Invalid Tags", function() 119 | -- two region tags 120 | assert.falsy(langtag:match "de-419-DE") 121 | -- use of a single-character subtag in primary position; 122 | -- note that there are a few grandfathered tags that start with "i-" that are valid 123 | assert.falsy(langtag:match "a-DE") 124 | -- two extensions with same single-letter prefix 125 | assert.falsy(langtag:match "ar-a-aaa-b-bbb-a-ccc") 126 | end) 127 | end) 128 | it("captures whole text when using Language_Tag", function() 129 | assert.same("en", Language_Tag:match "en") 130 | assert.same("hy-Latn-IT-arevela", Language_Tag:match "hy-Latn-IT-arevela") 131 | end) 132 | end) 133 | -------------------------------------------------------------------------------- /spec/phone_spec.lua: -------------------------------------------------------------------------------- 1 | local lpeg = require "lpeg" 2 | 3 | describe("Phone numbers", function() 4 | local phone = require "lpeg_patterns.phone" 5 | local any_only = phone.phone * lpeg.P(-1) 6 | 7 | it("NANP (North America Numbering Plan)", function() 8 | assert.truthy(any_only:match"+12345678900") 9 | assert.truthy(any_only:match"+1 (234) 567-8900") 10 | 11 | assert.truthy(phone.USA:match"1 (234) 567-8900") 12 | assert.truthy(phone.USA:match"(234) 567-8900") 13 | assert.falsy(phone.USA:match"2 (234) 567-8900") 14 | 15 | -- N11 not allowed 16 | assert.falsy(any_only:match"+12345118900") 17 | -- N9X not allowed 18 | assert.falsy(any_only:match"+12345978900") 19 | -- 37X not allowed 20 | assert.falsy(any_only:match"+12343778900") 21 | -- 96X not allowed 22 | assert.falsy(any_only:match"+12349678900") 23 | end) 24 | 25 | it("Australian numbers", function() 26 | assert.truthy(phone.Australia:match"0390000000") 27 | assert.truthy(phone.Australia:match"3 90000000") 28 | assert.truthy(phone.Australia:match"3 9000 0000") 29 | assert.truthy(phone.Australia:match"400 000 000") 30 | 31 | assert.truthy(any_only:match"+61390000000") 32 | assert.truthy(any_only:match"+61 3 90000000") 33 | assert.truthy(any_only:match"+61 3 9000 0000") 34 | assert.truthy(any_only:match"+61 400 000 000") 35 | 36 | assert.falsy(any_only:match"+610390000000") 37 | end) 38 | end) 39 | -------------------------------------------------------------------------------- /spec/uri_spec.lua: -------------------------------------------------------------------------------- 1 | local lpeg=require "lpeg" 2 | local uri_lib=require "lpeg_patterns.uri" 3 | 4 | describe("URI", function() 5 | local absolute_uri = uri_lib.absolute_uri * lpeg.P(-1) 6 | local uri = uri_lib.uri * lpeg.P(-1) 7 | local ref = uri_lib.uri_reference * lpeg.P(-1) 8 | local path = uri_lib.path * lpeg.P(-1) 9 | local segment = uri_lib.segment * lpeg.P(-1) 10 | it("Should break down full URIs correctly", function() 11 | assert.same({scheme="scheme", userinfo="userinfo", host="host", port=1234, path="/path", query="query", fragment="fragment"}, 12 | uri:match "scheme://userinfo@host:1234/path?query#fragment") 13 | assert.same({scheme="scheme", userinfo="userinfo", host="host", port=1234, path="/path", query="query"}, 14 | uri:match "scheme://userinfo@host:1234/path?query") 15 | assert.same({scheme="scheme", userinfo="userinfo", host="host", port=1234, path="/path"}, 16 | uri:match "scheme://userinfo@host:1234/path") 17 | assert.same({scheme="scheme", host="host", port=1234, path="/path"}, 18 | uri:match "scheme://host:1234/path") 19 | assert.same({scheme="scheme", host="host", path="/path"}, 20 | uri:match "scheme://host/path") 21 | assert.same({scheme="scheme", path="/path"}, 22 | uri:match "scheme:///path") 23 | assert.same({scheme="scheme"}, 24 | uri:match "scheme://") 25 | end) 26 | it("Normalises to lower case scheme", function() 27 | assert.same({scheme="scheme"}, uri:match "Scheme://") 28 | assert.same({scheme="scheme"}, uri:match "SCHEME://") 29 | end) 30 | it("shouldn't allow fragments when using absolute_uri", function() 31 | assert.falsy(absolute_uri:match "scheme://userinfo@host:1234/path?query#fragment") 32 | assert.same({scheme="scheme", userinfo="userinfo", host="host", port=1234, path="/path", query="query"}, 33 | absolute_uri:match "scheme://userinfo@host:1234/path?query") 34 | end) 35 | it("Should break down relative URIs correctly", function() 36 | assert.same({scheme="scheme", userinfo="userinfo", host="host", port=1234, path="/path", query="query", fragment="fragment"}, 37 | ref:match "scheme://userinfo@host:1234/path?query#fragment") 38 | assert.same({userinfo="userinfo", host="host", port=1234, path="/path", query="query", fragment="fragment"}, 39 | ref:match "//userinfo@host:1234/path?query#fragment") 40 | assert.same({host="host", port=1234, path="/path", query="query", fragment="fragment"}, 41 | ref:match "//host:1234/path?query#fragment") 42 | assert.same({host="host", path="/path", query="query", fragment="fragment"}, 43 | ref:match "//host/path?query#fragment") 44 | assert.same({path="/path", query="query", fragment="fragment"}, 45 | ref:match "///path?query#fragment") 46 | assert.same({path="/path", query="query", fragment="fragment"}, 47 | ref:match "/path?query#fragment") 48 | assert.same({path="/path", fragment="fragment"}, 49 | ref:match "/path#fragment") 50 | assert.same({path="/path"}, 51 | ref:match "/path") 52 | assert.same({}, 53 | ref:match "") 54 | assert.same({query="query"}, 55 | ref:match "?query") 56 | assert.same({fragment="fragment"}, 57 | ref:match "#fragment") 58 | end) 59 | it("Should match file urls", function() 60 | assert.same({scheme="file", path="/var/log/messages"}, uri:match "file:///var/log/messages") 61 | assert.same({scheme="file", path="/C:/Windows/"}, uri:match "file:///C:/Windows/") 62 | end) 63 | it("Should decode unreserved percent characters in path segment", function() 64 | assert.same("underscore_character", segment:match "underscore%5Fcharacter") 65 | assert.same("null%00byte", segment:match "null%00byte") 66 | end) 67 | it("Should decode unreserved percent characters path", function() 68 | assert.same("/underscore_character", path:match "/underscore%5Fcharacter") 69 | assert.same("/null%00byte", path:match "/null%00byte") 70 | end 71 | ) it("Should fail on incorrect percent characters", function() 72 | assert.falsy(path:match "/bad%x0percent") 73 | assert.falsy(path:match "/%s") 74 | end) 75 | it("Should not introduce ambiguiuty by decoding percent encoded entities", function() 76 | assert.same({query="query%26with&ersand"}, ref:match "?query%26with&ersand") 77 | end) 78 | it("Should decode unreserved percent characters in query and fragment", function() 79 | assert.same({query="query%20with_escapes"}, ref:match "?query%20with%5Fescapes") 80 | assert.same({fragment="fragment%20with_escapes"}, ref:match "#fragment%20with%5Fescapes") 81 | end) 82 | it("Should match localhost", function() 83 | assert.same({host="localhost"}, ref:match "//localhost") 84 | assert.same({host="localhost"}, ref:match "//LOCALHOST") 85 | assert.same({host="localhost"}, ref:match "//l%4FcAlH%6fSt") 86 | assert.same({host="localhost", port=8000}, ref:match "//localhost:8000") 87 | assert.same({scheme="http", host="localhost", port=8000}, uri:match "http://localhost:8000") 88 | end) 89 | it("Should work with IPv6", function() 90 | assert.same({host="0:0:0:0:0:0:0:1"}, ref:match "//[::1]") 91 | assert.same({host="0:0:0:0:0:0:0:1", port=80}, ref:match "//[::1]:80") 92 | end) 93 | it("IPvFuture", function() 94 | assert.same({host="v4.2", port=80}, ref:match "//[v4.2]:80") 95 | assert.same({host="v4.2", port=80}, ref:match "//[V4.2]:80") 96 | end) 97 | it("Should work with IPv6 zone local addresses", function() 98 | assert.same({host="0:0:0:0:0:0:0:1%eth0"}, ref:match "//[::1%25eth0]") 99 | end) 100 | it("Relative URI does not match authority when scheme is missing", function() 101 | assert.same({path="example.com/"}, ref:match "example.com/") -- should end up in path 102 | assert.same({scheme="scheme", host="example.com", path="/"}, ref:match "scheme://example.com/") 103 | end) 104 | it("Should work with mailto URIs", function() 105 | assert.same({scheme="mailto", path="user@example.com"}, 106 | uri:match "mailto:user@example.com") 107 | assert.same({scheme="mailto", path="someone@example.com,someoneelse@example.com"}, 108 | uri:match "mailto:someone@example.com,someoneelse@example.com") 109 | assert.same({scheme="mailto", path="user@example.com", query="subject=This%20is%20the%20subject&cc=someone_else@example.com&body=This%20is%20the%20body"}, 110 | uri:match "mailto:user@example.com?subject=This%20is%20the%20subject&cc=someone_else@example.com&body=This%20is%20the%20body") 111 | 112 | -- Examples from RFC-6068 113 | -- Section 6.1 114 | assert.same({scheme="mailto", path="chris@example.com"}, 115 | uri:match "mailto:chris@example.com") 116 | assert.same({scheme="mailto", path="infobot@example.com", query="subject=current-issue"}, 117 | uri:match "mailto:infobot@example.com?subject=current-issue") 118 | assert.same({scheme="mailto", path="infobot@example.com", query="body=send%20current-issue"}, 119 | uri:match "mailto:infobot@example.com?body=send%20current-issue") 120 | assert.same({scheme="mailto", path="infobot@example.com", query="body=send%20current-issue%0D%0Asend%20index"}, 121 | uri:match "mailto:infobot@example.com?body=send%20current-issue%0D%0Asend%20index") 122 | assert.same({scheme="mailto", path="list@example.org", query="In-Reply-To=%3C3469A91.D10AF4C@example.com%3E"}, 123 | uri:match "mailto:list@example.org?In-Reply-To=%3C3469A91.D10AF4C@example.com%3E") 124 | assert.same({scheme="mailto", path="majordomo@example.com", query="body=subscribe%20bamboo-l"}, 125 | uri:match "mailto:majordomo@example.com?body=subscribe%20bamboo-l") 126 | assert.same({scheme="mailto", path="joe@example.com", query="cc=bob@example.com&body=hello"}, 127 | uri:match "mailto:joe@example.com?cc=bob@example.com&body=hello") 128 | assert.same({scheme="mailto", path="gorby%25kremvax@example.com"}, 129 | uri:match "mailto:gorby%25kremvax@example.com") 130 | assert.same({scheme="mailto", path="unlikely%3Faddress@example.com", query="blat=foop"}, 131 | uri:match "mailto:unlikely%3Faddress@example.com?blat=foop") 132 | assert.same({scheme="mailto", path="Mike%26family@example.org"}, 133 | uri:match "mailto:Mike%26family@example.org") 134 | -- Section 6.2 135 | assert.same({scheme="mailto", path=[[%22not%40me%22@example.org]]}, 136 | uri:match "mailto:%22not%40me%22@example.org") 137 | assert.same({scheme="mailto", path=[[%22oh%5C%5Cno%22@example.org]]}, 138 | uri:match "mailto:%22oh%5C%5Cno%22@example.org") 139 | assert.same({scheme="mailto", path=[[%22%5C%5C%5C%22it's%5C%20ugly%5C%5C%5C%22%22@example.org]]}, 140 | uri:match "mailto:%22%5C%5C%5C%22it's%5C%20ugly%5C%5C%5C%22%22@example.org") 141 | end) 142 | it("Should work with xmpp URIs", function() 143 | -- Examples from RFC-5122 144 | assert.same({scheme="xmpp", path="node@example.com"}, 145 | uri:match "xmpp:node@example.com") 146 | assert.same({scheme="xmpp", userinfo="guest", host="example.com"}, 147 | uri:match "xmpp://guest@example.com") 148 | assert.same({scheme="xmpp", userinfo="guest", host="example.com", path="/support@example.com", query="message"}, 149 | uri:match "xmpp://guest@example.com/support@example.com?message") 150 | assert.same({scheme="xmpp", path="support@example.com", query="message"}, 151 | uri:match "xmpp:support@example.com?message") 152 | 153 | assert.same({scheme="xmpp", path="example-node@example.com"}, 154 | uri:match "xmpp:example-node@example.com") 155 | assert.same({scheme="xmpp", path="example-node@example.com/some-resource"}, 156 | uri:match "xmpp:example-node@example.com/some-resource") 157 | assert.same({scheme="xmpp", path="example.com"}, 158 | uri:match "xmpp:example.com") 159 | assert.same({scheme="xmpp", path="example-node@example.com", query="message"}, 160 | uri:match "xmpp:example-node@example.com?message") 161 | assert.same({scheme="xmpp", path="example-node@example.com", query="message;subject=Hello%20World"}, 162 | uri:match "xmpp:example-node@example.com?message;subject=Hello%20World") 163 | assert.same({scheme="xmpp", path=[[nasty!%23$%25()*+,-.;=%3F%5B%5C%5D%5E_%60%7B%7C%7D~node@example.com]]}, 164 | uri:match "xmpp:nasty!%23$%25()*+,-.;=%3F%5B%5C%5D%5E_%60%7B%7C%7D~node@example.com") 165 | assert.same({scheme="xmpp", path=[[node@example.com/repulsive%20!%23%22$%25&'()*+,-.%2F:;%3C=%3E%3F%40%5B%5C%5D%5E_%60%7B%7C%7D~resource]]}, 166 | uri:match [[xmpp:node@example.com/repulsive%20!%23%22$%25&'()*+,-.%2F:;%3C=%3E%3F%40%5B%5C%5D%5E_%60%7B%7C%7D~resource]]) 167 | assert.same({scheme="xmpp", path="ji%C5%99i@%C4%8Dechy.example/v%20Praze"}, 168 | uri:match "xmpp:ji%C5%99i@%C4%8Dechy.example/v%20Praze") 169 | end) 170 | end) 171 | 172 | describe("Sane URI", function() 173 | local sane_uri = uri_lib.sane_uri 174 | it("Not match the empty string", function() 175 | assert.falsy ( sane_uri:match "" ) 176 | end) 177 | it("Not match misc words", function() 178 | assert.falsy ( sane_uri:match "localhost" ) 179 | assert.falsy ( sane_uri:match "//localhost" ) 180 | assert.falsy ( sane_uri:match "the quick fox jumped over the lazy dog." ) 181 | end) 182 | it("Not match numbers", function() 183 | assert.falsy( sane_uri:match "123" ) 184 | assert.falsy( sane_uri:match "17.3" ) 185 | assert.falsy( sane_uri:match "17.3234" ) 186 | assert.falsy( sane_uri:match "17.3234" ) 187 | end) 188 | it("Should match a host when no // present", function() 189 | assert.same({host="example.com"}, sane_uri:match "example.com") 190 | end) 191 | it("Match a scheme without a //", function() 192 | assert.same({scheme="scheme", host="example.com"}, sane_uri:match "scheme:example.com") 193 | end) 194 | it("Will match up to but not including a close parenthsis with empty path", function() 195 | assert.same({scheme="scheme", host="example.com"}, sane_uri:match "scheme:example.com)") 196 | end) 197 | end) 198 | --------------------------------------------------------------------------------