🧚 A practical web framework for Gleam
3
fork

Configure Feed

Select the types of activity you want to include in your feed.

Optimise html_escape

authored by giacomocavalieri.me and committed by

Louis Pilfold 618e2332 754bd38e

+224 -105
+220 -105
src/wisp.gleam
··· 152 HttpResponse(Body) 153 154 /// Create an empty response with the given status code. 155 - /// 156 /// # Examples 157 - /// 158 /// ```gleam 159 /// response(200) 160 /// // -> Response(200, [], Empty) 161 /// ``` 162 - /// 163 pub fn response(status: Int) -> Response { 164 HttpResponse(status, [], Empty) 165 } 166 167 /// Set the body of a response. 168 - /// 169 /// # Examples 170 - /// 171 /// ```gleam 172 /// response(200) 173 /// |> set_body(File("/tmp/myfile.txt")) 174 /// // -> Response(200, [], File("/tmp/myfile.txt")) 175 /// ``` 176 - /// 177 pub fn set_body(response: Response, body: Body) -> Response { 178 response 179 |> response.set_body(body) ··· 193 /// `set_body` function with the `File` body variant. 194 /// 195 /// # Examples 196 - /// 197 /// ```gleam 198 /// response(200) 199 /// |> file_download(named: "myfile.txt", from: "/tmp/myfile.txt") ··· 229 /// as this can result in cross-site scripting vulnerabilities. 230 /// 231 /// # Examples 232 - /// 233 /// ```gleam 234 /// response(200) 235 /// |> file_download_from_memory(named: "myfile.txt", containing: "Hello, Joe!") ··· 255 } 256 257 /// Create a HTML response. 258 - /// 259 /// The body is expected to be valid HTML, though this is not validated. 260 /// The `content-type` header will be set to `text/html`. 261 - /// 262 /// # Examples 263 - /// 264 /// ```gleam 265 /// let body = string_builder.from_string("<h1>Hello, Joe!</h1>") 266 /// html_response(body, 200) 267 /// // -> Response(200, [#("content-type", "text/html")], Text(body)) 268 /// ``` 269 - /// 270 pub fn html_response(html: StringBuilder, status: Int) -> Response { 271 HttpResponse(status, [#("content-type", "text/html")], Text(html)) 272 } 273 274 /// Create a JSON response. 275 - /// 276 /// The body is expected to be valid JSON, though this is not validated. 277 /// The `content-type` header will be set to `application/json`. 278 - /// 279 /// # Examples 280 - /// 281 /// ```gleam 282 /// let body = string_builder.from_string("{\"name\": \"Joe\"}") 283 /// json_response(body, 200) 284 /// // -> Response(200, [#("content-type", "application/json")], Text(body)) 285 /// ``` 286 - /// 287 pub fn json_response(json: StringBuilder, status: Int) -> Response { 288 HttpResponse(status, [#("content-type", "application/json")], Text(json)) 289 } 290 291 /// Set the body of a response to a given HTML document, and set the 292 /// `content-type` header to `text/html`. 293 - /// 294 /// The body is expected to be valid HTML, though this is not validated. 295 - /// 296 /// # Examples 297 - /// 298 /// ```gleam 299 /// let body = string_builder.from_string("<h1>Hello, Joe!</h1>") 300 /// response(201) 301 /// |> html_body(body) 302 /// // -> Response(201, [#("content-type", "text/html")], Text(body)) 303 /// ``` 304 - /// 305 pub fn html_body(response: Response, html: StringBuilder) -> Response { 306 response 307 |> response.set_body(Text(html)) ··· 310 311 /// Set the body of a response to a given JSON document, and set the 312 /// `content-type` header to `application/json`. 313 - /// 314 /// The body is expected to be valid JSON, though this is not validated. 315 - /// 316 /// # Examples 317 - /// 318 /// ```gleam 319 /// let body = string_builder.from_string("{\"name\": \"Joe\"}") 320 /// response(201) 321 /// |> json_body(body) 322 /// // -> Response(201, [#("content-type", "application/json")], Text(body)) 323 /// ``` 324 - /// 325 pub fn json_body(response: Response, json: StringBuilder) -> Response { 326 response 327 |> response.set_body(Text(json)) ··· 334 /// appropriate value for the format of the content. 335 /// 336 /// # Examples 337 - /// 338 /// ```gleam 339 /// let body = string_builder.from_string("Hello, Joe!") 340 /// response(201) 341 /// |> string_builder_body(body) 342 /// // -> Response(201, [], Text(body)) 343 /// ``` 344 - /// 345 pub fn string_builder_body( 346 response: Response, 347 content: StringBuilder, ··· 356 /// appropriate value for the format of the content. 357 /// 358 /// # Examples 359 - /// 360 /// ```gleam 361 - /// let body = 362 /// response(201) 363 /// |> string_body("Hello, Joe!") 364 /// // -> Response( ··· 367 /// // Text(string_builder.from_string("Hello, Joe")) 368 /// // ) 369 /// ``` 370 - /// 371 pub fn string_body(response: Response, content: String) -> Response { 372 response 373 |> response.set_body(Text(string_builder.from_string(content))) ··· 384 /// escape_html("<h1>Hello, Joe!</h1>") 385 /// // -> "&lt;h1&gt;Hello, Joe!&lt;/h1&gt;" 386 /// ``` 387 - /// 388 pub fn escape_html(content: String) -> String { 389 - do_escape_html("", content) 390 } 391 392 - fn do_escape_html(escaped: String, content: String) -> String { 393 - case string.pop_grapheme(content) { 394 - Ok(#("<", xs)) -> do_escape_html(escaped <> "&lt;", xs) 395 - Ok(#(">", xs)) -> do_escape_html(escaped <> "&gt;", xs) 396 - Ok(#("&", xs)) -> do_escape_html(escaped <> "&amp;", xs) 397 - Ok(#(x, xs)) -> do_escape_html(escaped <> x, xs) 398 - Error(_) -> escaped <> content 399 } 400 } 401 ··· 593 // 594 595 /// The connection to the client for a HTTP request. 596 - /// 597 /// The body of the request can be read from this connection using functions 598 /// such as `require_multipart_body`. 599 - /// 600 pub opaque type Connection { 601 Connection( 602 reader: Reader, ··· 676 } 677 678 /// Get the maximum permitted size of a request body of the request in bytes. 679 - /// 680 pub fn get_max_body_size(request: Request) -> Int { 681 request.body.max_body_size 682 } 683 684 /// Set the secret key base used to sign cookies and other sensitive data. 685 - /// 686 /// This key must be at least 64 bytes long and should be kept secret. Anyone 687 /// with this secret will be able to manipulate signed cookies and other sensitive 688 /// data. 689 /// 690 /// # Panics 691 - /// 692 /// This function will panic if the key is less than 64 bytes long. 693 /// 694 pub fn set_secret_key_base(request: Request, key: String) -> Request { ··· 701 } 702 703 /// Get the secret key base used to sign cookies and other sensitive data. 704 - /// 705 pub fn get_secret_key_base(request: Request) -> String { 706 request.body.secret_key_base 707 } ··· 723 724 /// Get the maximum permitted total size of a files uploaded by a request in 725 /// bytes. 726 - /// 727 pub fn get_max_files_size(request: Request) -> Int { 728 request.body.max_files_size 729 } ··· 743 744 /// Get the size limit for each chunk of the request body when read from the 745 /// client. 746 - /// 747 pub fn get_read_chunk_size(request: Request) -> Int { 748 request.body.read_chunk_size 749 } 750 751 /// A convenient alias for a HTTP request with a Wisp connection as the body. 752 - /// 753 pub type Request = 754 HttpRequest(Connection) 755 ··· 758 /// if the method is not correct. 759 /// 760 /// # Examples 761 - /// 762 /// ```gleam 763 /// fn handle_request(request: Request) -> Response { 764 /// use <- wisp.require_method(request, http.Patch) ··· 779 780 // TODO: re-export once Gleam has a syntax for that 781 /// Return the non-empty segments of a request path. 782 - /// 783 /// # Examples 784 /// 785 /// ```gleam ··· 793 794 // TODO: re-export once Gleam has a syntax for that 795 /// Set a given header to a given value, replacing any existing value. 796 - /// 797 /// # Examples 798 /// 799 /// ```gleam ··· 863 /// return an incorrect value, depending on the underlying web server. It is the 864 /// responsibility of the caller to cache the body if it is needed multiple 865 /// times. 866 - /// 867 /// If the body is larger than the `max_body_size` limit then an empty response 868 /// with status code 413: Entity too large will be returned to the client. 869 - /// 870 /// If the body is found not to be valid UTF-8 then an empty response with 871 /// status code 400: Bad request will be returned to the client. 872 - /// 873 /// # Examples 874 /// 875 /// ```gleam ··· 899 /// return an incorrect value, depending on the underlying web server. It is the 900 /// responsibility of the caller to cache the body if it is needed multiple 901 /// times. 902 - /// 903 /// If the body is larger than the `max_body_size` limit then an empty response 904 /// with status code 413: Entity too large will be returned to the client. 905 - /// 906 /// # Examples 907 /// 908 /// ```gleam ··· 925 // TODO: don't always return entity to large. Other errors are possible, such as 926 // network errors. 927 /// Read the entire body of the request as a bit string. 928 - /// 929 /// You may instead wish to use the `require_bit_array_body` or the 930 /// `require_string_body` middleware functions instead. 931 - /// 932 /// This function does not cache the body in any way, so if you call this 933 /// function (or any other body reading function) more than once it may hang or 934 /// return an incorrect value, depending on the underlying web server. It is the 935 /// responsibility of the caller to cache the body if it is needed multiple 936 /// times. 937 - /// 938 /// If the body is larger than the `max_body_size` limit then an empty response 939 /// with status code 413: Entity too large will be returned to the client. 940 - /// 941 pub fn read_body_to_bitstring(request: Request) -> Result(BitArray, Nil) { 942 let connection = request.body 943 read_body_loop( ··· 971 /// A middleware which extracts form data from the body of a request that is 972 /// encoded as either `application/x-www-form-urlencoded` or 973 /// `multipart/form-data`. 974 - /// 975 /// Extracted fields are sorted into alphabetical order by key, so if you wish 976 /// to use pattern matching the order can be relied upon. 977 - /// 978 /// ```gleam 979 /// fn handle_request(request: Request) -> Response { 980 /// use form <- wisp.require_form(request) ··· 1003 /// 1004 /// If the body cannot be parsed successfully then an empty response with status 1005 /// code 400: Bad request will be returned to the client. 1006 - /// 1007 pub fn require_form( 1008 request: Request, 1009 next: fn(FormData) -> Response, ··· 1030 /// Unsupported media type if the header is not the expected value 1031 /// 1032 /// # Examples 1033 - /// 1034 /// ```gleam 1035 /// fn handle_request(request: Request) -> Response { 1036 /// use <- wisp.require_content_type(request, "application/json") ··· 1050 } 1051 1052 /// A middleware which extracts JSON from the body of a request. 1053 - /// 1054 /// ```gleam 1055 /// fn handle_request(request: Request) -> Response { 1056 /// use json <- wisp.require_json(request) ··· 1071 /// 1072 /// If the body cannot be parsed successfully then an empty response with status 1073 /// code 400: Bad request will be returned to the client. 1074 - /// 1075 pub fn require_json(request: Request, next: fn(Dynamic) -> Response) -> Response { 1076 use <- require_content_type(request, "application/json") 1077 use body <- require_string_body(request) ··· 1305 } 1306 1307 /// Data parsed from form sent in a request's body. 1308 - /// 1309 pub type FormData { 1310 FormData( 1311 /// String values of the form's fields. ··· 1429 /// 1430 /// The `under` parameter is the request path prefix that must match for the 1431 /// file to be served. 1432 - /// 1433 /// | `under` | `from` | `request.path` | `file` | 1434 /// |-----------|---------|--------------------|-------------------------| 1435 /// | `/static` | `/data` | `/static/file.txt` | `/data/file.txt` | ··· 1576 /// > erlang.priv_directory("my_app") 1577 /// // -> Ok("/some/location/my_app/priv") 1578 /// ``` 1579 - /// 1580 pub const priv_directory = erlang.priv_directory 1581 1582 // ··· 1585 1586 /// Configure the Erlang logger, setting the minimum log level to `info`, to be 1587 /// called when your application starts. 1588 - /// 1589 /// You may wish to use an alternative for this such as one provided by a more 1590 /// sophisticated logging library. 1591 - /// 1592 /// In future this function may be extended to change the output format. 1593 - /// 1594 pub fn configure_logger() -> Nil { 1595 logging.configure() 1596 } 1597 1598 /// Log a message to the Erlang logger with the level of `emergency`. 1599 - /// 1600 /// See the [Erlang logger documentation][1] for more information. 1601 - /// 1602 /// [1]: https://www.erlang.org/doc/man/logger 1603 - /// 1604 pub fn log_emergency(message: String) -> Nil { 1605 logging.log(logging.Emergency, message) 1606 } 1607 1608 /// Log a message to the Erlang logger with the level of `alert`. 1609 - /// 1610 /// See the [Erlang logger documentation][1] for more information. 1611 - /// 1612 /// [1]: https://www.erlang.org/doc/man/logger 1613 - /// 1614 pub fn log_alert(message: String) -> Nil { 1615 logging.log(logging.Alert, message) 1616 } 1617 1618 /// Log a message to the Erlang logger with the level of `critical`. 1619 - /// 1620 /// See the [Erlang logger documentation][1] for more information. 1621 - /// 1622 /// [1]: https://www.erlang.org/doc/man/logger 1623 - /// 1624 pub fn log_critical(message: String) -> Nil { 1625 logging.log(logging.Critical, message) 1626 } 1627 1628 /// Log a message to the Erlang logger with the level of `error`. 1629 - /// 1630 /// See the [Erlang logger documentation][1] for more information. 1631 - /// 1632 /// [1]: https://www.erlang.org/doc/man/logger 1633 - /// 1634 pub fn log_error(message: String) -> Nil { 1635 logging.log(logging.Error, message) 1636 } 1637 1638 /// Log a message to the Erlang logger with the level of `warning`. 1639 - /// 1640 /// See the [Erlang logger documentation][1] for more information. 1641 - /// 1642 /// [1]: https://www.erlang.org/doc/man/logger 1643 - /// 1644 pub fn log_warning(message: String) -> Nil { 1645 logging.log(logging.Warning, message) 1646 } 1647 1648 /// Log a message to the Erlang logger with the level of `notice`. 1649 - /// 1650 /// See the [Erlang logger documentation][1] for more information. 1651 - /// 1652 /// [1]: https://www.erlang.org/doc/man/logger 1653 - /// 1654 pub fn log_notice(message: String) -> Nil { 1655 logging.log(logging.Notice, message) 1656 } 1657 1658 /// Log a message to the Erlang logger with the level of `info`. 1659 - /// 1660 /// See the [Erlang logger documentation][1] for more information. 1661 - /// 1662 /// [1]: https://www.erlang.org/doc/man/logger 1663 - /// 1664 pub fn log_info(message: String) -> Nil { 1665 logging.log(logging.Info, message) 1666 } 1667 1668 /// Log a message to the Erlang logger with the level of `debug`. 1669 - /// 1670 /// See the [Erlang logger documentation][1] for more information. 1671 - /// 1672 /// [1]: https://www.erlang.org/doc/man/logger 1673 - /// 1674 pub fn log_debug(message: String) -> Nil { 1675 logging.log(logging.Debug, message) 1676 } ··· 1689 1690 /// Sign a message which can later be verified using the `verify_signed_message` 1691 /// function to detect if the message has been tampered with. 1692 - /// 1693 /// Signed messages are not encrypted and can be read by anyone. They are not 1694 /// suitable for storing sensitive information. 1695 - /// 1696 /// This function uses the secret key base from the request. If the secret 1697 /// changes then the signature will no longer be verifiable. 1698 - /// 1699 pub fn sign_message( 1700 request: Request, 1701 message: BitArray, ··· 1705 } 1706 1707 /// Verify a signed message which was signed using the `sign_message` function. 1708 - /// 1709 /// Returns the content of the message if the signature is valid, otherwise 1710 /// returns an error. 1711 - /// 1712 /// This function uses the secret key base from the request. If the secret 1713 /// changes then the signature will no longer be verifiable. 1714 - /// 1715 pub fn verify_signed_message( 1716 request: Request, 1717 message: String, ··· 1749 /// wisp.ok() 1750 /// |> wisp.set_cookie(request, "id", "123", wisp.PlainText, 60 * 60) 1751 /// ``` 1752 - /// 1753 /// Setting a signed cookie that the client can read but not modify: 1754 - /// 1755 /// ```gleam 1756 /// wisp.ok() 1757 /// |> wisp.set_cookie(request, "id", value, wisp.Signed, 60 * 60) ··· 1821 1822 // TODO: chunk the body 1823 /// Create a connection which will return the given body when read. 1824 - /// 1825 /// This function is intended for use in tests, though you probably want the 1826 /// `wisp/testing` module instead. 1827 - /// 1828 pub fn create_canned_connection( 1829 body: BitArray, 1830 secret_key_base: String,
··· 152 HttpResponse(Body) 153 154 /// Create an empty response with the given status code. 155 + /// 156 /// # Examples 157 + /// 158 /// ```gleam 159 /// response(200) 160 /// // -> Response(200, [], Empty) 161 /// ``` 162 + /// 163 pub fn response(status: Int) -> Response { 164 HttpResponse(status, [], Empty) 165 } 166 167 /// Set the body of a response. 168 + /// 169 /// # Examples 170 + /// 171 /// ```gleam 172 /// response(200) 173 /// |> set_body(File("/tmp/myfile.txt")) 174 /// // -> Response(200, [], File("/tmp/myfile.txt")) 175 /// ``` 176 + /// 177 pub fn set_body(response: Response, body: Body) -> Response { 178 response 179 |> response.set_body(body) ··· 193 /// `set_body` function with the `File` body variant. 194 /// 195 /// # Examples 196 + /// 197 /// ```gleam 198 /// response(200) 199 /// |> file_download(named: "myfile.txt", from: "/tmp/myfile.txt") ··· 229 /// as this can result in cross-site scripting vulnerabilities. 230 /// 231 /// # Examples 232 + /// 233 /// ```gleam 234 /// response(200) 235 /// |> file_download_from_memory(named: "myfile.txt", containing: "Hello, Joe!") ··· 255 } 256 257 /// Create a HTML response. 258 + /// 259 /// The body is expected to be valid HTML, though this is not validated. 260 /// The `content-type` header will be set to `text/html`. 261 + /// 262 /// # Examples 263 + /// 264 /// ```gleam 265 /// let body = string_builder.from_string("<h1>Hello, Joe!</h1>") 266 /// html_response(body, 200) 267 /// // -> Response(200, [#("content-type", "text/html")], Text(body)) 268 /// ``` 269 + /// 270 pub fn html_response(html: StringBuilder, status: Int) -> Response { 271 HttpResponse(status, [#("content-type", "text/html")], Text(html)) 272 } 273 274 /// Create a JSON response. 275 + /// 276 /// The body is expected to be valid JSON, though this is not validated. 277 /// The `content-type` header will be set to `application/json`. 278 + /// 279 /// # Examples 280 + /// 281 /// ```gleam 282 /// let body = string_builder.from_string("{\"name\": \"Joe\"}") 283 /// json_response(body, 200) 284 /// // -> Response(200, [#("content-type", "application/json")], Text(body)) 285 /// ``` 286 + /// 287 pub fn json_response(json: StringBuilder, status: Int) -> Response { 288 HttpResponse(status, [#("content-type", "application/json")], Text(json)) 289 } 290 291 /// Set the body of a response to a given HTML document, and set the 292 /// `content-type` header to `text/html`. 293 + /// 294 /// The body is expected to be valid HTML, though this is not validated. 295 + /// 296 /// # Examples 297 + /// 298 /// ```gleam 299 /// let body = string_builder.from_string("<h1>Hello, Joe!</h1>") 300 /// response(201) 301 /// |> html_body(body) 302 /// // -> Response(201, [#("content-type", "text/html")], Text(body)) 303 /// ``` 304 + /// 305 pub fn html_body(response: Response, html: StringBuilder) -> Response { 306 response 307 |> response.set_body(Text(html)) ··· 310 311 /// Set the body of a response to a given JSON document, and set the 312 /// `content-type` header to `application/json`. 313 + /// 314 /// The body is expected to be valid JSON, though this is not validated. 315 + /// 316 /// # Examples 317 + /// 318 /// ```gleam 319 /// let body = string_builder.from_string("{\"name\": \"Joe\"}") 320 /// response(201) 321 /// |> json_body(body) 322 /// // -> Response(201, [#("content-type", "application/json")], Text(body)) 323 /// ``` 324 + /// 325 pub fn json_body(response: Response, json: StringBuilder) -> Response { 326 response 327 |> response.set_body(Text(json)) ··· 334 /// appropriate value for the format of the content. 335 /// 336 /// # Examples 337 + /// 338 /// ```gleam 339 /// let body = string_builder.from_string("Hello, Joe!") 340 /// response(201) 341 /// |> string_builder_body(body) 342 /// // -> Response(201, [], Text(body)) 343 /// ``` 344 + /// 345 pub fn string_builder_body( 346 response: Response, 347 content: StringBuilder, ··· 356 /// appropriate value for the format of the content. 357 /// 358 /// # Examples 359 + /// 360 /// ```gleam 361 + /// let body = 362 /// response(201) 363 /// |> string_body("Hello, Joe!") 364 /// // -> Response( ··· 367 /// // Text(string_builder.from_string("Hello, Joe")) 368 /// // ) 369 /// ``` 370 + /// 371 pub fn string_body(response: Response, content: String) -> Response { 372 response 373 |> response.set_body(Text(string_builder.from_string(content))) ··· 384 /// escape_html("<h1>Hello, Joe!</h1>") 385 /// // -> "&lt;h1&gt;Hello, Joe!&lt;/h1&gt;" 386 /// ``` 387 + /// 388 pub fn escape_html(content: String) -> String { 389 + let bits = <<content:utf8>> 390 + let acc = do_escape_html(bits, 0, bits, []) 391 + 392 + list.reverse(acc) 393 + |> bit_array.concat 394 + // We know the bit array produced by `do_escape_html` is still a valid utf8 395 + // string so we coerce it without passing through the validation steps of 396 + // `bit_array.to_string`. 397 + |> coerce_bit_array_to_string 398 + } 399 + 400 + @external(erlang, "wisp_ffi", "coerce") 401 + fn coerce_bit_array_to_string(bit_array: BitArray) -> String 402 + 403 + // A possible way to escape chars would be to split the string into graphemes, 404 + // traverse those one by one and accumulate them back into a string escaping 405 + // ">", "<", etc. as we see them. 406 + // 407 + // However, we can be a lot more performant by working directly on the 408 + // `BitArray` representing a Gleam UTF-8 String. 409 + // This means that, instead of popping a grapheme at a time, we can work 410 + // directly on BitArray slices: this has the big advantage of making sure we 411 + // share as much as possible with the original string without having to build 412 + // a new one from scratch. 413 + // 414 + @target(erlang) 415 + fn do_escape_html( 416 + bin: BitArray, 417 + skip: Int, 418 + original: BitArray, 419 + acc: List(BitArray), 420 + ) -> List(BitArray) { 421 + case bin { 422 + // If we find a char to escape we just advance the `skip` counter so that 423 + // it will be ignored in the following slice, then we append the escaped 424 + // version to the accumulator. 425 + <<"<":utf8, rest:bits>> -> { 426 + let acc = [<<"&lt;":utf8>>, ..acc] 427 + do_escape_html(rest, skip + 1, original, acc) 428 + } 429 + 430 + <<">":utf8, rest:bits>> -> { 431 + let acc = [<<"&gt;":utf8>>, ..acc] 432 + do_escape_html(rest, skip + 1, original, acc) 433 + } 434 + 435 + <<"&":utf8, rest:bits>> -> { 436 + let acc = [<<"&amp;":utf8>>, ..acc] 437 + do_escape_html(rest, skip + 1, original, acc) 438 + } 439 + 440 + // For any other bit that doesn't need to be escaped we go into an inner 441 + // loop, consuming as much "non-escapable" chars as possible. 442 + <<_char, rest:bits>> -> do_escape_html_regular(rest, skip, original, acc, 1) 443 + 444 + <<>> -> acc 445 + 446 + _ -> panic as "non byte aligned string, all strings should be byte aligned" 447 + } 448 } 449 450 + @target(erlang) 451 + fn do_escape_html_regular( 452 + bin: BitArray, 453 + skip: Int, 454 + original: BitArray, 455 + acc: List(BitArray), 456 + len: Int, 457 + ) -> List(BitArray) { 458 + // Remember, if we're here it means we've found a char that doesn't need to be 459 + // escaped, so what we want to do is advance the `len` counter until we reach 460 + // a char that _does_ need to be escaped and take the slice going from 461 + // `skip` with size `len`. 462 + // 463 + // Imagine we're escaping this string: "abc<def&ghi" and we've reached 'd': 464 + // ``` 465 + // abc<def&ghi 466 + // ^ `skip` points here 467 + // ``` 468 + // We're going to be increasing `len` until we reach the '&': 469 + // ``` 470 + // abc<def&ghi 471 + // ^^^ len will be 3 when we reach the '&' that needs escaping 472 + // ``` 473 + // So we take the slice corresponding to "def". 474 + // 475 + case bin { 476 + // If we reach a char that has to be escaped we append the slice starting 477 + // from `skip` with size `len` and the escaped char. 478 + // This is what allows us to share as much of the original string as 479 + // possible: we only allocate a new BitArray for the escaped chars, 480 + // everything else is just a slice of the original String. 481 + <<"<":utf8, rest:bits>> -> { 482 + let assert Ok(slice) = bit_array.slice(original, skip, len) 483 + let acc = [<<"&lt;":utf8>>, slice, ..acc] 484 + do_escape_html(rest, skip + len + 1, original, acc) 485 + } 486 + 487 + <<">":utf8, rest:bits>> -> { 488 + let assert Ok(slice) = bit_array.slice(original, skip, len) 489 + let acc = [<<"&gt;":utf8>>, slice, ..acc] 490 + do_escape_html(rest, skip + len + 1, original, acc) 491 + } 492 + 493 + <<"&":utf8, rest:bits>> -> { 494 + let assert Ok(slice) = bit_array.slice(original, skip, len) 495 + let acc = [<<"&amp;":utf8>>, slice, ..acc] 496 + do_escape_html(rest, skip + len + 1, original, acc) 497 + } 498 + 499 + // If a char doesn't need escaping we keep increasing the length of the 500 + // slice we're going to take. 501 + <<_char, rest:bits>> -> 502 + do_escape_html_regular(rest, skip, original, acc, len + 1) 503 + 504 + <<>> -> 505 + case skip { 506 + 0 -> [original] 507 + _ -> { 508 + let assert Ok(slice) = bit_array.slice(original, skip, len) 509 + [slice, ..acc] 510 + } 511 + } 512 + 513 + _ -> panic as "non byte aligned string, all strings should be byte aligned" 514 } 515 } 516 ··· 708 // 709 710 /// The connection to the client for a HTTP request. 711 + /// 712 /// The body of the request can be read from this connection using functions 713 /// such as `require_multipart_body`. 714 + /// 715 pub opaque type Connection { 716 Connection( 717 reader: Reader, ··· 791 } 792 793 /// Get the maximum permitted size of a request body of the request in bytes. 794 + /// 795 pub fn get_max_body_size(request: Request) -> Int { 796 request.body.max_body_size 797 } 798 799 /// Set the secret key base used to sign cookies and other sensitive data. 800 + /// 801 /// This key must be at least 64 bytes long and should be kept secret. Anyone 802 /// with this secret will be able to manipulate signed cookies and other sensitive 803 /// data. 804 /// 805 /// # Panics 806 + /// 807 /// This function will panic if the key is less than 64 bytes long. 808 /// 809 pub fn set_secret_key_base(request: Request, key: String) -> Request { ··· 816 } 817 818 /// Get the secret key base used to sign cookies and other sensitive data. 819 + /// 820 pub fn get_secret_key_base(request: Request) -> String { 821 request.body.secret_key_base 822 } ··· 838 839 /// Get the maximum permitted total size of a files uploaded by a request in 840 /// bytes. 841 + /// 842 pub fn get_max_files_size(request: Request) -> Int { 843 request.body.max_files_size 844 } ··· 858 859 /// Get the size limit for each chunk of the request body when read from the 860 /// client. 861 + /// 862 pub fn get_read_chunk_size(request: Request) -> Int { 863 request.body.read_chunk_size 864 } 865 866 /// A convenient alias for a HTTP request with a Wisp connection as the body. 867 + /// 868 pub type Request = 869 HttpRequest(Connection) 870 ··· 873 /// if the method is not correct. 874 /// 875 /// # Examples 876 + /// 877 /// ```gleam 878 /// fn handle_request(request: Request) -> Response { 879 /// use <- wisp.require_method(request, http.Patch) ··· 894 895 // TODO: re-export once Gleam has a syntax for that 896 /// Return the non-empty segments of a request path. 897 + /// 898 /// # Examples 899 /// 900 /// ```gleam ··· 908 909 // TODO: re-export once Gleam has a syntax for that 910 /// Set a given header to a given value, replacing any existing value. 911 + /// 912 /// # Examples 913 /// 914 /// ```gleam ··· 978 /// return an incorrect value, depending on the underlying web server. It is the 979 /// responsibility of the caller to cache the body if it is needed multiple 980 /// times. 981 + /// 982 /// If the body is larger than the `max_body_size` limit then an empty response 983 /// with status code 413: Entity too large will be returned to the client. 984 + /// 985 /// If the body is found not to be valid UTF-8 then an empty response with 986 /// status code 400: Bad request will be returned to the client. 987 + /// 988 /// # Examples 989 /// 990 /// ```gleam ··· 1014 /// return an incorrect value, depending on the underlying web server. It is the 1015 /// responsibility of the caller to cache the body if it is needed multiple 1016 /// times. 1017 + /// 1018 /// If the body is larger than the `max_body_size` limit then an empty response 1019 /// with status code 413: Entity too large will be returned to the client. 1020 + /// 1021 /// # Examples 1022 /// 1023 /// ```gleam ··· 1040 // TODO: don't always return entity to large. Other errors are possible, such as 1041 // network errors. 1042 /// Read the entire body of the request as a bit string. 1043 + /// 1044 /// You may instead wish to use the `require_bit_array_body` or the 1045 /// `require_string_body` middleware functions instead. 1046 + /// 1047 /// This function does not cache the body in any way, so if you call this 1048 /// function (or any other body reading function) more than once it may hang or 1049 /// return an incorrect value, depending on the underlying web server. It is the 1050 /// responsibility of the caller to cache the body if it is needed multiple 1051 /// times. 1052 + /// 1053 /// If the body is larger than the `max_body_size` limit then an empty response 1054 /// with status code 413: Entity too large will be returned to the client. 1055 + /// 1056 pub fn read_body_to_bitstring(request: Request) -> Result(BitArray, Nil) { 1057 let connection = request.body 1058 read_body_loop( ··· 1086 /// A middleware which extracts form data from the body of a request that is 1087 /// encoded as either `application/x-www-form-urlencoded` or 1088 /// `multipart/form-data`. 1089 + /// 1090 /// Extracted fields are sorted into alphabetical order by key, so if you wish 1091 /// to use pattern matching the order can be relied upon. 1092 + /// 1093 /// ```gleam 1094 /// fn handle_request(request: Request) -> Response { 1095 /// use form <- wisp.require_form(request) ··· 1118 /// 1119 /// If the body cannot be parsed successfully then an empty response with status 1120 /// code 400: Bad request will be returned to the client. 1121 + /// 1122 pub fn require_form( 1123 request: Request, 1124 next: fn(FormData) -> Response, ··· 1145 /// Unsupported media type if the header is not the expected value 1146 /// 1147 /// # Examples 1148 + /// 1149 /// ```gleam 1150 /// fn handle_request(request: Request) -> Response { 1151 /// use <- wisp.require_content_type(request, "application/json") ··· 1165 } 1166 1167 /// A middleware which extracts JSON from the body of a request. 1168 + /// 1169 /// ```gleam 1170 /// fn handle_request(request: Request) -> Response { 1171 /// use json <- wisp.require_json(request) ··· 1186 /// 1187 /// If the body cannot be parsed successfully then an empty response with status 1188 /// code 400: Bad request will be returned to the client. 1189 + /// 1190 pub fn require_json(request: Request, next: fn(Dynamic) -> Response) -> Response { 1191 use <- require_content_type(request, "application/json") 1192 use body <- require_string_body(request) ··· 1420 } 1421 1422 /// Data parsed from form sent in a request's body. 1423 + /// 1424 pub type FormData { 1425 FormData( 1426 /// String values of the form's fields. ··· 1544 /// 1545 /// The `under` parameter is the request path prefix that must match for the 1546 /// file to be served. 1547 + /// 1548 /// | `under` | `from` | `request.path` | `file` | 1549 /// |-----------|---------|--------------------|-------------------------| 1550 /// | `/static` | `/data` | `/static/file.txt` | `/data/file.txt` | ··· 1691 /// > erlang.priv_directory("my_app") 1692 /// // -> Ok("/some/location/my_app/priv") 1693 /// ``` 1694 + /// 1695 pub const priv_directory = erlang.priv_directory 1696 1697 // ··· 1700 1701 /// Configure the Erlang logger, setting the minimum log level to `info`, to be 1702 /// called when your application starts. 1703 + /// 1704 /// You may wish to use an alternative for this such as one provided by a more 1705 /// sophisticated logging library. 1706 + /// 1707 /// In future this function may be extended to change the output format. 1708 + /// 1709 pub fn configure_logger() -> Nil { 1710 logging.configure() 1711 } 1712 1713 /// Log a message to the Erlang logger with the level of `emergency`. 1714 + /// 1715 /// See the [Erlang logger documentation][1] for more information. 1716 + /// 1717 /// [1]: https://www.erlang.org/doc/man/logger 1718 + /// 1719 pub fn log_emergency(message: String) -> Nil { 1720 logging.log(logging.Emergency, message) 1721 } 1722 1723 /// Log a message to the Erlang logger with the level of `alert`. 1724 + /// 1725 /// See the [Erlang logger documentation][1] for more information. 1726 + /// 1727 /// [1]: https://www.erlang.org/doc/man/logger 1728 + /// 1729 pub fn log_alert(message: String) -> Nil { 1730 logging.log(logging.Alert, message) 1731 } 1732 1733 /// Log a message to the Erlang logger with the level of `critical`. 1734 + /// 1735 /// See the [Erlang logger documentation][1] for more information. 1736 + /// 1737 /// [1]: https://www.erlang.org/doc/man/logger 1738 + /// 1739 pub fn log_critical(message: String) -> Nil { 1740 logging.log(logging.Critical, message) 1741 } 1742 1743 /// Log a message to the Erlang logger with the level of `error`. 1744 + /// 1745 /// See the [Erlang logger documentation][1] for more information. 1746 + /// 1747 /// [1]: https://www.erlang.org/doc/man/logger 1748 + /// 1749 pub fn log_error(message: String) -> Nil { 1750 logging.log(logging.Error, message) 1751 } 1752 1753 /// Log a message to the Erlang logger with the level of `warning`. 1754 + /// 1755 /// See the [Erlang logger documentation][1] for more information. 1756 + /// 1757 /// [1]: https://www.erlang.org/doc/man/logger 1758 + /// 1759 pub fn log_warning(message: String) -> Nil { 1760 logging.log(logging.Warning, message) 1761 } 1762 1763 /// Log a message to the Erlang logger with the level of `notice`. 1764 + /// 1765 /// See the [Erlang logger documentation][1] for more information. 1766 + /// 1767 /// [1]: https://www.erlang.org/doc/man/logger 1768 + /// 1769 pub fn log_notice(message: String) -> Nil { 1770 logging.log(logging.Notice, message) 1771 } 1772 1773 /// Log a message to the Erlang logger with the level of `info`. 1774 + /// 1775 /// See the [Erlang logger documentation][1] for more information. 1776 + /// 1777 /// [1]: https://www.erlang.org/doc/man/logger 1778 + /// 1779 pub fn log_info(message: String) -> Nil { 1780 logging.log(logging.Info, message) 1781 } 1782 1783 /// Log a message to the Erlang logger with the level of `debug`. 1784 + /// 1785 /// See the [Erlang logger documentation][1] for more information. 1786 + /// 1787 /// [1]: https://www.erlang.org/doc/man/logger 1788 + /// 1789 pub fn log_debug(message: String) -> Nil { 1790 logging.log(logging.Debug, message) 1791 } ··· 1804 1805 /// Sign a message which can later be verified using the `verify_signed_message` 1806 /// function to detect if the message has been tampered with. 1807 + /// 1808 /// Signed messages are not encrypted and can be read by anyone. They are not 1809 /// suitable for storing sensitive information. 1810 + /// 1811 /// This function uses the secret key base from the request. If the secret 1812 /// changes then the signature will no longer be verifiable. 1813 + /// 1814 pub fn sign_message( 1815 request: Request, 1816 message: BitArray, ··· 1820 } 1821 1822 /// Verify a signed message which was signed using the `sign_message` function. 1823 + /// 1824 /// Returns the content of the message if the signature is valid, otherwise 1825 /// returns an error. 1826 + /// 1827 /// This function uses the secret key base from the request. If the secret 1828 /// changes then the signature will no longer be verifiable. 1829 + /// 1830 pub fn verify_signed_message( 1831 request: Request, 1832 message: String, ··· 1864 /// wisp.ok() 1865 /// |> wisp.set_cookie(request, "id", "123", wisp.PlainText, 60 * 60) 1866 /// ``` 1867 + /// 1868 /// Setting a signed cookie that the client can read but not modify: 1869 + /// 1870 /// ```gleam 1871 /// wisp.ok() 1872 /// |> wisp.set_cookie(request, "id", value, wisp.Signed, 60 * 60) ··· 1936 1937 // TODO: chunk the body 1938 /// Create a connection which will return the given body when read. 1939 + /// 1940 /// This function is intended for use in tests, though you probably want the 1941 /// `wisp/testing` module instead. 1942 + /// 1943 pub fn create_canned_connection( 1944 body: BitArray, 1945 secret_key_base: String,
+4
src/wisp_ffi.erl
···
··· 1 + -module(wisp_ffi). 2 + -export([coerce/1]). 3 + 4 + coerce(X) -> X.