snap-core 0.2.16 → 1.0.5.1
raw patch · 85 files changed
Files
- CONTRIBUTORS +4/−0
- LICENSE +1/−0
- README.SNAP.md +13/−27
- README.md +12/−20
- cbits/timefuncs.c +7/−1
- changelog.md +59/−0
- extra/fonts/DroidSerif-Bold.eot binary
- extra/fonts/DroidSerif-Bold.svg +0/−223
- extra/fonts/DroidSerif-Bold.ttf binary
- extra/fonts/DroidSerif-Bold.woff binary
- extra/fonts/DroidSerif-BoldItalic.eot binary
- extra/fonts/DroidSerif-BoldItalic.svg +0/−223
- extra/fonts/DroidSerif-BoldItalic.ttf binary
- extra/fonts/DroidSerif-BoldItalic.woff binary
- extra/fonts/DroidSerif-Italic.eot binary
- extra/fonts/DroidSerif-Italic.svg +0/−223
- extra/fonts/DroidSerif-Italic.ttf binary
- extra/fonts/DroidSerif-Italic.woff binary
- extra/fonts/DroidSerif-Regular.eot binary
- extra/fonts/DroidSerif-Regular.svg +0/−223
- extra/fonts/DroidSerif-Regular.ttf binary
- extra/fonts/DroidSerif-Regular.woff binary
- extra/haddock.css +1/−45
- haddock.sh +1/−2
- project_template/barebones/foo.cabal +0/−35
- project_template/barebones/src/Main.hs +0/−22
- project_template/barebones/src/Server.hs +0/−111
- project_template/default/foo.cabal +0/−37
- project_template/default/src/Glue.hs +0/−46
- project_template/default/src/Main.hs +0/−30
- project_template/default/src/Server.hs +0/−111
- runTestsAndCoverage.sh +62/−0
- snap-core.cabal +234/−129
- src/Data/CIByteString.hs +0/−60
- src/Snap/Core.hs +174/−0
- src/Snap/Internal/Core.hs +2073/−0
- src/Snap/Internal/Debug.hs +67/−44
- src/Snap/Internal/Http/Types.hs +1413/−771
- src/Snap/Internal/Instances.hs +83/−0
- src/Snap/Internal/Iteratee/Debug.hs +0/−77
- src/Snap/Internal/Parsing.hs +594/−13
- src/Snap/Internal/Routing.hs +210/−110
- src/Snap/Internal/Test/Assertions.hs +168/−0
- src/Snap/Internal/Test/RequestBuilder.hs +1077/−0
- src/Snap/Internal/Types.hs +0/−661
- src/Snap/Internal/Util/FileServe.hs +809/−0
- src/Snap/Internal/Util/FileUploads.hs +1125/−0
- src/Snap/Iteratee.hs +0/−519
- src/Snap/Starter.hs +0/−99
- src/Snap/StarterTH.hs +0/−55
- src/Snap/Test.hs +58/−0
- src/Snap/Types.hs +0/−129
- src/Snap/Types/Headers.hs +349/−0
- src/Snap/Util/CORS.hs +281/−0
- src/Snap/Util/FileServe.hs +42/−421
- src/Snap/Util/FileUploads.hs +125/−0
- src/Snap/Util/GZip.hs +117/−160
- src/Snap/Util/Proxy.hs +90/−0
- test/Snap/Core/Tests.hs +954/−0
- test/Snap/Internal/Http/Types/Tests.hs +205/−0
- test/Snap/Internal/Parsing/Tests.hs +177/−0
- test/Snap/Internal/Routing/Tests.hs +513/−0
- test/Snap/Test/Common.hs +143/−0
- test/Snap/Test/Tests.hs +449/−0
- test/Snap/Types/Headers/Tests.hs +77/−0
- test/Snap/Util/CORS/Tests.hs +121/−0
- test/Snap/Util/FileServe/Tests.hs +627/−0
- test/Snap/Util/FileUploads/Tests.hs +1199/−0
- test/Snap/Util/GZip/Tests.hs +391/−0
- test/Snap/Util/Proxy/Tests.hs +164/−0
- test/TestSuite.hs +45/−0
- test/data/fileServe/mydir1/index.txt +1/−0
- test/data/fileServe/mydir2/dir/foo.txt +1/−0
- test/data/fileServe/mydir2/foo.txt +1/−0
- test/data/fileServe/mydir3/altindex.html +1/−0
- test/runTestsAndCoverage.sh +0/−62
- test/snap-core-testsuite.cabal +0/−55
- test/suite/Snap/Internal/Http/Types/Tests.hs +0/−130
- test/suite/Snap/Internal/Routing/Tests.hs +0/−301
- test/suite/Snap/Iteratee/Tests.hs +0/−406
- test/suite/Snap/Test/Common.hs +0/−27
- test/suite/Snap/Types/Tests.hs +0/−418
- test/suite/Snap/Util/FileServe/Tests.hs +0/−264
- test/suite/Snap/Util/GZip/Tests.hs +0/−386
- test/suite/TestSuite.hs +0/−28
CONTRIBUTORS view
@@ -6,3 +6,7 @@ James Sanders <jimmyjazz14@gmail.com> Jacob Stanley <jystic@jystic.com> Jonas Kramer <jkramer@nex.scrapping.cc>+Jurriën Stutterheim <j.stutterheim@me.com>+Jasper Van der Jeugt <m@jaspervdj.be>+Bryan O'Sullivan <bos@serpentine.com>+Koz Ross <koz@mlabs.city>
LICENSE view
@@ -1,4 +1,5 @@ Copyright (c) 2009, Snap Framework authors (see CONTRIBUTORS)+Copyright (c) 2010, Google, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without
README.SNAP.md view
@@ -1,32 +1,30 @@ Snap Framework -------------- -This is the first developer prerelease of the Snap framework. Snap is a simple-and fast web development framework and server written in Haskell. For more-information or to download the latest version, you can visit the Snap project-website at http://snapframework.com/.+Snap is a simple and fast web development framework and server written in+Haskell. For more information or to download the latest version, you can visit+the Snap project website at http://snapframework.com/. Snap Status and Features ------------------------ -This developer prerelease contains only the Snap core system, namely:-- * a high-speed HTTP server, with an optional high-concurrency backend using- the [libev](http://software.schmorp.de/pkg/libev.html) library+The Snap core system consists of: * a sensible and clean monad for web programming - * an xml-based templating system for generating HTML based on- [expat](http://expat.sourceforge.net/) (via- [hexpat](http://hackage.haskell.org/package/hexpat)) that allows you to- bind Haskell functionality to XML tags without getting PHP-style tag soup- all over your pants+ * a high-speed HTTP server called "snap-server" -Snap currently only runs on Unix platforms; it has been tested on Linux and Mac-OSX Snow Leopard.+ * an xml-based templating system called "heist" for generating HTML that+ allows you to bind Haskell functionality to XML tags without getting+ PHP-style tag soup all over your pants + * a "snaplet" system for building web sites from composable pieces. +Snap is currently only officially supported on Unix platforms; it has been+tested on Linux and Mac OSX, and is reported to work on Windows.++ Snap Philosophy --------------- @@ -41,15 +39,3 @@ * Excellent documentation * Robustness and high test coverage---Snap Roadmap---------------Where are we going?--1. First prerelease: HTTP server, monad, template system--2. Second prerelease: component system with a collection of useful stock-modules (called "Snaplets") for things like user and session management,-caching, an administrative interface, etc.
README.md view
@@ -1,14 +1,11 @@ Snap Framework Core =================== -This is the first developer prerelease of the Snap Framework Core library. For-more information about Snap, read the `README.SNAP.md` or visit the Snap-project website at http://www.snapframework.com/.--Snap is a nascent web framework for Haskell, based on iteratee I/O (as-[popularized by Oleg-Kiselyov](http://okmij.org/ftp/Streams.html#iteratee)).+[](https://github.com/snapframework/snap-core/actions) +Snap is a web framework for Haskell. For more information about Snap, read the+`README.SNAP.md` or visit the Snap project website at+http://www.snapframework.com/. ## Library contents @@ -17,11 +14,7 @@ * primitive types and functions for HTTP (requests, responses, cookies, post/query parameters, etc). - * type aliases and helper functions for Iteratee I/O.-- * a "Snap" monad interface, inspired by- [happstack's](http://happstack.com/index.html), for programming web- handlers, which allows:+ * a "Snap" monad interface for programming web handlers, which allows: * stateful access to the HTTP request and response objects. @@ -31,7 +24,8 @@ * early termination of the computation if you know early what you want to return and want to prevent further monadic processing. - * Some useful utilities for web handlers, including gzip/zlib compression.+ * useful utilities for web handlers, like file serving and gzip/zlib+ compression. Building snap-core@@ -57,18 +51,16 @@ ## Building the testsuite -Snap is still in its very early stages, so most of the "action" (and a big-chunk of the code) right now is centred on the test suite. Snap aims for 100%-test coverage, and we're trying hard to stick to that.+Snap aims for 100% test coverage, and we're trying hard to stick to that. -To build the test suite, `cd` into the `test/` directory and run+To build the test suite, configure with --enable-tests: - $ cabal configure+ $ cabal configure --enable-tests $ cabal build From here you can invoke the testsuite by running: - $ ./runTestsAndCoverage.sh + $ ./runTestsAndCoverage.sh -The testsuite generates an `hpc` test coverage report in `test/dist/hpc`.+The testsuite generates an `hpc` test coverage report in `dist/hpc`.
cbits/timefuncs.c view
@@ -1,4 +1,6 @@+#define _DEFAULT_SOURCE #define _XOPEN_SOURCE+#define _BSD_SOURCE #include <time.h> #include <locale.h> @@ -8,9 +10,13 @@ } +// When given invalid input, returns 0. time_t c_parse_http_time(char* s) {+ if (s == NULL) return 0; struct tm dest;- strptime(s, "%a, %d %b %Y %H:%M:%S GMT", &dest);+ if (strptime(s, "%a, %d %b %Y %H:%M:%S GMT", &dest) == NULL) {+ return 0;+ } return timegm(&dest); }
+ changelog.md view
@@ -0,0 +1,59 @@+## 1.0.5.0++- Add `head` helper for `Snap.Test`+- Add support for GHC 9+- Expose Snap.Internal.Util.FileServe+- Ensure parseHttpTime returns 0 on invalid input+- Add test helper for HEAD requests+- Misc version bumps++## 1.0.4.0+- Allow `network` 3.0.++## 1.0.3.2+- Allow `unix-compat` 0.5++- Stop using deprecated Data.Map.insertWith'++- Test with GHC 8.4.1++## 1.0.3.1+- Allow `io-streams` 1.5.++## 1.0.3.0+### Added+- Alternative file upload handling into Snap.Util.FileUploads++### Fixes+- Fixed parsing of field values in multipart/form-data headers with encodings+ other than US-ASCII++## 1.0.2.1+### Dependencies+- Allow `io-streams` 1.4.++## 1.0.2.0+### Added+- Merged CORS functionality from snap-cors project into Snap.Util.CORS++### Dependencies+- Bumped `time` dependency.++## 1.0.1.1+### Fixes+- Fixed a bug in token parsing in Snap.Util.Parsing++## 1.0.1.0+### Fixes+- fixed a FileUpload exception test++- Exported `compressibleMimeTypes`++- Added a missing file to the tarball.++## 1.0.0.0+### Added+### Removed++ - Removed support for `iteratees` in favor of+ [io-streams](https://hackage.haskell.org/package/io-streams)
− extra/fonts/DroidSerif-Bold.eot
binary file changed (47454 → absent bytes)
− extra/fonts/DroidSerif-Bold.svg
@@ -1,223 +0,0 @@-<?xml version="1.0" standalone="no"?>-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >-<svg xmlns="http://www.w3.org/2000/svg">-<defs>-<font id="DroidSerif-Bold" horiz-adv-x="1145" >-<font-face font-family="Droid Serif" font-weight="700" font-stretch="normal" units-per-em="2048" panose-1="2 2 8 0 6 5 0 2 2 0" ascent="1638" descent="-410" x-height="1098" cap-height="1462" bbox="-80 -492 2185 1907" underline-thickness="102" underline-position="-103" unicode-range="U+0020-U+2122"/>-<missing-glyph horiz-adv-x="532" />-<glyph glyph-name=".notdef" horiz-adv-x="532" />-<glyph glyph-name=".null" horiz-adv-x="0" />-<glyph glyph-name="nonmarkingreturn" horiz-adv-x="682" />-<glyph glyph-name="space" unicode=" " horiz-adv-x="532" />-<glyph glyph-bame="tab" unicode="	" horiz-adv-x="532" />-<glyph glyph-bame="uni00A0" unicode=" " horiz-adv-x="532" />-<glyph glyph-name="exclam" unicode="!" horiz-adv-x="782" d="M213 1462h356l-120 -995h-115zM225 147q0 47 13 78.5t35.5 50.5t53.5 27t66 8q34 0 64.5 -8t53.5 -27t36.5 -50.5t13.5 -78.5q0 -46 -13.5 -77t-36.5 -50t-53.5 -27.5t-64.5 -8.5q-35 0 -66 8.5t-53.5 27.5t-35.5 50t-13 77z" />-<glyph glyph-name="quotedbl" unicode=""" horiz-adv-x="1040" d="M584 1462h319l-72 -563h-135zM137 1462h320l-92 -563h-136z" />-<glyph glyph-name="numbersign" unicode="#" d="M1022 551v-141h-256l-76 -410h-145l76 410h-213l-78 -410h-146l78 410h-205v141h232l71 371h-237v143h264l72 397h147l-74 -397h213l76 397h146l-76 -397h196v-143h-223l-69 -371h227zM434 551h215l72 371h-215z" />-<glyph glyph-name="dollar" unicode="$" d="M639 1556v-155q91 -3 157 -20t109 -44.5t63.5 -63.5t20.5 -77q0 -65 -56 -107t-169 -42q0 30 -7 65t-22 67.5t-38.5 59t-57.5 41.5v-399q6 -3 23 -9.5t28 -11.5q94 -37 161 -76t109.5 -85t62 -99.5t19.5 -118.5q0 -78 -27.5 -144.5t-79 -117.5t-126.5 -83.5t-170 -43.5 v-254h-115v250q-117 3 -198 30t-131.5 68t-72.5 91t-22 98q0 45 16.5 76t43.5 50.5t61.5 28.5t71.5 9q0 -77 16.5 -137t47 -102.5t73 -68.5t94.5 -34v442l-35 14q-98 40 -163.5 81t-106 87.5t-57.5 100.5t-17 120q0 74 28 135.5t78 107.5t119.5 75.5t153.5 38.5v157h115z M799 389q0 65 -40 111t-120 88v-387q77 17 118.5 68t41.5 120zM385 1128q0 -27 6 -52t22 -48.5t43 -46t68 -45.5v354q-60 -11 -99.5 -51.5t-39.5 -110.5z" />-<glyph glyph-name="percent" unicode="%" horiz-adv-x="1855" d="M807 1030q0 -103 -23 -187t-68.5 -144t-115 -93t-162.5 -33q-97 0 -167 33t-114.5 93t-65.5 144t-21 187t21 186.5t66 143t115 91.5t168 32q92 0 161 -32t115 -91.5t68.5 -143t22.5 -186.5zM301 1030q0 -84 7.5 -149.5t23.5 -111t42.5 -69.5t63.5 -24q39 0 64.5 24 t41.5 69.5t22.5 111t6.5 149.5q0 82 -6.5 146.5t-22 109.5t-41 68.5t-63.5 23.5t-64.5 -23.5t-43 -68.5t-24 -109.5t-7.5 -146.5zM651 0h-159l706 1462h158zM1786 436q0 -103 -23 -187t-68.5 -144t-115 -92.5t-162.5 -32.5q-97 0 -167 32.5t-114.5 92.5t-65.5 144t-21 187 t21 186.5t66 143t115 91.5t168 32q92 0 161 -32t115 -91.5t68.5 -143t22.5 -186.5zM1280 436q0 -84 7.5 -149.5t23.5 -111t42.5 -69.5t63.5 -24q39 0 64.5 24t41.5 69.5t22.5 111t6.5 149.5q0 82 -6.5 146.5t-22 109.5t-41 68.5t-63.5 23.5t-64.5 -23.5t-43 -68.5 t-24 -109.5t-7.5 -146.5z" />-<glyph glyph-name="ampersand" unicode="&" horiz-adv-x="1640" d="M1208 0l-141 150q-38 -38 -84.5 -69t-102.5 -53.5t-121.5 -35t-142.5 -12.5q-126 0 -223 29.5t-163 85.5t-100 137t-34 184q0 90 28.5 153.5t76.5 110t111.5 79t133.5 60.5q-40 48 -69.5 91.5t-48.5 86.5t-28 88t-9 97q0 68 26 123t77.5 95t128.5 61.5t179 21.5 q99 0 172.5 -23t122 -63t73 -93t24.5 -112q0 -62 -19 -112t-57.5 -92.5t-97 -79.5t-138.5 -74q5 -8 15 -15l290 -305q13 71 17 140.5t4 127.5v90h448v-108h-32q-34 0 -66.5 -4t-60.5 -18.5t-49.5 -42t-34.5 -74.5q-16 -57 -37.5 -121.5t-52.5 -130.5l204 -213q29 -31 71 -41 t97 -10h19v-109h-406zM397 406q0 -61 19.5 -113.5t54.5 -91.5t85.5 -61.5t111.5 -22.5q109 0 184 35.5t125 95.5l-438 467q-43 -31 -70.5 -65.5t-43.5 -73t-22 -81t-6 -89.5zM846 1190q0 36 -6.5 67t-22.5 54t-44 36t-71 13q-34 0 -59.5 -13t-41.5 -36t-24 -54.5t-8 -68.5 q0 -35 8 -67t23.5 -63.5t39.5 -63.5t56 -68q43 24 72 51t46 58.5t24.5 69.5t7.5 85z" />-<glyph glyph-name="quotesingle" unicode="'" horiz-adv-x="594" d="M137 1462h320l-92 -563h-136z" />-<glyph glyph-name="parenleft" unicode="(" horiz-adv-x="819" d="M446 651q0 -122 12.5 -239.5t46.5 -222t94 -192.5t155 -151v-131q-171 69 -292 161.5t-197.5 209.5t-112 258t-35.5 307q0 165 35.5 305.5t112 256.5t197.5 208t292 160v-129q-95 -63 -155 -150.5t-94 -191.5t-46.5 -220.5t-12.5 -238.5z" />-<glyph glyph-name="parenright" unicode=")" horiz-adv-x="819" d="M373 651q0 122 -12.5 238.5t-46 220.5t-93.5 191.5t-155 150.5v129q171 -68 292 -160t197.5 -208t111.5 -256.5t35 -305.5q0 -166 -35 -307t-111.5 -258t-197.5 -209.5t-292 -161.5v131q95 63 155 151t93.5 192.5t46 222t12.5 239.5z" />-<glyph glyph-name="asterisk" unicode="*" horiz-adv-x="1028" d="M72 1237l92 207l305 -221l-68 333h230l-74 -329l307 213l92 -205l-319 -80l319 -82l-92 -203l-307 213l72 -331h-228l66 333l-303 -217l-92 203l319 84z" />-<glyph glyph-name="plus" unicode="+" d="M645 659v-366h-147v366h-365v146h365v366h147v-366h367v-146h-367z" />-<glyph glyph-name="comma" unicode="," horiz-adv-x="602" d="M459 86q0 -65 -20 -126t-64 -113t-113.5 -93.5t-169.5 -68.5v106q45 16 78.5 32t56 34.5t34 41t11.5 52.5q0 19 -11 31.5t-27.5 23.5t-36 22.5t-36 29.5t-27.5 44.5t-11 67.5q0 69 42 105t105 36q87 0 138 -60t51 -165z" />-<glyph glyph-name="hyphen" unicode="-" horiz-adv-x="635" d="M51 451v215h533v-215h-533z" />-<glyph glyph-name="period" unicode="." horiz-adv-x="623" d="M143 147q0 47 13 78.5t35.5 50.5t53.5 27t66 8q34 0 64.5 -8t53.5 -27t36.5 -50.5t13.5 -78.5q0 -46 -13.5 -77t-36.5 -50t-53.5 -27.5t-64.5 -8.5q-35 0 -66 8.5t-53.5 27.5t-35.5 50t-13 77z" />-<glyph glyph-name="slash" unicode="/" horiz-adv-x="590" d="M147 -250l-147 2l446 1804h144z" />-<glyph glyph-name="zero" unicode="0" d="M1075 733q0 -169 -28 -308t-89 -238t-156 -153t-229 -54q-140 0 -236 54t-155.5 153t-85.5 238.5t-26 309.5t26 308t85.5 236t156 151t237.5 53q133 0 228 -53t155.5 -151t88.5 -236.5t28 -309.5zM387 733q0 -147 8 -262t28.5 -194.5t56.5 -121.5t93 -42t93 42 t56.5 121.5t28 194.5t7.5 262t-7.5 261.5t-27.5 193.5t-56 120.5t-92 41.5q-57 0 -93.5 -41.5t-57.5 -120.5t-29 -193.5t-8 -261.5z" />-<glyph glyph-name="one" unicode="1" d="M199 0v109h168q26 0 45 6.5t32 21.5t19 40.5t6 64.5v1026l-73.5 -87t-66.5 -67t-62.5 -43.5t-61.5 -15.5q-25 0 -46.5 10.5t-37.5 30.5t-25.5 48t-9.5 62q31 8 65 19.5t72.5 30t82.5 45t95 63.5l144 106h229v-1228q0 -32 4.5 -57t16 -42t32 -25.5t52.5 -8.5h165v-109 h-845z" />-<glyph glyph-name="two" unicode="2" d="M999 1143q0 -48 -10.5 -93t-35 -93t-64.5 -101.5t-99 -118t-137.5 -142.5l-181.5 -175l-205 -195h477q63 0 107.5 36.5t62.5 105.5l17 63h121l-11 -430h-968v211l303 309q95 97 156 175t96 149t49 140.5t14 148.5q0 117 -42.5 171t-117.5 54q-104 0 -146 -89.5 t-42 -264.5q-58 0 -106.5 11t-83 35t-53.5 62.5t-19 93.5q0 56 28.5 106t84.5 88t140.5 60.5t196.5 22.5q115 0 202.5 -23.5t147 -67.5t89.5 -107t30 -142z" />-<glyph glyph-name="three" unicode="3" d="M498 -20q-116 0 -199.5 20t-137 54t-79 78.5t-25.5 92.5q0 39 14 69t38 50.5t55.5 30.5t66.5 10q0 -65 18.5 -114t51.5 -82t79.5 -49.5t102.5 -16.5q50 0 95.5 14t80.5 48t55.5 92.5t20.5 146.5q0 59 -20.5 108t-61 84.5t-99.5 55.5t-136 20h-127v127h135q66 0 115 21.5 t81 61.5t48 95.5t16 122.5q0 112 -43 175t-127 63q-55 0 -89.5 -27t-53.5 -72t-26 -102t-7 -117q-128 0 -197 44.5t-69 138.5q0 54 28.5 101.5t85.5 83t141.5 55.5t196.5 20q109 0 196.5 -22t149 -65t94.5 -105t33 -142q0 -69 -24 -127t-66 -104t-99 -80.5t-124 -57.5 q62 -11 126.5 -35.5t117.5 -66.5t87 -105t34 -151q0 -125 -46.5 -210t-124 -136.5t-177 -73.5t-205.5 -22z" />-<glyph glyph-name="four" unicode="4" d="M893 381v-125q0 -48 13.5 -77t36 -44.5t53.5 -20.5t65 -5h26v-109h-718v109h57q35 0 65.5 5t53 20.5t36 44.5t13.5 77v125h-567v125l581 956h285v-946h213v-135h-213zM594 874q0 90 3 194t11 200q-10 -24 -32 -66l-47 -86.5l-48 -84t-35 -58.5l-276 -457h424v358z" />-<glyph glyph-name="five" unicode="5" d="M471 123q51 0 96.5 16t80 53.5t55 101t20.5 157.5q0 153 -73 228t-206 75q-39 0 -70.5 -3t-58.5 -8.5t-51 -12t-47 -13.5l-94 37l65 708h775l10 -372h-117l-16 69q-5 18 -11 32.5t-16.5 24.5t-26.5 15.5t-41 5.5h-426q0 -1 -1.5 -17.5t-3.5 -43.5t-5 -61l-6 -69l-16 -188 q16 6 41.5 11.5t55.5 10t63.5 7t64.5 2.5q119 0 215.5 -27.5t165 -82t106 -136.5t37.5 -192q0 -133 -48.5 -223.5t-125.5 -145.5t-172 -78.5t-188 -23.5q-115 0 -195 20.5t-130 55t-72.5 79t-22.5 92.5q0 72 44.5 107t119.5 35q0 -44 11.5 -88t38 -79t69.5 -57t106 -22z" />-<glyph glyph-name="six" unicode="6" d="M657 1362q-60 0 -108.5 -36t-84 -105t-56.5 -169.5t-27 -230.5q21 14 48 27.5t61.5 24.5t78 17.5t95.5 6.5q93 0 168.5 -29t129 -83.5t82.5 -133t29 -178.5q0 -110 -30 -200.5t-89.5 -155.5t-148 -101t-205.5 -36q-114 0 -207.5 41.5t-160 127.5t-103.5 217t-37 310 q0 169 35.5 316t108 256t182.5 172t258 63q99 0 168 -19t112 -50.5t62.5 -72t19.5 -84.5q0 -31 -13 -60t-42 -51t-75.5 -35t-114.5 -13q0 46 -6 93t-21 85t-41.5 62t-67.5 24zM588 772q-28 0 -59 -8.5t-59.5 -22.5t-52 -31.5t-36.5 -35.5q1 -147 18.5 -253t48 -174t71 -100 t87.5 -32q79 0 124.5 80.5t45.5 257.5q0 166 -50 242.5t-138 76.5z" />-<glyph glyph-name="seven" unicode="7" d="M338 0l545 1239h-539q-56 0 -84.5 -26.5t-34.5 -77.5l-12 -103h-123l10 430h977v-100l-565 -1362h-174z" />-<glyph glyph-name="eight" unicode="8" d="M61 373q0 66 21.5 119t61.5 96.5t95.5 80t122.5 70.5q-122 71 -187.5 161t-65.5 210q0 71 24.5 138.5t80.5 119.5t147 83.5t223 31.5q108 0 189.5 -27t136.5 -75t82.5 -113.5t27.5 -143.5q0 -61 -19.5 -108t-55.5 -85.5t-87 -71.5t-115 -66q92 -45 157 -90.5t105.5 -95 t59 -104t18.5 -116.5q0 -97 -35.5 -172.5t-103 -127.5t-164.5 -79.5t-221 -27.5q-128 0 -222 31t-155.5 84t-91 125t-29.5 153zM569 96q53 0 95.5 19t72.5 53t46 81t16 103q0 50 -17 90.5t-53.5 76t-93 69t-135.5 69.5q-34 -22 -64 -50.5t-51.5 -64t-34 -79.5t-12.5 -98 q0 -60 16.5 -109.5t47 -85t73 -55t94.5 -19.5zM762 1128q0 44 -12.5 86.5t-36.5 75.5t-59.5 53.5t-82.5 20.5q-42 0 -74.5 -17.5t-54.5 -48t-33.5 -72.5t-11.5 -91q0 -53 14.5 -93t42.5 -72t69 -57.5t95 -50.5q38 22 65 47t44.5 57t26 71.5t8.5 90.5z" />-<glyph glyph-name="nine" unicode="9" d="M465 -20q-93 0 -157 19.5t-104 52t-57.5 74t-17.5 85.5q0 45 24 75.5t76 41.5q15 -46 36 -86t50.5 -69.5t69 -46.5t90.5 -17q138 0 212.5 149t84.5 444q-24 -30 -53.5 -55t-66.5 -44t-81.5 -29.5t-97.5 -10.5q-88 0 -161.5 27.5t-127 81t-83 133t-29.5 182.5 q0 111 32 202t92.5 156.5t147.5 101.5t197 36q111 0 205 -40t162.5 -124t106.5 -214t38 -310q0 -169 -33 -317.5t-103.5 -259t-182 -174.5t-269.5 -64zM561 711q74 0 127 37.5t84 99.5q-1 131 -17.5 225t-45 154.5t-68.5 89.5t-88 29q-88 0 -136 -86.5t-48 -260.5 q0 -142 48 -215t144 -73z" />-<glyph glyph-name="colon" unicode=":" horiz-adv-x="623" d="M143 147q0 47 13 78.5t35.5 50.5t53.5 27t66 8q34 0 64.5 -8t53.5 -27t36.5 -50.5t13.5 -78.5q0 -46 -13.5 -77t-36.5 -50t-53.5 -27.5t-64.5 -8.5q-35 0 -66 8.5t-53.5 27.5t-35.5 50t-13 77zM143 952q0 47 13 78.5t35.5 50.5t53.5 27t66 8q34 0 64.5 -8t53.5 -27 t36.5 -50.5t13.5 -78.5q0 -46 -13.5 -77.5t-36.5 -50.5t-53.5 -27.5t-64.5 -8.5q-35 0 -66 8.5t-53.5 27.5t-35.5 50.5t-13 77.5z" />-<glyph glyph-name="semicolon" unicode=";" horiz-adv-x="602" d="M459 86q0 -65 -20 -126t-64 -113t-113.5 -93.5t-169.5 -68.5v106q45 16 78.5 32t56 34.5t34 41t11.5 52.5q0 19 -11 31.5t-27.5 23.5t-36 22.5t-36 29.5t-27.5 44.5t-11 67.5q0 69 42 105t105 36q87 0 138 -60t51 -165zM143 952q0 47 13 78.5t35.5 50.5t53.5 27t66 8 q34 0 64.5 -8t53.5 -27t36.5 -50.5t13.5 -78.5q0 -46 -13.5 -77.5t-36.5 -50.5t-53.5 -27.5t-64.5 -8.5q-35 0 -66 8.5t-53.5 27.5t-35.5 50.5t-13 77.5z" />-<glyph glyph-name="less" unicode="<" d="M133 690v84l879 479v-161l-660 -361l660 -356v-162z" />-<glyph glyph-name="equal" unicode="=" d="M1016 604v-143h-887v143h887zM1016 1001v-143h-887v143h887z" />-<glyph glyph-name="greater" unicode=">" d="M133 213v162l660 356l-660 361v161l879 -479v-84z" />-<glyph glyph-name="question" unicode="?" horiz-adv-x="1126" d="M1014 1106q0 -82 -25.5 -147t-76.5 -119t-127.5 -99.5t-178.5 -87.5l-41 -186h-125l-41 252q88 35 144 82.5t88.5 102t44.5 114.5t12 121q0 49 -9 89.5t-28.5 70t-50 45.5t-72.5 16q-46 0 -77 -21t-50 -56t-27.5 -82t-8.5 -99q-63 0 -116 9t-91.5 26.5t-60 44.5t-21.5 63 q0 50 26 93.5t79.5 75.5t135.5 50.5t195 18.5q108 0 200 -25.5t159 -74t105 -118.5t38 -159zM332 147q0 47 13 78.5t35.5 50.5t53.5 27t66 8q34 0 64.5 -8t53.5 -27t36.5 -50.5t13.5 -78.5q0 -46 -13.5 -77t-36.5 -50t-53.5 -27.5t-64.5 -8.5q-35 0 -66 8.5t-53.5 27.5 t-35.5 50t-13 77z" />-<glyph glyph-name="at" unicode="@" horiz-adv-x="1886" d="M1806 803q0 -153 -37 -273t-97 -202.5t-135.5 -126t-152.5 -43.5q-91 0 -154 44.5t-97 147.5h-13q-21 -40 -48 -75t-61 -61t-76.5 -41t-96.5 -15q-64 0 -122.5 21t-103 64t-70.5 109t-26 156q0 55 13 119t41.5 127.5t72 123t105 105t141 73t178.5 27.5q71 0 122 -18 t83 -43l86 43h49l-86 -512q-2 -12 -4.5 -31t-4.5 -39t-3.5 -38.5t-1.5 -30.5q0 -42 9 -69t23 -42.5t31.5 -21t34.5 -5.5q41 0 82.5 34t75.5 99.5t55.5 162.5t21.5 223q0 140 -42.5 245.5t-118 176t-180 106t-228.5 35.5q-97 0 -191.5 -27t-180 -79t-157.5 -127.5t-125 -172 t-82.5 -213.5t-29.5 -252q0 -181 54 -309t146.5 -209t216 -118t261.5 -37q83 0 162 15t150 39t132 55t109 63l49 -78q-54 -41 -122.5 -77t-149 -63t-171 -43t-188.5 -16q-117 0 -225 21.5t-202.5 65.5t-172.5 109.5t-133.5 153.5t-86 198t-30.5 243q0 137 33.5 262t95 231.5 t149.5 193t197 148t236.5 94.5t269.5 33q178 0 313 -51t225.5 -139.5t136.5 -209t46 -259.5zM768 487q0 -102 36 -156.5t95 -54.5q44 0 75.5 21t53 55.5t35.5 77.5t22 88l82 418q-6 14 -17.5 24.5t-26.5 18t-31.5 11t-32.5 3.5q-52 0 -93.5 -26t-74 -68.5t-55.5 -97 t-38.5 -110.5t-22.5 -109.5t-7 -94.5z" />-<glyph glyph-name="A" unicode="A" horiz-adv-x="1542" d="M446 481l-59 -176q-8 -25 -16.5 -58.5t-8.5 -58.5q0 -20 8 -35t21 -24.5t29.5 -14.5t34.5 -5h86v-109h-533v109h25q27 0 49.5 6t42 22.5t37 45.5t35.5 75l444 1204h281l428 -1206q15 -43 31.5 -71.5t36 -45t41.5 -23.5t46 -7h37v-109h-676v109h80q15 0 31 4.5t28.5 14 t20.5 24.5t8 36q0 25 -6 48t-12 40l-72 205h-498zM795 942l-22.5 73.5l-23 78.5l-22 79.5t-18.5 75.5q-9 -31 -21 -68l-25.5 -76l-27 -78l-25.5 -75l-121 -346h414z" />-<glyph glyph-name="B" unicode="B" horiz-adv-x="1376" d="M1241 1090q0 -68 -18.5 -118.5t-52.5 -87t-81.5 -62.5t-105.5 -44v-10q71 -18 129.5 -49.5t101 -77t66 -105.5t23.5 -135q0 -202 -147 -301.5t-439 -99.5h-660v109h84q23 0 42.5 4.5t33.5 18.5t22.5 40t8.5 70v981q0 41 -8.5 66.5t-23 39.5t-34 19.5t-41.5 5.5h-84v108 h598q292 0 439 -90t147 -282zM571 125h136q68 0 116 15t78 48.5t43.5 87.5t13.5 132q0 75 -12 130.5t-41 91.5t-76 54t-118 18h-140v-577zM571 827h78q72 0 119.5 15t76 46.5t40.5 81.5t12 120t-13.5 117t-43.5 76t-78 41.5t-117 12.5h-74v-510z" />-<glyph glyph-name="C" unicode="C" horiz-adv-x="1368" d="M870 143q75 0 133.5 17.5t104.5 45.5t80.5 63t60.5 69q16 -12 25.5 -37t9.5 -51q0 -43 -23 -90.5t-77 -87.5t-143 -66t-220 -26q-178 0 -311 54t-221 153t-132 238t-44 308q0 166 45 304t133.5 237t221 154t306.5 55q118 0 205.5 -18.5t145.5 -50.5t87 -75.5t29 -93.5 q0 -38 -16.5 -70.5t-49 -56.5t-80.5 -37.5t-110 -13.5q0 51 -11 101.5t-37.5 91.5t-69.5 66.5t-107 25.5q-97 0 -164 -41t-107.5 -120t-58.5 -194.5t-18 -263.5t22 -259t71 -184.5t127.5 -110t192.5 -36.5z" />-<glyph glyph-name="D" unicode="D" horiz-adv-x="1571" d="M1458 758q0 -168 -46 -307.5t-137.5 -239.5t-227 -155.5t-314.5 -55.5h-676v109h86q23 0 42 4.5t33 18.5t22 40t8 70v987q0 41 -8 65.5t-22.5 37.5t-33.5 17.5t-41 4.5h-86v108h676q170 0 304.5 -44.5t228 -133t143 -220.5t49.5 -306zM1112 758q0 293 -107.5 436 t-316.5 143h-117v-1210h115q105 0 185 43.5t133.5 125.5t80.5 198.5t27 263.5z" />-<glyph glyph-name="E" unicode="E" horiz-adv-x="1337" d="M573 125h410q31 0 53 11.5t38 32.5t25.5 49.5t14.5 62.5l16 96h142l-15 -377h-1200v109h86q22 0 41 4t33.5 16.5t22.5 37t8 64.5v990q0 44 -8 70t-22 40t-33 18.5t-42 4.5h-86v108h1137l8 -377h-143l-10 97q-8 75 -38 115t-93 40h-345v-512h486v-123h-486v-577z" />-<glyph glyph-name="F" unicode="F" horiz-adv-x="1272" d="M1059 1085l-10 99q-10 81 -49.5 117t-102.5 36h-326v-559h486v-121h-486v-415q0 -44 8.5 -70t22.5 -40t34 -18.5t42 -4.5h127v-109h-748v109h86q22 0 41 4t33.5 16.5t22.5 37t8 64.5v998q0 41 -8 65.5t-22.5 37.5t-33.5 17.5t-41 4.5h-86v108h1135l10 -377h-143z" />-<glyph glyph-name="G" unicode="G" horiz-adv-x="1575" d="M866 -20q-190 0 -331.5 54t-235.5 153t-140 238t-46 308q0 166 49 304t145 237t239 154t331 55q128 0 223.5 -18.5t160 -50.5t96.5 -75.5t32 -93.5q0 -36 -17 -67.5t-51 -55.5t-83 -37.5t-114 -13.5q0 62 -15 114t-46.5 89t-79.5 57.5t-113 20.5q-112 0 -190.5 -41 t-128 -120t-72 -194.5t-22.5 -263.5t23.5 -262.5t76 -192.5t136 -118.5t204.5 -40.5q38 0 76 3t74 11v301q0 82 -37 114t-109 32h-27v108h644v-108h-27q-31 0 -54 -7.5t-38 -25.5t-22 -47.5t-7 -73.5v-336q-120 -55 -242 -82.5t-262 -27.5z" />-<glyph glyph-name="H" unicode="H" horiz-adv-x="1677" d="M913 0v109h88q22 0 41 4.5t33.5 18.5t22.5 40t8 70v458h-535v-458q0 -44 8.5 -70t22.5 -40t33 -18.5t41 -4.5h88v-109h-707v109h86q22 0 41 4.5t33.5 18.5t22.5 40t8 70v986q0 41 -8.5 65.5t-23 37.5t-33.5 17.5t-40 4.5h-86v109h707v-109h-88q-22 0 -41 -4.5t-33 -18.5 t-22.5 -40t-8.5 -70v-395h535v395q0 44 -8 70t-22.5 40t-33.5 18.5t-41 4.5h-88v109h707v-109h-86q-21 0 -40 -4.5t-33.5 -18.5t-22.5 -40t-8 -70v-989q0 -40 8.5 -64.5t23 -37t33 -16.5t39.5 -4h86v-109h-707z" />-<glyph glyph-name="I" unicode="I" horiz-adv-x="821" d="M57 0v109h86q23 0 42 4.5t33 18.5t22 40t8 70v978q0 44 -8 70t-22 40t-33 18.5t-42 4.5h-86v109h707v-109h-86q-22 0 -41.5 -4.5t-33.5 -18.5t-22 -40t-8 -70v-978q0 -44 8 -70t22 -40t33.5 -18.5t41.5 -4.5h86v-109h-707z" />-<glyph glyph-name="J" unicode="J" horiz-adv-x="754" d="M731 1354h-86q-22 0 -41.5 -4.5t-33.5 -18.5t-22.5 -40t-8.5 -70v-1225q0 -93 -21 -165t-58 -126.5t-88.5 -92t-112 -60.5t-128 -33.5t-137.5 -10.5h-74v119h37q49 0 95.5 18.5t82.5 61.5t58 113.5t22 173.5v1235q0 41 -8 65.5t-22.5 37.5t-33.5 17.5t-40 4.5h-86v108 h706v-108z" />-<glyph glyph-name="K" unicode="K" horiz-adv-x="1503" d="M889 1090q33 35 55.5 63.5t36 53t19.5 46.5t6 43q0 34 -26 47t-89 13v106h555v-106q-42 0 -79.5 -13t-73.5 -37t-71 -57t-71 -73l-270 -297l381 -605q30 -48 57.5 -79.5t55.5 -50.5t57 -27t63 -8h8v-109h-110q-78 0 -137 5t-103.5 15t-76.5 25.5t-56 36.5t-43.5 47.5 t-38.5 58.5l-276 471l-91 -71v-346q0 -44 8.5 -70t22.5 -40t34 -18.5t42 -4.5h86v-109h-707v109h86q22 0 41 4t33.5 16.5t22.5 37t8 64.5v998q0 41 -8 65.5t-22.5 37.5t-33.5 17.5t-41 4.5h-86v108h707v-108h-86q-22 0 -42 -4.5t-34 -18.5t-22.5 -40t-8.5 -70v-478z" />-<glyph glyph-name="L" unicode="L" horiz-adv-x="1339" d="M57 0v109h86q23 0 42 4.5t33 18.5t22 40t8 70v979q0 44 -8 70t-22 40t-33 18.5t-42 4.5h-86v108h707v-108h-86q-21 0 -40.5 -4.5t-33.5 -17.5t-22.5 -37.5t-8.5 -65.5v-1104h381q72 0 112 45.5t58 132.5l33 154h115l-15 -457h-1200z" />-<glyph glyph-name="M" unicode="M" horiz-adv-x="1950" d="M1237 0v109h20q32 0 56.5 4t40.5 16t24 34.5t8 59.5v1037l-413 -1260h-139l-433 1260v-1018q0 -44 10.5 -70t30 -40t47.5 -18.5t64 -4.5h12v-109h-508v109h86q22 0 41 4t33.5 16.5t22.5 37t8 64.5v998q0 41 -8 65.5t-22.5 37.5t-33.5 17.5t-41 4.5h-86v108h590l340 -989 l326 989h579v-108h-86q-22 0 -41 -4.5t-33 -18.5t-22 -40t-8 -70v-979q0 -44 8 -70t22 -40t33 -18.5t41 -4.5h86v-109h-655z" />-<glyph glyph-name="N" unicode="N" horiz-adv-x="1614" d="M1200 0l-799 1128v-886q0 -44 8.5 -70t22.5 -40t33.5 -18.5t40.5 -4.5h86v-109h-535v109h86q22 0 41 4.5t33.5 18.5t22.5 40t8 70v987q0 41 -8.5 65.5t-23 37.5t-33.5 17.5t-40 4.5h-86v108h455l723 -1022v789q0 41 -8.5 65.5t-23 37.5t-33.5 17.5t-40 4.5h-86v108h535 v-108h-86q-21 0 -40 -4.5t-33.5 -18.5t-22.5 -40t-8 -70v-1221h-189z" />-<glyph glyph-name="O" unicode="O" horiz-adv-x="1612" d="M1499 733q0 -169 -45.5 -308t-134 -238t-217 -153t-295.5 -54q-175 0 -305.5 54t-216.5 153t-129 238.5t-43 309.5t43 308.5t129.5 236.5t217 151.5t306.5 53.5q166 0 294.5 -53.5t216.5 -152t133.5 -237t45.5 -309.5zM457 733q0 -147 19 -262t61 -194.5t108.5 -121.5 t161.5 -42q96 0 162.5 42t107.5 121.5t59.5 194.5t18.5 262t-18.5 262t-59.5 194.5t-107 121t-161 41.5q-96 0 -163 -41.5t-109 -121t-61 -194.5t-19 -262z" />-<glyph glyph-name="P" unicode="P" horiz-adv-x="1307" d="M1245 1028q0 -92 -32 -177t-104 -150t-187 -103.5t-281 -38.5h-70v-328q0 -40 9 -64.5t24 -37t35 -16.5t41 -4h125v-109h-748v109h86q23 0 42 4.5t33 18.5t22 40t8 70v987q0 41 -8 65.5t-22.5 37.5t-33.5 17.5t-41 4.5h-86v108h629q142 0 247 -30t174.5 -86.5 t103.5 -136.5t34 -181zM571 678h50q76 0 129 19t86.5 60t49 106t15.5 157q0 82 -13.5 142.5t-43 100t-77 59t-116.5 19.5h-80v-663z" />-<glyph glyph-name="Q" unicode="Q" horiz-adv-x="1612" d="M1499 733q0 -144 -32.5 -265.5t-95.5 -215.5t-156 -158.5t-214 -93.5q9 -113 37 -185.5t68.5 -114t89.5 -57.5t100 -16h33v-119h-147q-92 0 -181.5 27t-162 85.5t-121 150t-58.5 221.5q-138 21 -240.5 83.5t-170.5 158.5t-101.5 223t-33.5 278q0 170 43 308.5 t129.5 236.5t217 151.5t306.5 53.5q166 0 294.5 -53.5t216.5 -152t133.5 -237t45.5 -309.5zM457 733q0 -147 19 -262t61 -194.5t108.5 -121.5t161.5 -42q96 0 162.5 42t107.5 121.5t59.5 194.5t18.5 262t-18.5 262t-59.5 194.5t-107 121t-161 41.5q-96 0 -163 -41.5 t-109 -121t-61 -194.5t-19 -262z" />-<glyph glyph-name="R" unicode="R" horiz-adv-x="1448" d="M57 109h86q21 0 40 4t33.5 16.5t23 37t8.5 64.5v998q0 41 -8.5 65.5t-23 37.5t-33.5 17.5t-40 4.5h-86v108h660q290 0 425.5 -97t135.5 -282q0 -81 -24 -144t-63.5 -110t-89.5 -79.5t-102 -53.5l244 -420q26 -44 50.5 -75.5t49 -52t50.5 -30t55 -9.5h8v-109h-67 q-92 0 -160.5 7t-120 27.5t-89 56.5t-69.5 93l-243 451h-136v-404q0 -40 8.5 -64.5t23 -37t33.5 -16.5t40 -4h88v-109h-707v109zM571 754h115q71 0 118.5 19t76 56.5t41 94t12.5 131.5q0 78 -14 132t-44 88.5t-78 50t-116 15.5h-111v-587z" />-<glyph glyph-name="S" unicode="S" horiz-adv-x="1200" d="M541 -20q-138 0 -230 26t-147.5 67t-79 92t-23.5 101q0 53 19.5 89t52 59t74 33t86.5 10q0 -88 21 -154.5t58 -111t88.5 -67t112.5 -22.5q60 0 107 17t80 47t50 70.5t17 87.5q0 52 -21 92.5t-63.5 75t-107 66t-150.5 67.5q-107 44 -180.5 90.5t-118.5 100.5t-64.5 117 t-19.5 138q0 94 38 170t105 130t159 83t200 29q113 0 195 -18.5t135.5 -50.5t79.5 -75.5t26 -93.5q0 -36 -15.5 -67t-47 -54t-79 -36t-110.5 -13q0 43 -11.5 92.5t-37.5 92t-67.5 70.5t-100.5 28q-41 0 -78 -12t-64.5 -36t-43.5 -59.5t-16 -81.5q0 -43 14 -81t51.5 -74.5 t103 -73t169.5 -78.5q105 -43 179 -88t121.5 -96.5t69 -113t21.5 -136.5q0 -99 -39 -181.5t-112 -141.5t-175.5 -92t-230.5 -33z" />-<glyph glyph-name="T" unicode="T" horiz-adv-x="1337" d="M831 242q0 -44 8 -70t22.5 -40t33.5 -18.5t41 -4.5h86v-109h-707v109h86q23 0 42 4.5t33 18.5t22 40t8 70v1095h-164q-46 0 -77 -10t-50.5 -28.5t-29 -45.5t-13.5 -61l-14 -127h-138l11 397h1276l10 -397h-137l-15 127q-4 34 -13.5 61t-29 45.5t-50.5 28.5t-77 10h-164 v-1095z" />-<glyph glyph-name="U" unicode="U" horiz-adv-x="1530" d="M1497 1354h-88q-21 0 -40 -4.5t-33.5 -18.5t-22.5 -40t-8 -70v-799q0 -103 -29.5 -185t-92 -139t-159.5 -87.5t-231 -30.5t-239.5 25.5t-178.5 81.5t-111.5 144.5t-38.5 214.5v783q0 41 -8.5 65.5t-23 37.5t-33 17.5t-39.5 4.5h-88v108h706v-108h-86q-21 0 -40 -4.5 t-33.5 -18.5t-22.5 -40t-8 -70v-791q0 -86 21.5 -144.5t61.5 -94t96 -51t126 -15.5q68 0 123 17.5t93.5 54.5t59.5 94t21 135v803q0 41 -8 65.5t-22.5 37.5t-33.5 17.5t-40 4.5h-86v108h536v-108z" />-<glyph glyph-name="V" unicode="V" horiz-adv-x="1430" d="M1430 1354h-99q-14 0 -25 -6t-22 -22.5t-23 -45.5t-28 -76l-418 -1204h-205l-436 1255q-10 29 -25.5 48.5t-33 30.5t-35.5 15.5t-33 4.5h-47v108h676v-108h-94q-15 0 -29 -3.5t-25.5 -11.5t-18.5 -21t-7 -32q0 -20 6 -45.5t12 -42.5l209 -631q8 -23 16.5 -54t16.5 -65 t14.5 -67.5t9.5 -61.5q3 17 10.5 46.5t17.5 64l20.5 69l19.5 62.5l211 623l8.5 24.5t8 27.5t6 27t2.5 21q0 34 -22.5 52t-59.5 18h-107v108h529v-108z" />-<glyph glyph-name="W" unicode="W" horiz-adv-x="2185" d="M1237 1448l283 -782q18 -48 33.5 -97.5l28.5 -94.5t23 -83t15 -65l17 82l19.5 94.5l22 101.5t23.5 101l108 442q3 12 6.5 28t6.5 31.5t4.5 29t1.5 21.5q0 53 -31.5 75t-99.5 22h-47v108h534v-108h-39q-30 0 -53 -6t-41 -23.5t-32.5 -48.5t-28.5 -82l-318 -1194h-233 l-344 961l-295 -961h-254l-375 1245q-9 32 -22 53t-30.5 33.5t-40 17.5t-52.5 5h-27v108h684v-108h-47q-62 0 -92.5 -24t-30.5 -75q0 -17 6.5 -49.5t14.5 -60.5l137 -473l23.5 -84.5t23.5 -92t20 -89.5t13 -76q14 66 30.5 123.5l34.5 117.5l270 877h150z" />-<glyph glyph-name="X" unicode="X" horiz-adv-x="1499" d="M1071 1294q0 18 -9 30t-24.5 18.5t-36.5 9t-45 2.5h-6v108h480v-108h-19q-30 0 -54 -7.5t-47.5 -25t-47.5 -45.5t-54 -70l-297 -405l377 -584q40 -63 83 -85.5t81 -22.5h27v-109h-676v109h10q123 0 123 67q0 12 -2 23t-9.5 26t-20.5 37t-36 55l-188 285l-221 -299 q-19 -26 -36 -59.5t-17 -65.5q0 -34 28 -51.5t96 -17.5h7v-109h-517v109h13q37 0 65 10t52.5 28.5t47.5 45t48 59.5l358 467l-338 526q-17 27 -33.5 47.5t-37 34t-45.5 20.5t-60 7h-27v108h678v-108h-6q-35 0 -58.5 -4.5t-37.5 -13t-19.5 -19.5t-5.5 -23q0 -21 15.5 -53 t38.5 -67l170 -254l178 249q18 28 36.5 62.5t18.5 62.5z" />-<glyph glyph-name="Y" unicode="Y" horiz-adv-x="1419" d="M336 0v109h96q25 0 46.5 3t37.5 15.5t25.5 37.5t9.5 68v285l-379 727q-15 30 -28.5 51t-29.5 33.5t-36.5 18.5t-50.5 6h-27v108h686v-108h-35q-62 0 -92.5 -25t-30.5 -67q0 -26 9.5 -56.5t21.5 -56.5l152 -301q32 -65 52.5 -115t37.5 -98q21 54 48.5 117.5t61.5 132.5 l119 245q22 45 28.5 75.5t6.5 47.5q0 53 -33.5 77t-101.5 24h-47v108h536v-108h-39q-26 0 -44.5 -7t-35 -25t-34 -49t-41.5 -79l-348 -678v-280q0 -45 9 -70t24.5 -38t36 -16t44.5 -3h101v-109h-756z" />-<glyph glyph-name="Z" unicode="Z" horiz-adv-x="1364" d="M1229 1358l-770 -1233h442q63 0 103 17.5t63.5 46.5t34 65.5t14.5 75.5l10 92h148l-10 -422h-1178v102l768 1235h-403q-48 0 -79 -13t-49.5 -37.5t-27 -59t-12.5 -76.5l-11 -111h-147l10 422h1094v-104z" />-<glyph glyph-name="bracketleft" unicode="[" horiz-adv-x="848" d="M205 -262v1818h567v-108h-88q-34 0 -65 -5t-53.5 -20.5t-36 -44.5t-13.5 -78v-1306q0 -48 13.5 -77.5t36 -45t53.5 -20.5t65 -5h88v-108h-567z" />-<glyph glyph-name="backslash" unicode="\" horiz-adv-x="590" d="M0 1556h145l445 -1804h-148z" />-<glyph glyph-name="bracketright" unicode="]" horiz-adv-x="848" d="M643 1556v-1818h-567v108h88q35 0 65.5 5t53 20.5t36 45t13.5 77.5v1306q0 49 -13.5 78t-36 44.5t-53 20.5t-65.5 5h-88v108h567z" />-<glyph glyph-name="asciicircum" unicode="^" d="M526 1462h88l435 -925h-160l-320 702l-315 -702h-160z" />-<glyph glyph-name="underscore" unicode="_" horiz-adv-x="940" d="M950 -291h-960v121h960v-121z" />-<glyph glyph-name="grave" unicode="`" horiz-adv-x="1182" d="M713 1241q-48 28 -106.5 69.5t-113 85.5t-98.5 84.5t-63 67.5v21h321q16 -34 41 -74.5l52.5 -81l55.5 -78.5l50 -67v-27h-139z" />-<glyph glyph-name="a" unicode="a" horiz-adv-x="1227" d="M410 305q0 -88 28 -132t88 -44q44 0 79.5 18.5t61 52.5t39 82.5t13.5 108.5v154l-92 -6q-61 -3 -103 -19.5t-67 -45.5t-36 -71t-11 -98zM592 999q-39 0 -65.5 -16.5t-42.5 -46t-22.5 -70t-6.5 -88.5q-138 0 -207.5 31t-69.5 106q0 56 33.5 96t91 65t134 36.5t163.5 11.5 q108 0 188.5 -18.5t134 -59.5t80.5 -107.5t27 -162.5v-522q0 -42 6.5 -70t20.5 -44.5t38 -23.5t58 -7h8v-109h-381l-43 141h-18q-34 -41 -64 -71.5t-64 -50.5t-76.5 -29.5t-100.5 -9.5q-68 0 -126.5 20t-102 61.5t-68.5 104t-25 147.5q0 167 114.5 247t344.5 87l168 6v113 q0 51 -4 94t-17 74t-38.5 48t-67.5 17z" />-<glyph glyph-name="b" unicode="b" horiz-adv-x="1329" d="M1233 553q0 -149 -27.5 -257t-80.5 -178.5t-131.5 -104t-180.5 -33.5q-59 0 -107 13t-86.5 37t-68 57t-51.5 73h-19l-57 -160h-389v109h14q29 0 57 5t50 20t35.5 43t13.5 75v1059q0 44 -13 70.5t-35 41.5t-51.5 20t-62.5 5h-8v108h481v-350q0 -32 -2.5 -75t-5.5 -81 l-8 -92h14q44 76 117.5 120t189.5 44q100 0 176.5 -33.5t129 -103t79.5 -177t27 -255.5zM725 971q-61 0 -101 -25.5t-64 -77.5t-34 -130.5t-10 -184.5q0 -101 10 -179.5t34 -132t65 -81t102 -27.5q101 0 146 109t45 313q0 209 -45 312.5t-148 103.5z" />-<glyph glyph-name="c" unicode="c" horiz-adv-x="1079" d="M604 -20q-113 0 -206.5 29t-160.5 96t-104 175t-37 265q0 166 38.5 277t106 178t159.5 95.5t198 28.5q99 0 175 -16.5t127 -45.5t77.5 -68.5t26.5 -84.5q0 -33 -11.5 -64.5t-41 -55.5t-81 -39t-131.5 -15q0 53 -6.5 101t-21.5 84t-40.5 57.5t-64.5 21.5 q-45 0 -80.5 -21.5t-61 -73.5t-39 -139t-13.5 -218q0 -208 57 -311t186 -103q53 0 99.5 12.5t84.5 35t66.5 53t46.5 65.5q25 -15 37 -40.5t12 -53.5q0 -39 -23 -79t-71.5 -72.5t-123.5 -53t-179 -20.5z" />-<glyph glyph-name="d" unicode="d" horiz-adv-x="1329" d="M1122 252q0 -47 14 -75t37.5 -43t54 -20t64.5 -5h8v-109h-397l-57 160h-19q-23 -42 -52.5 -75t-68 -56.5t-86.5 -36t-108 -12.5q-100 0 -178 33.5t-131.5 103t-81 176.5t-27.5 254q0 148 27.5 256.5t80.5 179t130 105t176 34.5q58 0 104.5 -12.5t84 -34t66.5 -51.5 t50 -66h14q-4 57 -8 107q-1 21 -2.5 43t-3 41.5t-2 34.5t-0.5 24v113q0 42 -13.5 67.5t-36 38.5t-52.5 17t-64 4h-16v108h493v-1304zM600 133q61 0 101.5 25.5t65 77.5t34.5 130.5t10 184.5q0 101 -10 179.5t-34.5 132t-65 81t-101.5 27.5q-51 0 -87.5 -27.5t-59 -81.5 t-33 -132.5t-10.5 -180.5q0 -209 43.5 -312.5t146.5 -103.5z" />-<glyph glyph-name="e" unicode="e" horiz-adv-x="1169" d="M608 991q-86 0 -136.5 -85t-55.5 -249h354q0 78 -9 140t-28.5 105t-50 66t-74.5 23zM627 -20q-130 0 -229.5 38t-166.5 110t-101 176.5t-34 238.5q0 144 34 253t99 182t159.5 109.5t215.5 36.5q111 0 200 -31.5t151 -93.5t95 -154t33 -214v-105h-671q3 -103 22.5 -177 t54 -122t84 -71t113.5 -23q53 0 98 12.5t81.5 35t64 53t45.5 65.5q55 -30 55 -100q0 -44 -24 -83.5t-74 -69.5t-125.5 -48t-179.5 -18z" />-<glyph glyph-name="f" unicode="f" horiz-adv-x="834" d="M39 0v109h45q22 0 47.5 4.5t47 18.5t36 40t14.5 70v716h-182v140h182v76q0 95 24.5 169.5t76 126t130.5 78.5t187 27q99 0 165 -11.5t105 -32t55.5 -47.5t16.5 -59q0 -69 -62.5 -101t-189.5 -32q0 25 -5 54t-17 54t-31.5 41.5t-48.5 16.5q-25 0 -43 -13.5t-29.5 -43 t-16.5 -76.5t-5 -115v-112h282v-140h-282v-716q0 -44 14.5 -70t36 -40t47 -18.5t47.5 -4.5h96v-109h-743z" />-<glyph glyph-name="g" unicode="g" horiz-adv-x="1147" d="M1137 1059q0 -23 -7 -44.5t-23.5 -37.5t-45 -25.5t-70.5 -9.5q0 21 -6.5 37t-17.5 27t-25 16.5t-29 5.5q-17 0 -36.5 -7t-30.5 -15q21 -23 39 -52.5t31.5 -63.5t21.5 -70.5t8 -74.5q0 -77 -24.5 -142.5t-75.5 -113.5t-129 -75t-185 -27q-11 0 -28 0.5l-34 1t-32.5 2 t-23.5 2.5q-13 -7 -26.5 -17.5t-24 -23.5t-17 -29t-6.5 -34q0 -34 26.5 -50t69.5 -16h248q102 0 176 -21.5t122.5 -61.5t71.5 -98.5t23 -131.5q0 -95 -36 -169.5t-109.5 -126t-185 -79t-263.5 -27.5q-242 0 -360.5 72.5t-118.5 212.5q0 110 71 168.5t206 69.5q-28 11 -55 27 t-48 38.5t-34 51.5t-13 65q0 34 13.5 63t37.5 55.5t57.5 50t73.5 46.5q-45 16 -83.5 44.5t-66 68.5t-43.5 91t-16 113q0 180 104.5 279.5t323.5 99.5q73 0 135.5 -18.5t108.5 -44.5l37.5 38.5t46.5 37.5t58 28t71 11q37 0 64.5 -9.5t46 -25.5t28 -37t9.5 -45zM246 -203 q0 -85 61.5 -125.5t179.5 -40.5q180 0 262 52t82 153q0 65 -41 91t-124 26h-238q-31 0 -63 -5.5t-58.5 -22.5t-43.5 -47.5t-17 -80.5zM383 745q0 -54 7.5 -97.5t24.5 -74.5t44 -48t67 -17q41 0 68 16.5t43 48t22.5 76t6.5 99.5q0 57 -7 103.5t-23.5 79.5t-43.5 51.5 t-68 18.5q-40 0 -67 -19t-43.5 -53t-23.5 -81t-7 -103z" />-<glyph glyph-name="h" unicode="h" horiz-adv-x="1366" d="M858 0v674q0 66 -8.5 117.5t-28 86.5t-51 53.5t-76.5 18.5q-51 0 -85 -26t-54.5 -69.5t-29.5 -101t-9 -120.5v-391q0 -43 10.5 -69t29.5 -40.5t46.5 -19t61.5 -4.5h6v-109h-629v109h4q35 0 64 4.5t50.5 19.5t33.5 43t12 74v1073q0 41 -14.5 65.5t-36.5 38t-47.5 17.5 t-47.5 4h-24v108h481v-337q0 -40 -2.5 -86t-5.5 -85l-8 -90h18q31 58 65.5 91t73.5 49.5t82.5 21t91.5 4.5q79 0 142 -23t106.5 -71t66.5 -122.5t23 -178.5v-475q0 -48 8.5 -76.5t25.5 -43.5t43 -20t61 -5h6v-109h-455z" />-<glyph glyph-name="i" unicode="i" horiz-adv-x="721" d="M176 1430q0 39 13.5 67t37 45.5t55 26t68.5 8.5q35 0 67 -8.5t56 -26t38.5 -45.5t14.5 -67q0 -38 -14.5 -66t-38.5 -46t-56 -27t-67 -9q-37 0 -68.5 9t-55 27t-37 46t-13.5 66zM59 109q23 0 48.5 4.5t47 18.5t36 40t14.5 70v622q0 41 -14.5 65.5t-36.5 38t-47.5 17.5 t-47.5 4h-24v109h481v-856q0 -44 14.5 -70t36 -40t47.5 -18.5t48 -4.5h24v-109h-651v109h24z" />-<glyph glyph-name="j" unicode="j" horiz-adv-x="707" d="M516 1098v-1114q0 -138 -38.5 -229.5t-107 -146t-163 -77.5t-207.5 -23h-41v127h14q56 0 99.5 18t73 60t44.5 112t15 175v854q0 44 -13 70.5t-35.5 41t-52.5 19t-63 4.5h-6v109h481zM178 1430q0 39 13.5 67t37 45.5t55 26t68.5 8.5q35 0 67 -8.5t56 -26t38.5 -45.5 t14.5 -67q0 -38 -14.5 -66t-38.5 -46t-56 -27t-67 -9q-37 0 -68.5 9t-55 27t-37 46t-13.5 66z" />-<glyph glyph-name="k" unicode="k" horiz-adv-x="1303" d="M1303 0h-29q-112 0 -188 5t-131 23t-95 54.5t-80 99.5l-182 287l-82 -61v-166q0 -44 14.5 -70t36 -40t47.5 -18.5t48 -4.5h24v-109h-651v109h24q23 0 48.5 4.5t47 18.5t36 40t14.5 70v1081q0 41 -14.5 65.5t-36.5 38t-47.5 17.5t-47.5 4h-24v108h481v-790 q0 -28 -1.5 -64.5t-3.5 -69.5q-3 -39 -5 -79l258 289q34 38 44.5 60.5t10.5 37.5q0 26 -27.5 40.5t-78.5 14.5v103h516v-103q-71 0 -139.5 -41.5t-149.5 -132.5l-129 -147l272 -406q54 -81 109 -120t104 -39h7v-109z" />-<glyph glyph-name="l" unicode="l" horiz-adv-x="721" d="M59 109q23 0 48.5 4.5t47 18.5t36 40t14.5 70v1081q0 41 -14.5 65.5t-36.5 38t-47.5 17.5t-47.5 4h-24v108h481v-1314q0 -44 14.5 -70t36 -40t47.5 -18.5t48 -4.5h24v-109h-651v109h24z" />-<glyph glyph-name="m" unicode="m" horiz-adv-x="2019" d="M858 0v674q0 66 -8.5 117.5t-28 86.5t-51 53.5t-76.5 18.5q-51 0 -85 -26t-54.5 -69.5t-29.5 -101t-9 -120.5v-391q0 -43 10.5 -69t29.5 -40.5t46.5 -19t61.5 -4.5h6v-109h-629v109h4q35 0 64 4.5t50.5 19.5t33.5 43t12 74v612q0 43 -10.5 69t-30 40.5t-46.5 19t-61 4.5 h-6v103h430l27 -146h10q31 58 65.5 92t73.5 52t82.5 23t91.5 5q113 0 193 -40.5t117 -131.5h18q31 58 67.5 92t78 52t87 23t93.5 5q79 0 142 -23t106.5 -71t66.5 -122.5t23 -178.5v-475q0 -48 8.5 -76.5t25.5 -43.5t43 -20t60 -5h6v-109h-455v674q0 132 -36.5 204t-126.5 72 q-48 0 -82 -23.5t-55.5 -63.5t-31.5 -93.5t-10 -112.5v-403q0 -48 8.5 -76.5t25.5 -43.5t43 -20t61 -5h6v-109h-455z" />-<glyph glyph-name="n" unicode="n" horiz-adv-x="1366" d="M858 0v674q0 66 -8.5 117.5t-28 86.5t-51 53.5t-76.5 18.5q-51 0 -85 -26t-54.5 -69.5t-29.5 -101t-9 -120.5v-391q0 -43 10.5 -69t29.5 -40.5t46.5 -19t61.5 -4.5h6v-109h-629v109h4q35 0 64 4.5t50.5 19.5t33.5 43t12 74v606q0 43 -10.5 69t-30 40.5t-46.5 19t-61 4.5 h-6v109h430l27 -146h10q31 58 65.5 92t73.5 52t82.5 23t91.5 5q79 0 142 -23t106.5 -71t66.5 -122.5t23 -178.5v-475q0 -48 8.5 -76.5t25.5 -43.5t43 -20t61 -5h6v-109h-455z" />-<glyph glyph-name="o" unicode="o" horiz-adv-x="1255" d="M1159 553q0 -289 -135.5 -431t-398.5 -142q-123 0 -221 35.5t-166.5 106.5t-105 179t-36.5 252q0 289 135.5 430t399.5 141q123 0 220.5 -35t166 -106t105 -178.5t36.5 -251.5zM414 553q0 -109 11.5 -192t37 -139t66.5 -84.5t100 -28.5t99.5 28.5t66 84.5t36.5 139 t11 192q0 110 -11.5 192.5t-37 137.5t-66.5 82.5t-100 27.5t-100 -27.5t-66 -82.5t-36 -137.5t-11 -192.5z" />-<glyph glyph-name="p" unicode="p" horiz-adv-x="1321" d="M51 -383q23 0 48.5 4.5t47 18.5t36 40t14.5 70v1096q0 47 -10.5 75t-29 43t-44 20t-54.5 5h-14v109h401l37 -160h9q22 40 51.5 73t68 57t86.5 37t107 13q102 0 180.5 -33.5t131.5 -104t80.5 -178.5t27.5 -257q0 -148 -27 -255.5t-79.5 -177.5t-129 -103.5t-176.5 -33.5 q-117 0 -190 44.5t-117 119.5h-14l8 -92q3 -38 5.5 -81t2.5 -75v-141q0 -44 14.5 -70t36 -40t47 -18.5t47.5 -4.5h45v-109h-671v109h24zM717 127q103 0 147.5 103.5t44.5 312.5q0 204 -44.5 313t-145.5 109q-61 0 -102 -27.5t-65 -81t-34 -132t-10 -179.5q0 -106 10 -184.5 t34 -130.5t64 -77.5t101 -25.5z" />-<glyph glyph-name="q" unicode="q" horiz-adv-x="1327" d="M608 -492v109h37q34 0 64 4t52.5 17t36 38.5t13.5 67.5v150q0 9 0.5 24t2 34.5l3 41t2.5 42.5q4 49 8 107h-14q-21 -35 -50 -65t-66.5 -51.5t-84 -34t-104.5 -12.5q-99 0 -176 33.5t-130 103.5t-80.5 178t-27.5 256q0 147 27.5 254t81 176.5t131.5 103t178 33.5 q60 0 108 -12.5t86.5 -36t68 -56.5t52.5 -75h19l41 160h413v-109h-8q-34 0 -64.5 -5t-54 -20t-37.5 -43t-14 -75v-1102q0 -42 13.5 -67.5t36 -38.5t52.5 -17t64 -4h37v-109h-717zM600 965q-103 0 -146.5 -103.5t-43.5 -312.5q0 -205 43.5 -311.5t146.5 -106.5q61 0 101.5 27 t65 79.5t34.5 130t10 179.5q0 106 -10 184.5t-34.5 130.5t-65 77.5t-101.5 25.5z" />-<glyph glyph-name="r" unicode="r" horiz-adv-x="1071" d="M725 0h-684v109h6q35 0 64 5t50 20.5t32.5 44.5t11.5 77v594q0 45 -10.5 72.5t-30 42.5t-46.5 19.5t-61 4.5h-6v109h414l41 -160h10q20 46 44 80.5t57.5 57.5t79 34.5t108.5 11.5q121 0 178 -42.5t57 -123.5q0 -94 -68 -146t-192 -52q0 42 -4.5 74t-16 53t-31 32 t-48.5 11q-36 0 -62 -18t-44 -46.5t-29.5 -64.5t-18 -72.5t-8.5 -69.5t-2 -57v-354q0 -45 11 -72.5t30 -41.5t45.5 -18.5t56.5 -4.5h66v-109z" />-<glyph glyph-name="s" unicode="s" horiz-adv-x="999" d="M473 -20q-111 0 -189.5 18t-128 50t-72 76t-22.5 95q0 50 19.5 80.5t49 47.5t62.5 22.5t60 5.5q0 -64 16 -115t45 -86.5t69.5 -54.5t90.5 -19q53 0 91.5 11t63 30.5t36.5 45t12 53.5q0 34 -12.5 60.5t-41.5 50t-77.5 46t-120.5 47.5q-83 29 -146.5 61.5t-107 74t-66 95 t-22.5 122.5q0 82 31.5 143t89.5 101.5t139 60.5t180 20q97 0 165 -16.5t111 -43.5t63 -61t20 -69q0 -69 -48.5 -105t-160.5 -36q0 102 -45.5 158.5t-134.5 56.5q-33 0 -63.5 -8t-53 -24t-36 -39.5t-13.5 -55.5q0 -33 12.5 -59t44 -49.5t85.5 -47.5t138 -53 q68 -24 125.5 -54.5t99 -70t64.5 -90t23 -115.5q0 -85 -29.5 -152t-86.5 -113t-140 -70.5t-189 -24.5z" />-<glyph glyph-name="t" unicode="t" horiz-adv-x="829" d="M643 145q40 0 76.5 5.5t68.5 13.5v-133q-16 -8 -43.5 -17t-64 -16.5t-82.5 -12.5t-100 -5q-69 0 -125 16.5t-95 55t-60 100t-21 151.5v655h-154v105q66 0 118 26t83 60q63 66 94 203h170v-254h262v-140h-262v-634q0 -93 31.5 -136t103.5 -43z" />-<glyph glyph-name="u" unicode="u" horiz-adv-x="1366" d="M907 0l-45 145h-10q-31 -53 -67 -85.5t-77 -50t-86.5 -23.5t-93.5 -6q-160 0 -245.5 99t-85.5 306v459q0 45 -8 73t-25 44t-43.5 22t-63.5 6h-4v109h455v-656q0 -66 7.5 -119t25.5 -90.5t48 -58t75 -20.5q49 0 84.5 21.5t58 62t33 98t10.5 129.5v393q0 45 -12 71 t-32 39.5t-47 17t-57 3.5h-6v109h465v-860q0 -45 10.5 -70.5t28.5 -38.5t43 -16.5t55 -3.5h17v-109h-408z" />-<glyph glyph-name="v" unicode="v" horiz-adv-x="1241" d="M1241 989h-41q-21 0 -37.5 -5t-32 -20.5t-30.5 -45t-33 -78.5l-297 -840h-270l-336 891q-11 29 -25.5 48t-34 30t-45 15.5t-59.5 4.5v109h635v-109h-86q-31 0 -49.5 -16.5t-18.5 -48.5q0 -25 6.5 -49t12.5 -41l137 -388q25 -65 43.5 -131t28.5 -118q5 24 14 56l18 62.5 l16.5 55.5l10.5 35l139 411q10 28 16.5 56.5t6.5 56.5q0 34 -22.5 46.5t-57.5 12.5h-59v109h450v-109z" />-<glyph glyph-name="w" unicode="w" horiz-adv-x="1753" d="M1012 1087l200 -604q25 -74 36 -125.5t18 -89.5h6l9.5 49t11 48l14.5 54.5t20 67.5l90 299q8 26 13.5 56t5.5 47q0 52 -33.5 76t-102.5 24h-14v109h469v-109h-26q-30 0 -52 -6t-39 -23.5t-32 -49t-31 -81.5l-264 -829h-221l-224 680l-235 -680h-223l-263 881 q-11 32 -24 53t-30 33t-39 17t-52 5h-6v109h600v-109h-27q-62 0 -92.5 -17.5t-30.5 -68.5q0 -17 5.5 -41t11.5 -47l76 -266q10 -37 19 -76l17 -76t14.5 -70t10.5 -59h6q9 51 24.5 105.5t41.5 130.5l209 583h133z" />-<glyph glyph-name="x" unicode="x" horiz-adv-x="1323" d="M891 928q0 37 -28.5 49t-76.5 12h-6v109h473v-109h-18q-30 0 -53.5 -5.5t-47 -20t-48.5 -40t-58 -65.5l-211 -258l301 -383q47 -59 86.5 -83.5t77.5 -24.5h27v-109h-652v109h11q123 0 123 67q0 12 -3 24t-11 28t-23.5 37.5t-40.5 51.5l-117 142l-143 -178 q-19 -25 -31 -51t-12 -52q0 -34 29 -51.5t98 -17.5h6v-109h-520v109h18q37 0 65 6.5t52.5 21.5t49.5 40t56 63l258 311l-258 330q-43 54 -86 81t-90 27h-27v109h644v-109h-7q-35 0 -57.5 -4.5t-35.5 -12.5t-18 -19t-5 -23q0 -22 11 -42.5t36 -51.5l114 -140l97 117 q24 31 37.5 59t13.5 56z" />-<glyph glyph-name="y" unicode="y" horiz-adv-x="1186" d="M1186 1098v-109q-31 0 -54.5 -7t-42.5 -24.5t-35 -46t-32 -71.5l-75 -208l-78.5 -212.5l-79 -212l-76.5 -207.5q-36 -99 -68 -174t-67.5 -130t-78.5 -91t-100 -57.5t-132.5 -30.5t-176.5 -9h-35v119q107 0 182 26t127 74t85.5 115t58.5 148l-369 901q-12 29 -27 46 t-32 26t-37 11.5t-43 2.5v121h584v-109q-60 0 -94.5 -15.5t-34.5 -60.5q0 -17 7 -40.5t13 -38.5l37.5 -93.5l38 -101l35.5 -97.5l30 -83q15 -39 25 -70.5t16.5 -57t10.5 -47t6 -42.5q2 21 8 46.5t13 49t14 42l10 26.5l135 401q4 10 7.5 23.5t6.5 27.5t4.5 27t1.5 22 q0 47 -39 64.5t-110 17.5v109h461z" />-<glyph glyph-name="z" unicode="z" horiz-adv-x="1083" d="M754 139q31 0 52.5 9t37 29t26 51.5t19.5 76.5l12 66h121l-10 -371h-975v84l604 874h-299q-23 0 -40 -6.5t-30.5 -23t-24.5 -44t-22 -69.5l-12 -49h-123l25 332h884v-88l-606 -871h361z" />-<glyph glyph-name="braceleft" unicode="{" horiz-adv-x="905" d="M649 -256q-86 0 -151.5 21t-109.5 60.5t-66.5 95.5t-22.5 126v350q0 59 -18.5 95.5t-50.5 57.5t-75.5 29.5t-93.5 10.5v125q50 2 93.5 10t75.5 29t50.5 57.5t18.5 93.5v352q0 144 88.5 221.5t261.5 77.5h195v-108h-80q-44 0 -72 -13t-44.5 -38t-23 -62.5t-6.5 -85.5v-348 q0 -93 -53 -152.5t-172 -86.5v-17q118 -29 171.5 -89t53.5 -153v-350q0 -48 6.5 -85.5t23 -63t44.5 -38.5t72 -13h80v-109h-195z" />-<glyph glyph-name="bar" unicode="|" d="M645 -492h-147v2048h147v-2048z" />-<glyph glyph-name="braceright" unicode="}" horiz-adv-x="905" d="M61 -256v109h80q44 0 72 13t44.5 38.5t23 63t6.5 85.5v350q0 93 53.5 153t171.5 89v17q-119 27 -172 86.5t-53 152.5v348q0 48 -6.5 85.5t-23 62.5t-44.5 38t-72 13h-80v108h195q173 0 261.5 -77.5t88.5 -221.5v-352q0 -57 18.5 -93.5t50.5 -57.5t75.5 -29t93.5 -10v-125 q-50 -2 -93.5 -10.5t-75.5 -29.5t-50.5 -57.5t-18.5 -95.5v-350q0 -70 -22.5 -126t-66.5 -95.5t-109.5 -60.5t-151.5 -21h-195z" />-<glyph glyph-name="asciitilde" unicode="~" horiz-adv-x="1149" d="M541 664q-37 16 -65 26.5t-50.5 17t-43 9t-42.5 2.5q-28 0 -58 -9.5t-59.5 -25.5t-57.5 -38t-52 -48v158q99 108 247 108q29 0 54 -2.5t52.5 -9t61 -19t80.5 -32.5q37 -16 65 -27t51 -17.5t43.5 -9t41.5 -2.5q29 0 58.5 9.5t59 26t57.5 38.5t52 47v-157 q-98 -109 -248 -109q-29 0 -53.5 2.5t-52 9.5t-61 19.5t-80.5 32.5z" />-<glyph glyph-name="exclamdown" unicode="¡" horiz-adv-x="782" d="M569 -369h-356l121 996h115zM557 946q0 -47 -13 -78.5t-35.5 -50.5t-53.5 -27t-66 -8q-34 0 -64.5 8t-53.5 27t-36.5 50.5t-13.5 78.5q0 46 13.5 77.5t36.5 50.5t53.5 27.5t64.5 8.5q35 0 66 -8.5t53.5 -27.5t35.5 -50.5t13 -77.5z" />-<glyph glyph-name="cent" unicode="¢" d="M571 170q-103 7 -186.5 41.5t-143 101.5t-92 170.5t-32.5 249.5q0 153 34 259.5t94.5 174.5t144 102t181.5 42v151h127v-151q88 -5 156 -23.5t114 -47t69.5 -65t23.5 -77.5q0 -33 -11.5 -64.5t-41 -55.5t-80.5 -39t-131 -15q0 43 -5 84t-16.5 75t-30.5 58.5t-47 36.5 v-856q52 1 97.5 14.5t83 35t66 51t46.5 64.5q26 -15 38.5 -40.5t12.5 -53.5q0 -35 -20.5 -72t-62.5 -68.5t-107 -54t-154 -28.5v-170h-127v170zM430 735q0 -158 33.5 -255t107.5 -134v828q-33 -15 -59 -46.5t-44.5 -84t-28 -128.5t-9.5 -180z" />-<glyph glyph-name="sterling" unicode="£" d="M553 711l12.5 -42.5t9.5 -39t6.5 -40t2.5 -46.5q0 -161 -185 -281l7 -10q6 2 18.5 5t29 6t35.5 5t37 2q21 0 40 -2.5t39.5 -8t45 -13l56.5 -17.5q45 -14 81.5 -22t81.5 -8q70 0 122.5 41.5t92.5 109.5v-229q-40 -71 -98 -106t-139 -35q-31 0 -64.5 4t-65.5 10t-61.5 14 t-52.5 17q-50 17 -105.5 31t-97.5 14q-35 0 -65.5 -4.5t-58.5 -12.5t-54.5 -19.5t-54.5 -25.5l-90 -47v184l65 41q32 20 65 48t60 66.5t44.5 88t17.5 111.5q0 59 -12.5 107.5t-32.5 101.5h-236v125h191q-11 25 -22.5 57t-21.5 68t-16 75t-6 78q0 180 117 275.5t338 95.5 q110 0 187 -18.5t125.5 -50.5t70.5 -75.5t22 -93.5q0 -72 -65 -119t-191 -47q0 47 -7 96.5t-25 90t-50 66.5t-82 26q-77 0 -122.5 -58.5t-45.5 -179.5q0 -42 6.5 -82.5t16 -77.5t21.5 -69t24 -57h363v-125z" />-<glyph glyph-name="currency" unicode="¤" d="M195 729q0 59 16.5 113.5t48.5 99.5l-123 123l103 102l122 -123q45 29 97 45.5t110 16.5q57 0 109.5 -16.5t97.5 -47.5l127 127l103 -104l-125 -127q31 -45 48 -97.5t17 -111.5t-16 -110.5t-45 -96.5l121 -121l-103 -102l-121 121q-46 -31 -100 -48.5t-113 -17.5 q-58 0 -112 16.5t-99 47.5l-118 -119l-105 104l119 119q-59 91 -59 207zM334 729q0 -49 18.5 -92t50 -75.5t74.5 -51t92 -18.5q51 0 94.5 18.5t75 51t49 75.5t17.5 92q0 51 -17.5 94.5t-49 76t-75 51t-94.5 18.5q-49 0 -92 -18.5t-74.5 -51t-50 -76t-18.5 -94.5z" />-<glyph glyph-name="yen" unicode="¥" horiz-adv-x="1104" d="M195 0v109h47q34 0 64 1t52.5 11.5t36.5 34.5t15 69v113h-287v121h287v119h-287v120h252l-242 547q-14 31 -26.5 52t-27.5 33.5t-34 18t-45 5.5h-6v108h586v-108h-15q-62 0 -89 -19t-27 -61q0 -25 8.5 -54.5t23.5 -66.5l78 -195q26 -65 45 -125t27 -102q6 28 21 67l32 83 l86 225q13 35 21 60t12.5 44.5t6 34t1.5 27.5q0 42 -28.5 62t-96.5 20h-6v108h426v-108h-6q-26 0 -46 -7t-37.5 -25t-34.5 -49t-38 -79l-211 -496h268v-120h-286v-119h286v-121h-286v-102q0 -49 13.5 -75t36 -37.5t53.5 -13t65 -1.5h47v-109h-735z" />-<glyph glyph-name="brokenbar" unicode="¦" d="M645 737h-147v819h147v-819zM645 -492h-147v820h147v-820z" />-<glyph glyph-name="section" unicode="§" horiz-adv-x="1114" d="M565 -231q-115 0 -191.5 23.5t-122 61t-64.5 84t-19 93.5q0 40 13.5 70t37.5 50.5t57.5 31t73.5 10.5q0 -68 15.5 -125.5t45.5 -99.5t75.5 -65.5t105.5 -23.5q99 0 154 54t55 145q0 42 -12.5 74.5t-50 66t-106.5 73t-183 95.5q-85 42 -145.5 85t-98 89.5t-55 98 t-17.5 112.5q0 69 37 124.5t96 86.5q-35 37 -61.5 98.5t-26.5 145.5q0 78 30 139.5t84 103.5t128 64t162 22q90 0 158.5 -17.5t114.5 -48.5t69.5 -73.5t23.5 -93.5q0 -63 -50 -105t-144 -42q0 45 -8 92.5t-28.5 86.5t-55.5 64t-89 25q-86 0 -138 -50t-52 -143 q0 -49 16.5 -82.5t48 -59.5t76.5 -49t103 -50q108 -52 185.5 -98t127 -94t73 -101t23.5 -119q0 -37 -10.5 -73t-28 -68t-41.5 -58t-51 -43q34 -43 53 -101t19 -118q0 -81 -26 -149t-77.5 -116.5t-129 -75.5t-179.5 -27zM836 526q0 35 -11 66t-39 62.5t-76.5 64t-123.5 69.5 l-128 62.5t-110 64.5q-15 -17 -24 -40.5t-9 -49.5q0 -42 14.5 -75t48 -63.5t89 -62t137.5 -71.5l115.5 -60.5t89.5 -54.5q11 16 19 38t8 50z" />-<glyph glyph-name="dieresis" unicode="¨" horiz-adv-x="1182" d="M258 1409q0 35 10 59.5t26.5 39.5t38.5 21.5t46 6.5t46 -6.5t39 -21.5t27.5 -39.5t10.5 -59.5q0 -34 -10.5 -58.5t-27.5 -39.5t-39 -22t-46 -7t-46 7t-38.5 22t-26.5 39.5t-10 58.5zM678 1409q0 35 10 59.5t27 39.5t39 21.5t47 6.5q23 0 44.5 -6.5t38.5 -21.5t27.5 -39.5 t10.5 -59.5q0 -34 -10.5 -58.5t-27.5 -39.5t-38.5 -22t-44.5 -7q-25 0 -47 7t-39 22t-27 39.5t-10 58.5z" />-<glyph glyph-name="copyright" unicode="©" horiz-adv-x="1731" d="M113 731q0 104 27 200.5t75.5 180t117 152.5t152 118t179.5 76t200 27t200.5 -27t180 -76t152.5 -118t118 -152.5t76 -180t27 -200.5t-27 -200t-76 -179.5t-118 -152t-152.5 -117.5t-180 -75.5t-200.5 -26.5t-200 26.5t-179.5 75.5t-152 117.5t-117 152t-75.5 179.5 t-27 200zM225 731q0 -88 23 -169.5t64.5 -152.5t100 -129.5t129.5 -100t152.5 -64.5t169.5 -23q89 0 171 23t153 64.5t129.5 100t100 129.5t64.5 152.5t23 169.5t-23 170t-64.5 153.5t-100 130t-129.5 100t-153 64.5t-171 23q-88 0 -169.5 -23t-152.5 -64.5t-129.5 -100 t-100 -130t-64.5 -153t-23 -170.5zM901 365q47 0 87 12t72 32t55.5 45t37.5 50q12 -9 20.5 -25.5t8.5 -38.5q0 -28 -19.5 -57t-58 -53t-96 -39t-134.5 -15q-102 0 -178.5 33t-128 93t-77 144t-25.5 187q0 100 28 183t83.5 143t137 93.5t189.5 33.5q71 0 123.5 -11.5 t87.5 -31t52.5 -46t17.5 -57.5q0 -22 -11 -40t-30 -30.5t-44.5 -19t-54.5 -6.5q0 29 -7.5 56.5t-24.5 50t-44.5 36t-68.5 13.5q-72 0 -122.5 -24t-82.5 -71t-46.5 -115t-14.5 -157q0 -177 69 -272.5t199 -95.5z" />-<glyph glyph-name="ordfeminine" unicode="ª" horiz-adv-x="860" d="M295 991q0 -54 23 -76t55 -22q63 0 94 39.5t31 101.5v84l-47 -2q-85 -3 -120.5 -34.5t-35.5 -90.5zM416 1384q-25 0 -40 -9.5t-23.5 -27t-11.5 -40.5t-3 -50q-106 0 -164.5 20.5t-58.5 68.5q0 35 24 59t65.5 39t97 22t118.5 7q155 0 232 -48.5t77 -164.5v-302 q0 -45 19.5 -61t68.5 -16v-90h-239l-50 100h-14q-18 -22 -41 -42.5t-51.5 -36.5t-63 -25t-75.5 -9q-104 0 -164 53t-60 156q0 102 86 150.5t261 56.5l92 4v55q0 29 -3 53.5t-11.5 41.5t-24.5 26.5t-43 9.5z" />-<glyph glyph-name="guillemotleft" unicode="«" horiz-adv-x="1135" d="M133 606l313 357h144l-207 -410l207 -410h-144l-313 357v106zM545 606l313 357h143l-206 -410l206 -410h-143l-313 357v106z" />-<glyph glyph-name="logicalnot" unicode="¬" d="M1012 805v-512h-148v366h-731v146h879z" />-<glyph glyph-name="uni00AD" unicode="­" horiz-adv-x="635" d="M51 451v215h533v-215h-533z" />-<glyph glyph-name="registered" unicode="®" horiz-adv-x="1731" d="M477 358h27q20 0 38 2.5t32 10t22 21.5t8 38v600q0 24 -8 38.5t-22 22t-32 9.5t-38 2h-27v72h369q352 0 352 -244q0 -49 -16 -86t-42 -64t-59.5 -45.5t-68.5 -29.5l182 -293q10 -17 19.5 -27.5t22.5 -16.5t30.5 -8t42.5 -2v-69h-217l-226 381h-108v-240q2 -24 10 -38 t21.5 -21.5t31 -10t37.5 -2.5h29v-69h-410v69zM758 748h86q55 0 92 11t59 33t31.5 56t9.5 80q0 45 -11 77t-34.5 51.5t-61 28.5t-91.5 9h-80v-346zM113 731q0 104 27 200.5t75.5 180t117 152.5t152 118t179.5 76t200 27t200.5 -27t180 -76t152.5 -118t118 -152.5t76 -180 t27 -200.5t-27 -200t-76 -179.5t-118 -152t-152.5 -117.5t-180 -75.5t-200.5 -26.5t-200 26.5t-179.5 75.5t-152 117.5t-117 152t-75.5 179.5t-27 200zM225 731q0 -88 23 -169.5t64.5 -152.5t100 -129.5t129.5 -100t152.5 -64.5t169.5 -23q89 0 171 23t153 64.5t129.5 100 t100 129.5t64.5 152.5t23 169.5t-23 170t-64.5 153.5t-100 130t-129.5 100t-153 64.5t-171 23q-88 0 -169.5 -23t-152.5 -64.5t-129.5 -100t-100 -130t-64.5 -153t-23 -170.5z" />-<glyph glyph-name="macron" unicode="¯" horiz-adv-x="940" d="M950 1556h-960v121h960v-121z" />-<glyph glyph-name="degree" unicode="°" horiz-adv-x="819" d="M98 1151q0 65 24.5 121.5t66.5 98.5t98.5 66.5t120.5 24.5q65 0 121.5 -24.5t98.5 -66.5t66.5 -98.5t24.5 -121.5q0 -64 -24.5 -120.5t-66.5 -98.5t-98.5 -66t-121.5 -24q-64 0 -120.5 24t-98.5 66t-66.5 98.5t-24.5 120.5zM240 1151q0 -34 13 -65t36 -54.5t53.5 -37 t65.5 -13.5q36 0 67 13.5t54 37t36 54.5t13 65q0 36 -13 67.5t-36 54.5t-54 36.5t-67 13.5q-35 0 -65.5 -13.5t-53.5 -36.5t-36 -54.5t-13 -67.5z" />-<glyph glyph-name="plusminus" unicode="±" d="M1012 0h-879v145h879v-145zM645 659v-366h-147v366h-365v146h365v366h147v-366h367v-146h-367z" />-<glyph glyph-name="uni00B2" unicode="²" horiz-adv-x="819" d="M698 1264q0 -45 -12.5 -85t-45.5 -84t-90.5 -96t-146.5 -122l-167 -132h327q44 0 62 19.5t24 48.5l10 55h76l-8 -282h-651v147l225 189q53 44 87.5 85.5t54.5 81.5t28 80t8 82q0 66 -21.5 103t-74.5 37q-35 0 -56 -18.5t-32.5 -47.5t-15 -65.5t-3.5 -71.5q-34 0 -67 7 t-59 21.5t-42 38t-16 56.5q0 37 19 68.5t56 54.5t91.5 36t124.5 13q83 0 142.5 -15.5t98 -44t56.5 -69t18 -90.5z" />-<glyph glyph-name="uni00B3" unicode="³" horiz-adv-x="819" d="M479 1260q0 63 -21.5 97t-74.5 34q-32 0 -53.5 -14.5t-34 -39.5t-18 -57t-5.5 -68q-41 0 -74 4t-56 13.5t-35.5 26.5t-12.5 44q0 38 19 71.5t56 58t92 39t126 14.5t128 -14t98 -40t63 -63.5t22 -85.5q0 -90 -56 -146.5t-153 -80.5v-11q46 -7 90 -18t78 -33t55 -57.5 t21 -91.5q0 -75 -38 -126.5t-96.5 -83t-128 -45.5t-132.5 -14q-84 0 -151 15t-107 35v106q22 -9 50 -18t58.5 -16.5t62.5 -12t62 -4.5q98 0 152.5 39.5t54.5 128.5q0 69 -55.5 106t-167.5 37h-68v96h68q38 0 71.5 12.5t58 35.5t38.5 55.5t14 71.5z" />-<glyph glyph-name="acute" unicode="´" horiz-adv-x="1182" d="M330 1268l49.5 67l55.5 78.5t52.5 81t40.5 74.5h322v-21q-19 -27 -63 -67.5t-98.5 -84.5t-113 -85.5t-106.5 -69.5h-139v27z" />-<glyph glyph-name="uni00B5" unicode="µ" horiz-adv-x="1362" d="M573 -20q-85 0 -145 31.5t-100 88.5q0 -85 7 -157t30 -126t66.5 -87.5t115.5 -42.5q0 -39 -13 -71.5t-37.5 -56.5t-59.5 -37.5t-79 -13.5q-38 0 -73 14t-61.5 45.5t-42 83t-15.5 126.5q0 61 3 127.5t7.5 147.5t8.5 181t5 228v637h312v-676q0 -55 7.5 -104t25 -85.5 t47.5 -57.5t75 -21q43 0 77.5 23t59 64.5t37.5 100t13 129.5v627h311v-815q0 -94 34 -134t105 -40h15v-109h-179q-105 0 -172.5 49t-93.5 129h-10q-24 -45 -50.5 -82t-59.5 -62.5t-75 -39.5t-96 -14z" />-<glyph glyph-name="paragraph" unicode="¶" horiz-adv-x="1284" d="M860 1430h-172v-1657h-342v108h27q35 0 65.5 4.5t53 20t36 44.5t13.5 77v764h-146q-90 0 -150 32.5t-96 88t-51.5 128t-15.5 152.5q0 82 17 149t55 115t98 74t147 26h803v-108h-26q-35 0 -65.5 -5t-53.5 -20.5t-36 -44.5t-13 -78v-1273q0 -48 13 -77t36 -44.5t53.5 -20 t65.5 -4.5h26v-108h-342v1657z" />-<glyph glyph-name="periodcentered" unicode="·" horiz-adv-x="623" d="M143 727q0 47 13 78.5t35.5 50.5t53.5 27t66 8q34 0 64.5 -8t53.5 -27t36.5 -50.5t13.5 -78.5q0 -46 -13.5 -77.5t-36.5 -50.5t-53.5 -27.5t-64.5 -8.5q-35 0 -66 8.5t-53.5 27.5t-35.5 50.5t-13 77.5z" />-<glyph glyph-name="cedilla" unicode="¸" horiz-adv-x="682" d="M571 -258q0 -54 -21 -97.5t-59 -74t-91.5 -46.5t-118.5 -16q-16 0 -39 1.5t-48 4.5t-49 7.5t-43 9.5v125q38 -8 75 -12t65 -4q58 0 91.5 20.5t33.5 73.5q0 30 -11.5 49t-32.5 30.5t-49 17t-61 7.5l43 180h117l-21 -88q50 -4 90.5 -18.5t69 -38.5t44 -57.5t15.5 -73.5z " />-<glyph glyph-name="uni00B9" unicode="¹" horiz-adv-x="819" d="M528 1473v-717q0 -26 7.5 -41.5t20.5 -23.5t30.5 -10.5t36.5 -2.5h84v-90h-578v90h104q20 0 36.5 2.5t29.5 10.5t20 23.5t7 41.5v581q-48 -57 -85.5 -92t-74.5 -35q-29 0 -49.5 29.5t-20.5 71.5q37 9 82.5 29.5t106.5 64.5l92 68h151z" />-<glyph glyph-name="ordmasculine" unicode="º" horiz-adv-x="895" d="M311 1126q0 -121 30.5 -183t107.5 -62q75 0 106 62t31 183t-32 180.5t-108 59.5q-75 0 -105 -59.5t-30 -180.5zM823 1126q0 -176 -95.5 -262t-281.5 -86q-178 0 -276 86.5t-98 261.5q0 176 96 261.5t283 85.5q86 0 155 -21.5t117 -64.5t74 -108t26 -153z" />-<glyph glyph-name="guillemotright" unicode="»" horiz-adv-x="1135" d="M1001 500l-313 -357h-143l207 410l-207 410h143l313 -357v-106zM590 500l-314 -357h-143l207 410l-207 410h143l314 -357v-106z" />-<glyph glyph-name="onequarter" unicode="¼" horiz-adv-x="1720" d="M1319 595q0 32 2.5 63.5t7.5 69.5q-8 -17 -17 -33.5t-14 -24.5l-233 -321h254v246zM1522 240v-71q0 -26 7 -41.5t19 -23.5t27.5 -10.5t32.5 -2.5h41v-90h-471v90h57q17 0 32 2.5t26.5 10.5t18.5 23.5t7 41.5v71h-389v82l407 564h185v-537h159v-109h-159zM578 0h-160 l731 1462h158zM443 1473v-717q0 -26 7.5 -41.5t20.5 -23.5t30.5 -10.5t36.5 -2.5h84v-90h-578v90h104q20 0 36.5 2.5t29.5 10.5t20 23.5t7 41.5v581q-48 -57 -85.5 -92t-74.5 -35q-29 0 -49.5 29.5t-20.5 71.5q37 9 82.5 29.5t106.5 64.5l92 68h151z" />-<glyph glyph-name="onehalf" unicode="½" horiz-adv-x="1720" d="M537 0h-160l731 1462h158zM443 1473v-717q0 -26 7.5 -41.5t20.5 -23.5t30.5 -10.5t36.5 -2.5h84v-90h-578v90h104q20 0 36.5 2.5t29.5 10.5t20 23.5t7 41.5v581q-48 -57 -85.5 -92t-74.5 -35q-29 0 -49.5 29.5t-20.5 71.5q37 9 82.5 29.5t106.5 64.5l92 68h151zM1628 679 q0 -45 -12.5 -85t-45.5 -84t-90.5 -96t-146.5 -122l-167 -132h327q44 0 62 19.5t24 48.5l10 55h76l-8 -282h-651v147l225 189q53 44 87.5 85.5t54.5 81.5t28 80t8 82q0 66 -21.5 103t-74.5 37q-35 0 -56 -18.5t-32.5 -47.5t-15 -65.5t-3.5 -71.5q-34 0 -67 7t-59 21.5 t-42 38t-16 56.5q0 37 19 68.5t56 54.5t91.5 36t124.5 13q83 0 142.5 -15.5t98 -44t56.5 -69t18 -90.5z" />-<glyph glyph-name="threequarters" unicode="¾" horiz-adv-x="1720" d="M1319 595q0 32 2.5 63.5t7.5 69.5q-8 -17 -17 -33.5t-14 -24.5l-233 -321h254v246zM1522 240v-71q0 -26 7 -41.5t19 -23.5t27.5 -10.5t32.5 -2.5h41v-90h-471v90h57q17 0 32 2.5t26.5 10.5t18.5 23.5t7 41.5v71h-389v82l407 564h185v-537h159v-109h-159zM619 0h-160 l731 1462h158zM479 1260q0 63 -21.5 97t-74.5 34q-32 0 -53.5 -14.5t-34 -39.5t-18 -57t-5.5 -68q-41 0 -74 4t-56 13.5t-35.5 26.5t-12.5 44q0 38 19 71.5t56 58t92 39t126 14.5t128 -14t98 -40t63 -63.5t22 -85.5q0 -90 -56 -146.5t-153 -80.5v-11q46 -7 90 -18t78 -33 t55 -57.5t21 -91.5q0 -75 -38 -126.5t-96.5 -83t-128 -45.5t-132.5 -14q-84 0 -151 15t-107 35v106q22 -9 50 -18t58.5 -16.5t62.5 -12t62 -4.5q98 0 152.5 39.5t54.5 128.5q0 69 -55.5 106t-167.5 37h-68v96h68q38 0 71.5 12.5t58 35.5t38.5 55.5t14 71.5z" />-<glyph glyph-name="questiondown" unicode="¿" horiz-adv-x="1126" d="M113 -14q0 82 25.5 147t76 119t127 99t178.5 87l41 187h125l41 -252q-87 -35 -143.5 -82.5t-88.5 -102t-44.5 -114.5t-12.5 -121q0 -49 9 -89.5t28.5 -70t50 -45.5t72.5 -16q46 0 77.5 20.5t50.5 56t27.5 82t8.5 99.5q63 0 116 -9t91.5 -27t60 -45t21.5 -63 q0 -50 -26 -93t-79.5 -75t-136 -50.5t-195.5 -18.5q-108 0 -200 25.5t-158.5 74t-104.5 118.5t-38 159zM795 944q0 -47 -13 -78.5t-35.5 -50.5t-53.5 -27t-66 -8q-34 0 -64.5 8t-53.5 27t-36.5 50.5t-13.5 78.5q0 46 13.5 77.5t36.5 50.5t53.5 27.5t64.5 8.5q35 0 66 -8.5 t53.5 -27.5t35.5 -50.5t13 -77.5z" />-<glyph glyph-name="Agrave" unicode="À" horiz-adv-x="1542" d="M446 481l-59 -176q-8 -25 -16.5 -58.5t-8.5 -58.5q0 -20 8 -35t21 -24.5t29.5 -14.5t34.5 -5h86v-109h-533v109h25q27 0 49.5 6t42 22.5t37 45.5t35.5 75l444 1204h281l428 -1206q15 -43 31.5 -71.5t36 -45t41.5 -23.5t46 -7h37v-109h-676v109h80q15 0 31 4.5t28.5 14 t20.5 24.5t8 36q0 25 -6 48t-12 40l-72 205h-498zM795 942l-22.5 73.5l-23 78.5l-22 79.5t-18.5 75.5q-9 -31 -21 -68l-25.5 -76l-27 -78l-25.5 -75l-121 -346h414zM758 1579q-48 28 -106.5 69.5t-113 85.5t-98.5 84.5t-63 67.5v21h321q16 -34 41 -74.5l52.5 -81l55.5 -78.5 l50 -67v-27h-139z" />-<glyph glyph-name="Aacute" unicode="Á" horiz-adv-x="1542" d="M446 481l-59 -176q-8 -25 -16.5 -58.5t-8.5 -58.5q0 -20 8 -35t21 -24.5t29.5 -14.5t34.5 -5h86v-109h-533v109h25q27 0 49.5 6t42 22.5t37 45.5t35.5 75l444 1204h281l428 -1206q15 -43 31.5 -71.5t36 -45t41.5 -23.5t46 -7h37v-109h-676v109h80q15 0 31 4.5t28.5 14 t20.5 24.5t8 36q0 25 -6 48t-12 40l-72 205h-498zM795 942l-22.5 73.5l-23 78.5l-22 79.5t-18.5 75.5q-9 -31 -21 -68l-25.5 -76l-27 -78l-25.5 -75l-121 -346h414zM641 1606l49.5 67l55.5 78.5t52.5 81t40.5 74.5h322v-21q-19 -27 -63 -67.5t-98.5 -84.5t-113 -85.5 t-106.5 -69.5h-139v27z" />-<glyph glyph-name="Acircumflex" unicode="Â" horiz-adv-x="1542" d="M446 481l-59 -176q-8 -25 -16.5 -58.5t-8.5 -58.5q0 -20 8 -35t21 -24.5t29.5 -14.5t34.5 -5h86v-109h-533v109h25q27 0 49.5 6t42 22.5t37 45.5t35.5 75l444 1204h281l428 -1206q15 -43 31.5 -71.5t36 -45t41.5 -23.5t46 -7h37v-109h-676v109h80q15 0 31 4.5t28.5 14 t20.5 24.5t8 36q0 25 -6 48t-12 40l-72 205h-498zM795 942l-22.5 73.5l-23 78.5l-22 79.5t-18.5 75.5q-9 -31 -21 -68l-25.5 -76l-27 -78l-25.5 -75l-121 -346h414zM415 1606l56.5 67l64.5 78.5t60.5 81t44.5 74.5h276q16 -34 44.5 -74.5t60.5 -81l64 -78.5l56 -67v-27h-141 l-50 40l-60 48l-60.5 50l-52.5 46l-52.5 -46l-60 -50l-59 -48l-49.5 -40h-142v27z" />-<glyph glyph-name="Atilde" unicode="Ã" horiz-adv-x="1542" d="M446 481l-59 -176q-8 -25 -16.5 -58.5t-8.5 -58.5q0 -20 8 -35t21 -24.5t29.5 -14.5t34.5 -5h86v-109h-533v109h25q27 0 49.5 6t42 22.5t37 45.5t35.5 75l444 1204h281l428 -1206q15 -43 31.5 -71.5t36 -45t41.5 -23.5t46 -7h37v-109h-676v109h80q15 0 31 4.5t28.5 14 t20.5 24.5t8 36q0 25 -6 48t-12 40l-72 205h-498zM795 942l-22.5 73.5l-23 78.5l-22 79.5t-18.5 75.5q-9 -31 -21 -68l-25.5 -76l-27 -78l-25.5 -75l-121 -346h414zM942 1774q22 0 39.5 9t30 24t20 34.5t10.5 40.5h133q-3 -62 -23 -117t-53.5 -96.5t-79 -65.5t-100.5 -24 t-99.5 20t-83 44.5l-72 44.5t-66.5 20q-23 0 -40.5 -9t-30 -24t-20 -34.5t-10.5 -40.5h-133q3 62 23.5 117t54 96.5t79.5 65.5t101 24t99.5 -20t83 -44.5l72 -44.5t65.5 -20z" />-<glyph glyph-name="Adieresis" unicode="Ä" horiz-adv-x="1542" d="M446 481l-59 -176q-8 -25 -16.5 -58.5t-8.5 -58.5q0 -20 8 -35t21 -24.5t29.5 -14.5t34.5 -5h86v-109h-533v109h25q27 0 49.5 6t42 22.5t37 45.5t35.5 75l444 1204h281l428 -1206q15 -43 31.5 -71.5t36 -45t41.5 -23.5t46 -7h37v-109h-676v109h80q15 0 31 4.5t28.5 14 t20.5 24.5t8 36q0 25 -6 48t-12 40l-72 205h-498zM795 942l-22.5 73.5l-23 78.5l-22 79.5t-18.5 75.5q-9 -31 -21 -68l-25.5 -76l-27 -78l-25.5 -75l-121 -346h414zM436 1747q0 35 10 59.5t26.5 39.5t38.5 21.5t46 6.5t46 -6.5t39 -21.5t27.5 -39.5t10.5 -59.5 q0 -34 -10.5 -58.5t-27.5 -39.5t-39 -22t-46 -7t-46 7t-38.5 22t-26.5 39.5t-10 58.5zM856 1747q0 35 10 59.5t27 39.5t39 21.5t47 6.5q23 0 44.5 -6.5t38.5 -21.5t27.5 -39.5t10.5 -59.5q0 -34 -10.5 -58.5t-27.5 -39.5t-38.5 -22t-44.5 -7q-25 0 -47 7t-39 22t-27 39.5 t-10 58.5z" />-<glyph glyph-name="Aring" unicode="Å" horiz-adv-x="1542" d="M899 1573q0 30 -9.5 51t-26 34t-38.5 19t-47 6t-47 -6t-38.5 -19t-26 -34t-9.5 -51t9.5 -51t26 -34.5t38.5 -19.5t47 -6t47 6t38.5 19.5t26 34.5t9.5 51zM1024 1573q0 -55 -19 -96.5t-52.5 -69.5t-78.5 -42.5t-96 -14.5t-96 14.5t-78.5 42.5t-52.5 69.5t-19 96.5 q0 54 19 96t52.5 70t78.5 42.5t96 14.5t96 -14.5t78.5 -42.5t52.5 -70t19 -96zM446 481l-59 -176q-8 -25 -16.5 -58.5t-8.5 -58.5q0 -20 8 -35t21 -24.5t29.5 -14.5t34.5 -5h86v-109h-533v109h25q27 0 49.5 6t42 22.5t37 45.5t35.5 75l444 1204h281l428 -1206 q15 -43 31.5 -71.5t36 -45t41.5 -23.5t46 -7h37v-109h-676v109h80q15 0 31 4.5t28.5 14t20.5 24.5t8 36q0 25 -6 48t-12 40l-72 205h-498zM795 942l-22.5 73.5l-23 78.5l-22 79.5t-18.5 75.5q-9 -31 -21 -68l-25.5 -76l-27 -78l-25.5 -75l-121 -346h414z" />-<glyph glyph-name="AE" unicode="Æ" horiz-adv-x="2034" d="M1270 125h409q31 0 53 11.5t38 32.5t25.5 49.5t14.5 62.5l17 96h141l-14 -377h-1200v109h86q56 0 80 34.5t24 87.5v238h-471l-86 -172q-17 -32 -25 -61t-8 -48q0 -42 31.5 -60.5t91.5 -18.5h35v-109h-512v109h25q53 0 95 38.5t87 120.5l575 1075l-135 11v108h1243l8 -377 h-143l-10 97q-8 75 -38 115t-93 40h-344v-512h485v-123h-485v-577zM924 1337l-385 -745h405v745h-20z" />-<glyph glyph-name="Ccedilla" unicode="Ç" horiz-adv-x="1368" d="M870 143q75 0 133.5 17.5t104.5 45.5t80.5 63t60.5 69q16 -12 25.5 -37t9.5 -51q0 -43 -23 -90.5t-77 -87.5t-143 -66t-220 -26q-178 0 -311 54t-221 153t-132 238t-44 308q0 166 45 304t133.5 237t221 154t306.5 55q118 0 205.5 -18.5t145.5 -50.5t87 -75.5t29 -93.5 q0 -38 -16.5 -70.5t-49 -56.5t-80.5 -37.5t-110 -13.5q0 51 -11 101.5t-37.5 91.5t-69.5 66.5t-107 25.5q-97 0 -164 -41t-107.5 -120t-58.5 -194.5t-18 -263.5t22 -259t71 -184.5t127.5 -110t192.5 -36.5zM962 -258q0 -54 -21 -97.5t-59 -74t-91.5 -46.5t-118.5 -16 q-16 0 -39 1.5t-48 4.5t-49 7.5t-43 9.5v125q38 -8 75 -12t65 -4q58 0 91.5 20.5t33.5 73.5q0 30 -11.5 49t-32.5 30.5t-49 17t-61 7.5l43 180h117l-21 -88q50 -4 90.5 -18.5t69 -38.5t44 -57.5t15.5 -73.5z" />-<glyph glyph-name="Egrave" unicode="È" horiz-adv-x="1337" d="M573 125h410q31 0 53 11.5t38 32.5t25.5 49.5t14.5 62.5l16 96h142l-15 -377h-1200v109h86q22 0 41 4t33.5 16.5t22.5 37t8 64.5v990q0 44 -8 70t-22 40t-33 18.5t-42 4.5h-86v108h1137l8 -377h-143l-10 97q-8 75 -38 115t-93 40h-345v-512h486v-123h-486v-577zM689 1579 q-48 28 -106.5 69.5t-113 85.5t-98.5 84.5t-63 67.5v21h321q16 -34 41 -74.5l52.5 -81l55.5 -78.5l50 -67v-27h-139z" />-<glyph glyph-name="Eacute" unicode="É" horiz-adv-x="1337" d="M573 125h410q31 0 53 11.5t38 32.5t25.5 49.5t14.5 62.5l16 96h142l-15 -377h-1200v109h86q22 0 41 4t33.5 16.5t22.5 37t8 64.5v990q0 44 -8 70t-22 40t-33 18.5t-42 4.5h-86v108h1137l8 -377h-143l-10 97q-8 75 -38 115t-93 40h-345v-512h486v-123h-486v-577zM523 1606 l49.5 67l55.5 78.5t52.5 81t40.5 74.5h322v-21q-19 -27 -63 -67.5t-98.5 -84.5t-113 -85.5t-106.5 -69.5h-139v27z" />-<glyph glyph-name="Ecircumflex" unicode="Ê" horiz-adv-x="1337" d="M573 125h410q31 0 53 11.5t38 32.5t25.5 49.5t14.5 62.5l16 96h142l-15 -377h-1200v109h86q22 0 41 4t33.5 16.5t22.5 37t8 64.5v990q0 44 -8 70t-22 40t-33 18.5t-42 4.5h-86v108h1137l8 -377h-143l-10 97q-8 75 -38 115t-93 40h-345v-512h486v-123h-486v-577zM288 1606 l56.5 67l64.5 78.5t60.5 81t44.5 74.5h276q16 -34 44.5 -74.5t60.5 -81l64 -78.5l56 -67v-27h-141l-50 40l-60 48l-60.5 50l-52.5 46l-52.5 -46l-60 -50l-59 -48l-49.5 -40h-142v27z" />-<glyph glyph-name="Edieresis" unicode="Ë" horiz-adv-x="1337" d="M573 125h410q31 0 53 11.5t38 32.5t25.5 49.5t14.5 62.5l16 96h142l-15 -377h-1200v109h86q22 0 41 4t33.5 16.5t22.5 37t8 64.5v990q0 44 -8 70t-22 40t-33 18.5t-42 4.5h-86v108h1137l8 -377h-143l-10 97q-8 75 -38 115t-93 40h-345v-512h486v-123h-486v-577zM356 1747 q0 35 10 59.5t26.5 39.5t38.5 21.5t46 6.5t46 -6.5t39 -21.5t27.5 -39.5t10.5 -59.5q0 -34 -10.5 -58.5t-27.5 -39.5t-39 -22t-46 -7t-46 7t-38.5 22t-26.5 39.5t-10 58.5zM776 1747q0 35 10 59.5t27 39.5t39 21.5t47 6.5q23 0 44.5 -6.5t38.5 -21.5t27.5 -39.5t10.5 -59.5 q0 -34 -10.5 -58.5t-27.5 -39.5t-38.5 -22t-44.5 -7q-25 0 -47 7t-39 22t-27 39.5t-10 58.5z" />-<glyph glyph-name="Igrave" unicode="Ì" horiz-adv-x="821" d="M57 0v109h86q23 0 42 4.5t33 18.5t22 40t8 70v978q0 44 -8 70t-22 40t-33 18.5t-42 4.5h-86v109h707v-109h-86q-22 0 -41.5 -4.5t-33.5 -18.5t-22 -40t-8 -70v-978q0 -44 8 -70t22 -40t33.5 -18.5t41.5 -4.5h86v-109h-707zM429 1579q-48 28 -106.5 69.5t-113 85.5 t-98.5 84.5t-63 67.5v21h321q16 -34 41 -74.5l52.5 -81l55.5 -78.5l50 -67v-27h-139z" />-<glyph glyph-name="Iacute" unicode="Í" horiz-adv-x="821" d="M57 0v109h86q23 0 42 4.5t33 18.5t22 40t8 70v978q0 44 -8 70t-22 40t-33 18.5t-42 4.5h-86v109h707v-109h-86q-22 0 -41.5 -4.5t-33.5 -18.5t-22 -40t-8 -70v-978q0 -44 8 -70t22 -40t33.5 -18.5t41.5 -4.5h86v-109h-707zM253 1606l49.5 67l55.5 78.5t52.5 81t40.5 74.5 h322v-21q-19 -27 -63 -67.5t-98.5 -84.5t-113 -85.5t-106.5 -69.5h-139v27z" />-<glyph glyph-name="Icircumflex" unicode="Î" horiz-adv-x="821" d="M57 0v109h86q23 0 42 4.5t33 18.5t22 40t8 70v978q0 44 -8 70t-22 40t-33 18.5t-42 4.5h-86v109h707v-109h-86q-22 0 -41.5 -4.5t-33.5 -18.5t-22 -40t-8 -70v-978q0 -44 8 -70t22 -40t33.5 -18.5t41.5 -4.5h86v-109h-707zM48 1606l56.5 67l64.5 78.5t60.5 81t44.5 74.5 h276q16 -34 44.5 -74.5t60.5 -81l64 -78.5l56 -67v-27h-141l-50 40l-60 48l-60.5 50l-52.5 46l-52.5 -46l-60 -50l-59 -48l-49.5 -40h-142v27z" />-<glyph glyph-name="Idieresis" unicode="Ï" horiz-adv-x="821" d="M57 0v109h86q23 0 42 4.5t33 18.5t22 40t8 70v978q0 44 -8 70t-22 40t-33 18.5t-42 4.5h-86v109h707v-109h-86q-22 0 -41.5 -4.5t-33.5 -18.5t-22 -40t-8 -70v-978q0 -44 8 -70t22 -40t33.5 -18.5t41.5 -4.5h86v-109h-707zM79 1747q0 35 10 59.5t26.5 39.5t38.5 21.5 t46 6.5t46 -6.5t39 -21.5t27.5 -39.5t10.5 -59.5q0 -34 -10.5 -58.5t-27.5 -39.5t-39 -22t-46 -7t-46 7t-38.5 22t-26.5 39.5t-10 58.5zM499 1747q0 35 10 59.5t27 39.5t39 21.5t47 6.5q23 0 44.5 -6.5t38.5 -21.5t27.5 -39.5t10.5 -59.5q0 -34 -10.5 -58.5t-27.5 -39.5 t-38.5 -22t-44.5 -7q-25 0 -47 7t-39 22t-27 39.5t-10 58.5z" />-<glyph glyph-name="Eth" unicode="Ð" horiz-adv-x="1571" d="M1458 758q0 -168 -46 -307.5t-137.5 -239.5t-227 -155.5t-314.5 -55.5h-676v109h86q23 0 42 4.5t33 18.5t22 40t8 70v460h-170v125h170v402q0 41 -8 65.5t-22.5 37.5t-33.5 17.5t-41 4.5h-86v108h676q170 0 304.5 -44.5t228 -133t143 -220.5t49.5 -306zM1112 758 q0 293 -107.5 436t-316.5 143h-117v-510h256v-125h-256v-575h115q105 0 185 43.5t133.5 125.5t80.5 198.5t27 263.5z" />-<glyph glyph-name="Ntilde" unicode="Ñ" horiz-adv-x="1614" d="M1200 0l-799 1128v-886q0 -44 8.5 -70t22.5 -40t33.5 -18.5t40.5 -4.5h86v-109h-535v109h86q22 0 41 4.5t33.5 18.5t22.5 40t8 70v987q0 41 -8.5 65.5t-23 37.5t-33.5 17.5t-40 4.5h-86v108h455l723 -1022v789q0 41 -8.5 65.5t-23 37.5t-33.5 17.5t-40 4.5h-86v108h535 v-108h-86q-21 0 -40 -4.5t-33.5 -18.5t-22.5 -40t-8 -70v-1221h-189zM998 1774q22 0 39.5 9t30 24t20 34.5t10.5 40.5h133q-3 -62 -23 -117t-53.5 -96.5t-79 -65.5t-100.5 -24t-99.5 20t-83 44.5l-72 44.5t-66.5 20q-23 0 -40.5 -9t-30 -24t-20 -34.5t-10.5 -40.5h-133 q3 62 23.5 117t54 96.5t79.5 65.5t101 24t99.5 -20t83 -44.5l72 -44.5t65.5 -20z" />-<glyph glyph-name="Ograve" unicode="Ò" horiz-adv-x="1612" d="M1499 733q0 -169 -45.5 -308t-134 -238t-217 -153t-295.5 -54q-175 0 -305.5 54t-216.5 153t-129 238.5t-43 309.5t43 308.5t129.5 236.5t217 151.5t306.5 53.5q166 0 294.5 -53.5t216.5 -152t133.5 -237t45.5 -309.5zM457 733q0 -147 19 -262t61 -194.5t108.5 -121.5 t161.5 -42q96 0 162.5 42t107.5 121.5t59.5 194.5t18.5 262t-18.5 262t-59.5 194.5t-107 121t-161 41.5q-96 0 -163 -41.5t-109 -121t-61 -194.5t-19 -262zM813 1579q-48 28 -106.5 69.5t-113 85.5t-98.5 84.5t-63 67.5v21h321q16 -34 41 -74.5l52.5 -81l55.5 -78.5l50 -67 v-27h-139z" />-<glyph glyph-name="Oacute" unicode="Ó" horiz-adv-x="1612" d="M1499 733q0 -169 -45.5 -308t-134 -238t-217 -153t-295.5 -54q-175 0 -305.5 54t-216.5 153t-129 238.5t-43 309.5t43 308.5t129.5 236.5t217 151.5t306.5 53.5q166 0 294.5 -53.5t216.5 -152t133.5 -237t45.5 -309.5zM457 733q0 -147 19 -262t61 -194.5t108.5 -121.5 t161.5 -42q96 0 162.5 42t107.5 121.5t59.5 194.5t18.5 262t-18.5 262t-59.5 194.5t-107 121t-161 41.5q-96 0 -163 -41.5t-109 -121t-61 -194.5t-19 -262zM666 1606l49.5 67l55.5 78.5t52.5 81t40.5 74.5h322v-21q-19 -27 -63 -67.5t-98.5 -84.5t-113 -85.5t-106.5 -69.5 h-139v27z" />-<glyph glyph-name="Ocircumflex" unicode="Ô" horiz-adv-x="1612" d="M1499 733q0 -169 -45.5 -308t-134 -238t-217 -153t-295.5 -54q-175 0 -305.5 54t-216.5 153t-129 238.5t-43 309.5t43 308.5t129.5 236.5t217 151.5t306.5 53.5q166 0 294.5 -53.5t216.5 -152t133.5 -237t45.5 -309.5zM457 733q0 -147 19 -262t61 -194.5t108.5 -121.5 t161.5 -42q96 0 162.5 42t107.5 121.5t59.5 194.5t18.5 262t-18.5 262t-59.5 194.5t-107 121t-161 41.5q-96 0 -163 -41.5t-109 -121t-61 -194.5t-19 -262zM440 1606l56.5 67l64.5 78.5t60.5 81t44.5 74.5h276q16 -34 44.5 -74.5t60.5 -81l64 -78.5l56 -67v-27h-141l-50 40 l-60 48l-60.5 50l-52.5 46l-52.5 -46l-60 -50l-59 -48l-49.5 -40h-142v27z" />-<glyph glyph-name="Otilde" unicode="Õ" horiz-adv-x="1612" d="M1499 733q0 -169 -45.5 -308t-134 -238t-217 -153t-295.5 -54q-175 0 -305.5 54t-216.5 153t-129 238.5t-43 309.5t43 308.5t129.5 236.5t217 151.5t306.5 53.5q166 0 294.5 -53.5t216.5 -152t133.5 -237t45.5 -309.5zM457 733q0 -147 19 -262t61 -194.5t108.5 -121.5 t161.5 -42q96 0 162.5 42t107.5 121.5t59.5 194.5t18.5 262t-18.5 262t-59.5 194.5t-107 121t-161 41.5q-96 0 -163 -41.5t-109 -121t-61 -194.5t-19 -262zM975 1774q22 0 39.5 9t30 24t20 34.5t10.5 40.5h133q-3 -62 -23 -117t-53.5 -96.5t-79 -65.5t-100.5 -24t-99.5 20 t-83 44.5l-72 44.5t-66.5 20q-23 0 -40.5 -9t-30 -24t-20 -34.5t-10.5 -40.5h-133q3 62 23.5 117t54 96.5t79.5 65.5t101 24t99.5 -20t83 -44.5l72 -44.5t65.5 -20z" />-<glyph glyph-name="Odieresis" unicode="Ö" horiz-adv-x="1612" d="M1499 733q0 -169 -45.5 -308t-134 -238t-217 -153t-295.5 -54q-175 0 -305.5 54t-216.5 153t-129 238.5t-43 309.5t43 308.5t129.5 236.5t217 151.5t306.5 53.5q166 0 294.5 -53.5t216.5 -152t133.5 -237t45.5 -309.5zM457 733q0 -147 19 -262t61 -194.5t108.5 -121.5 t161.5 -42q96 0 162.5 42t107.5 121.5t59.5 194.5t18.5 262t-18.5 262t-59.5 194.5t-107 121t-161 41.5q-96 0 -163 -41.5t-109 -121t-61 -194.5t-19 -262zM463 1747q0 35 10 59.5t26.5 39.5t38.5 21.5t46 6.5t46 -6.5t39 -21.5t27.5 -39.5t10.5 -59.5q0 -34 -10.5 -58.5 t-27.5 -39.5t-39 -22t-46 -7t-46 7t-38.5 22t-26.5 39.5t-10 58.5zM883 1747q0 35 10 59.5t27 39.5t39 21.5t47 6.5q23 0 44.5 -6.5t38.5 -21.5t27.5 -39.5t10.5 -59.5q0 -34 -10.5 -58.5t-27.5 -39.5t-38.5 -22t-44.5 -7q-25 0 -47 7t-39 22t-27 39.5t-10 58.5z" />-<glyph glyph-name="multiply" unicode="×" d="M571 631l-301 -303l-104 104l301 303l-301 301l104 103l301 -301l301 303l105 -105l-303 -303l303 -303l-103 -102z" />-<glyph glyph-name="Oslash" unicode="Ø" horiz-adv-x="1612" d="M1499 733q0 -169 -45.5 -308t-134 -238t-217 -153t-295.5 -54q-173 0 -303 53l-62 -115h-145l94 174q-140 96 -209 260.5t-69 382.5q0 170 43 308.5t129.5 236.5t217 151.5t306.5 53.5q85 0 160 -15t141 -43l64 119h143l-96 -178q136 -96 207 -258t71 -377zM457 733 q0 -128 14 -230t45 -179l506 942q-38 42 -91.5 64t-121.5 22q-96 0 -163 -41.5t-109 -121t-61 -194.5t-19 -262zM1155 733q0 127 -14 228.5t-43 179.5l-506 -944q40 -42 93 -63t122 -21q96 0 162.5 42t107.5 121.5t59.5 194.5t18.5 262z" />-<glyph glyph-name="Ugrave" unicode="Ù" horiz-adv-x="1530" d="M1497 1354h-88q-21 0 -40 -4.5t-33.5 -18.5t-22.5 -40t-8 -70v-799q0 -103 -29.5 -185t-92 -139t-159.5 -87.5t-231 -30.5t-239.5 25.5t-178.5 81.5t-111.5 144.5t-38.5 214.5v783q0 41 -8.5 65.5t-23 37.5t-33 17.5t-39.5 4.5h-88v108h706v-108h-86q-21 0 -40 -4.5 t-33.5 -18.5t-22.5 -40t-8 -70v-791q0 -86 21.5 -144.5t61.5 -94t96 -51t126 -15.5q68 0 123 17.5t93.5 54.5t59.5 94t21 135v803q0 41 -8 65.5t-22.5 37.5t-33.5 17.5t-40 4.5h-86v108h536v-108zM813 1579q-48 28 -106.5 69.5t-113 85.5t-98.5 84.5t-63 67.5v21h321 q16 -34 41 -74.5l52.5 -81l55.5 -78.5l50 -67v-27h-139z" />-<glyph glyph-name="Uacute" unicode="Ú" horiz-adv-x="1530" d="M1497 1354h-88q-21 0 -40 -4.5t-33.5 -18.5t-22.5 -40t-8 -70v-799q0 -103 -29.5 -185t-92 -139t-159.5 -87.5t-231 -30.5t-239.5 25.5t-178.5 81.5t-111.5 144.5t-38.5 214.5v783q0 41 -8.5 65.5t-23 37.5t-33 17.5t-39.5 4.5h-88v108h706v-108h-86q-21 0 -40 -4.5 t-33.5 -18.5t-22.5 -40t-8 -70v-791q0 -86 21.5 -144.5t61.5 -94t96 -51t126 -15.5q68 0 123 17.5t93.5 54.5t59.5 94t21 135v803q0 41 -8 65.5t-22.5 37.5t-33.5 17.5t-40 4.5h-86v108h536v-108zM643 1606l49.5 67l55.5 78.5t52.5 81t40.5 74.5h322v-21q-19 -27 -63 -67.5 t-98.5 -84.5t-113 -85.5t-106.5 -69.5h-139v27z" />-<glyph glyph-name="Ucircumflex" unicode="Û" horiz-adv-x="1530" d="M1497 1354h-88q-21 0 -40 -4.5t-33.5 -18.5t-22.5 -40t-8 -70v-799q0 -103 -29.5 -185t-92 -139t-159.5 -87.5t-231 -30.5t-239.5 25.5t-178.5 81.5t-111.5 144.5t-38.5 214.5v783q0 41 -8.5 65.5t-23 37.5t-33 17.5t-39.5 4.5h-88v108h706v-108h-86q-21 0 -40 -4.5 t-33.5 -18.5t-22.5 -40t-8 -70v-791q0 -86 21.5 -144.5t61.5 -94t96 -51t126 -15.5q68 0 123 17.5t93.5 54.5t59.5 94t21 135v803q0 41 -8 65.5t-22.5 37.5t-33.5 17.5t-40 4.5h-86v108h536v-108zM454 1606l56.5 67l64.5 78.5t60.5 81t44.5 74.5h276q16 -34 44.5 -74.5 t60.5 -81l64 -78.5l56 -67v-27h-141l-50 40l-60 48l-60.5 50l-52.5 46l-52.5 -46l-60 -50l-59 -48l-49.5 -40h-142v27z" />-<glyph glyph-name="Udieresis" unicode="Ü" horiz-adv-x="1530" d="M1497 1354h-88q-21 0 -40 -4.5t-33.5 -18.5t-22.5 -40t-8 -70v-799q0 -103 -29.5 -185t-92 -139t-159.5 -87.5t-231 -30.5t-239.5 25.5t-178.5 81.5t-111.5 144.5t-38.5 214.5v783q0 41 -8.5 65.5t-23 37.5t-33 17.5t-39.5 4.5h-88v108h706v-108h-86q-21 0 -40 -4.5 t-33.5 -18.5t-22.5 -40t-8 -70v-791q0 -86 21.5 -144.5t61.5 -94t96 -51t126 -15.5q68 0 123 17.5t93.5 54.5t59.5 94t21 135v803q0 41 -8 65.5t-22.5 37.5t-33.5 17.5t-40 4.5h-86v108h536v-108zM494 1747q0 35 10 59.5t26.5 39.5t38.5 21.5t46 6.5t46 -6.5t39 -21.5 t27.5 -39.5t10.5 -59.5q0 -34 -10.5 -58.5t-27.5 -39.5t-39 -22t-46 -7t-46 7t-38.5 22t-26.5 39.5t-10 58.5zM914 1747q0 35 10 59.5t27 39.5t39 21.5t47 6.5q23 0 44.5 -6.5t38.5 -21.5t27.5 -39.5t10.5 -59.5q0 -34 -10.5 -58.5t-27.5 -39.5t-38.5 -22t-44.5 -7 q-25 0 -47 7t-39 22t-27 39.5t-10 58.5z" />-<glyph glyph-name="Yacute" unicode="Ý" horiz-adv-x="1419" d="M336 0v109h96q25 0 46.5 3t37.5 15.5t25.5 37.5t9.5 68v285l-379 727q-15 30 -28.5 51t-29.5 33.5t-36.5 18.5t-50.5 6h-27v108h686v-108h-35q-62 0 -92.5 -25t-30.5 -67q0 -26 9.5 -56.5t21.5 -56.5l152 -301q32 -65 52.5 -115t37.5 -98q21 54 48.5 117.5t61.5 132.5 l119 245q22 45 28.5 75.5t6.5 47.5q0 53 -33.5 77t-101.5 24h-47v108h536v-108h-39q-26 0 -44.5 -7t-35 -25t-34 -49t-41.5 -79l-348 -678v-280q0 -45 9 -70t24.5 -38t36 -16t44.5 -3h101v-109h-756zM602 1606l49.5 67l55.5 78.5t52.5 81t40.5 74.5h322v-21 q-19 -27 -63 -67.5t-98.5 -84.5t-113 -85.5t-106.5 -69.5h-139v27z" />-<glyph glyph-name="Thorn" unicode="Þ" horiz-adv-x="1307" d="M571 410h50q76 0 129 19t86.5 60t49 106t15.5 157q0 82 -13.5 142.5t-43 100t-77 59t-116.5 19.5h-80v-663zM1245 760q0 -92 -32 -177t-104 -150t-187 -103.5t-281 -38.5h-70v-60q2 -40 11 -64.5t23.5 -37t34 -16.5t40.5 -4h84v-109h-707v109h86q23 0 42 4.5t33 18.5 t22 40t8 70v979q0 44 -8 70t-22 40t-33 18.5t-42 4.5h-86v108h707v-108h-84q-21 0 -41 -4t-35 -17t-24 -37t-9 -65v-37h115q142 -2 247 -32.5t174.5 -86t103.5 -135t34 -180.5z" />-<glyph glyph-name="germandbls" unicode="ß" horiz-adv-x="1446" d="M205 1118q0 128 41 216t112.5 142t168.5 77.5t210 23.5q142 0 241 -37t161.5 -109.5t91 -179t29.5 -245.5h-115q-50 0 -93.5 -6t-76 -22t-51.5 -42.5t-19 -67.5q0 -36 12.5 -63t39.5 -50.5t70.5 -47t105.5 -52.5q64 -30 108 -67.5t71.5 -80.5t39.5 -89t12 -94 q0 -87 -27.5 -151.5t-79 -107.5t-126.5 -64t-170 -21q-93 0 -176.5 18.5t-149.5 56.5v248h110q7 -41 24.5 -78t44 -64.5t61.5 -44t77 -16.5q69 0 109.5 36t40.5 102q0 36 -9.5 66.5t-33 58.5t-63.5 55t-101 55q-75 35 -124.5 75.5t-79 86.5t-42 96t-12.5 103 q0 71 24.5 122.5t69 86t105.5 53t133 22.5q-1 94 -16.5 159.5t-44.5 106.5t-70.5 59.5t-94.5 18.5q-52 0 -94.5 -17t-72.5 -52.5t-46 -90t-16 -128.5v-1174h-502v109h88q22 0 41 4.5t33.5 18.5t22.5 40t8 70v714h-164v142h164v20z" />-<glyph glyph-name="agrave" unicode="à" horiz-adv-x="1227" d="M410 305q0 -88 28 -132t88 -44q44 0 79.5 18.5t61 52.5t39 82.5t13.5 108.5v154l-92 -6q-61 -3 -103 -19.5t-67 -45.5t-36 -71t-11 -98zM592 999q-39 0 -65.5 -16.5t-42.5 -46t-22.5 -70t-6.5 -88.5q-138 0 -207.5 31t-69.5 106q0 56 33.5 96t91 65t134 36.5t163.5 11.5 q108 0 188.5 -18.5t134 -59.5t80.5 -107.5t27 -162.5v-522q0 -42 6.5 -70t20.5 -44.5t38 -23.5t58 -7h8v-109h-381l-43 141h-18q-34 -41 -64 -71.5t-64 -50.5t-76.5 -29.5t-100.5 -9.5q-68 0 -126.5 20t-102 61.5t-68.5 104t-25 147.5q0 167 114.5 247t344.5 87l168 6v113 q0 51 -4 94t-17 74t-38.5 48t-67.5 17zM618 1241q-48 28 -106.5 69.5t-113 85.5t-98.5 84.5t-63 67.5v21h321q16 -34 41 -74.5l52.5 -81l55.5 -78.5l50 -67v-27h-139z" />-<glyph glyph-name="aacute" unicode="á" horiz-adv-x="1227" d="M410 305q0 -88 28 -132t88 -44q44 0 79.5 18.5t61 52.5t39 82.5t13.5 108.5v154l-92 -6q-61 -3 -103 -19.5t-67 -45.5t-36 -71t-11 -98zM592 999q-39 0 -65.5 -16.5t-42.5 -46t-22.5 -70t-6.5 -88.5q-138 0 -207.5 31t-69.5 106q0 56 33.5 96t91 65t134 36.5t163.5 11.5 q108 0 188.5 -18.5t134 -59.5t80.5 -107.5t27 -162.5v-522q0 -42 6.5 -70t20.5 -44.5t38 -23.5t58 -7h8v-109h-381l-43 141h-18q-34 -41 -64 -71.5t-64 -50.5t-76.5 -29.5t-100.5 -9.5q-68 0 -126.5 20t-102 61.5t-68.5 104t-25 147.5q0 167 114.5 247t344.5 87l168 6v113 q0 51 -4 94t-17 74t-38.5 48t-67.5 17zM467 1268l49.5 67l55.5 78.5t52.5 81t40.5 74.5h322v-21q-19 -27 -63 -67.5t-98.5 -84.5t-113 -85.5t-106.5 -69.5h-139v27z" />-<glyph glyph-name="acircumflex" unicode="â" horiz-adv-x="1227" d="M410 305q0 -88 28 -132t88 -44q44 0 79.5 18.5t61 52.5t39 82.5t13.5 108.5v154l-92 -6q-61 -3 -103 -19.5t-67 -45.5t-36 -71t-11 -98zM592 999q-39 0 -65.5 -16.5t-42.5 -46t-22.5 -70t-6.5 -88.5q-138 0 -207.5 31t-69.5 106q0 56 33.5 96t91 65t134 36.5t163.5 11.5 q108 0 188.5 -18.5t134 -59.5t80.5 -107.5t27 -162.5v-522q0 -42 6.5 -70t20.5 -44.5t38 -23.5t58 -7h8v-109h-381l-43 141h-18q-34 -41 -64 -71.5t-64 -50.5t-76.5 -29.5t-100.5 -9.5q-68 0 -126.5 20t-102 61.5t-68.5 104t-25 147.5q0 167 114.5 247t344.5 87l168 6v113 q0 51 -4 94t-17 74t-38.5 48t-67.5 17zM256 1268l56.5 67l64.5 78.5t60.5 81t44.5 74.5h276q16 -34 44.5 -74.5t60.5 -81l64 -78.5l56 -67v-27h-141l-50 40l-60 48l-60.5 50l-52.5 46l-52.5 -46l-60 -50l-59 -48l-49.5 -40h-142v27z" />-<glyph glyph-name="atilde" unicode="ã" horiz-adv-x="1227" d="M410 305q0 -88 28 -132t88 -44q44 0 79.5 18.5t61 52.5t39 82.5t13.5 108.5v154l-92 -6q-61 -3 -103 -19.5t-67 -45.5t-36 -71t-11 -98zM592 999q-39 0 -65.5 -16.5t-42.5 -46t-22.5 -70t-6.5 -88.5q-138 0 -207.5 31t-69.5 106q0 56 33.5 96t91 65t134 36.5t163.5 11.5 q108 0 188.5 -18.5t134 -59.5t80.5 -107.5t27 -162.5v-522q0 -42 6.5 -70t20.5 -44.5t38 -23.5t58 -7h8v-109h-381l-43 141h-18q-34 -41 -64 -71.5t-64 -50.5t-76.5 -29.5t-100.5 -9.5q-68 0 -126.5 20t-102 61.5t-68.5 104t-25 147.5q0 167 114.5 247t344.5 87l168 6v113 q0 51 -4 94t-17 74t-38.5 48t-67.5 17zM789 1436q22 0 39.5 9t30 24t20 34.5t10.5 40.5h133q-3 -62 -23 -117t-53.5 -96.5t-79 -65.5t-100.5 -24t-99.5 20t-83 44.5l-72 44.5t-66.5 20q-23 0 -40.5 -9t-30 -24t-20 -34.5t-10.5 -40.5h-133q3 62 23.5 117t54 96.5t79.5 65.5 t101 24t99.5 -20t83 -44.5l72 -44.5t65.5 -20z" />-<glyph glyph-name="adieresis" unicode="ä" horiz-adv-x="1227" d="M410 305q0 -88 28 -132t88 -44q44 0 79.5 18.5t61 52.5t39 82.5t13.5 108.5v154l-92 -6q-61 -3 -103 -19.5t-67 -45.5t-36 -71t-11 -98zM592 999q-39 0 -65.5 -16.5t-42.5 -46t-22.5 -70t-6.5 -88.5q-138 0 -207.5 31t-69.5 106q0 56 33.5 96t91 65t134 36.5t163.5 11.5 q108 0 188.5 -18.5t134 -59.5t80.5 -107.5t27 -162.5v-522q0 -42 6.5 -70t20.5 -44.5t38 -23.5t58 -7h8v-109h-381l-43 141h-18q-34 -41 -64 -71.5t-64 -50.5t-76.5 -29.5t-100.5 -9.5q-68 0 -126.5 20t-102 61.5t-68.5 104t-25 147.5q0 167 114.5 247t344.5 87l168 6v113 q0 51 -4 94t-17 74t-38.5 48t-67.5 17zM278 1409q0 35 10 59.5t26.5 39.5t38.5 21.5t46 6.5t46 -6.5t39 -21.5t27.5 -39.5t10.5 -59.5q0 -34 -10.5 -58.5t-27.5 -39.5t-39 -22t-46 -7t-46 7t-38.5 22t-26.5 39.5t-10 58.5zM698 1409q0 35 10 59.5t27 39.5t39 21.5t47 6.5 q23 0 44.5 -6.5t38.5 -21.5t27.5 -39.5t10.5 -59.5q0 -34 -10.5 -58.5t-27.5 -39.5t-38.5 -22t-44.5 -7q-25 0 -47 7t-39 22t-27 39.5t-10 58.5z" />-<glyph glyph-name="aring" unicode="å" horiz-adv-x="1227" d="M740 1444q0 30 -9.5 51t-26 34t-38.5 19t-47 6t-47 -6t-38.5 -19t-26 -34t-9.5 -51t9.5 -51t26 -34.5t38.5 -19.5t47 -6t47 6t38.5 19.5t26 34.5t9.5 51zM865 1444q0 -55 -19 -96.5t-52.5 -69.5t-78.5 -42.5t-96 -14.5t-96 14.5t-78.5 42.5t-52.5 69.5t-19 96.5 q0 54 19 96t52.5 70t78.5 42.5t96 14.5t96 -14.5t78.5 -42.5t52.5 -70t19 -96zM410 305q0 -88 28 -132t88 -44q44 0 79.5 18.5t61 52.5t39 82.5t13.5 108.5v154l-92 -6q-61 -3 -103 -19.5t-67 -45.5t-36 -71t-11 -98zM592 999q-39 0 -65.5 -16.5t-42.5 -46t-22.5 -70 t-6.5 -88.5q-138 0 -207.5 31t-69.5 106q0 56 33.5 96t91 65t134 36.5t163.5 11.5q108 0 188.5 -18.5t134 -59.5t80.5 -107.5t27 -162.5v-522q0 -42 6.5 -70t20.5 -44.5t38 -23.5t58 -7h8v-109h-381l-43 141h-18q-34 -41 -64 -71.5t-64 -50.5t-76.5 -29.5t-100.5 -9.5 q-68 0 -126.5 20t-102 61.5t-68.5 104t-25 147.5q0 167 114.5 247t344.5 87l168 6v113q0 51 -4 94t-17 74t-38.5 48t-67.5 17z" />-<glyph glyph-name="ae" unicode="æ" horiz-adv-x="1792" d="M410 305q0 -88 28 -132t88 -44q44 0 79.5 18.5t61 52.5t39 82.5t13.5 108.5v154l-92 -6q-61 -3 -103 -19.5t-67 -45.5t-36 -71t-11 -98zM1231 991q-85 0 -136.5 -85t-56.5 -249h355q0 160 -39 247t-123 87zM1253 -20q-151 0 -260.5 50t-175.5 146q-69 -103 -171 -149.5 t-232 -46.5q-68 0 -126.5 20t-102 61.5t-68.5 104t-25 147.5q0 167 114.5 247t344.5 87l168 6v113q0 51 -4 94t-17 74t-38.5 48t-67.5 17q-39 0 -65.5 -16.5t-42.5 -46t-22.5 -70t-6.5 -88.5q-138 0 -207.5 31t-69.5 106q0 56 33.5 96t91 65t134 36.5t163.5 11.5 q109 0 190.5 -27t135.5 -89q60 57 135 86.5t166 29.5q111 0 200 -31.5t151 -93.5t95 -154t33 -214v-105h-672q3 -103 22.5 -177t54.5 -122t84.5 -71t113.5 -23q53 0 98 12.5t81.5 35t63.5 53t45 65.5q56 -30 56 -100q0 -44 -23.5 -83.5t-72.5 -69.5t-124.5 -48t-179.5 -18z " />-<glyph glyph-name="ccedilla" unicode="ç" horiz-adv-x="1079" d="M604 -20q-113 0 -206.5 29t-160.5 96t-104 175t-37 265q0 166 38.5 277t106 178t159.5 95.5t198 28.5q99 0 175 -16.5t127 -45.5t77.5 -68.5t26.5 -84.5q0 -33 -11.5 -64.5t-41 -55.5t-81 -39t-131.5 -15q0 53 -6.5 101t-21.5 84t-40.5 57.5t-64.5 21.5 q-45 0 -80.5 -21.5t-61 -73.5t-39 -139t-13.5 -218q0 -208 57 -311t186 -103q53 0 99.5 12.5t84.5 35t66.5 53t46.5 65.5q25 -15 37 -40.5t12 -53.5q0 -39 -23 -79t-71.5 -72.5t-123.5 -53t-179 -20.5zM839 -258q0 -54 -21 -97.5t-59 -74t-91.5 -46.5t-118.5 -16 q-16 0 -39 1.5t-48 4.5t-49 7.5t-43 9.5v125q38 -8 75 -12t65 -4q58 0 91.5 20.5t33.5 73.5q0 30 -11.5 49t-32.5 30.5t-49 17t-61 7.5l43 180h117l-21 -88q50 -4 90.5 -18.5t69 -38.5t44 -57.5t15.5 -73.5z" />-<glyph glyph-name="egrave" unicode="è" horiz-adv-x="1169" d="M608 991q-86 0 -136.5 -85t-55.5 -249h354q0 78 -9 140t-28.5 105t-50 66t-74.5 23zM627 -20q-130 0 -229.5 38t-166.5 110t-101 176.5t-34 238.5q0 144 34 253t99 182t159.5 109.5t215.5 36.5q111 0 200 -31.5t151 -93.5t95 -154t33 -214v-105h-671q3 -103 22.5 -177 t54 -122t84 -71t113.5 -23q53 0 98 12.5t81.5 35t64 53t45.5 65.5q55 -30 55 -100q0 -44 -24 -83.5t-74 -69.5t-125.5 -48t-179.5 -18zM642 1241q-48 28 -106.5 69.5t-113 85.5t-98.5 84.5t-63 67.5v21h321q16 -34 41 -74.5l52.5 -81l55.5 -78.5l50 -67v-27h-139z" />-<glyph glyph-name="eacute" unicode="é" horiz-adv-x="1169" d="M608 991q-86 0 -136.5 -85t-55.5 -249h354q0 78 -9 140t-28.5 105t-50 66t-74.5 23zM627 -20q-130 0 -229.5 38t-166.5 110t-101 176.5t-34 238.5q0 144 34 253t99 182t159.5 109.5t215.5 36.5q111 0 200 -31.5t151 -93.5t95 -154t33 -214v-105h-671q3 -103 22.5 -177 t54 -122t84 -71t113.5 -23q53 0 98 12.5t81.5 35t64 53t45.5 65.5q55 -30 55 -100q0 -44 -24 -83.5t-74 -69.5t-125.5 -48t-179.5 -18zM473 1268l49.5 67l55.5 78.5t52.5 81t40.5 74.5h322v-21q-19 -27 -63 -67.5t-98.5 -84.5t-113 -85.5t-106.5 -69.5h-139v27z" />-<glyph glyph-name="ecircumflex" unicode="ê" horiz-adv-x="1169" d="M608 991q-86 0 -136.5 -85t-55.5 -249h354q0 78 -9 140t-28.5 105t-50 66t-74.5 23zM627 -20q-130 0 -229.5 38t-166.5 110t-101 176.5t-34 238.5q0 144 34 253t99 182t159.5 109.5t215.5 36.5q111 0 200 -31.5t151 -93.5t95 -154t33 -214v-105h-671q3 -103 22.5 -177 t54 -122t84 -71t113.5 -23q53 0 98 12.5t81.5 35t64 53t45.5 65.5q55 -30 55 -100q0 -44 -24 -83.5t-74 -69.5t-125.5 -48t-179.5 -18zM254 1268l56.5 67l64.5 78.5t60.5 81t44.5 74.5h276q16 -34 44.5 -74.5t60.5 -81l64 -78.5l56 -67v-27h-141l-50 40l-60 48l-60.5 50 l-52.5 46l-52.5 -46l-60 -50l-59 -48l-49.5 -40h-142v27z" />-<glyph glyph-name="edieresis" unicode="ë" horiz-adv-x="1169" d="M608 991q-86 0 -136.5 -85t-55.5 -249h354q0 78 -9 140t-28.5 105t-50 66t-74.5 23zM627 -20q-130 0 -229.5 38t-166.5 110t-101 176.5t-34 238.5q0 144 34 253t99 182t159.5 109.5t215.5 36.5q111 0 200 -31.5t151 -93.5t95 -154t33 -214v-105h-671q3 -103 22.5 -177 t54 -122t84 -71t113.5 -23q53 0 98 12.5t81.5 35t64 53t45.5 65.5q55 -30 55 -100q0 -44 -24 -83.5t-74 -69.5t-125.5 -48t-179.5 -18zM281 1409q0 35 10 59.5t26.5 39.5t38.5 21.5t46 6.5t46 -6.5t39 -21.5t27.5 -39.5t10.5 -59.5q0 -34 -10.5 -58.5t-27.5 -39.5t-39 -22 t-46 -7t-46 7t-38.5 22t-26.5 39.5t-10 58.5zM701 1409q0 35 10 59.5t27 39.5t39 21.5t47 6.5q23 0 44.5 -6.5t38.5 -21.5t27.5 -39.5t10.5 -59.5q0 -34 -10.5 -58.5t-27.5 -39.5t-38.5 -22t-44.5 -7q-25 0 -47 7t-39 22t-27 39.5t-10 58.5z" />-<glyph glyph-name="igrave" unicode="ì" horiz-adv-x="721" d="M59 109q23 0 48.5 4.5t47 18.5t36 40t14.5 70v622q0 41 -14.5 65.5t-36.5 38t-47.5 17.5t-47.5 4h-24v109h481v-856q0 -44 14.5 -70t36 -40t47.5 -18.5t48 -4.5h24v-109h-651v109h24zM370 1241q-48 28 -106.5 69.5t-113 85.5t-98.5 84.5t-63 67.5v21h321q16 -34 41 -74.5 l52.5 -81l55.5 -78.5l50 -67v-27h-139z" />-<glyph glyph-name="iacute" unicode="í" horiz-adv-x="721" d="M59 109q23 0 48.5 4.5t47 18.5t36 40t14.5 70v622q0 41 -14.5 65.5t-36.5 38t-47.5 17.5t-47.5 4h-24v109h481v-856q0 -44 14.5 -70t36 -40t47.5 -18.5t48 -4.5h24v-109h-651v109h24zM267 1268l49.5 67l55.5 78.5t52.5 81t40.5 74.5h322v-21q-19 -27 -63 -67.5 t-98.5 -84.5t-113 -85.5t-106.5 -69.5h-139v27z" />-<glyph glyph-name="icircumflex" unicode="î" horiz-adv-x="721" d="M59 109q23 0 48.5 4.5t47 18.5t36 40t14.5 70v622q0 41 -14.5 65.5t-36.5 38t-47.5 17.5t-47.5 4h-24v109h481v-856q0 -44 14.5 -70t36 -40t47.5 -18.5t48 -4.5h24v-109h-651v109h24zM-30 1268l56.5 67l64.5 78.5t60.5 81t44.5 74.5h276q16 -34 44.5 -74.5t60.5 -81 l64 -78.5l56 -67v-27h-141l-50 40l-60 48l-60.5 50l-52.5 46l-52.5 -46l-60 -50l-59 -48l-49.5 -40h-142v27z" />-<glyph glyph-name="idieresis" unicode="ï" horiz-adv-x="721" d="M59 109q23 0 48.5 4.5t47 18.5t36 40t14.5 70v622q0 41 -14.5 65.5t-36.5 38t-47.5 17.5t-47.5 4h-24v109h481v-856q0 -44 14.5 -70t36 -40t47.5 -18.5t48 -4.5h24v-109h-651v109h24zM-1 1409q0 35 10 59.5t26.5 39.5t38.5 21.5t46 6.5t46 -6.5t39 -21.5t27.5 -39.5 t10.5 -59.5q0 -34 -10.5 -58.5t-27.5 -39.5t-39 -22t-46 -7t-46 7t-38.5 22t-26.5 39.5t-10 58.5zM419 1409q0 35 10 59.5t27 39.5t39 21.5t47 6.5q23 0 44.5 -6.5t38.5 -21.5t27.5 -39.5t10.5 -59.5q0 -34 -10.5 -58.5t-27.5 -39.5t-38.5 -22t-44.5 -7q-25 0 -47 7t-39 22 t-27 39.5t-10 58.5z" />-<glyph glyph-name="eth" unicode="ð" horiz-adv-x="1253" d="M1157 596q0 -312 -135.5 -464t-398.5 -152q-122 0 -219.5 33t-166 100.5t-105 169.5t-36.5 239q0 136 32.5 237.5t95.5 169t155 101t211 33.5q68 0 126.5 -16t96.5 -39q-11 33 -25 65.5t-34 65t-48 65t-67 64.5l-289 -148v133l182 95q-51 32 -108.5 50.5t-132.5 31.5v129 q38 -3 90.5 -14.5t108.5 -29t109.5 -39.5t92.5 -46l258 131v-136l-143 -73q78 -61 142.5 -140.5t110.5 -175.5t71.5 -206.5t25.5 -233.5zM414 520q0 -101 11.5 -178t37 -128.5t66.5 -78t100 -26.5q58 0 98.5 26.5t66 78t37 128.5t11.5 178q0 105 -11.5 182.5t-37 128.5 t-66.5 76t-100 25q-58 0 -99 -24.5t-66 -75t-36.5 -128t-11.5 -184.5z" />-<glyph glyph-name="ntilde" unicode="ñ" horiz-adv-x="1366" d="M858 0v674q0 66 -8.5 117.5t-28 86.5t-51 53.5t-76.5 18.5q-51 0 -85 -26t-54.5 -69.5t-29.5 -101t-9 -120.5v-391q0 -43 10.5 -69t29.5 -40.5t46.5 -19t61.5 -4.5h6v-109h-629v109h4q35 0 64 4.5t50.5 19.5t33.5 43t12 74v606q0 43 -10.5 69t-30 40.5t-46.5 19t-61 4.5 h-6v109h430l27 -146h10q31 58 65.5 92t73.5 52t82.5 23t91.5 5q79 0 142 -23t106.5 -71t66.5 -122.5t23 -178.5v-475q0 -48 8.5 -76.5t25.5 -43.5t43 -20t61 -5h6v-109h-455zM842 1436q22 0 39.5 9t30 24t20 34.5t10.5 40.5h133q-3 -62 -23 -117t-53.5 -96.5t-79 -65.5 t-100.5 -24t-99.5 20t-83 44.5l-72 44.5t-66.5 20q-23 0 -40.5 -9t-30 -24t-20 -34.5t-10.5 -40.5h-133q3 62 23.5 117t54 96.5t79.5 65.5t101 24t99.5 -20t83 -44.5l72 -44.5t65.5 -20z" />-<glyph glyph-name="ograve" unicode="ò" horiz-adv-x="1255" d="M1159 553q0 -289 -135.5 -431t-398.5 -142q-123 0 -221 35.5t-166.5 106.5t-105 179t-36.5 252q0 289 135.5 430t399.5 141q123 0 220.5 -35t166 -106t105 -178.5t36.5 -251.5zM414 553q0 -109 11.5 -192t37 -139t66.5 -84.5t100 -28.5t99.5 28.5t66 84.5t36.5 139 t11 192q0 110 -11.5 192.5t-37 137.5t-66.5 82.5t-100 27.5t-100 -27.5t-66 -82.5t-36 -137.5t-11 -192.5zM618 1241q-48 28 -106.5 69.5t-113 85.5t-98.5 84.5t-63 67.5v21h321q16 -34 41 -74.5l52.5 -81l55.5 -78.5l50 -67v-27h-139z" />-<glyph glyph-name="oacute" unicode="ó" horiz-adv-x="1255" d="M1159 553q0 -289 -135.5 -431t-398.5 -142q-123 0 -221 35.5t-166.5 106.5t-105 179t-36.5 252q0 289 135.5 430t399.5 141q123 0 220.5 -35t166 -106t105 -178.5t36.5 -251.5zM414 553q0 -109 11.5 -192t37 -139t66.5 -84.5t100 -28.5t99.5 28.5t66 84.5t36.5 139 t11 192q0 110 -11.5 192.5t-37 137.5t-66.5 82.5t-100 27.5t-100 -27.5t-66 -82.5t-36 -137.5t-11 -192.5zM504 1268l49.5 67l55.5 78.5t52.5 81t40.5 74.5h322v-21q-19 -27 -63 -67.5t-98.5 -84.5t-113 -85.5t-106.5 -69.5h-139v27z" />-<glyph glyph-name="ocircumflex" unicode="ô" horiz-adv-x="1255" d="M1159 553q0 -289 -135.5 -431t-398.5 -142q-123 0 -221 35.5t-166.5 106.5t-105 179t-36.5 252q0 289 135.5 430t399.5 141q123 0 220.5 -35t166 -106t105 -178.5t36.5 -251.5zM414 553q0 -109 11.5 -192t37 -139t66.5 -84.5t100 -28.5t99.5 28.5t66 84.5t36.5 139 t11 192q0 110 -11.5 192.5t-37 137.5t-66.5 82.5t-100 27.5t-100 -27.5t-66 -82.5t-36 -137.5t-11 -192.5zM262 1268l56.5 67l64.5 78.5t60.5 81t44.5 74.5h276q16 -34 44.5 -74.5t60.5 -81l64 -78.5l56 -67v-27h-141l-50 40l-60 48l-60.5 50l-52.5 46l-52.5 -46l-60 -50 l-59 -48l-49.5 -40h-142v27z" />-<glyph glyph-name="otilde" unicode="õ" horiz-adv-x="1255" d="M1159 553q0 -289 -135.5 -431t-398.5 -142q-123 0 -221 35.5t-166.5 106.5t-105 179t-36.5 252q0 289 135.5 430t399.5 141q123 0 220.5 -35t166 -106t105 -178.5t36.5 -251.5zM414 553q0 -109 11.5 -192t37 -139t66.5 -84.5t100 -28.5t99.5 28.5t66 84.5t36.5 139 t11 192q0 110 -11.5 192.5t-37 137.5t-66.5 82.5t-100 27.5t-100 -27.5t-66 -82.5t-36 -137.5t-11 -192.5zM795 1436q22 0 39.5 9t30 24t20 34.5t10.5 40.5h133q-3 -62 -23 -117t-53.5 -96.5t-79 -65.5t-100.5 -24t-99.5 20t-83 44.5l-72 44.5t-66.5 20q-23 0 -40.5 -9 t-30 -24t-20 -34.5t-10.5 -40.5h-133q3 62 23.5 117t54 96.5t79.5 65.5t101 24t99.5 -20t83 -44.5l72 -44.5t65.5 -20z" />-<glyph glyph-name="odieresis" unicode="ö" horiz-adv-x="1255" d="M1159 553q0 -289 -135.5 -431t-398.5 -142q-123 0 -221 35.5t-166.5 106.5t-105 179t-36.5 252q0 289 135.5 430t399.5 141q123 0 220.5 -35t166 -106t105 -178.5t36.5 -251.5zM414 553q0 -109 11.5 -192t37 -139t66.5 -84.5t100 -28.5t99.5 28.5t66 84.5t36.5 139 t11 192q0 110 -11.5 192.5t-37 137.5t-66.5 82.5t-100 27.5t-100 -27.5t-66 -82.5t-36 -137.5t-11 -192.5zM293 1409q0 35 10 59.5t26.5 39.5t38.5 21.5t46 6.5t46 -6.5t39 -21.5t27.5 -39.5t10.5 -59.5q0 -34 -10.5 -58.5t-27.5 -39.5t-39 -22t-46 -7t-46 7t-38.5 22 t-26.5 39.5t-10 58.5zM713 1409q0 35 10 59.5t27 39.5t39 21.5t47 6.5q23 0 44.5 -6.5t38.5 -21.5t27.5 -39.5t10.5 -59.5q0 -34 -10.5 -58.5t-27.5 -39.5t-38.5 -22t-44.5 -7q-25 0 -47 7t-39 22t-27 39.5t-10 58.5z" />-<glyph glyph-name="divide" unicode="÷" d="M459 1120q0 35 8.5 59t24 38.5t36.5 21t45 6.5t45 -6.5t36.5 -21t24.5 -38.5t9 -59q0 -34 -9 -58t-24.5 -39t-36.5 -21.5t-45 -6.5q-49 0 -81.5 28t-32.5 97zM459 344q0 35 8.5 59t24 38.5t36.5 21t45 6.5t45 -6.5t36.5 -21t24.5 -38.5t9 -59q0 -34 -9 -58t-24.5 -39 t-36.5 -21.5t-45 -6.5q-49 0 -81.5 28t-32.5 97zM1012 659h-879v146h879v-146z" />-<glyph glyph-name="oslash" unicode="ø" horiz-adv-x="1255" d="M1159 553q0 -289 -135.5 -431t-398.5 -142q-138 0 -242 43l-66 -105h-145l102 168q-86 71 -132 187t-46 280q0 289 135.5 430t399.5 141q72 0 136 -12t118 -37l65 105h144l-105 -172q82 -72 126 -184t44 -271zM414 553q0 -63 3 -117.5t11 -99.5l352 571q-52 86 -153 86 q-59 0 -100 -27.5t-66 -82.5t-36 -137.5t-11 -192.5zM842 553q0 56 -3 104.5t-10 92.5l-348 -566q51 -75 148 -75q59 0 99.5 28.5t66 84.5t36.5 139t11 192z" />-<glyph glyph-name="ugrave" unicode="ù" horiz-adv-x="1366" d="M907 0l-45 145h-10q-31 -53 -67 -85.5t-77 -50t-86.5 -23.5t-93.5 -6q-160 0 -245.5 99t-85.5 306v459q0 45 -8 73t-25 44t-43.5 22t-63.5 6h-4v109h455v-656q0 -66 7.5 -119t25.5 -90.5t48 -58t75 -20.5q49 0 84.5 21.5t58 62t33 98t10.5 129.5v393q0 45 -12 71 t-32 39.5t-47 17t-57 3.5h-6v109h465v-860q0 -45 10.5 -70.5t28.5 -38.5t43 -16.5t55 -3.5h17v-109h-408zM634 1241q-48 28 -106.5 69.5t-113 85.5t-98.5 84.5t-63 67.5v21h321q16 -34 41 -74.5l52.5 -81l55.5 -78.5l50 -67v-27h-139z" />-<glyph glyph-name="uacute" unicode="ú" horiz-adv-x="1366" d="M907 0l-45 145h-10q-31 -53 -67 -85.5t-77 -50t-86.5 -23.5t-93.5 -6q-160 0 -245.5 99t-85.5 306v459q0 45 -8 73t-25 44t-43.5 22t-63.5 6h-4v109h455v-656q0 -66 7.5 -119t25.5 -90.5t48 -58t75 -20.5q49 0 84.5 21.5t58 62t33 98t10.5 129.5v393q0 45 -12 71 t-32 39.5t-47 17t-57 3.5h-6v109h465v-860q0 -45 10.5 -70.5t28.5 -38.5t43 -16.5t55 -3.5h17v-109h-408zM527 1268l49.5 67l55.5 78.5t52.5 81t40.5 74.5h322v-21q-19 -27 -63 -67.5t-98.5 -84.5t-113 -85.5t-106.5 -69.5h-139v27z" />-<glyph glyph-name="ucircumflex" unicode="û" horiz-adv-x="1366" d="M907 0l-45 145h-10q-31 -53 -67 -85.5t-77 -50t-86.5 -23.5t-93.5 -6q-160 0 -245.5 99t-85.5 306v459q0 45 -8 73t-25 44t-43.5 22t-63.5 6h-4v109h455v-656q0 -66 7.5 -119t25.5 -90.5t48 -58t75 -20.5q49 0 84.5 21.5t58 62t33 98t10.5 129.5v393q0 45 -12 71 t-32 39.5t-47 17t-57 3.5h-6v109h465v-860q0 -45 10.5 -70.5t28.5 -38.5t43 -16.5t55 -3.5h17v-109h-408zM295 1268l56.5 67l64.5 78.5t60.5 81t44.5 74.5h276q16 -34 44.5 -74.5t60.5 -81l64 -78.5l56 -67v-27h-141l-50 40l-60 48l-60.5 50l-52.5 46l-52.5 -46l-60 -50 l-59 -48l-49.5 -40h-142v27z" />-<glyph glyph-name="udieresis" unicode="ü" horiz-adv-x="1366" d="M907 0l-45 145h-10q-31 -53 -67 -85.5t-77 -50t-86.5 -23.5t-93.5 -6q-160 0 -245.5 99t-85.5 306v459q0 45 -8 73t-25 44t-43.5 22t-63.5 6h-4v109h455v-656q0 -66 7.5 -119t25.5 -90.5t48 -58t75 -20.5q49 0 84.5 21.5t58 62t33 98t10.5 129.5v393q0 45 -12 71 t-32 39.5t-47 17t-57 3.5h-6v109h465v-860q0 -45 10.5 -70.5t28.5 -38.5t43 -16.5t55 -3.5h17v-109h-408zM332 1409q0 35 10 59.5t26.5 39.5t38.5 21.5t46 6.5t46 -6.5t39 -21.5t27.5 -39.5t10.5 -59.5q0 -34 -10.5 -58.5t-27.5 -39.5t-39 -22t-46 -7t-46 7t-38.5 22 t-26.5 39.5t-10 58.5zM752 1409q0 35 10 59.5t27 39.5t39 21.5t47 6.5q23 0 44.5 -6.5t38.5 -21.5t27.5 -39.5t10.5 -59.5q0 -34 -10.5 -58.5t-27.5 -39.5t-38.5 -22t-44.5 -7q-25 0 -47 7t-39 22t-27 39.5t-10 58.5z" />-<glyph glyph-name="yacute" unicode="ý" horiz-adv-x="1186" d="M1186 1098v-109q-31 0 -54.5 -7t-42.5 -24.5t-35 -46t-32 -71.5l-75 -208l-78.5 -212.5l-79 -212l-76.5 -207.5q-36 -99 -68 -174t-67.5 -130t-78.5 -91t-100 -57.5t-132.5 -30.5t-176.5 -9h-35v119q107 0 182 26t127 74t85.5 115t58.5 148l-369 901q-12 29 -27 46 t-32 26t-37 11.5t-43 2.5v121h584v-109q-60 0 -94.5 -15.5t-34.5 -60.5q0 -17 7 -40.5t13 -38.5l37.5 -93.5l38 -101l35.5 -97.5l30 -83q15 -39 25 -70.5t16.5 -57t10.5 -47t6 -42.5q2 21 8 46.5t13 49t14 42l10 26.5l135 401q4 10 7.5 23.5t6.5 27.5t4.5 27t1.5 22 q0 47 -39 64.5t-110 17.5v109h461zM463 1268l49.5 67l55.5 78.5t52.5 81t40.5 74.5h322v-21q-19 -27 -63 -67.5t-98.5 -84.5t-113 -85.5t-106.5 -69.5h-139v27z" />-<glyph glyph-name="thorn" unicode="þ" horiz-adv-x="1321" d="M51 -383q22 0 48 4.5t47.5 18.5t36 40t14.5 70v1555q0 47 -10.5 75t-29 43t-44 20t-54.5 5h-14v108h463v-376q0 -29 -4 -70.5t-9 -79.5q-5 -45 -12 -92h9q22 40 51.5 73t68 57t86.5 37t107 13q102 0 180.5 -33.5t131.5 -104t80.5 -178.5t27.5 -257q0 -148 -27 -255.5 t-79.5 -177.5t-129 -103.5t-176.5 -33.5q-117 0 -190 44.5t-117 119.5h-14l8 -92q3 -38 5.5 -81t2.5 -75v-141q0 -44 14.5 -70t36 -40t47 -18.5t47.5 -4.5h45v-109h-671v109h24zM717 127q103 0 147.5 103.5t44.5 312.5q0 204 -44.5 313t-145.5 109q-62 0 -102.5 -27.5 t-64.5 -81t-34 -132t-10 -179.5q0 -106 10 -184.5t34 -130.5t64 -77.5t101 -25.5z" />-<glyph glyph-name="ydieresis" unicode="ÿ" horiz-adv-x="1186" d="M1186 1098v-109q-31 0 -54.5 -7t-42.5 -24.5t-35 -46t-32 -71.5l-75 -208l-78.5 -212.5l-79 -212l-76.5 -207.5q-36 -99 -68 -174t-67.5 -130t-78.5 -91t-100 -57.5t-132.5 -30.5t-176.5 -9h-35v119q107 0 182 26t127 74t85.5 115t58.5 148l-369 901q-12 29 -27 46 t-32 26t-37 11.5t-43 2.5v121h584v-109q-60 0 -94.5 -15.5t-34.5 -60.5q0 -17 7 -40.5t13 -38.5l37.5 -93.5l38 -101l35.5 -97.5l30 -83q15 -39 25 -70.5t16.5 -57t10.5 -47t6 -42.5q2 21 8 46.5t13 49t14 42l10 26.5l135 401q4 10 7.5 23.5t6.5 27.5t4.5 27t1.5 22 q0 47 -39 64.5t-110 17.5v109h461zM266 1409q0 35 10 59.5t26.5 39.5t38.5 21.5t46 6.5t46 -6.5t39 -21.5t27.5 -39.5t10.5 -59.5q0 -34 -10.5 -58.5t-27.5 -39.5t-39 -22t-46 -7t-46 7t-38.5 22t-26.5 39.5t-10 58.5zM686 1409q0 35 10 59.5t27 39.5t39 21.5t47 6.5 q23 0 44.5 -6.5t38.5 -21.5t27.5 -39.5t10.5 -59.5q0 -34 -10.5 -58.5t-27.5 -39.5t-38.5 -22t-44.5 -7q-25 0 -47 7t-39 22t-27 39.5t-10 58.5z" />-<glyph glyph-name="OE" unicode="Œ" horiz-adv-x="2087" d="M1323 125h410q31 0 53 11.5t37.5 32.5t25 49.5t15.5 62.5l16 96h141l-14 -377h-987q-45 -9 -103.5 -14.5t-109.5 -5.5q-175 0 -305.5 54t-216.5 153t-129 238.5t-43 309.5t43 308.5t129.5 236.5t217 151.5t306.5 53.5q53 0 112.5 -6t104.5 -17h918l8 -377h-144l-10 97 q-8 75 -38 115t-93 40h-344v-512h485v-123h-485v-577zM807 113q59 0 105.5 15t84.5 44v1118q-34 26 -82.5 44t-105.5 18q-96 0 -163 -41.5t-109 -121t-61 -194.5t-19 -262t19 -262t61 -194.5t108.5 -121.5t161.5 -42z" />-<glyph glyph-name="oe" unicode="œ" horiz-adv-x="1915" d="M414 553q0 -109 11.5 -192t37 -139t66.5 -84.5t100 -28.5q56 0 95.5 26t65 77t38 127.5t14.5 176.5v55q-1 211 -50.5 316.5t-164.5 105.5q-59 0 -100 -27.5t-66 -82.5t-36 -137.5t-11 -192.5zM1354 991q-85 0 -136.5 -85t-56.5 -249h355q0 160 -39 247t-123 87zM1376 -20 q-118 0 -211 40t-161 117q-67 -81 -162.5 -119t-216.5 -38q-123 0 -221 35.5t-166.5 106.5t-105 179t-36.5 252q0 289 135.5 430t399.5 141q114 0 206 -39.5t160 -117.5q63 78 152 117.5t201 39.5q111 0 200 -31.5t151 -93.5t95 -154t33 -214v-105h-670q0 -6 -1 -12t-1 -12 q5 -97 25.5 -166.5t55 -114.5t83.5 -66.5t111 -21.5q53 0 98 12.5t81.5 35t63.5 53t45 65.5q56 -30 56 -100q0 -44 -23.5 -83.5t-72.5 -69.5t-124.5 -48t-179.5 -18z" />-<glyph glyph-name="Ydieresis" unicode="Ÿ" horiz-adv-x="1419" d="M336 0v109h96q25 0 46.5 3t37.5 15.5t25.5 37.5t9.5 68v285l-379 727q-15 30 -28.5 51t-29.5 33.5t-36.5 18.5t-50.5 6h-27v108h686v-108h-35q-62 0 -92.5 -25t-30.5 -67q0 -26 9.5 -56.5t21.5 -56.5l152 -301q32 -65 52.5 -115t37.5 -98q21 54 48.5 117.5t61.5 132.5 l119 245q22 45 28.5 75.5t6.5 47.5q0 53 -33.5 77t-101.5 24h-47v108h536v-108h-39q-26 0 -44.5 -7t-35 -25t-34 -49t-41.5 -79l-348 -678v-280q0 -45 9 -70t24.5 -38t36 -16t44.5 -3h101v-109h-756zM401 1747q0 35 10 59.5t26.5 39.5t38.5 21.5t46 6.5t46 -6.5t39 -21.5 t27.5 -39.5t10.5 -59.5q0 -34 -10.5 -58.5t-27.5 -39.5t-39 -22t-46 -7t-46 7t-38.5 22t-26.5 39.5t-10 58.5zM821 1747q0 35 10 59.5t27 39.5t39 21.5t47 6.5q23 0 44.5 -6.5t38.5 -21.5t27.5 -39.5t10.5 -59.5q0 -34 -10.5 -58.5t-27.5 -39.5t-38.5 -22t-44.5 -7 q-25 0 -47 7t-39 22t-27 39.5t-10 58.5z" />-<glyph glyph-name="circumflex" unicode="ˆ" horiz-adv-x="1182" d="M227 1268l56.5 67l64.5 78.5t60.5 81t44.5 74.5h276q16 -34 44.5 -74.5t60.5 -81l64 -78.5l56 -67v-27h-141l-50 40l-60 48l-60.5 50l-52.5 46l-52.5 -46l-60 -50l-59 -48l-49.5 -40h-142v27z" />-<glyph glyph-name="tilde" unicode="˜" horiz-adv-x="1182" d="M762 1436q22 0 39.5 9t30 24t20 34.5t10.5 40.5h133q-3 -62 -23 -117t-53.5 -96.5t-79 -65.5t-100.5 -24t-99.5 20t-83 44.5l-72 44.5t-66.5 20q-23 0 -40.5 -9t-30 -24t-20 -34.5t-10.5 -40.5h-133q3 62 23.5 117t54 96.5t79.5 65.5t101 24t99.5 -20t83 -44.5l72 -44.5 t65.5 -20z" />-<glyph glyph-name="endash" unicode="–" horiz-adv-x="1024" d="M1034 477h-1044v146h1044v-146z" />-<glyph glyph-name="emdash" unicode="—" horiz-adv-x="2048" d="M2058 477h-2068v146h2068v-146z" />-<glyph glyph-name="quoteleft" unicode="‘" horiz-adv-x="573" d="M115 1061q0 65 20 126t63.5 113t113 93.5t169.5 68.5v-106q-45 -16 -78.5 -32t-56 -34.5t-34 -41t-11.5 -52.5q0 -20 11 -32t28 -23l36 -22.5t36 -29t28 -44.5t11 -68q0 -69 -42.5 -105t-105.5 -36q-87 0 -137.5 60t-50.5 165z" />-<glyph glyph-name="quoteright" unicode="’" horiz-adv-x="573" d="M459 1237q0 -65 -20 -126t-64 -113t-113.5 -93.5t-169.5 -68.5v106q45 16 78.5 32t56 34.5t34 41t11.5 52.5q0 20 -11 32t-27.5 23t-36 22.5t-36 29.5t-27.5 44.5t-11 67.5q0 69 42 105t105 36q87 0 138 -60t51 -165z" />-<glyph glyph-name="quotesinglbase" unicode="‚" horiz-adv-x="602" d="M459 86q0 -65 -20 -126t-64 -113t-113.5 -93.5t-169.5 -68.5v106q45 16 78.5 32t56 34.5t34 41t11.5 52.5q0 19 -11 31.5t-27.5 23.5t-36 22.5t-36 29.5t-27.5 44.5t-11 67.5q0 69 42 105t105 36q87 0 138 -60t51 -165z" />-<glyph glyph-name="quotedblleft" unicode="“" horiz-adv-x="1001" d="M543 1061q0 65 20 126t63.5 113t113 93.5t169.5 68.5v-106q-44 -16 -78 -32t-56.5 -34.5t-34 -41t-11.5 -52.5q0 -20 11 -32t28 -23l36 -22.5t36 -29t28 -44.5t11 -68q0 -69 -42.5 -105t-105.5 -36q-87 0 -137.5 60t-50.5 165zM115 1061q0 65 20 126t63.5 113t113 93.5 t169.5 68.5v-106q-45 -16 -78.5 -32t-56 -34.5t-34 -41t-11.5 -52.5q0 -20 11 -32t28 -23l36 -22.5t36 -29t28 -44.5t11 -68q0 -69 -42.5 -105t-105.5 -36q-87 0 -137.5 60t-50.5 165z" />-<glyph glyph-name="quotedblright" unicode="”" horiz-adv-x="1001" d="M459 1237q0 -65 -20 -126t-64 -113t-113.5 -93.5t-169.5 -68.5v106q45 16 78.5 32t56 34.5t34 41t11.5 52.5q0 20 -11 32t-27.5 23t-36 22.5t-36 29.5t-27.5 44.5t-11 67.5q0 69 42 105t105 36q87 0 138 -60t51 -165zM887 1237q0 -65 -20 -126t-64 -113t-113.5 -93.5 t-169.5 -68.5v106q45 16 78.5 32t56 34.5t34 41t11.5 52.5q0 20 -11 32t-27.5 23t-36 22.5t-36 29.5t-27.5 44.5t-11 67.5q0 69 42 105t105 36q87 0 138 -60t51 -165z" />-<glyph glyph-name="quotedblbase" unicode="„" horiz-adv-x="1030" d="M459 86q0 -65 -20 -126t-64 -113t-113.5 -93.5t-169.5 -68.5v106q45 16 78.5 32t56 34.5t34 41t11.5 52.5q0 19 -11 31.5t-27.5 23.5t-36 22.5t-36 29.5t-27.5 44.5t-11 67.5q0 69 42 105t105 36q87 0 138 -60t51 -165zM887 86q0 -65 -20 -126t-64 -113t-113.5 -93.5 t-169.5 -68.5v106q45 16 78.5 32t56 34.5t34 41t11.5 52.5q0 19 -11 31.5t-27.5 23.5t-36 22.5t-36 29.5t-27.5 44.5t-11 67.5q0 69 42 105t105 36q87 0 138 -60t51 -165z" />-<glyph glyph-name="bullet" unicode="•" horiz-adv-x="819" d="M115 729q0 92 22 154.5t61.5 100t93.5 53.5t118 16q60 0 114 -16t94 -53.5t63.5 -100t23.5 -154.5q0 -91 -23.5 -153t-63.5 -100t-94 -54t-114 -16q-64 0 -118 16t-93.5 54t-61.5 100t-22 153z" />-<glyph glyph-name="ellipsis" unicode="…" horiz-adv-x="1868" d="M143 147q0 47 13 78.5t35.5 50.5t53.5 27t66 8q34 0 64.5 -8t53.5 -27t36.5 -50.5t13.5 -78.5q0 -46 -13.5 -77t-36.5 -50t-53.5 -27.5t-64.5 -8.5q-35 0 -66 8.5t-53.5 27.5t-35.5 50t-13 77zM766 147q0 47 13 78.5t35.5 50.5t53.5 27t66 8q34 0 64.5 -8t53.5 -27 t36.5 -50.5t13.5 -78.5q0 -46 -13.5 -77t-36.5 -50t-53.5 -27.5t-64.5 -8.5q-35 0 -66 8.5t-53.5 27.5t-35.5 50t-13 77zM1389 147q0 47 13 78.5t35.5 50.5t53 27t65.5 8q34 0 64.5 -8t53.5 -27t36.5 -50.5t13.5 -78.5q0 -46 -13.5 -77t-36.5 -50t-53.5 -27.5t-64.5 -8.5 q-35 0 -65.5 8.5t-53 27.5t-35.5 50t-13 77z" />-<glyph glyph-name="guilsinglleft" unicode="‹" horiz-adv-x="723" d="M133 606l313 357h144l-207 -410l207 -410h-144l-313 357v106z" />-<glyph glyph-name="guilsinglright" unicode="›" horiz-adv-x="723" d="M590 500l-314 -357h-143l207 410l-207 410h143l314 -357v-106z" />-<glyph glyph-name="Euro" unicode="€" d="M737 -20q-105 0 -198 30.5t-167 97.5t-124.5 174t-71.5 261h-121v127h109v63v64h-109v127h119q18 159 68 265.5t123 171t164.5 91.5t193.5 27q91 0 162.5 -18t121.5 -50t76.5 -75t26.5 -93q0 -35 -16.5 -66.5t-49 -55.5t-79.5 -38t-109 -14q0 48 -7.5 98t-25.5 90.5 t-47.5 66.5t-73.5 26q-43 0 -81.5 -18t-69 -65.5t-51 -130t-29.5 -212.5h377v-127h-383v-68v-59h301v-127h-291q17 -206 90.5 -308t212.5 -102q49 0 91.5 12.5t77.5 35t61.5 53t44.5 65.5q21 -17 32 -41.5t11 -52.5q0 -39 -19 -79t-61.5 -72.5t-111 -53t-167.5 -20.5z" />-<glyph glyph-name="trademark" unicode="™" horiz-adv-x="1710" d="M1321 754v55q35 0 56 10t24 45v457l-232 -567h-71l-232 569v-451q0 -41 21 -52t61 -11v-55h-248v55h21q31 0 54.5 10t25.5 47v482q-1 19 -7.5 30.5t-17.5 17.5t-25.5 7.5t-29.5 1.5h-21v57h236l225 -567l232 567h231v-57h-22q-17 0 -31.5 -2t-25 -8t-16 -18.5t-5.5 -33.5 v-471q0 -21 5.5 -33t16 -19t25 -9t31.5 -2h22v-55h-303zM422 872q0 -21 6.5 -33t17 -19t25 -9t29.5 -2h32v-55h-348v55h35q15 0 29 2t24.5 8t16.5 17.5t6 29.5v531h-90q-22 -1 -37 -6.5t-24 -15.5t-14 -22.5t-7 -27.5l-8 -49h-60l6 186h598l7 -186h-60l-8 49q-4 29 -21 50 t-61 22h-94v-525z" />-</font>-</defs></svg>
− extra/fonts/DroidSerif-Bold.ttf
binary file changed (47256 → absent bytes)
− extra/fonts/DroidSerif-Bold.woff
binary file changed (30824 → absent bytes)
− extra/fonts/DroidSerif-BoldItalic.eot
binary file changed (44230 → absent bytes)
− extra/fonts/DroidSerif-BoldItalic.svg
@@ -1,223 +0,0 @@-<?xml version="1.0" standalone="no"?>-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >-<svg xmlns="http://www.w3.org/2000/svg">-<defs>-<font id="DroidSerif-BoldItalic" horiz-adv-x="1145" >-<font-face font-family="Droid Serif" font-weight="700" font-style="italic" font-stretch="normal" units-per-em="2048" panose-1="2 2 8 0 6 5 0 9 2 0" ascent="1638" descent="-410" x-height="1098" cap-height="1462" bbox="-344 -492 2390 1907" underline-thickness="102" underline-position="-103" slope="-12" unicode-range="U+0020-U+2122"/>-<missing-glyph horiz-adv-x="532" />-<glyph glyph-name=".notdef" horiz-adv-x="532" />-<glyph glyph-name=".null" horiz-adv-x="0" />-<glyph glyph-name="nonmarkingreturn" horiz-adv-x="682" />-<glyph glyph-name="space" unicode=" " horiz-adv-x="532" />-<glyph glyph-bame="tab" unicode="	" horiz-adv-x="532" />-<glyph glyph-bame="uni00A0" unicode=" " horiz-adv-x="532" />-<glyph glyph-name="exclam" unicode="!" horiz-adv-x="809" d="M430 1462h356l-354 -995h-119zM502 168q0 -51 -15.5 -86.5t-41 -57t-57.5 -31t-64 -9.5q-75 0 -120.5 36t-45.5 111q0 42 12.5 76t35.5 58t56 37t74 13q35 0 65.5 -9t53 -27t35 -46t12.5 -65z" />-<glyph glyph-name="quotedbl" unicode=""" horiz-adv-x="1040" d="M807 1462h319l-213 -563h-135zM360 1462h320l-234 -563h-135z" />-<glyph glyph-name="numbersign" unicode="#" d="M1036 551l-14 -141h-256l-119 -410h-145l119 410h-213l-121 -410h-146l121 410h-205l15 141h231l111 371h-238l17 143h264l112 397h148l-115 -397h213l117 397h145l-116 -397h196l-16 -143h-223l-109 -371h227zM449 551h215l110 371h-215z" />-<glyph glyph-name="dollar" unicode="$" d="M1030 481q0 -95 -34.5 -169t-99 -126t-156 -80t-205.5 -30l-50 -238h-114l51 240q-187 15 -279 87t-92 187q0 97 59.5 141t168.5 44q0 -72 12 -127t34.5 -95t53.5 -66t70 -40l92 436q-73 39 -129 78.5t-94.5 86t-58 102t-19.5 126.5q0 85 33.5 152.5t94.5 114.5t146.5 73 t190.5 29l32 149h115l-35 -153q162 -13 243 -68t81 -139q0 -42 -12 -75t-39 -56t-71.5 -35t-109.5 -12q0 37 -7 75.5t-21 74t-36.5 64t-53.5 44.5l-86 -404l47 -24q141 -74 209.5 -162t68.5 -205zM768 389q0 28 -5 52.5t-19 47.5t-38.5 47t-62.5 50l-84 -393q48 1 86.5 15.5 t65.5 40.5t42 62t15 78zM504 1106q0 -59 24.5 -98t73.5 -74l76 356q-35 -5 -66.5 -17.5t-55.5 -35t-38 -55t-14 -76.5z" />-<glyph glyph-name="percent" unicode="%" horiz-adv-x="1835" d="M526 0h-159l1015 1462h154zM147 872q0 64 12 134t37.5 138t63.5 129.5t90.5 108t119 74t148.5 27.5q78 0 134 -23t92 -64t53.5 -97t17.5 -121q0 -59 -12.5 -127t-37.5 -135t-64 -129t-92.5 -109.5t-121.5 -76t-151 -28.5q-66 0 -119.5 19.5t-91 57.5t-58 93.5 t-20.5 128.5zM612 1376q-40 0 -73.5 -27.5t-60.5 -71.5t-47.5 -100t-34 -114t-20.5 -112.5t-7 -94.5q0 -81 20 -129.5t70 -48.5q40 0 73.5 27.5t60.5 71.5t47.5 100.5t34 114.5t20.5 112.5t7 95.5q0 82 -19.5 128t-70.5 48zM997 279q0 64 12 134t37.5 138t63.5 129 t90.5 107.5t119 74t148.5 27.5q78 0 134 -23t92 -64t53.5 -97t17.5 -121q0 -59 -12.5 -126.5t-37.5 -135t-64 -129t-92.5 -109t-121.5 -76t-151 -28.5q-66 0 -119.5 19.5t-91 57t-58 93.5t-20.5 129zM1462 782q-40 0 -73.5 -27.5t-60.5 -71.5t-47.5 -100t-34 -114 t-20.5 -112.5t-7 -94.5q0 -81 20 -129.5t70 -48.5q40 0 73.5 27.5t60.5 71.5t47.5 100.5t34 114.5t20.5 112.5t7 95.5q0 82 -19.5 128t-70.5 48z" />-<glyph glyph-name="ampersand" unicode="&" horiz-adv-x="1640" d="M1110 0l-102 141q-85 -72 -197.5 -116.5t-261.5 -44.5q-124 0 -214 29t-149 81t-87.5 122t-28.5 153q0 102 36 174.5t97 125.5t142 91t171 71q-46 79 -71 155t-25 148q0 88 31 154t89.5 110t144 66.5t194.5 22.5q94 0 162.5 -20.5t113.5 -56.5t66.5 -84.5t21.5 -104.5 q0 -71 -21 -124.5t-68.5 -98.5t-123.5 -85.5t-186 -85.5l252 -331q35 74 60 151t40 152l16 77h410l-23 -108h-18q-35 0 -68 -4t-64.5 -18.5t-59.5 -41.5t-52 -73l-65.5 -127.5t-79.5 -137.5l129 -169q31 -42 77.5 -63t112.5 -21h15l-23 -109h-393zM371 381q0 -57 16 -105.5 t47 -83.5t76 -55t102 -20q96 0 174.5 36.5t141.5 98.5l-334 463q-61 -34 -103.5 -70t-69 -76.5t-38.5 -87t-12 -100.5zM1010 1188q0 81 -36.5 126.5t-109.5 45.5q-36 0 -67 -14t-53.5 -41t-35 -67t-12.5 -91q0 -59 20 -112t60 -113q63 27 107.5 54t72.5 58t41 68.5t13 85.5z " />-<glyph glyph-name="quotesingle" unicode="'" horiz-adv-x="594" d="M340 1462h319l-213 -563h-135z" />-<glyph glyph-name="parenleft" unicode="(" horiz-adv-x="819" d="M401 285q0 -63 9 -126t30 -120.5t55.5 -107t84.5 -85.5l-27 -131q-124 43 -212.5 111t-145 154.5t-83 188t-26.5 212.5q0 127 23.5 256t70.5 251t118 233t166.5 202t215 157.5t264.5 100.5l-20 -129q-77 -42 -137.5 -97t-107.5 -117t-82 -129t-60.5 -133t-43 -128 t-30.5 -115q-38 -154 -50 -264.5t-12 -183.5z" />-<glyph glyph-name="parenright" unicode=")" horiz-adv-x="819" d="M453 1014q0 63 -9 126t-30 120t-55.5 106.5t-84.5 85.5l27 131q124 -43 212.5 -111t145 -154.5t83 -188t26.5 -211.5q0 -127 -23.5 -256t-70.5 -251t-118 -233t-166.5 -202t-215 -158t-264.5 -103l20 131q77 42 137.5 97t107.5 117t82 129t60.5 133t43 128t30.5 115 q38 155 50 264.5t12 184.5z" />-<glyph glyph-name="asterisk" unicode="*" horiz-adv-x="1266" d="M295 1323l133 184l252 -280l4 340l225 -47l-141 -308l344 146l47 -219l-328 -13l295 -147l-133 -178l-256 272l2 -340l-223 47l135 314l-342 -150l-49 217l330 17z" />-<glyph glyph-name="plus" unicode="+" d="M678 659v-366h-148v366h-364v146h364v366h148v-366h366v-146h-366z" />-<glyph glyph-name="comma" unicode="," horiz-adv-x="602" d="M428 127q0 -78 -28.5 -148t-85 -127t-141 -98.5t-196.5 -62.5l23 108q211 50 211 170q0 21 -8.5 36.5t-21.5 28t-27.5 24.5t-27.5 27.5t-21.5 36t-8.5 50.5q0 33 12.5 59t33.5 44.5t49 28t59 9.5q37 0 69.5 -12.5t56.5 -36t38 -58t14 -79.5z" />-<glyph glyph-name="hyphen" unicode="-" horiz-adv-x="635" d="M20 451l48 215h532l-47 -215h-533z" />-<glyph glyph-name="period" unicode="." horiz-adv-x="623" d="M434 168q0 -51 -15.5 -86.5t-41 -57t-57.5 -31t-64 -9.5q-74 0 -120 36t-46 111q0 42 12.5 76t36 58t56 37t73.5 13q35 0 65.5 -9t53 -27t35 -46t12.5 -65z" />-<glyph glyph-name="slash" unicode="/" horiz-adv-x="590" d="M-51 -248h-160l873 1804h159z" />-<glyph glyph-name="zero" unicode="0" d="M76 473q0 106 17.5 222t53 228.5t89.5 214.5t127.5 179t166.5 122.5t207 45.5q88 0 164 -27.5t132.5 -85t89 -146t32.5 -210.5q0 -103 -17 -220.5t-51.5 -233t-87.5 -221.5t-125.5 -187t-165.5 -129t-206 -48q-95 0 -173.5 32t-134.5 95t-87 155.5t-31 213.5zM356 385 q0 -137 43 -202.5t115 -65.5q60 0 111 47.5t92 126.5t72.5 180t52.5 208.5t32 212.5t11 191q0 135 -41.5 200t-116.5 65q-58 0 -108 -47t-91 -125t-73 -179t-54 -208t-33.5 -212t-11.5 -192z" />-<glyph glyph-name="one" unicode="1" d="M969 1470l-260 -1222l-8 -36t-5 -36q0 -22 11.5 -35.5t32.5 -20.5t49 -9t61 -2h100l-22 -109h-846l22 109h121q73 0 118 29.5t60 103.5l158 745q13 62 25 113t22 89q11 44 21 79h-8l-88.5 -89.5t-75.5 -67t-67.5 -42t-65.5 -14.5q-44 0 -71.5 39.5t-27.5 111.5 q35 9 72 21t79 30t90.5 43.5t106.5 63.5l166 106h230z" />-<glyph glyph-name="two" unicode="2" d="M-29 0l43 211l478 403q104 88 171 161t106 137.5t54 125t15 125.5q0 42 -9.5 77.5t-28 61t-45.5 40t-61 14.5q-51 0 -90.5 -27t-67.5 -73.5t-44.5 -109.5t-20.5 -136q-51 0 -94 10.5t-74.5 32t-49 55t-17.5 78.5q0 61 31.5 115t92 94.5t150 64t205.5 23.5q105 0 186 -20 t136 -56.5t83.5 -89.5t28.5 -119q0 -68 -28 -133.5t-83.5 -134.5t-138.5 -144.5t-192 -164.5l-492 -396h477q49 0 83.5 14.5t58.5 38t40.5 53.5t30.5 62l15 37h120l-100 -430h-969z" />-<glyph glyph-name="three" unicode="3" d="M457 125q51 0 99 20t85 60.5t59.5 100.5t22.5 140q0 118 -76 182t-213 64h-88l21 127h65q86 0 159 26t126.5 74t83.5 115t30 148q0 38 -8.5 70.5t-25.5 56t-43.5 36.5t-63.5 13q-56 0 -95.5 -25t-66.5 -67t-44 -97.5t-27 -115.5q-49 0 -91.5 6.5t-73.5 21.5t-49 40 t-18 63q0 63 39.5 117.5t107.5 95t159.5 63.5t195.5 23q94 0 171.5 -19.5t133 -57t85.5 -92t30 -124.5q0 -72 -25.5 -141t-77 -126.5t-129 -100t-180.5 -60.5q71 -8 128 -31t96.5 -61t61 -90.5t21.5 -118.5q0 -91 -26 -162t-71 -124t-105 -90.5t-128 -60.5t-140.5 -33.5 t-141.5 -10.5q-120 0 -202.5 22.5t-133.5 58t-73.5 79.5t-22.5 87q0 81 59.5 127.5t165.5 46.5q0 -60 14 -110.5t43 -87t73 -57.5t104 -21z" />-<glyph glyph-name="four" unicode="4" d="M674 874q8 41 19 89.5l23.5 100.5t26.5 104t29 100q-22 -35 -51 -77l-59.5 -84.5l-59.5 -80.5l-51 -65l-363 -445h410zM264 0l21 109h59q30 0 61 3.5t58 18t48 44t32 81.5l26 125h-569l29 127l784 954h287l-203 -946h213l-29 -135h-213l-26 -127q-3 -12 -5.5 -33 t-2.5 -31q0 -26 9.5 -42t27 -24.5t42.5 -11.5t56 -3h37l-23 -109h-719z" />-<glyph glyph-name="five" unicode="5" d="M434 123q67 0 121.5 23t93 69t59.5 115.5t21 161.5q0 66 -19 115t-52 82t-78.5 49t-98.5 16q-37 0 -67 -4t-55 -10.5t-47 -14.5l-42 -16l-86 43l209 710h774l-59 -372h-125q2 26 4 55.5t2 42.5q0 23 -21 37t-79 14h-412l-121 -383q38 11 88 22t125 11q106 0 192 -25 t146.5 -73.5t93.5 -120t33 -164.5q0 -112 -43 -208.5t-123.5 -167t-196.5 -110.5t-263 -40q-107 0 -181 19t-120 51.5t-66.5 76t-20.5 92.5q0 34 11.5 63.5t35 52t59.5 35.5t85 13q1 -44 10.5 -90t34 -84t67.5 -62t111 -24z" />-<glyph glyph-name="six" unicode="6" d="M829 1362q-132 0 -231 -140.5t-162 -426.5q23 18 53 36t67.5 32.5t82 24t96.5 9.5q89 0 154.5 -29t108 -78.5t63 -115.5t20.5 -139q0 -108 -33.5 -208t-102 -177t-172 -123.5t-243.5 -46.5q-88 0 -165.5 30.5t-135.5 92.5t-91.5 154.5t-33.5 217.5q0 128 23.5 252t69 236 t111.5 207t150.5 165t187 109t220.5 39q84 0 147.5 -17t106.5 -46.5t64.5 -67.5t21.5 -80q0 -82 -67 -128t-189 -46q0 50 -3.5 97.5t-16 84.5t-36.5 59.5t-65 22.5zM641 772q-33 0 -65.5 -9.5t-62.5 -24.5t-56 -34t-47 -38q-25 -149 -25 -256q0 -155 47 -226t127 -71 q52 0 95 31t74 85.5t48 128.5t17 160q0 124 -38.5 189t-113.5 65z" />-<glyph glyph-name="seven" unicode="7" d="M213 0l809 1239h-526q-56 0 -92 -26.5t-50 -77.5l-28 -103h-125l84 430h977l-23 -121l-852 -1341h-174z" />-<glyph glyph-name="eight" unicode="8" d="M506 -20q-108 0 -196 26t-151 73t-97.5 111.5t-34.5 141.5q0 86 31 148t85 109t127.5 83t159.5 69q-44 33 -80 73.5t-62 86.5t-40.5 97t-14.5 106q0 77 28.5 146t91 120.5t161 82t238.5 30.5q97 0 173 -22.5t128.5 -62.5t80 -95t27.5 -119q0 -78 -25 -137.5t-70.5 -106.5 t-109 -84.5t-139.5 -71.5q53 -34 98.5 -72.5t78.5 -84t52 -98.5t19 -115q0 -112 -41 -194t-115.5 -135t-177 -79t-225.5 -26zM522 98q54 0 102 16t84 48.5t57 80.5t21 113q0 50 -15 92t-44 79.5t-72.5 71t-101.5 65.5q-117 -48 -183.5 -131t-66.5 -209q0 -106 58 -166 t161 -60zM903 1169q0 42 -9.5 77.5t-29.5 61.5t-50.5 41t-72.5 15q-48 0 -89 -17.5t-71 -50t-47.5 -78.5t-17.5 -104q0 -160 174 -266q59 31 100 66.5t66 75.5t36 85t11 94z" />-<glyph glyph-name="nine" unicode="9" d="M391 109q75 0 135.5 39t111 114t92.5 185.5t81 254.5q-26 -27 -58 -52t-70 -44.5t-83 -31t-98 -11.5q-82 0 -146 25.5t-107.5 70.5t-66 107.5t-22.5 136.5q0 128 46.5 235t125.5 183.5t183.5 119t221.5 42.5q92 0 166.5 -27t127 -78.5t80.5 -126.5t28 -172q0 -24 -2 -60 t-6.5 -78t-12 -88.5t-18.5 -92.5q-22 -99 -55.5 -195t-80 -182.5t-106 -160.5t-133.5 -127.5t-162.5 -84t-193.5 -30.5q-82 0 -141 17t-97 45.5t-56 66.5t-18 81q0 69 42.5 105.5t117.5 36.5q1 -39 11 -79t31 -72t53.5 -52t78.5 -20zM606 711q40 1 74.5 15t64 36.5 t53.5 50.5t42 57q6 25 11.5 55t9.5 60t6.5 58t2.5 49q0 67 -11 115.5t-31.5 79t-48 45t-60.5 14.5q-58 0 -106.5 -38t-83.5 -99.5t-54.5 -139t-19.5 -156.5q0 -100 41 -151t110 -51z" />-<glyph glyph-name="colon" unicode=":" horiz-adv-x="733" d="M434 168q0 -51 -15.5 -86.5t-41 -57t-57.5 -31t-64 -9.5q-74 0 -120 36t-46 111q0 42 12.5 76t36 58t56 37t73.5 13q35 0 65.5 -9t53 -27t35 -46t12.5 -65zM614 961q0 -52 -15.5 -87.5t-41 -57t-57.5 -31t-64 -9.5q-74 0 -120 36t-46 112q0 41 12.5 75t36 58t56.5 37.5 t74 13.5q35 0 65.5 -9t52.5 -27t34.5 -46t12.5 -65z" />-<glyph glyph-name="semicolon" unicode=";" horiz-adv-x="733" d="M428 127q0 -78 -28.5 -148t-85 -127t-141 -98.5t-196.5 -62.5l23 108q211 50 211 170q0 21 -8.5 36.5t-21.5 28t-27.5 24.5t-27.5 27.5t-21.5 36t-8.5 50.5q0 33 12.5 59t33.5 44.5t49 28t59 9.5q37 0 69.5 -12.5t56.5 -36t38 -58t14 -79.5zM614 961q0 -52 -15.5 -87.5 t-41 -57t-57.5 -31t-64 -9.5q-74 0 -120 36t-46 112q0 41 12.5 75t36 58t56.5 37.5t74 13.5q35 0 65.5 -9t52.5 -27t34.5 -46t12.5 -65z" />-<glyph glyph-name="less" unicode="<" d="M166 690v84l878 479v-161l-659 -361l659 -356v-162z" />-<glyph glyph-name="equal" unicode="=" d="M1053 604v-143h-887v143h887zM1053 1001v-143h-887v143h887z" />-<glyph glyph-name="greater" unicode=">" d="M166 213v162l659 356l-659 361v161l878 -479v-84z" />-<glyph glyph-name="question" unicode="?" horiz-adv-x="1096" d="M1122 1143q0 -89 -39 -162.5t-107 -134t-161.5 -109.5t-202.5 -88l-73 -182h-131l-5 254q91 33 164.5 72.5t126 90t81 115t28.5 148.5q0 51 -11.5 90t-31 66t-47 41t-58.5 14q-48 0 -82.5 -25t-57.5 -66.5t-36 -95.5t-20 -112q-252 0 -252 158q0 55 33 103.5t94.5 84.5 t149 57t196.5 21q102 0 184 -20t139 -62t88 -106t31 -152zM602 168q0 -51 -15.5 -86.5t-41 -57t-57.5 -31t-64 -9.5q-74 0 -120 36t-46 111q0 42 12.5 76t36 58t56 37t73.5 13q35 0 65.5 -9t53 -27t35 -46t12.5 -65z" />-<glyph glyph-name="at" unicode="@" horiz-adv-x="1886" d="M1790 803q0 -153 -37 -273t-97 -202.5t-135.5 -126t-152.5 -43.5q-91 0 -154.5 44.5t-97.5 147.5h-12q-21 -40 -48 -75t-61 -61t-77 -41t-97 -15q-65 0 -123 21t-102 64t-70 109t-26 156q0 55 13 119t41 127.5t72 123t105 105t140.5 73t179.5 27.5q71 0 121.5 -18 t82.5 -43l86 43h50l-86 -512q-3 -12 -5.5 -31t-4.5 -39t-3.5 -38.5t-1.5 -30.5q0 -42 9 -69t23 -42.5t32 -21t35 -5.5q40 0 82 34t76 99.5t55.5 162.5t21.5 223q0 140 -42.5 245.5t-118.5 176t-180.5 106t-227.5 35.5q-97 0 -191.5 -27t-180 -79t-157.5 -127.5t-125 -172 t-82.5 -213.5t-29.5 -252q0 -181 54 -309t146.5 -209t216 -118t261.5 -37q83 0 162 15t150 39t132 55t109 63l49 -78q-54 -41 -123 -77t-149 -63t-171 -43t-188 -16q-117 0 -225 21.5t-202.5 65.5t-172.5 109.5t-133.5 153.5t-86 198t-30.5 243q0 137 33.5 262t95 231.5 t149.5 193t197 148t236.5 94.5t269.5 33q177 0 312 -51t226 -139.5t137 -209t46 -259.5zM752 487q0 -102 36 -156.5t95 -54.5q43 0 74.5 21t53.5 55.5t36 77.5t22 88l82 418q-6 14 -17.5 24.5t-26.5 18t-32 11t-33 3.5q-52 0 -93.5 -26t-73.5 -68.5t-55.5 -97t-38.5 -110.5 t-22 -109.5t-7 -94.5z" />-<glyph glyph-name="A" unicode="A" horiz-adv-x="1542" d="M438 481l-100 -184q-20 -37 -29.5 -65t-9.5 -50q0 -38 28 -55.5t87 -17.5h41l-23 -109h-532l22 109h43q28 0 49.5 6.5t41.5 24.5t41.5 49t49.5 79l695 1194h280l172 -1206q5 -32 13.5 -59t22.5 -46.5t35.5 -30.5t53.5 -11h37l-22 -109h-676l22 109h47q66 0 105 20.5 t39 65.5q0 17 -1 33.5t-3 31.5l-31 221h-498zM885 950l-9.5 84.5t-8 76.5t-6 72t-3.5 72q-15 -37 -30 -68.5l-32 -64l-37.5 -69l-47.5 -84.5l-203 -363h414z" />-<glyph glyph-name="B" unicode="B" horiz-adv-x="1376" d="M-25 109h27q32 0 62.5 4t55.5 17t43.5 37t26.5 64l209 986q5 20 9 39.5t4 27.5q0 22 -11 36t-30.5 21.5t-47 10t-59.5 2.5h-26l22 108h635q238 0 358.5 -77.5t120.5 -229.5q0 -84 -25.5 -148t-70.5 -110.5t-106 -76.5t-132 -46l-2 -8q55 -10 101 -34t79 -61.5t51.5 -88.5 t18.5 -115q0 -112 -38 -199t-112 -146.5t-184.5 -90.5t-255.5 -31h-745zM489 125h142q87 0 147.5 23t98.5 68t55.5 111.5t17.5 153.5q0 112 -51 166.5t-158 54.5h-129zM639 829h115q145 0 209.5 75.5t64.5 215.5q0 109 -49 163t-143 54h-91z" />-<glyph glyph-name="C" unicode="C" horiz-adv-x="1368" d="M823 152q71 0 128 16t102 42.5t79 59.5t60 68q12 -9 23.5 -28t11.5 -42q0 -51 -31 -102.5t-91.5 -93t-149.5 -67t-205 -25.5q-147 0 -262.5 36.5t-195 107t-121.5 173t-42 233.5q0 123 29 242t84 226t134.5 197t180 156t220.5 103t257 37q102 0 178.5 -17.5t127 -49 t75.5 -77t25 -100.5q0 -45 -22 -79t-60.5 -57.5t-91.5 -35.5t-115 -12q0 43 -6 95t-24.5 97t-52.5 75t-91 30q-81 0 -149.5 -37t-125 -100.5t-100 -147.5t-73 -177.5t-45 -191t-15.5 -188.5q0 -175 89 -270.5t265 -95.5z" />-<glyph glyph-name="D" unicode="D" horiz-adv-x="1573" d="M-27 109h29q32 0 62.5 4t55.5 17t43.5 37t26.5 64l213 1006q4 11 6.5 25t2.5 22q0 22 -11 36t-30.5 21.5t-47 10t-59.5 2.5h-26l22 108h655q147 0 262.5 -36t195 -106.5t121.5 -174.5t42 -240q0 -122 -25.5 -236.5t-75 -215t-122.5 -184t-169 -143.5t-214.5 -93 t-257.5 -33h-721zM584 127q144 0 257 58.5t191 164t119 251.5t41 321q0 107 -25 185.5t-71.5 129.5t-113 75.5t-148.5 24.5h-86l-256 -1210h92z" />-<glyph glyph-name="E" unicode="E" horiz-adv-x="1296" d="M-25 109h27q32 0 62.5 4t55.5 17t43.5 37t26.5 64l213 1006q4 11 6.5 25t2.5 22q0 22 -11 36t-30.5 21.5t-47 10t-59.5 2.5h-26l22 108h1112l-72 -381h-145q1 7 2.5 25l3 37.5t3 36t1.5 20.5q0 28 -8 53t-25 44t-44.5 29.5t-67.5 10.5h-272l-109 -512h465l-27 -123h-465 l-123 -577h324q48 0 86 13.5t66 36.5t47.5 53.5t31.5 64.5l33 88h146l-95 -381h-1175z" />-<glyph glyph-name="F" unicode="F" horiz-adv-x="1272" d="M514 242q-5 -23 -7.5 -39.5t-2.5 -24.5q0 -22 11 -35.5t31 -21t47.5 -10t59.5 -2.5h27l-23 -109h-704l22 109h27q32 0 62.5 4t55.5 17t43.5 37t26.5 64l209 983q13 53 13 70q0 22 -11 36t-30.5 21.5t-47 10t-59.5 2.5h-26l22 108h1126l-71 -381h-146q2 17 4 36t3.5 36.5 l2.5 30t1 16.5q0 29 -8 54t-26 43.5t-47.5 29t-72.5 10.5h-278l-119 -559h485l-24 -121h-486z" />-<glyph glyph-name="G" unicode="G" horiz-adv-x="1630" d="M1544 590h-2q-34 0 -65.5 -4.5t-57 -17.5t-44.5 -38t-27 -65l-78 -391q-62 -24 -121.5 -41.5t-121 -29.5t-126.5 -17.5t-137 -5.5q-158 0 -276.5 38t-198.5 109t-120 172.5t-40 228.5q0 132 31 254.5t89 228.5t142 193t189 149t230.5 96t266.5 34q118 0 205 -21t145 -55 t86.5 -78t28.5 -90q0 -40 -23.5 -74.5t-63.5 -60t-94.5 -40.5t-115.5 -15q0 64 -13.5 119.5t-42 96.5t-72 64t-103.5 23q-78 0 -149 -32t-132 -89t-110.5 -136t-84.5 -173t-54 -200.5t-19 -217.5q0 -86 17 -156.5t54.5 -121.5t96 -79t141.5 -28q47 0 96 8t86 20l68 304 q12 54 12 69q0 22 -11 36t-30.5 21.5t-46.5 10t-59 2.5h-27l23 108h682z" />-<glyph glyph-name="H" unicode="H" horiz-adv-x="1677" d="M807 0l22 109h27q33 0 64 4t57 17.5t44.5 37.5t27.5 65l98 467h-535l-100 -475q-3 -11 -5.5 -25t-2.5 -22q0 -22 11 -35.5t31 -21t47.5 -10t59.5 -2.5h27l-23 -109h-704l22 109h27q32 0 62.5 4t55.5 17t43.5 37t26.5 64l209 983q13 53 13 70q0 22 -11 36t-30.5 21.5 t-47 10t-59.5 2.5h-26l22 108h707l-23 -108h-26q-34 0 -65.5 -4.5t-57.5 -17.5t-45 -38t-27 -65l-84 -404h535l86 398q5 21 7.5 37t2.5 24q0 22 -11.5 36t-31.5 21.5t-47.5 10t-59.5 2.5h-26l22 108h707l-23 -108h-26q-33 0 -64 -4.5t-57 -17.5t-44.5 -38t-27.5 -65 l-211 -1004q-3 -11 -5.5 -25t-2.5 -22q0 -22 11 -35.5t30.5 -21t46.5 -10t59 -2.5h27l-23 -109h-706z" />-<glyph glyph-name="I" unicode="I" horiz-adv-x="821" d="M-47 0l22 109h27q32 0 62.5 4t55.5 17t43.5 37t26.5 64l213 1006q4 11 6.5 25t2.5 22q0 22 -11 36t-30.5 21.5t-47 10t-59.5 2.5h-26l22 108h707l-23 -108h-26q-34 0 -65.5 -4.5t-57.5 -17.5t-45 -38t-27 -65l-211 -1004q-2 -11 -5 -25t-3 -22q0 -22 11 -35.5t31 -21 t47.5 -10t59.5 -2.5h27l-23 -109h-704z" />-<glyph glyph-name="J" unicode="J" horiz-adv-x="821" d="M463 -6q-26 -125 -87 -216.5t-144 -151.5t-183 -89t-205 -29q-55 0 -92.5 6t-75.5 17l29 113q13 -5 29.5 -8.5t34.5 -6t36 -3.5t33 -1q53 0 98.5 19t83 62.5t67.5 114t52 173.5l264 1243q4 11 6.5 25t2.5 22q0 22 -11 36t-30.5 21.5t-47 10t-59.5 2.5h-26l22 108h707 l-23 -108h-26q-34 0 -65.5 -4.5t-57.5 -17.5t-45 -38t-27 -65z" />-<glyph glyph-name="K" unicode="K" horiz-adv-x="1518" d="M1036 1110q42 37 68.5 66.5t42 53t21.5 41.5t6 32q0 26 -20 39.5t-71 13.5l23 106h553l-23 -106q-41 0 -79 -10t-77 -31t-81.5 -53t-91.5 -76l-332 -293l239 -576q25 -57 45.5 -97t42 -64.5t47 -35.5t60.5 -11h6l-20 -109h-107q-102 0 -169.5 12.5t-114 44.5t-79 86.5 t-65.5 139.5l-151 391l-121 -86l-76 -363q-2 -11 -5 -25t-3 -22q0 -22 11 -35.5t31 -21t47.5 -10t59.5 -2.5h27l-23 -109h-704l22 109h27q32 0 62.5 4t55.5 17t43.5 37t26.5 64l209 983q13 53 13 70q0 22 -11 36t-30.5 21.5t-47 10t-59.5 2.5h-26l22 108h707l-23 -108h-26 q-34 0 -65.5 -4.5t-57.5 -17.5t-45 -38t-27 -65l-102 -486z" />-<glyph glyph-name="L" unicode="L" horiz-adv-x="1339" d="M834 125q56 0 97 14t71.5 41.5t53.5 69.5t42 98l37 109h131l-113 -457h-1200l22 109h27q32 0 62.5 4t55.5 17t43.5 37t26.5 64l209 983q13 53 13 70q0 22 -11 36t-30.5 21.5t-47 10t-59.5 2.5h-26l22 108h707l-23 -108h-26q-34 0 -65.5 -4.5t-57.5 -17.5t-45 -38t-27 -65 l-231 -1104h342z" />-<glyph glyph-name="M" unicode="M" horiz-adv-x="1950" d="M2097 1462l-22 -108h-27q-33 0 -64 -4.5t-57 -17.5t-44.5 -38t-27.5 -65l-210 -1004q-4 -11 -6.5 -25t-2.5 -22q0 -22 11 -35.5t30.5 -21t47 -10t59.5 -2.5h26l-22 -109h-698l22 109h27q33 0 64 4t56.5 17.5t44.5 37.5t27 65l217 1027l-678 -1260h-141l-166 1260 l-217 -1018q-5 -23 -7.5 -39.5t-2.5 -24.5q0 -22 11 -35.5t31 -21t47.5 -10t59.5 -2.5h7l-23 -109h-516l22 109h27q32 0 62.5 4t55.5 17t43.5 37t26.5 64l209 983q13 53 13 70q0 22 -11 36t-30.5 21.5t-47 10t-59.5 2.5h-26l22 108h571l138 -995l536 995h592z" />-<glyph glyph-name="N" unicode="N" horiz-adv-x="1614" d="M1094 0l-555 1159l-193 -917q-5 -23 -7.5 -39.5t-2.5 -24.5q0 -22 11 -35.5t31 -21t47.5 -10t59.5 -2.5h7l-23 -109h-516l22 109h27q32 0 62.5 4t55.5 17t43.5 37t26.5 64l209 983q13 53 13 70q0 22 -11 36t-30.5 21.5t-47 10t-59.5 2.5h-26l22 108h455l499 -1050 l172 809q11 47 11 63q0 22 -11.5 36t-31.5 21.5t-47.5 10t-59.5 2.5h-6l23 108h516l-23 -108h-26q-32 0 -62.5 -4.5t-55.5 -17.5t-43.5 -37t-27.5 -64l-260 -1231h-188z" />-<glyph glyph-name="O" unicode="O" horiz-adv-x="1622" d="M129 545q0 98 24.5 206t73.5 212.5t122 199t169.5 166t217.5 114t265 42.5q113 0 215.5 -33t180.5 -102t124 -175t46 -253q0 -94 -23.5 -201t-71 -212t-119 -201t-168 -169.5t-218 -117.5t-267.5 -44q-121 0 -225.5 36.5t-181 108.5t-120.5 178t-44 245zM463 496 q0 -101 21.5 -173.5t59 -119t88.5 -68.5t109 -22q85 0 154.5 41t124.5 109.5t95.5 157t67 183.5t39.5 189.5t13 175.5q0 100 -21 172t-57.5 119t-87.5 69.5t-111 22.5q-85 0 -154.5 -41t-124.5 -109.5t-96 -157t-67.5 -184t-39.5 -190t-13 -174.5z" />-<glyph glyph-name="P" unicode="P" horiz-adv-x="1358" d="M610 678h86q93 0 157.5 28t104.5 78.5t58 120t18 152.5q0 77 -17.5 131t-48.5 88t-74.5 49.5t-94.5 15.5h-45zM512 225q-3 -11 -5.5 -25t-2.5 -22q0 -22 11 -35.5t31 -21t47.5 -10t59.5 -2.5h68l-23 -109h-745l22 109h27q32 0 62.5 4t55.5 17t43.5 37t26.5 64l209 983 q13 53 13 70q0 22 -11 36t-30.5 21.5t-47 10t-59.5 2.5h-26l22 108h586q120 0 217 -25.5t164.5 -76t104 -125t36.5 -172.5q0 -52 -10 -110t-35.5 -114t-70.5 -106.5t-114.5 -89t-167 -61.5t-229.5 -23h-155z" />-<glyph glyph-name="Q" unicode="Q" horiz-adv-x="1612" d="M127 545q0 98 24.5 206t73.5 212.5t122 199t169.5 166t217.5 114t265 42.5q113 0 215.5 -33t180.5 -102t124 -175t46 -253q0 -84 -18.5 -177t-55 -186.5t-92.5 -182t-130.5 -163t-168.5 -130.5t-207 -83q3 -113 21 -185.5t49 -114t72.5 -57.5t92.5 -16h33l-26 -119h-148 q-92 0 -171 27.5t-137.5 86t-92.5 150.5t-37 222q-93 19 -170.5 63.5t-133.5 114t-87 163t-31 210.5zM461 496q0 -101 21.5 -173.5t59 -119t88.5 -68.5t109 -22q85 0 154.5 41t124.5 109.5t95.5 157t67 183.5t39.5 189.5t13 175.5q0 100 -21 172t-57.5 119t-87.5 69.5 t-111 22.5q-85 0 -154.5 -41t-124.5 -109.5t-96 -157t-67.5 -184t-39.5 -190t-13 -174.5zM700 -23z" />-<glyph glyph-name="R" unicode="R" horiz-adv-x="1448" d="M621 754h127q65 0 121.5 19t98 59.5t65.5 105t24 156.5q0 69 -12 116t-37 76.5t-63 42t-89 12.5h-104zM918 1462q245 0 366 -85t121 -257q0 -104 -33.5 -177t-86 -122.5t-117.5 -78.5t-128 -44l142 -381q21 -58 41 -98t42.5 -64.5t49 -35t61.5 -10.5h8l-22 -109h-21 q-107 0 -185.5 9t-134.5 34.5t-93 71t-60 118.5l-129 402h-141l-80 -375q-8 -37 -11 -55.5t-3 -26.5q0 -22 11 -35.5t31 -21t47.5 -10t59.5 -2.5h68l-23 -109h-745l22 109h27q32 0 62.5 4t55.5 17t43.5 37t26.5 64l209 983q13 53 13 70q0 22 -11 36t-30.5 21.5t-47 10 t-59.5 2.5h-26l22 108h658z" />-<glyph glyph-name="S" unicode="S" horiz-adv-x="1200" d="M535 -20q-127 0 -222 23t-158 63t-94.5 94.5t-31.5 118.5q0 48 19.5 84t55 60t84 36t107.5 12q0 -94 20 -163t56 -114.5t86 -67.5t110 -22q58 0 103 16.5t76.5 45t48 67t16.5 82.5q0 43 -10.5 79t-38.5 70t-77 69t-126 77q-80 44 -140.5 90.5t-101 100.5t-61 118.5 t-20.5 145.5q0 92 37 169t106 132.5t167 86t220 30.5q114 0 199 -18t141.5 -49t84.5 -73t28 -90q0 -83 -63 -130.5t-206 -47.5q0 46 -9 96t-32 91.5t-62 68.5t-99 27q-41 0 -81.5 -13t-72.5 -39t-52 -65.5t-20 -93.5t17 -92t51 -70.5t85 -63t120 -67.5q155 -84 229 -185.5 t74 -232.5q0 -109 -39.5 -194t-113 -143t-177.5 -88.5t-233 -30.5z" />-<glyph glyph-name="T" unicode="T" horiz-adv-x="1337" d="M213 0l23 109h26q32 0 62.5 4t55.5 17t43.5 37t27.5 64l231 1106h-164q-48 0 -81.5 -12t-58 -35.5t-41.5 -57.5t-32 -79l-29 -88h-137l94 397h1274l-71 -397h-138q1 7 3.5 31.5l5.5 52.5l5.5 51.5t2.5 28.5q0 54 -32.5 81t-111.5 27h-163l-236 -1112q-2 -11 -5 -25 t-3 -22q0 -22 11 -35.5t31 -21t47.5 -10t59.5 -2.5h27l-22 -109h-705z" />-<glyph glyph-name="U" unicode="U" horiz-adv-x="1530" d="M772 125q93 0 157 27t107 74.5t69 113t42 143.5l158 740q3 17 5.5 35t2.5 26q0 22 -11 36t-30.5 21.5t-47 10t-59.5 2.5h-26l22 108h539l-23 -108h-26q-33 0 -64 -4.5t-57 -17.5t-44.5 -38t-27.5 -65l-164 -778q-24 -115 -71.5 -203t-121 -147.5t-173.5 -90t-230 -30.5 q-120 0 -212 23.5t-155 69t-95.5 111.5t-32.5 150q0 16 2 40t5 49.5t7 50t8 42.5l150 698q12 55 12 70q0 22 -11 36t-30.5 21.5t-46.5 10t-59 2.5h-27l23 108h708l-22 -108h-27q-34 0 -65.5 -4.5t-57.5 -17t-44.5 -36.5t-27.5 -63l-155 -721l-8.5 -40t-8 -45.5t-6 -47.5 t-2.5 -45q0 -103 64 -156t188 -53z" />-<glyph glyph-name="V" unicode="V" horiz-adv-x="1430" d="M659 1272q0 -17 1.5 -36.5t3.5 -33.5l65 -540q4 -36 7.5 -83.5t5 -96t2.5 -92t1 -71.5l25.5 59l26 60l23.5 54t20 40l368 662q20 35 28.5 61t8.5 48q0 30 -30 40.5t-89 10.5h-47l21 108h534l-22 -108h-49q-30 0 -51.5 -5t-40.5 -19.5t-38 -41.5t-44 -71l-682 -1217h-205 l-172 1255q-5 31 -16.5 50.5t-28 30.5t-38 14.5t-46.5 3.5h-27l23 108h675l-22 -108h-47q-66 0 -105 -16.5t-39 -65.5z" />-<glyph glyph-name="W" unicode="W" horiz-adv-x="2185" d="M2367 1354h-49q-30 0 -53 -6t-42.5 -22.5t-39 -46t-41.5 -75.5l-573 -1204h-234l-141 977l-498 -977h-254l-110 1255q-3 31 -14 50.5t-28.5 30.5t-39.5 14.5t-47 3.5h-27l23 108h669l-22 -108h-47q-66 0 -105 -16.5t-39 -65.5q0 -17 1 -39t3 -51l39 -510q3 -27 6 -71.5 t5.5 -93.5t4 -96.5t1.5 -80.5q21 60 49 120.5t59 120.5l457 877h150l116 -782q14 -97 24.5 -185.5t10.5 -154.5l29 79l37.5 95.5l43.5 102.5l46 102l227 489q14 29 22.5 58t8.5 51q0 30 -27 40.5t-86 10.5h-47l20 108h535z" />-<glyph glyph-name="X" unicode="X" horiz-adv-x="1499" d="M1610 1354h-19q-30 0 -55 -7t-51.5 -24t-56 -45t-68.5 -70l-385 -405l252 -586q28 -63 65.5 -85.5t75.5 -22.5h27l-23 -109h-676l23 109h31q61 0 88.5 18.5t27.5 52.5q0 29 -10 64.5t-29 77.5l-125 282l-286 -297q-37 -38 -59.5 -73t-22.5 -64q0 -15 5 -26t17.5 -19 t34 -12t55.5 -4h7l-23 -109h-516l23 109h20q31 0 57.5 7.5t54 24.5t59 45t71.5 70l452 467l-229 526q-15 34 -30.5 55t-34.5 32t-42.5 14.5t-52.5 3.5h-18l22 108h678l-22 -108h-48q-51 0 -73.5 -15t-22.5 -43q0 -20 9.5 -49.5t27.5 -72.5l109 -254l241 258q28 29 50 63 t22 62q0 26 -25 38.5t-73 12.5h-6l22 108h477z" />-<glyph glyph-name="Y" unicode="Y" horiz-adv-x="1419" d="M227 0l23 109h55q32 0 62 4.5t55 18t43 37.5t27 64l59 285l-221 727q-9 30 -18.5 51t-22.5 33.5t-32.5 18.5t-49.5 6h-27l23 108h665l-22 -108h-92q-47 0 -66.5 -16t-19.5 -44q0 -18 6 -52t18 -81l74 -289l15 -64t14.5 -68t11 -66.5t6.5 -57.5q19 41 58 104t92 138 l202 289q25 35 43.5 76t18.5 75q0 28 -28.5 42t-96.5 14h-43l24 108h537l-23 -108h-38q-23 0 -42.5 -6t-39.5 -21.5t-42.5 -41t-50.5 -64.5l-507 -705l-62 -291q-2 -11 -5 -25t-3 -22q0 -22 11 -35.5t31 -21t47.5 -10t59.5 -2.5h50l-23 -109h-756z" />-<glyph glyph-name="Z" unicode="Z" horiz-adv-x="1364" d="M1423 1358l-1011 -1233h422q46 0 87 12t75 38t61 68.5t45 104.5l22 74h148l-98 -422h-1178l18 102l1010 1235h-356q-93 0 -149.5 -42t-82.5 -136l-35 -119h-147l100 422h1090z" />-<glyph glyph-name="bracketleft" unicode="[" horiz-adv-x="848" d="M43 -262l387 1818h567l-22 -108h-88q-34 0 -65.5 -5t-58.5 -20.5t-47 -44.5t-30 -78l-276 -1306q-3 -16 -5 -28t-2 -23q0 -32 11 -51t30.5 -29t45.5 -13.5t55 -3.5h88l-23 -108h-567z" />-<glyph glyph-name="backslash" unicode="\" horiz-adv-x="590" d="M221 1556h144l110 -1804h-143z" />-<glyph glyph-name="bracketright" unicode="]" horiz-adv-x="848" d="M844 1556l-387 -1818h-566l23 108h88q34 0 65.5 5t57.5 20.5t46 45t30 77.5l276 1306q6 31 6 52q0 31 -11 50t-30 29.5t-45 13.5t-55 3h-88l22 108h568z" />-<glyph glyph-name="asciicircum" unicode="^" d="M569 1462h88l435 -925h-160l-320 702l-315 -702h-160z" />-<glyph glyph-name="underscore" unicode="_" horiz-adv-x="940" d="M772 -291h-958l26 121h959z" />-<glyph glyph-name="grave" unicode="`" horiz-adv-x="1182" d="M831 1241q-44 26 -93.5 66t-95.5 83.5t-84.5 85.5t-59.5 72l6 21h301q12 -37 31.5 -80t40.5 -84t42.5 -77t38.5 -60l-6 -27h-121z" />-<glyph glyph-name="a" unicode="a" horiz-adv-x="1362" d="M969 -20q-91 0 -146 41.5t-55 121.5q0 32 3 62.5t11 70.5h-14q-38 -71 -75.5 -126.5t-83.5 -93t-102.5 -57t-131.5 -19.5q-73 0 -128 28.5t-91.5 78.5t-55 118.5t-18.5 148.5t18.5 167.5t55.5 172.5t91.5 162t126 135t159.5 92.5t192 34.5q40 0 81.5 -6.5t79.5 -16.5 t71.5 -22.5t56.5 -25.5l108 49h109l-135 -639q-4 -17 -10 -45t-11.5 -59.5t-9.5 -61t-4 -49.5q0 -51 20 -76t52 -25q29 0 55.5 12.5t58.5 30.5l45 -82q-26 -20 -59 -41.5t-73.5 -39.5t-88 -29.5t-102.5 -11.5zM393 352q0 -59 8.5 -99t23 -64.5t35 -35t44.5 -10.5q45 0 93 39 t90 102.5t75.5 143.5t50.5 162l72 340q-20 29 -55.5 43t-75.5 14q-60 0 -109.5 -31t-89.5 -83t-70.5 -118t-51 -137t-30.5 -140t-10 -126z" />-<glyph glyph-name="b" unicode="b" horiz-adv-x="1276" d="M315 1274l6 27t5 26.5t3 21.5t1 13q0 28 -6 45t-19 26t-34 12t-50 3h-47l19 108h497l-82 -389q-4 -19 -12.5 -48l-19 -63t-23 -69.5l-23.5 -67.5l-20 -58l-14 -40h10q37 75 77 130.5t87 92.5t102 55.5t123 18.5q65 0 120 -24t94.5 -70.5t62 -116t22.5 -159.5 q0 -83 -20 -172t-59 -174.5t-95.5 -162t-130 -134t-162.5 -91.5t-192 -34q-42 0 -81 6t-72.5 16t-61 23t-46.5 28l-116 -59h-115zM883 733q0 61 -8.5 103t-23.5 68t-35 38t-42 12q-38 0 -73 -22.5t-66.5 -58.5t-58 -80.5t-47 -89t-34.5 -84t-20 -64.5l-84 -387 q22 -25 56 -41t77 -16q60 0 110 31.5t90 83t69.5 117.5t49.5 135t30 135.5t10 119.5z" />-<glyph glyph-name="c" unicode="c" horiz-adv-x="1081" d="M621 135q54 0 97.5 14.5t78.5 38.5t62 54t48 61q13 -12 22 -35.5t9 -54.5q0 -40 -23.5 -81.5t-73 -75t-125.5 -55t-181 -21.5q-93 0 -175.5 26t-144 79t-97.5 132t-36 186q0 76 19 158.5t57 162t94.5 151t131.5 125.5t168 86t204 32q85 0 146.5 -17.5t101 -47t58.5 -69 t19 -83.5q0 -32 -13.5 -62.5t-43.5 -55t-77.5 -39.5t-115.5 -15q0 54 -9.5 102.5t-28.5 85t-46.5 57.5t-64.5 21q-44 0 -82.5 -29.5t-70.5 -78t-56.5 -111t-42 -129t-26.5 -131t-9 -117.5q0 -125 57 -194.5t169 -69.5z" />-<glyph glyph-name="d" unicode="d" horiz-adv-x="1362" d="M393 352q0 -58 8.5 -98t23 -64.5t35 -35.5t44.5 -11q45 0 92.5 39t89.5 102t75.5 143t51.5 163l72 340q-22 28 -60 42.5t-75 14.5q-58 0 -107 -31.5t-88.5 -83t-70 -118t-50.5 -137t-30.5 -140t-10.5 -125.5zM1092 440l-8.5 -42.5t-10.5 -57.5t-8.5 -59t-3.5 -48 q0 -48 22 -70t50 -22q26 0 57.5 14t56.5 29l45 -80q-27 -22 -61 -44t-74.5 -40t-87.5 -29t-100 -11q-45 0 -82 10.5t-63.5 30.5t-41 50.5t-14.5 71.5q0 19 1 34t2.5 29.5t4 31t6.5 38.5h-12q-38 -79 -80 -135t-89.5 -92t-102 -52.5t-117.5 -16.5q-79 0 -135.5 30t-93 81 t-53.5 119t-17 144q0 80 18.5 167.5t55 172.5t90 162t123.5 135t155.5 92.5t186.5 34.5q60 0 112 -13t92 -36q3 13 6 30.5t6.5 35t7 33l5.5 24.5l18 82l6 27t5 26.5t3 21.5t1 13q0 28 -6 45t-19 26t-34 12t-50 3h-47l19 108h493z" />-<glyph glyph-name="e" unicode="e" horiz-adv-x="1096" d="M618 135q50 0 95 14.5t83.5 38t69 53t52.5 60.5q12 -15 19 -38.5t7 -49.5q0 -37 -24 -78t-73.5 -75.5t-126 -57t-181.5 -22.5q-94 0 -177 26t-145.5 78.5t-98.5 132t-36 186.5q0 76 20 159t59.5 162.5t98 151t135.5 125.5t172.5 85.5t208.5 31.5q147 0 228 -63.5 t81 -177.5q0 -83 -46 -159t-127 -133.5t-193 -91.5t-244 -34h-37t-39 2q-3 -20 -3.5 -40.5t-0.5 -37.5q0 -119 55.5 -183.5t167.5 -64.5zM690 995q-50 0 -95 -37.5t-81 -97.5t-62 -134.5t-38 -147.5h18q76 0 141.5 20t114 57.5t76 89.5t27.5 117q0 63 -26.5 98t-74.5 35z " />-<glyph glyph-name="f" unicode="f" horiz-adv-x="827" d="M870 958h-260l-194 -915q-23 -109 -59.5 -191t-82 -141.5t-98.5 -98.5t-109 -62t-114 -32.5t-113 -9.5q-15 0 -33 1.5t-35 4t-32 5.5t-25 6l25 119q17 -5 44 -10t50 -5q50 0 88.5 17t69 57.5t55 108.5t45.5 170l207 976h-186l20 97l197 43l16 76q24 101 64 176.5t99 126 t138.5 75.5t183.5 25q79 0 139.5 -14t101 -38t61 -56.5t20.5 -69.5q0 -68 -60 -106t-175 -38q0 39 -4.5 75.5t-15.5 64.5t-29 44.5t-46 16.5q-65 0 -99.5 -56t-55.5 -171l-27 -131h258z" />-<glyph glyph-name="g" unicode="g" horiz-adv-x="1188" d="M182 674q0 71 31.5 149.5t95 144t159.5 108t226 42.5q68 0 127.5 -15.5t104.5 -49.5q23 29 49 54.5t55 44t62 29.5t71 11q63 0 92 -30.5t29 -78.5q0 -25 -6.5 -51t-21.5 -47t-40.5 -34t-62.5 -13q0 42 -18.5 65t-53.5 23q-28 0 -51 -10t-43 -27q22 -29 36.5 -73t14.5 -91 q0 -45 -13 -94.5t-39 -98t-66 -93t-95 -78.5t-125 -54t-155 -20q-9 0 -23.5 0.5l-29.5 1t-29 1.5t-23 3q-48 -21 -80 -48.5t-32 -59.5q0 -17 8.5 -28t23 -17.5t34.5 -10.5t44 -6l230 -22q150 -14 233.5 -81.5t83.5 -197.5q0 -93 -42.5 -169.5t-124 -130.5t-200 -84 t-270.5 -30q-125 0 -213.5 18.5t-144.5 52.5t-82.5 82.5t-26.5 107.5q0 54 27.5 98t73.5 76.5t105 54.5t122 33q-20 10 -39 27.5t-33.5 40t-23.5 48t-9 52.5q0 32 13 60t40 55t69 54.5t100 59.5q-68 32 -106 95t-38 151zM721 -174q0 58 -44.5 89t-150.5 40l-182 16 q-45 -6 -84.5 -23t-69.5 -42t-47.5 -56.5t-17.5 -66.5q0 -32 11.5 -59.5t40 -48t78 -32.5t124.5 -12q90 0 154.5 16t106 43t61.5 62.5t20 73.5zM551 512q36 0 66 17t54 45t42 63.5t29.5 73.5t17.5 74.5t6 66.5q0 72 -28.5 105.5t-82.5 33.5q-53 0 -93 -35t-67 -87 t-41 -110.5t-14 -105.5q0 -75 29 -108t82 -33z" />-<glyph glyph-name="h" unicode="h" horiz-adv-x="1343" d="M754 190q0 44 7.5 105t24.5 139l35 162q5 20 11.5 49t12 60.5t9.5 63t4 56.5q0 19 -3.5 40t-12.5 38t-26 28t-44 11q-35 0 -69.5 -23.5t-66.5 -62t-60.5 -88t-52 -101.5t-40.5 -102t-26 -90l-107 -475h-307l270 1274l6 27t5 26.5t3 21.5t1 13q0 28 -6 45t-19 26t-34 12 t-50 3h-43l19 108h495l-96 -450l-17.5 -70l-18.5 -74q-11 -41 -21 -83h12q39 61 80.5 106t89.5 74.5t104.5 44t124.5 14.5q65 0 110 -19t72.5 -51.5t39.5 -75t12 -89.5q0 -26 -4 -55.5t-9.5 -59.5t-12 -60t-11.5 -57l-47 -219q-13 -56 -21 -104.5t-8 -91.5q0 -45 19 -69 t53 -24q32 0 58.5 12t62.5 35l49 -69q-25 -26 -56 -51.5t-69.5 -45.5t-85.5 -32t-103 -12q-117 0 -180 55.5t-63 154.5z" />-<glyph glyph-name="i" unicode="i" horiz-adv-x="725" d="M621 1098l-142 -666q-12 -56 -20 -104.5t-8 -91.5q0 -45 18.5 -69t52.5 -24q32 0 59 12t62 35l49 -69q-24 -26 -55 -51.5t-70 -45.5t-85.5 -32t-102.5 -12q-118 0 -181 55.5t-63 154.5q0 44 8 105t25 139l80 381q3 13 5.5 27t4.5 26.5t3 21.5t1 13q0 28 -6 45t-19 26 t-33.5 12t-49.5 3h-37l18 109h486zM479 1282q-34 0 -63 7.5t-50.5 23.5t-34 40t-12.5 56q0 45 15.5 76.5t41.5 52t61 30t73 9.5q33 0 62.5 -7t52 -23t36 -40.5t13.5 -58.5q0 -42 -16.5 -73.5t-44 -52t-62.5 -30.5t-72 -10z" />-<glyph glyph-name="j" unicode="j" horiz-adv-x="692" d="M479 1282q-34 0 -63 7.5t-50.5 23.5t-34 40t-12.5 56q0 45 15.5 76.5t41.5 52t61 30t73 9.5q33 0 62.5 -7t52 -23t36 -40.5t13.5 -58.5q0 -42 -16.5 -73.5t-44 -52t-62.5 -30.5t-72 -10zM621 1098l-238 -1114q-28 -137 -75 -228.5t-108.5 -146.5t-133.5 -78t-150 -23 q-65 0 -112 7t-91 18l25 107q15 -5 42.5 -10t49.5 -5q48 0 83.5 17.5t63.5 59.5t50.5 111t44.5 171l176 831q3 13 5.5 27t4.5 26.5t3 21.5t1 13q0 28 -6 45t-19 26t-33.5 12t-49.5 3h-37l18 109h486z" />-<glyph glyph-name="k" unicode="k" horiz-adv-x="1223" d="M438 408l-88 -408h-307l270 1274l6 27t5 26.5t3 21.5t1 13q0 28 -6 45t-19 26t-34 12t-50 3h-43l19 108h495l-108 -499l-31.5 -144.5t-27 -120.5l-28 -114l-32.5 -125l319 289q25 22 40 39t23.5 29.5t11.5 22t3 17.5q0 22 -25 33.5t-71 11.5l20 103h516l-20 -103 q-71 0 -149 -41.5t-177 -132.5l-163 -151l153 -334q33 -72 64 -116t58 -69t50 -33.5t41 -8.5h8l-22 -109h-105q-75 0 -134 10.5t-104 34t-77 60t-53 88.5l-121 286z" />-<glyph glyph-name="l" unicode="l" horiz-adv-x="725" d="M489 143q32 0 59 12t62 35l49 -69q-24 -26 -55 -51.5t-69.5 -45.5t-85.5 -32t-103 -12q-118 0 -181 55.5t-63 154.5q0 88 33 244l178 840l6 27t5 26.5t3 21.5t1 13q0 28 -6 45t-19 26t-34 12t-50 3h-43l19 108h495l-244 -1124q-12 -56 -20 -104.5t-8 -91.5 q0 -45 18.5 -69t52.5 -24z" />-<glyph glyph-name="m" unicode="m" horiz-adv-x="1985" d="M1638 -14q-117 0 -180 56t-63 155q0 43 7.5 104t24.5 139l35 162q5 20 11.5 49t12 60.5t9.5 63t4 56.5q0 19 -3.5 40t-12.5 38t-25 28t-41 11q-33 0 -65.5 -23t-63.5 -60.5t-58.5 -86t-50 -99.5t-38.5 -100.5t-23 -89.5l-16 -86q-4 -21 -9.5 -57.5l-11.5 -78.5l-12 -86 t-10.5 -81.5t-7 -64.5t-2.5 -35h-318q0 12 3.5 42t9.5 70.5t14 87.5l16 94t16 89.5t15 73.5l31 143q4 19 9.5 48l11 60.5t9.5 63.5t4 57q0 19 -2 39t-8.5 36.5t-20 27t-36.5 10.5q-34 0 -67.5 -21t-64.5 -56t-59 -80t-50.5 -92.5t-39 -94t-24.5 -84.5l-115 -514h-307 l172 815q3 13 5.5 27t4.5 26.5t3 21.5t1 13q0 28 -6 45t-19 26t-33.5 12t-49.5 3h-37l18 109h475l-39 -209h15q40 62 82.5 105.5t89.5 71t99.5 40t113.5 12.5q64 0 107 -18t69 -49t37.5 -71t11.5 -85v-16h12q38 61 78.5 107t87.5 76.5t102 46t121 15.5q63 0 106 -19 t70 -51.5t38.5 -75t11.5 -89.5q0 -26 -4 -55.5t-9.5 -59.5t-12 -60t-11.5 -57l-47 -219q-13 -55 -21 -104t-8 -92q0 -45 19 -68.5t53 -23.5q32 0 58.5 12t62.5 35l49 -70q-25 -26 -56 -51.5t-69.5 -45.5t-85.5 -32t-103 -12z" />-<glyph glyph-name="n" unicode="n" horiz-adv-x="1376" d="M786 190q0 88 33 244l33 162l10 49l12 60.5t10.5 63t4.5 56.5q0 19 -3.5 40t-13 38t-26 28t-43.5 11q-35 0 -69.5 -23.5t-66.5 -62t-61 -88t-52.5 -101.5t-40.5 -102t-26 -90l-104 -475h-307l172 815q3 13 5.5 27t4.5 26.5t3 21.5t1 13q0 28 -6 45t-19 26t-33.5 12 t-49.5 3h-37l18 109h455l-23 -219h13q39 61 80.5 106t89.5 74.5t104.5 44t124.5 14.5q65 0 109.5 -19t72 -51.5t39.5 -75t12 -89.5q0 -26 -3.5 -55.5t-9 -59.5t-12 -60t-11.5 -57l-46 -219q-12 -56 -20 -104.5t-8 -91.5q0 -45 18.5 -69t53.5 -24q32 0 58.5 12t61.5 35 l49 -69q-24 -26 -55 -51.5t-69.5 -45.5t-85.5 -32t-103 -12q-118 0 -181 55.5t-63 154.5z" />-<glyph glyph-name="o" unicode="o" horiz-adv-x="1266" d="M82 410q0 77 18 159t54.5 161t91.5 149.5t129.5 123.5t169 84t209.5 31q88 0 166 -24.5t136 -76.5t92 -133.5t34 -195.5q0 -74 -17 -155t-52.5 -159.5t-90 -150t-128.5 -125.5t-168 -86t-210 -32q-96 0 -176 26.5t-137 80t-89 134.5t-32 189zM553 127q55 0 99.5 31.5 t80 83t61.5 117.5t42.5 136t25 137.5t8.5 121.5q0 121 -41.5 174t-113.5 53q-52 0 -96 -30t-79.5 -80t-62.5 -114.5t-45.5 -133.5t-27.5 -137.5t-9 -125.5q0 -125 42 -179t116 -54z" />-<glyph glyph-name="p" unicode="p" horiz-adv-x="1276" d="M885 733q0 42 -5.5 81.5t-18 70.5t-34 50t-53.5 19q-34 0 -67.5 -19.5t-65 -52.5t-59 -76t-50 -89.5t-39 -93.5t-24.5 -88l-78 -367q9 -9 24 -19.5t34 -18.5t41.5 -13.5t46.5 -5.5q55 0 102.5 30t86 80t68.5 114.5t50 133.5t30.5 137.5t10.5 126.5zM-61 -492l278 1307 q3 13 5.5 27t4.5 26.5t3 21.5t1 13q0 28 -6 45t-19 26t-33.5 12t-49.5 3h-37l18 109h441l-53 -273h14q35 64 72 118t82 93t102 60.5t133 21.5q67 0 122.5 -24t94.5 -71t60.5 -116t21.5 -159q0 -74 -17.5 -159.5t-52.5 -171t-87.5 -164.5t-123.5 -139.5t-159.5 -97 t-194.5 -36.5q-62 0 -115.5 12.5t-93.5 34.5q-1 -16 -4 -33q-2 -14 -5 -31.5t-7 -34.5l-29 -137q-3 -13 -5.5 -27t-4.5 -26.5t-3 -21.5t-1 -13q0 -28 6 -45t19 -26t33.5 -12t49.5 -3h78l-18 -109h-520z" />-<glyph glyph-name="q" unicode="q" horiz-adv-x="1292" d="M393 360q0 -59 8.5 -100t23 -67t35 -38t44.5 -12q31 0 62.5 18.5t62.5 51t59.5 76.5t52 94t42 103.5t28.5 105.5l74 338q-14 23 -49 40t-84 17q-61 0 -111 -32t-90 -84.5t-69.5 -119.5t-49 -136.5t-29.5 -136t-10 -118.5zM705 -82q3 16 8 39.5l11 51.5l12.5 58.5 l12.5 60.5l33 148h-12q-35 -67 -72 -121.5t-82.5 -93.5t-104.5 -60t-138 -21q-65 0 -118.5 24t-91.5 71t-59.5 117t-21.5 162q0 80 19.5 167.5t56.5 172.5t91.5 162t124.5 135t154.5 92.5t182.5 34.5q45 0 88 -6t82 -15.5t72 -21.5t59 -24l121 67h102l-281 -1327 q-6 -26 -10 -55t-4 -49q0 -37 25 -53.5t84 -16.5h77l-18 -109h-485z" />-<glyph glyph-name="r" unicode="r" horiz-adv-x="1128" d="M262 918q0 38 -25 54.5t-83 16.5h-37l18 109h453l-43 -281h10q35 74 70.5 130t78 94t96.5 57.5t126 19.5q109 0 162 -41t53 -119q0 -106 -62 -158t-180 -52q0 46 -4.5 83.5t-16 64t-31 40.5t-48.5 14t-61 -23.5t-64 -64.5t-62.5 -97.5t-58.5 -123t-51 -139.5t-39 -148 l-78 -354h-309l174 823q3 17 7.5 45t4.5 50z" />-<glyph glyph-name="s" unicode="s" horiz-adv-x="1053" d="M457 102q42 0 78 10.5t62 30t41 48t15 65.5q0 35 -13.5 62t-42 50.5t-72 46.5t-103.5 52q-60 28 -108.5 60t-82.5 71t-52.5 87t-18.5 108q0 75 32 135t92 102.5t145.5 65t193.5 22.5q103 0 174.5 -16t116 -42.5t64 -61.5t19.5 -72q0 -82 -63 -119t-172 -37q0 41 -8 82.5 t-27 75.5t-49 55t-74 21q-40 0 -71.5 -11.5t-54 -31t-34.5 -45.5t-12 -56q0 -34 13.5 -59t42 -47t72.5 -43.5l104 -48.5q66 -30 114 -63.5t79.5 -73t47 -86t15.5 -103.5q0 -83 -31.5 -149t-94.5 -112t-156 -70.5t-216 -24.5q-88 0 -159.5 15t-122 44t-78 71t-27.5 95 q0 48 19 81t49.5 53.5t70 30t80.5 9.5q0 -55 11 -105t35 -88.5t62.5 -61t94.5 -22.5z" />-<glyph glyph-name="t" unicode="t" horiz-adv-x="854" d="M553 147q48 0 87 16t73 38l47 -78q-34 -29 -71.5 -55t-81.5 -45.5t-94.5 -31t-109.5 -11.5q-56 0 -104.5 14.5t-84.5 44t-56.5 74.5t-20.5 106q0 18 1.5 40t4.5 45t7 45.5t8 41.5l123 567h-158l22 105q44 0 96 15t106.5 49t109 89t102.5 134h119l-53 -252h247l-28 -140 h-250l-123 -561q-11 -45 -15.5 -81.5t-4.5 -65.5q0 -51 28 -77t74 -26z" />-<glyph glyph-name="u" unicode="u" horiz-adv-x="1378" d="M793 176q0 9 1 21t2.5 25t3 24.5t3.5 19.5h-17q-45 -64 -88.5 -116t-92.5 -89.5t-106 -58t-130 -20.5q-66 0 -111 18.5t-72 49.5t-39 72.5t-12 86.5q0 51 12 112l23 113l74 361q8 42 13 76t5 51q0 41 -25 54t-83 13h-37l18 109h492l-135 -613q-6 -24 -12 -54t-11 -60 t-8.5 -57t-3.5 -46q0 -45 19 -71.5t69 -26.5q45 0 92.5 40.5t91 105t77.5 144t52 156.5l109 482h305l-139 -666q-13 -56 -21 -104.5t-8 -91.5q0 -45 18.5 -69t53.5 -24q32 0 58.5 12t61.5 35l50 -69q-25 -26 -56 -51.5t-69.5 -45.5t-85.5 -32t-103 -12q-68 0 -113.5 15.5 t-73.5 42t-40 62t-12 76.5z" />-<glyph glyph-name="v" unicode="v" horiz-adv-x="1200" d="M195 881q-6 32 -17.5 53t-29 33t-41.5 17t-54 5h-20l24 109h402l94 -606q15 -104 21.5 -185.5t11.5 -132.5h6q70 72 134 155t113 166.5t78 162t29 141.5q0 69 -39 101t-96 32q0 84 46 134t124 50q32 0 60 -10.5t49 -33.5t33 -59t12 -87q0 -80 -26 -167t-68.5 -174.5 t-97.5 -172t-113.5 -160t-115.5 -140t-104 -110.5l-270 -20z" />-<glyph glyph-name="w" unicode="w" horiz-adv-x="1792" d="M1745 944q0 -106 -45 -224t-120 -240.5t-172 -244t-200 -233.5l-250 -22q0 63 -1.5 136.5l-3 151.5l-3 156t-3.5 149t-4 131t-3 103q-80 -190 -183 -394.5t-239 -406.5l-268 -18l-21 852q-1 47 -12 76t-29.5 45.5t-44 22t-55.5 5.5h-12l33 109h395q6 -102 11 -203.5 t8 -190t5 -159t2 -111.5q0 -78 -1 -125.5t-1 -52.5h15q51 81 102 177t101 202t98 218.5l92 225.5l250 8q6 -51 11.5 -125t10.5 -158t9.5 -172t8 -166.5t5.5 -142t2 -98.5h14q90 111 149 200.5t93.5 163.5t48.5 134t14 111q0 30 -9 54t-23 41.5t-32.5 28t-37.5 15.5 q3 39 16.5 66.5t34 45t46.5 25.5t55 8q68 0 105.5 -45t37.5 -129z" />-<glyph glyph-name="x" unicode="x" horiz-adv-x="1237" d="M-41 0l21 109h26q29 0 52.5 4.5t47 18.5t51.5 40t66 68l258 284l-141 340q-16 39 -31.5 63t-33.5 38t-40 19t-51 5h-24l20 109h385l144 -389l284 389h250l-20 -109h-37q-30 0 -53.5 -6t-47 -22.5t-50 -45t-62.5 -73.5l-211 -244l149 -344q19 -43 35.5 -71t34.5 -44.5 t40 -23t52 -6.5h27l-21 -109h-397l-154 414l-319 -414h-250z" />-<glyph glyph-name="y" unicode="y" horiz-adv-x="1200" d="M160 881q-17 61 -49.5 84.5t-92.5 23.5h-20l25 109h401l135 -531q10 -40 21 -93.5l21 -107t17 -100t9 -71.5h6q61 82 115 165t94.5 161.5t64 149.5t23.5 130q0 69 -33.5 105t-93.5 36q0 40 12.5 72.5t34.5 55.5t52 35.5t65 12.5q83 0 125.5 -46t42.5 -128q0 -80 -27 -169 t-77 -189t-121.5 -212t-161.5 -237l-139 -191t-127.5 -157t-127 -122.5t-137 -88t-158 -53t-190.5 -17.5q-59 0 -114.5 10t-98.5 27l31 115q24 -8 67.5 -17.5t106.5 -9.5q94 0 172 25t144.5 67.5t125 99.5t113.5 122z" />-<glyph glyph-name="z" unicode="z" horiz-adv-x="1143" d="M715 139q35 0 62 16.5t47.5 43t36.5 59.5t30 66l20 47h119l-88 -371h-942l23 104l684 854h-261q-41 0 -69 -13.5t-49.5 -37.5t-39 -58t-36.5 -75l-4 -8h-125l90 332h879l-23 -107l-686 -852h332z" />-<glyph glyph-name="braceleft" unicode="{" horiz-adv-x="905" d="M487 -256q-146 0 -219 59t-73 164q0 9 0.5 20t2.5 27t6 39.5t11 57.5l61 286q7 29 7 56q0 40 -15.5 66t-43 41.5t-64.5 21.5t-80 8l26 125q51 1 96.5 9.5t82 29.5t62.5 57.5t38 93.5l74 352q16 77 46 133.5t78.5 93t119.5 54.5t169 18h195l-23 -108h-79q-44 0 -74.5 -13 t-52 -38t-35.5 -62.5t-25 -85.5l-76 -348q-20 -93 -84.5 -152.5t-189.5 -86.5l-4 -17q93 -25 136.5 -70.5t43.5 -113.5q0 -27 -6 -58l-76 -350q-6 -26 -8 -49t-2 -43q0 -53 26 -80.5t89 -27.5h80l-25 -109h-195z" />-<glyph glyph-name="bar" unicode="|" d="M678 -492h-148v2048h148v-2048z" />-<glyph glyph-name="braceright" unicode="}" horiz-adv-x="905" d="M469 1559q147 0 220 -59t73 -165q0 -9 -0.5 -20t-3 -27t-6.5 -39t-11 -57l-61 -287q-6 -30 -6 -55q0 -41 15.5 -67t43 -41t64.5 -21.5t80 -7.5l-27 -125q-51 -2 -96 -10t-81.5 -29t-63 -57.5t-38.5 -94.5l-73 -352q-16 -77 -46 -133.5t-79 -93t-120 -54.5t-169 -18h-195 l23 109h80q44 0 74.5 13t52 38t35 62t24.5 85l76 348q20 93 84.5 152.5t189.5 87.5l4 16q-92 25 -136 71t-44 114q0 27 6 57l76 350q6 26 8 49t2 43q0 53 -25.5 81t-88.5 28h-80l24 109h195z" />-<glyph glyph-name="asciitilde" unicode="~" horiz-adv-x="1149" d="M528 664q-37 16 -64.5 26.5t-50 17t-43 9t-42.5 2.5q-29 0 -58.5 -9.5t-59 -25.5t-57.5 -38.5t-53 -47.5v158q100 108 248 108q29 0 54 -2.5t52.5 -9t61 -19t80.5 -32.5l64.5 -27t51 -17.5t43.5 -9t42 -2.5q28 0 58 9.5t59 26t57 38.5t53 47v-157q-50 -54 -111 -81.5 t-137 -27.5q-29 0 -54 2.5t-52.5 9.5t-61 19.5t-80.5 32.5z" />-<glyph glyph-name="exclamdown" unicode="¡" horiz-adv-x="809" d="M381 -367h-356l354 996h119zM309 928q0 51 15.5 86.5t41 57t57.5 31t64 9.5q75 0 120.5 -36t45.5 -111q0 -42 -12.5 -76t-35.5 -58t-56 -37.5t-74 -13.5q-35 0 -65.5 9t-53 27t-35 46t-12.5 66z" />-<glyph glyph-name="cent" unicode="¢" d="M692 324q59 0 106 14.5t84.5 38.5t66.5 54t52 61q13 -13 23 -36.5t10 -54.5q0 -38 -23 -78t-71 -73.5t-122 -56t-175 -25.5l-35 -168h-127l37 174q-81 12 -151 43.5t-121 83.5t-80 124.5t-29 166.5q0 71 17.5 148.5t52.5 153.5t87.5 145.5t121.5 125.5t154.5 93.5 t187.5 48.5l33 155h127l-33 -151q77 -5 132.5 -24t91 -48t52.5 -66t17 -77q0 -32 -14 -62.5t-44 -55t-77.5 -39.5t-114.5 -15q0 74 -19 135t-57 96l-176 -831h16zM451 588q0 -84 26.5 -142.5t79.5 -89.5l174 830q-44 -10 -81.5 -44.5t-68.5 -84.5t-55 -111t-41 -124 t-25.5 -124t-8.5 -110z" />-<glyph glyph-name="sterling" unicode="£" d="M600 711q1 -8 2 -20t2 -26t1.5 -29t0.5 -28q0 -63 -20 -114.5t-58 -95t-93 -81t-126 -71.5l2 -8q54 20 98 28t83 8q46 0 95 -11.5t95.5 -25l88 -25t73.5 -11.5q35 0 62 4.5t50.5 14t46.5 24.5t50 37l65 -103q-24 -25 -64 -59t-88.5 -65t-102.5 -52.5t-105 -21.5 t-99.5 14.5t-99 32.5t-105.5 32.5t-118 14.5q-41 0 -75.5 -3.5t-66 -11t-63 -19.5t-65.5 -28l-33 -14l-45 108l69 44q51 30 102.5 71.5t93 94.5t67.5 118.5t26 143.5q0 34 -3.5 66.5t-8.5 64.5h-236l29 125h192q-6 38 -13 91t-7 103q0 108 38 193t107.5 143t168 88.5 t218.5 30.5q87 0 154 -17.5t112.5 -48t69 -71t23.5 -86.5q0 -88 -66 -134.5t-190 -46.5q0 140 -40 209.5t-110 69.5q-103 0 -158 -86t-55 -244q0 -60 7 -109.5l12 -84.5h364l-28 -125z" />-<glyph glyph-name="currency" unicode="¤" d="M227 729q0 59 17 113.5t49 99.5l-123 123l102 102l123 -123q45 29 96.5 45.5t110.5 16.5q57 0 109.5 -16.5t97.5 -47.5l127 127l102 -104l-125 -127q31 -45 48.5 -97.5t17.5 -111.5t-16 -110.5t-45 -96.5l120 -121l-102 -102l-121 121q-46 -31 -100 -48.5t-113 -17.5 t-112.5 16.5t-98.5 47.5l-119 -119l-104 104l119 119q-60 92 -60 207zM367 729q0 -49 18.5 -92t50 -75.5t74.5 -51t92 -18.5q51 0 94 18.5t74.5 51t49.5 75.5t18 92q0 51 -18 94.5t-49.5 76t-74.5 51t-94 18.5q-49 0 -92 -18.5t-74.5 -51t-50 -76t-18.5 -94.5z" />-<glyph glyph-name="yen" unicode="¥" horiz-adv-x="1104" d="M762 698h262l-25 -120h-286l-27 -119h287l-25 -121h-286l-23 -98q-5 -22 -7.5 -38t-2.5 -24q0 -22 11 -35.5t30.5 -21t46.5 -10t59 -2.5h49l-22 -109h-733l22 109h55q32 0 62 4.5t55 18t43 37.5t27 64l22 105h-286l24 121h287l27 119h-287l24 120h271l-125 547 q-7 31 -14 51.5t-18.5 33.5t-30 18.5t-48.5 5.5h-26l22 108h559l-22 -108h-58q-46 0 -66 -16t-20 -44q0 -18 8 -55.5t17 -85.5l37 -195q12 -65 21.5 -125t9.5 -102q11 28 32 67t47 83l158 266q22 37 42 77t20 74q0 28 -23 42t-57 14h-41l24 108h434l-22 -108h-6 q-47 0 -91.5 -39t-97.5 -121z" />-<glyph glyph-name="brokenbar" unicode="¦" d="M676 737h-148v819h148v-819zM676 -492h-148v820h148v-820z" />-<glyph glyph-name="section" unicode="§" horiz-adv-x="1114" d="M444 -236q-93 0 -167 18t-125.5 50.5t-79 78t-27.5 99.5q0 52 19 85.5t48 52t63 25.5t63 7q0 -65 15.5 -120t45 -95t72 -63t96.5 -23q53 0 95.5 17t72 46t45.5 69t16 85q0 41 -11 73.5t-41 64t-82 65.5t-134 78q-78 41 -129 82t-81 83t-42 87t-12 94q0 50 16.5 91.5 t43.5 76t61.5 62t70.5 48.5q-18 29 -33.5 76t-15.5 109q0 88 35.5 157t96 116t140.5 72t169 25q93 0 160.5 -17.5t112 -47.5t66 -70.5t21.5 -85.5q0 -44 -14.5 -73.5t-41.5 -47t-64.5 -25t-82.5 -7.5q0 55 -11.5 103t-34 83t-55.5 55.5t-75 20.5q-37 0 -73.5 -12t-66 -37 t-47.5 -63.5t-18 -90.5q0 -45 12.5 -78.5t41.5 -63t76.5 -58.5t117.5 -66q75 -40 127 -77.5t84.5 -76t46.5 -79t14 -87.5q0 -48 -15.5 -91t-42.5 -81.5t-62.5 -71t-75.5 -57.5q18 -29 32.5 -72.5t14.5 -97.5q0 -87 -32 -163.5t-91 -133.5t-142.5 -90t-185.5 -33zM639 784 q-66 31 -120.5 62.5t-100.5 68.5q-32 -24 -53 -53.5t-21 -73.5q0 -33 11 -61t37 -54.5t70.5 -54t112.5 -59.5q57 -28 111.5 -60t89.5 -61q32 26 51 60.5t19 76.5q0 23 -9 48t-32.5 51t-63.5 53.5t-102 56.5z" />-<glyph glyph-name="dieresis" unicode="¨" horiz-adv-x="1182" d="M616 1417q0 -40 -11 -67t-28 -44t-39 -24.5t-44 -7.5q-51 0 -82 27.5t-31 84.5q0 66 33 105t90 39q49 0 80.5 -28t31.5 -85zM1044 1417q0 -40 -11 -67t-28 -44t-39 -24.5t-44 -7.5q-51 0 -82 27.5t-31 84.5q0 66 33 105t90 39q49 0 80.5 -28t31.5 -85z" />-<glyph glyph-name="copyright" unicode="©" horiz-adv-x="1731" d="M129 731q0 104 27 200.5t75.5 180t117.5 152.5t152.5 118t179.5 76t200 27t200 -27t179.5 -76t152.5 -118t118 -152.5t76 -180t27 -200.5t-27 -200t-76 -179.5t-118 -152t-152.5 -117.5t-179.5 -75.5t-200 -26.5t-200 26.5t-179.5 75.5t-152.5 117.5t-117.5 152 t-75.5 179.5t-27 200zM242 731q0 -88 23 -169.5t64.5 -152.5t99.5 -129.5t129 -100t153 -64.5t170 -23q89 0 170.5 23t153 64.5t130 100t100 129.5t64.5 152.5t23 169.5q0 133 -50.5 249.5t-137.5 203.5t-203.5 137.5t-249.5 50.5q-88 0 -170 -23t-153 -64.5t-129 -100 t-99.5 -130t-64.5 -153.5t-23 -170zM918 365q46 0 86 12t72.5 32t55.5 44.5t37 50.5q13 -9 21 -25.5t8 -38.5q0 -28 -19 -57t-57.5 -53t-96 -39t-134.5 -15q-102 0 -179 33t-128.5 93t-77 144t-25.5 187q0 100 28 183t83.5 143t137.5 93.5t190 33.5q70 0 123 -11.5t87.5 -31 t52 -46t17.5 -57.5q0 -22 -11 -40t-29.5 -30.5t-44 -19t-54.5 -6.5q0 28 -7.5 56t-24.5 50.5t-45 36t-69 13.5q-72 0 -122.5 -24t-82.5 -71t-46.5 -115.5t-14.5 -156.5q0 -177 69 -272.5t200 -95.5z" />-<glyph glyph-name="ordfeminine" unicode="ª" horiz-adv-x="883" d="M772 782q-68 0 -105.5 27.5t-37.5 75.5q0 25 3.5 57t6.5 55h-8q-26 -48 -55.5 -87t-63.5 -67t-73.5 -43.5t-84.5 -15.5q-51 0 -88.5 17.5t-62 48t-36 71.5t-11.5 89t12 101t37 104.5t63.5 97.5t90 81t117 55.5t145.5 20.5q55 0 91.5 -12t67.5 -31l72 29h70l-76 -373 q-8 -42 -15.5 -75.5t-7.5 -59.5q0 -53 43 -53q20 0 42.5 9.5t45.5 23.5l27 -49q-13 -14 -34.5 -31t-48.5 -31.5t-59.5 -24.5t-66.5 -10zM395 1022q0 -34 5 -56.5t14 -36.5t20.5 -20t24.5 -6q27 0 56 24.5t54 62t45 80.5t29 81l49 195q-12 18 -37.5 28t-50.5 10 q-34 0 -63 -17.5t-52 -47t-40.5 -67.5t-29.5 -78t-18 -79.5t-6 -72.5z" />-<glyph glyph-name="guillemotleft" unicode="«" horiz-adv-x="1135" d="M143 621l389 342h144l-293 -410l119 -410h-144l-237 371zM555 621l389 342h143l-292 -410l118 -410h-143l-238 371z" />-<glyph glyph-name="logicalnot" unicode="¬" d="M1044 805v-512h-147v366h-731v146h878z" />-<glyph glyph-name="uni00AD" unicode="­" horiz-adv-x="635" d="M20 451l48 215h532l-47 -215h-533z" />-<glyph glyph-name="registered" unicode="®" horiz-adv-x="1731" d="M494 358h26q20 0 38 2.5t32 10t22.5 21.5t8.5 38v600q0 24 -8.5 38t-22.5 21.5t-32 10t-38 2.5h-26v72h368q352 0 352 -244q0 -49 -16 -86t-42 -64t-59.5 -45.5t-68.5 -29.5l182 -293q10 -17 19.5 -27.5t22.5 -16.5t30 -8t43 -2v-69h-217l-225 381h-109v-240 q1 -24 9.5 -38t22 -21.5t31 -10t37.5 -2.5h29v-69h-409v69zM774 748h86q55 0 92.5 11t59.5 33t31.5 56t9.5 80q0 45 -11 77t-35 51.5t-61.5 28.5t-91.5 9h-80v-346zM129 731q0 104 27 200.5t75.5 180t117.5 152.5t152.5 118t179.5 76t200 27t200 -27t179.5 -76t152.5 -118 t118 -152.5t76 -180t27 -200.5t-27 -200t-76 -179.5t-118 -152t-152.5 -117.5t-179.5 -75.5t-200 -26.5t-200 26.5t-179.5 75.5t-152.5 117.5t-117.5 152t-75.5 179.5t-27 200zM242 731q0 -88 23 -169.5t64.5 -152.5t99.5 -129.5t129 -100t153 -64.5t170 -23q89 0 170.5 23 t153 64.5t130 100t100 129.5t64.5 152.5t23 169.5q0 133 -50.5 249.5t-137.5 203.5t-203.5 137.5t-249.5 50.5q-88 0 -170 -23t-153 -64.5t-129 -100t-99.5 -130t-64.5 -153.5t-23 -170z" />-<glyph glyph-name="macron" unicode="¯" horiz-adv-x="940" d="M950 1556h-960v121h960v-121z" />-<glyph glyph-name="degree" unicode="°" horiz-adv-x="819" d="M225 1151q0 64 24 121t66 99t98.5 66.5t121.5 24.5q64 0 121 -24.5t99 -66.5t66.5 -99t24.5 -121q0 -65 -24.5 -121t-66.5 -98t-99 -66t-121 -24q-65 0 -121.5 24t-98.5 66t-66 98t-24 121zM367 1151q0 -35 13 -65.5t36 -54t53.5 -37t65.5 -13.5q36 0 67 13.5t54 37 t36 54t13 65.5q0 36 -13 67.5t-36 54.5t-54 36.5t-67 13.5q-35 0 -65.5 -13.5t-53.5 -36.5t-36 -54.5t-13 -67.5z" />-<glyph glyph-name="plusminus" unicode="±" d="M1044 0h-878v145h878v-145zM678 659v-366h-148v366h-364v146h364v366h148v-366h366v-146h-366z" />-<glyph glyph-name="uni00B2" unicode="²" horiz-adv-x="819" d="M59 586l29 147l303 221q62 45 104 86.5t67 81.5t35.5 80.5t10.5 83.5q0 20 -4.5 39t-14.5 33.5t-26.5 23.5t-40.5 9q-29 0 -52 -17t-40.5 -45t-29 -64.5t-15.5 -76.5q-72 0 -114 29t-42 75q0 42 22 77t63.5 60.5t100.5 39.5t134 14q64 0 115.5 -13.5t87.5 -37.5t55.5 -58 t19.5 -76q0 -43 -14.5 -82.5t-50 -82t-93.5 -92t-145 -111.5l-256 -185h301q48 0 72.5 25t36.5 59l16 43h80l-57 -286h-658z" />-<glyph glyph-name="uni00B3" unicode="³" horiz-adv-x="819" d="M360 573q-149 0 -225 44.5t-76 121.5q0 28 11.5 49.5t32 36.5t48.5 23t60 8q0 -29 9 -60.5t29 -57t52 -42t78 -16.5q37 0 69.5 9t56.5 29.5t38 52.5t14 77q0 28 -11 54t-33.5 45t-58 30.5t-83.5 11.5h-68l16 96h70q43 0 82 14.5t68.5 41.5t47 66.5t17.5 90.5 q0 41 -18.5 67t-59.5 26q-33 0 -57.5 -14.5t-42 -39.5t-28 -57t-15.5 -68q-75 0 -120.5 22.5t-45.5 72.5q0 36 22 68t63.5 56t101.5 38t135 14q73 0 126.5 -14.5t88.5 -40t52 -59.5t17 -73q0 -54 -19 -94t-53.5 -69t-82 -48t-103.5 -30l-2 -11q94 -11 150.5 -57t56.5 -131 q0 -69 -31 -122t-85.5 -88.5t-130 -54t-163.5 -18.5z" />-<glyph glyph-name="acute" unicode="´" horiz-adv-x="1182" d="M504 1268q28 29 61 67l65.5 78.5l63 80.5t54.5 75h321l-6 -21q-16 -18 -44 -42.5t-63.5 -52.5l-76 -57.5l-82.5 -57.5l-83 -53.5t-77 -43.5h-139z" />-<glyph glyph-name="uni00B5" unicode="µ" horiz-adv-x="1386" d="M803 188q-35 -47 -68.5 -85.5t-70.5 -65.5t-79 -42t-93 -15q-91 0 -154 40.5t-100 106.5q-7 -36 -12.5 -74t-9 -74t-5.5 -68.5t-2 -57.5q0 -35 6.5 -63.5t25 -49.5t51 -34.5t83.5 -18.5q-8 -45 -31.5 -79t-57 -56t-74.5 -33t-83 -11q-29 0 -53.5 7.5t-42 25t-27.5 45.5 t-10 70q0 61 17 137t44 173l60 219t65 276l135 637h312l-137 -643l-7 -35t-7.5 -42.5t-6 -45t-2.5 -41.5q0 -28 6.5 -53t21 -43.5t36 -29.5t51.5 -11q95 0 160 91.5t106 286.5l121 566h311l-141 -666q-13 -56 -21 -104.5t-8 -91.5q0 -45 19 -69t53 -24q32 0 58.5 12t62.5 35 l49 -69q-25 -26 -56 -51.5t-71 -45.5t-89 -32t-110 -12q-63 0 -104.5 17.5t-66 46t-34.5 66t-10 78.5h-10z" />-<glyph glyph-name="paragraph" unicode="¶" horiz-adv-x="1284" d="M909 1430h-172v-1657h-342v108h27q34 0 65 4.5t53.5 20t36 44.5t13.5 77v764h-146q-90 0 -150 32.5t-96 88t-51.5 128t-15.5 152.5q0 82 17 149t55 115t98.5 74t147.5 26h802v-108h-26q-35 0 -65.5 -5t-53 -20.5t-36 -44.5t-13.5 -78v-1273q0 -48 13.5 -77t36 -44.5 t53 -20t65.5 -4.5h26v-108h-342v1657z" />-<glyph glyph-name="periodcentered" unicode="·" horiz-adv-x="623" d="M518 746q0 -51 -15.5 -86.5t-41 -57t-57.5 -31t-64 -9.5q-74 0 -120 36t-46 111q0 42 12.5 76t36 58t56 37t73.5 13q35 0 65.5 -9t53 -27t35 -46t12.5 -65z" />-<glyph glyph-name="cedilla" unicode="¸" horiz-adv-x="682" d="M387 -70q41 -5 74.5 -20t57.5 -37.5t37 -51t13 -61.5q0 -66 -30.5 -113.5t-81.5 -78.5t-116.5 -45.5t-135.5 -14.5q-32 0 -75.5 7.5t-78.5 19.5l27 121q35 -8 70 -12t63 -4q31 0 59 6t48 18.5t32 32t12 47.5q0 20 -10.5 36.5t-28.5 29t-43 19.5t-53 9l82 180h117z" />-<glyph glyph-name="uni00B9" unicode="¹" horiz-adv-x="819" d="M717 1473l-146 -689q-5 -18 -6.5 -33t-1.5 -20q0 -18 6.5 -28.5t17.5 -16t25.5 -7t30.5 -1.5h84l-18 -90h-578l19 90h104q19 0 36.5 2.5t31.5 10.5t24.5 23.5t15.5 41.5l123 581q-58 -57 -103.5 -92t-82.5 -35q-29 0 -44 29.5t-15 71.5q24 4 48 11t50 18t56 27t65 38 l106 68h152z" />-<glyph glyph-name="ordmasculine" unicode="º" horiz-adv-x="834" d="M922 1219q0 -43 -12 -92t-37 -98t-62.5 -94t-89 -79.5t-116.5 -55t-144 -20.5q-71 0 -129 16.5t-99 49.5t-63.5 81t-22.5 111q0 46 13.5 96.5t39.5 98.5t65 91.5t91 76.5t116 52.5t140 19.5q64 0 120.5 -15t98.5 -46t66.5 -79t24.5 -114zM692 1225q0 68 -21 103.5 t-69 35.5q-52 0 -93.5 -33t-70 -83.5t-44 -109.5t-15.5 -110q0 -69 23 -107t69 -38q55 0 96.5 34.5t69 86.5t41.5 111.5t14 109.5z" />-<glyph glyph-name="guillemotright" unicode="»" horiz-adv-x="1135" d="M987 487l-387 -344h-143l295 410l-121 410h143l236 -369zM575 487l-387 -344h-143l295 410l-121 410h143l236 -369z" />-<glyph glyph-name="onequarter" unicode="¼" horiz-adv-x="1720" d="M1335 578q6 32 17 63.5t22 69.5q-11 -17 -24 -33t-19 -24l-287 -305h244zM1466 240l-8 -36q-5 -23 -6 -39q-2 -17 -2 -23q0 -17 5.5 -27t15.5 -15.5t23 -7t27 -1.5h41l-18 -90h-471l18 90h58q17 0 32.5 2.5t29 10.5t23.5 24q10 15 15 41l14 71h-387l17 82l528 564h182 l-112 -537h159l-24 -109h-160zM460 0h-174l1039 1462h172zM640 1473l-146 -689q-5 -18 -6.5 -33t-1.5 -20q0 -18 6.5 -28.5t17.5 -16t25.5 -7t30.5 -1.5h84l-18 -90h-578l19 90h104q19 0 36.5 2.5t31.5 10.5t24.5 23.5t15.5 41.5l123 581q-58 -57 -103.5 -92t-82.5 -35 q-29 0 -44 29.5t-15 71.5q24 4 48 11t50 18t56 27t65 38l106 68h152z" />-<glyph glyph-name="onehalf" unicode="½" horiz-adv-x="1720" d="M450 0h-174l1039 1462h172zM640 1473l-146 -689q-5 -18 -6.5 -33t-1.5 -20q0 -18 6.5 -28.5t17.5 -16t25.5 -7t30.5 -1.5h84l-18 -90h-578l19 90h104q19 0 36.5 2.5t31.5 10.5t24.5 23.5t15.5 41.5l123 581q-58 -57 -103.5 -92t-82.5 -35q-29 0 -44 29.5t-15 71.5 q24 4 48 11t50 18t56 27t65 38l106 68h152zM899 1l29 147l303 221q62 45 104 86.5t67 81.5t35.5 80.5t10.5 83.5q0 20 -4.5 39t-14.5 33.5t-26.5 23.5t-40.5 9q-29 0 -52 -17t-40.5 -45t-29 -64.5t-15.5 -76.5q-72 0 -114 29t-42 75q0 42 22 77t63.5 60.5t100.5 39.5t134 14 q64 0 115.5 -13.5t87.5 -37.5t55.5 -58t19.5 -76q0 -43 -14.5 -82.5t-50 -82t-93.5 -92t-145 -111.5l-256 -185h301q48 0 72.5 25t36.5 59l16 43h80l-57 -286h-658z" />-<glyph glyph-name="threequarters" unicode="¾" horiz-adv-x="1720" d="M1343 578q6 32 17 63.5t22 69.5q-11 -17 -24 -33t-19 -24l-287 -305h244zM1474 240l-8 -36q-5 -23 -6 -39q-2 -17 -2 -23q0 -17 5.5 -27t15.5 -15.5t23 -7t27 -1.5h41l-18 -90h-471l18 90h58q17 0 32.5 2.5t29 10.5t23.5 24q10 15 15 41l14 71h-387l17 82l528 564h182 l-112 -537h159l-24 -109h-160zM514 0h-174l1039 1462h172zM357 573q-149 0 -225 44.5t-76 121.5q0 28 11.5 49.5t32 36.5t48.5 23t60 8q0 -29 9 -60.5t29 -57t52 -42t78 -16.5q37 0 69.5 9t56.5 29.5t38 52.5t14 77q0 28 -11 54t-33.5 45t-58 30.5t-83.5 11.5h-68l16 96h70 q43 0 82 14.5t68.5 41.5t47 66.5t17.5 90.5q0 41 -18.5 67t-59.5 26q-33 0 -57.5 -14.5t-42 -39.5t-28 -57t-15.5 -68q-75 0 -120.5 22.5t-45.5 72.5q0 36 22 68t63.5 56t101.5 38t135 14q73 0 126.5 -14.5t88.5 -40t52 -59.5t17 -73q0 -54 -19 -94t-53.5 -69t-82 -48 t-103.5 -30l-2 -11q94 -11 150.5 -57t56.5 -131q0 -69 -31 -122t-85.5 -88.5t-130 -54t-163.5 -18.5z" />-<glyph glyph-name="questiondown" unicode="¿" horiz-adv-x="1096" d="M4 -49q0 89 39 162.5t107 134t161.5 109t202.5 87.5l74 183h131l4 -254q-91 -33 -164.5 -72.5t-125.5 -90t-80.5 -115t-28.5 -148.5q0 -51 11 -90t31 -66t46.5 -41t58.5 -14q48 0 82.5 25t57.5 66t36.5 95t20.5 113q252 0 252 -158q0 -55 -33 -103.5t-94.5 -84.5 t-149.5 -57t-197 -21q-102 0 -184 20t-139 61.5t-88 105.5t-31 153zM524 926q0 51 15.5 86.5t41 57t57.5 31t64 9.5q75 0 120.5 -36t45.5 -111q0 -42 -12.5 -76t-35.5 -58t-56 -37.5t-74 -13.5q-35 0 -65.5 9t-53 27t-35 46t-12.5 66z" />-<glyph glyph-name="Agrave" unicode="À" horiz-adv-x="1542" d="M438 481l-100 -184q-20 -37 -29.5 -65t-9.5 -50q0 -38 28 -55.5t87 -17.5h41l-23 -109h-532l22 109h43q28 0 49.5 6.5t41.5 24.5t41.5 49t49.5 79l695 1194h280l172 -1206q5 -32 13.5 -59t22.5 -46.5t35.5 -30.5t53.5 -11h37l-22 -109h-676l22 109h47q66 0 105 20.5 t39 65.5q0 17 -1 33.5t-3 31.5l-31 221h-498zM885 950l-9.5 84.5t-8 76.5t-6 72t-3.5 72q-15 -37 -30 -68.5l-32 -64l-37.5 -69l-47.5 -84.5l-203 -363h414zM987 1579q-44 26 -93.5 66t-95.5 83.5t-84.5 85.5t-59.5 72l6 21h301q12 -37 31.5 -80t40.5 -84t42.5 -77t38.5 -60 l-6 -27h-121z" />-<glyph glyph-name="Aacute" unicode="Á" horiz-adv-x="1542" d="M438 481l-100 -184q-20 -37 -29.5 -65t-9.5 -50q0 -38 28 -55.5t87 -17.5h41l-23 -109h-532l22 109h43q28 0 49.5 6.5t41.5 24.5t41.5 49t49.5 79l695 1194h280l172 -1206q5 -32 13.5 -59t22.5 -46.5t35.5 -30.5t53.5 -11h37l-22 -109h-676l22 109h47q66 0 105 20.5 t39 65.5q0 17 -1 33.5t-3 31.5l-31 221h-498zM885 950l-9.5 84.5t-8 76.5t-6 72t-3.5 72q-15 -37 -30 -68.5l-32 -64l-37.5 -69l-47.5 -84.5l-203 -363h414zM864 1606q28 29 61 67l65.5 78.5l63 80.5t54.5 75h321l-6 -21q-16 -18 -44 -42.5t-63.5 -52.5l-76 -57.5 l-82.5 -57.5l-83 -53.5t-77 -43.5h-139z" />-<glyph glyph-name="Acircumflex" unicode="Â" horiz-adv-x="1542" d="M438 481l-100 -184q-20 -37 -29.5 -65t-9.5 -50q0 -38 28 -55.5t87 -17.5h41l-23 -109h-532l22 109h43q28 0 49.5 6.5t41.5 24.5t41.5 49t49.5 79l695 1194h280l172 -1206q5 -32 13.5 -59t22.5 -46.5t35.5 -30.5t53.5 -11h37l-22 -109h-676l22 109h47q66 0 105 20.5 t39 65.5q0 17 -1 33.5t-3 31.5l-31 221h-498zM885 950l-9.5 84.5t-8 76.5t-6 72t-3.5 72q-15 -37 -30 -68.5l-32 -64l-37.5 -69l-47.5 -84.5l-203 -363h414zM619 1606l68 66.5l78 77.5l78 80.5t67 76.5h276q13 -32 33 -72l42.5 -81l44.5 -80t40 -68l-6 -27h-129l-42 40 l-49 48l-49.5 50t-42.5 46l-61 -46l-69 -50l-68 -48l-58 -40h-159z" />-<glyph glyph-name="Atilde" unicode="Ã" horiz-adv-x="1542" d="M438 481l-100 -184q-20 -37 -29.5 -65t-9.5 -50q0 -38 28 -55.5t87 -17.5h41l-23 -109h-532l22 109h43q28 0 49.5 6.5t41.5 24.5t41.5 49t49.5 79l695 1194h280l172 -1206q5 -32 13.5 -59t22.5 -46.5t35.5 -30.5t53.5 -11h37l-22 -109h-676l22 109h47q66 0 105 20.5 t39 65.5q0 17 -1 33.5t-3 31.5l-31 221h-498zM885 950l-9.5 84.5t-8 76.5t-6 72t-3.5 72q-15 -37 -30 -68.5l-32 -64l-37.5 -69l-47.5 -84.5l-203 -363h414zM1188 1774q26 0 45 9t32.5 24t22 34.5t13.5 40.5h123q-11 -49 -33.5 -102.5t-56 -98t-78 -73.5t-99.5 -29t-99 20 t-81 44.5l-69.5 44.5t-65.5 20q-21 0 -40 -9t-34 -24t-25 -34.5t-14 -40.5h-122q9 46 32.5 99.5t59.5 98.5t82.5 75t101.5 30q54 0 96.5 -20t78 -44.5l67 -44.5t63.5 -20z" />-<glyph glyph-name="Adieresis" unicode="Ä" horiz-adv-x="1542" d="M438 481l-100 -184q-20 -37 -29.5 -65t-9.5 -50q0 -38 28 -55.5t87 -17.5h41l-23 -109h-532l22 109h43q28 0 49.5 6.5t41.5 24.5t41.5 49t49.5 79l695 1194h280l172 -1206q5 -32 13.5 -59t22.5 -46.5t35.5 -30.5t53.5 -11h37l-22 -109h-676l22 109h47q66 0 105 20.5 t39 65.5q0 17 -1 33.5t-3 31.5l-31 221h-498zM885 950l-9.5 84.5t-8 76.5t-6 72t-3.5 72q-15 -37 -30 -68.5l-32 -64l-37.5 -69l-47.5 -84.5l-203 -363h414zM913 1755q0 -40 -11 -67t-28 -44t-39 -24.5t-44 -7.5q-51 0 -82 27.5t-31 84.5q0 66 33 105t90 39q49 0 80.5 -28 t31.5 -85zM1341 1755q0 -40 -11 -67t-28 -44t-39 -24.5t-44 -7.5q-51 0 -82 27.5t-31 84.5q0 66 33 105t90 39q49 0 80.5 -28t31.5 -85z" />-<glyph glyph-name="Aring" unicode="Å" horiz-adv-x="1542" d="M1092 1573q0 30 -10 50.5t-26.5 34t-38.5 19.5t-46 6q-25 0 -47 -6t-38.5 -19.5t-26 -34t-9.5 -50.5t9.5 -51t26 -34.5t38.5 -19.5t47 -6q24 0 46 6t38.5 19.5t26.5 34.5t10 51zM1217 1573q0 -55 -19 -96.5t-52.5 -70t-78.5 -43t-96 -14.5t-96 14.5t-78.5 43t-52.5 70 t-19 96.5q0 54 19 96t52.5 70t78.5 42.5t96 14.5t96 -14.5t78.5 -42.5t52.5 -70t19 -96zM438 481l-100 -184q-20 -37 -29.5 -65t-9.5 -50q0 -38 28 -55.5t87 -17.5h41l-23 -109h-532l22 109h43q28 0 49.5 6.5t41.5 24.5t41.5 49t49.5 79l695 1194h280l172 -1206 q5 -32 13.5 -59t22.5 -46.5t35.5 -30.5t53.5 -11h37l-22 -109h-676l22 109h47q66 0 105 20.5t39 65.5q0 17 -1 33.5t-3 31.5l-31 221h-498zM885 950l-9.5 84.5t-8 76.5t-6 72t-3.5 72q-15 -37 -30 -68.5l-32 -64l-37.5 -69l-47.5 -84.5l-203 -363h414z" />-<glyph glyph-name="AE" unicode="Æ" horiz-adv-x="2034" d="M2083 1462l-72 -381h-145q1 7 2.5 25l3 37.5t3 36t1.5 20.5q0 28 -8 53t-25 44t-44.5 29.5t-67.5 10.5h-273l-108 -512h465l-27 -123h-465l-123 -577h324q48 0 85.5 13.5t66 36.5t48 53.5t31.5 64.5l33 88h145l-94 -381h-1175l22 109h27q32 0 62.5 4t55.5 17t43.5 37 t26.5 64l49 238h-467l-121 -172q-30 -43 -43.5 -76.5t-13.5 -50.5q0 -32 28 -46.5t81 -14.5h35l-23 -109h-516l22 109h25q27 0 52 9.5t50.5 29t52.5 49.5t58 71l805 1075l-133 11l22 108h1219zM1114 1337l-539 -745h402l158 745h-21z" />-<glyph glyph-name="Ccedilla" unicode="Ç" horiz-adv-x="1368" d="M823 152q71 0 128 16t102 42.5t79 59.5t60 68q12 -9 23.5 -28t11.5 -42q0 -51 -31 -102.5t-91.5 -93t-149.5 -67t-205 -25.5q-147 0 -262.5 36.5t-195 107t-121.5 173t-42 233.5q0 123 29 242t84 226t134.5 197t180 156t220.5 103t257 37q102 0 178.5 -17.5t127 -49 t75.5 -77t25 -100.5q0 -45 -22 -79t-60.5 -57.5t-91.5 -35.5t-115 -12q0 43 -6 95t-24.5 97t-52.5 75t-91 30q-81 0 -149.5 -37t-125 -100.5t-100 -147.5t-73 -177.5t-45 -191t-15.5 -188.5q0 -175 89 -270.5t265 -95.5zM684 -70q41 -5 74.5 -20t57.5 -37.5t37 -51t13 -61.5 q0 -66 -30.5 -113.5t-81.5 -78.5t-116.5 -45.5t-135.5 -14.5q-32 0 -75.5 7.5t-78.5 19.5l27 121q35 -8 70 -12t63 -4q31 0 59 6t48 18.5t32 32t12 47.5q0 20 -10.5 36.5t-28.5 29t-43 19.5t-53 9l82 180h117z" />-<glyph glyph-name="Egrave" unicode="È" horiz-adv-x="1296" d="M-25 109h27q32 0 62.5 4t55.5 17t43.5 37t26.5 64l213 1006q4 11 6.5 25t2.5 22q0 22 -11 36t-30.5 21.5t-47 10t-59.5 2.5h-26l22 108h1112l-72 -381h-145q1 7 2.5 25l3 37.5t3 36t1.5 20.5q0 28 -8 53t-25 44t-44.5 29.5t-67.5 10.5h-272l-109 -512h465l-27 -123h-465 l-123 -577h324q48 0 86 13.5t66 36.5t47.5 53.5t31.5 64.5l33 88h146l-95 -381h-1175zM870 1579q-44 26 -93.5 66t-95.5 83.5t-84.5 85.5t-59.5 72l6 21h301q12 -37 31.5 -80t40.5 -84t42.5 -77t38.5 -60l-6 -27h-121z" />-<glyph glyph-name="Eacute" unicode="É" horiz-adv-x="1296" d="M-25 109h27q32 0 62.5 4t55.5 17t43.5 37t26.5 64l213 1006q4 11 6.5 25t2.5 22q0 22 -11 36t-30.5 21.5t-47 10t-59.5 2.5h-26l22 108h1112l-72 -381h-145q1 7 2.5 25l3 37.5t3 36t1.5 20.5q0 28 -8 53t-25 44t-44.5 29.5t-67.5 10.5h-272l-109 -512h465l-27 -123h-465 l-123 -577h324q48 0 86 13.5t66 36.5t47.5 53.5t31.5 64.5l33 88h146l-95 -381h-1175zM748 1606q28 29 61 67l65.5 78.5l63 80.5t54.5 75h321l-6 -21q-16 -18 -44 -42.5t-63.5 -52.5l-76 -57.5l-82.5 -57.5l-83 -53.5t-77 -43.5h-139z" />-<glyph glyph-name="Ecircumflex" unicode="Ê" horiz-adv-x="1296" d="M-25 109h27q32 0 62.5 4t55.5 17t43.5 37t26.5 64l213 1006q4 11 6.5 25t2.5 22q0 22 -11 36t-30.5 21.5t-47 10t-59.5 2.5h-26l22 108h1112l-72 -381h-145q1 7 2.5 25l3 37.5t3 36t1.5 20.5q0 28 -8 53t-25 44t-44.5 29.5t-67.5 10.5h-272l-109 -512h465l-27 -123h-465 l-123 -577h324q48 0 86 13.5t66 36.5t47.5 53.5t31.5 64.5l33 88h146l-95 -381h-1175zM557 1606l68 66.5l78 77.5l78 80.5t67 76.5h276q13 -32 33 -72l42.5 -81l44.5 -80t40 -68l-6 -27h-129l-42 40l-49 48l-49.5 50t-42.5 46l-61 -46l-69 -50l-68 -48l-58 -40h-159z" />-<glyph glyph-name="Edieresis" unicode="Ë" horiz-adv-x="1296" d="M-25 109h27q32 0 62.5 4t55.5 17t43.5 37t26.5 64l213 1006q4 11 6.5 25t2.5 22q0 22 -11 36t-30.5 21.5t-47 10t-59.5 2.5h-26l22 108h1112l-72 -381h-145q1 7 2.5 25l3 37.5t3 36t1.5 20.5q0 28 -8 53t-25 44t-44.5 29.5t-67.5 10.5h-272l-109 -512h465l-27 -123h-465 l-123 -577h324q48 0 86 13.5t66 36.5t47.5 53.5t31.5 64.5l33 88h146l-95 -381h-1175zM811 1755q0 -40 -11 -67t-28 -44t-39 -24.5t-44 -7.5q-51 0 -82 27.5t-31 84.5q0 66 33 105t90 39q49 0 80.5 -28t31.5 -85zM1239 1755q0 -40 -11 -67t-28 -44t-39 -24.5t-44 -7.5 q-51 0 -82 27.5t-31 84.5q0 66 33 105t90 39q49 0 80.5 -28t31.5 -85z" />-<glyph glyph-name="Igrave" unicode="Ì" horiz-adv-x="821" d="M-47 0l22 109h27q32 0 62.5 4t55.5 17t43.5 37t26.5 64l213 1006q4 11 6.5 25t2.5 22q0 22 -11 36t-30.5 21.5t-47 10t-59.5 2.5h-26l22 108h707l-23 -108h-26q-34 0 -65.5 -4.5t-57.5 -17.5t-45 -38t-27 -65l-211 -1004q-2 -11 -5 -25t-3 -22q0 -22 11 -35.5t31 -21 t47.5 -10t59.5 -2.5h27l-23 -109h-704zM629 1579q-44 26 -93.5 66t-95.5 83.5t-84.5 85.5t-59.5 72l6 21h301q12 -37 31.5 -80t40.5 -84t42.5 -77t38.5 -60l-6 -27h-121z" />-<glyph glyph-name="Iacute" unicode="Í" horiz-adv-x="821" d="M-47 0l22 109h27q32 0 62.5 4t55.5 17t43.5 37t26.5 64l213 1006q4 11 6.5 25t2.5 22q0 22 -11 36t-30.5 21.5t-47 10t-59.5 2.5h-26l22 108h707l-23 -108h-26q-34 0 -65.5 -4.5t-57.5 -17.5t-45 -38t-27 -65l-211 -1004q-2 -11 -5 -25t-3 -22q0 -22 11 -35.5t31 -21 t47.5 -10t59.5 -2.5h27l-23 -109h-704zM501 1606q28 29 61 67l65.5 78.5l63 80.5t54.5 75h321l-6 -21q-16 -18 -44 -42.5t-63.5 -52.5l-76 -57.5l-82.5 -57.5l-83 -53.5t-77 -43.5h-139z" />-<glyph glyph-name="Icircumflex" unicode="Î" horiz-adv-x="821" d="M-47 0l22 109h27q32 0 62.5 4t55.5 17t43.5 37t26.5 64l213 1006q4 11 6.5 25t2.5 22q0 22 -11 36t-30.5 21.5t-47 10t-59.5 2.5h-26l22 108h707l-23 -108h-26q-34 0 -65.5 -4.5t-57.5 -17.5t-45 -38t-27 -65l-211 -1004q-2 -11 -5 -25t-3 -22q0 -22 11 -35.5t31 -21 t47.5 -10t59.5 -2.5h27l-23 -109h-704zM272 1606l68 66.5l78 77.5l78 80.5t67 76.5h276q13 -32 33 -72l42.5 -81l44.5 -80t40 -68l-6 -27h-129l-42 40l-49 48l-49.5 50t-42.5 46l-61 -46l-69 -50l-68 -48l-58 -40h-159z" />-<glyph glyph-name="Idieresis" unicode="Ï" horiz-adv-x="821" d="M-47 0l22 109h27q32 0 62.5 4t55.5 17t43.5 37t26.5 64l213 1006q4 11 6.5 25t2.5 22q0 22 -11 36t-30.5 21.5t-47 10t-59.5 2.5h-26l22 108h707l-23 -108h-26q-34 0 -65.5 -4.5t-57.5 -17.5t-45 -38t-27 -65l-211 -1004q-2 -11 -5 -25t-3 -22q0 -22 11 -35.5t31 -21 t47.5 -10t59.5 -2.5h27l-23 -109h-704zM556 1755q0 -40 -11 -67t-28 -44t-39 -24.5t-44 -7.5q-51 0 -82 27.5t-31 84.5q0 66 33 105t90 39q49 0 80.5 -28t31.5 -85zM984 1755q0 -40 -11 -67t-28 -44t-39 -24.5t-44 -7.5q-51 0 -82 27.5t-31 84.5q0 66 33 105t90 39 q49 0 80.5 -28t31.5 -85z" />-<glyph glyph-name="Eth" unicode="Ð" horiz-adv-x="1573" d="M-27 109h29q32 0 62.5 4t55.5 17t43.5 37t26.5 64l99 471h-168l26 125h168l88 410q3 11 5.5 25t2.5 22q0 22 -11 36t-30.5 21.5t-46.5 10t-59 2.5h-27l23 108h655q147 0 262 -36t195 -106.5t122 -174.5t42 -240q0 -122 -25.5 -236.5t-75 -215t-122.5 -184t-169 -143.5 t-214.5 -93t-257.5 -33h-721zM584 127q144 0 257 58.5t191 164t119 251.5t41 321q0 107 -25 185.5t-72 129.5t-113.5 75.5t-148.5 24.5h-86l-108 -510h258l-27 -125h-258l-121 -575h93z" />-<glyph glyph-name="Ntilde" unicode="Ñ" horiz-adv-x="1614" d="M1094 0l-555 1159l-193 -917q-5 -23 -7.5 -39.5t-2.5 -24.5q0 -22 11 -35.5t31 -21t47.5 -10t59.5 -2.5h7l-23 -109h-516l22 109h27q32 0 62.5 4t55.5 17t43.5 37t26.5 64l209 983q13 53 13 70q0 22 -11 36t-30.5 21.5t-47 10t-59.5 2.5h-26l22 108h455l499 -1050 l172 809q11 47 11 63q0 22 -11.5 36t-31.5 21.5t-47.5 10t-59.5 2.5h-6l23 108h516l-23 -108h-26q-32 0 -62.5 -4.5t-55.5 -17.5t-43.5 -37t-27.5 -64l-260 -1231h-188zM1247 1774q26 0 45 9t32.5 24t22 34.5t13.5 40.5h123q-11 -49 -33.5 -102.5t-56 -98t-78 -73.5 t-99.5 -29t-99 20t-81 44.5l-69.5 44.5t-65.5 20q-21 0 -40 -9t-34 -24t-25 -34.5t-14 -40.5h-122q9 46 32.5 99.5t59.5 98.5t82.5 75t101.5 30q54 0 96.5 -20t78 -44.5l67 -44.5t63.5 -20z" />-<glyph glyph-name="Ograve" unicode="Ò" horiz-adv-x="1622" d="M129 545q0 98 24.5 206t73.5 212.5t122 199t169.5 166t217.5 114t265 42.5q113 0 215.5 -33t180.5 -102t124 -175t46 -253q0 -94 -23.5 -201t-71 -212t-119 -201t-168 -169.5t-218 -117.5t-267.5 -44q-121 0 -225.5 36.5t-181 108.5t-120.5 178t-44 245zM463 496 q0 -101 21.5 -173.5t59 -119t88.5 -68.5t109 -22q85 0 154.5 41t124.5 109.5t95.5 157t67 183.5t39.5 189.5t13 175.5q0 100 -21 172t-57.5 119t-87.5 69.5t-111 22.5q-85 0 -154.5 -41t-124.5 -109.5t-96 -157t-67.5 -184t-39.5 -190t-13 -174.5zM983 1579q-44 26 -93.5 66 t-95.5 83.5t-84.5 85.5t-59.5 72l6 21h301q12 -37 31.5 -80t40.5 -84t42.5 -77t38.5 -60l-6 -27h-121z" />-<glyph glyph-name="Oacute" unicode="Ó" horiz-adv-x="1622" d="M129 545q0 98 24.5 206t73.5 212.5t122 199t169.5 166t217.5 114t265 42.5q113 0 215.5 -33t180.5 -102t124 -175t46 -253q0 -94 -23.5 -201t-71 -212t-119 -201t-168 -169.5t-218 -117.5t-267.5 -44q-121 0 -225.5 36.5t-181 108.5t-120.5 178t-44 245zM463 496 q0 -101 21.5 -173.5t59 -119t88.5 -68.5t109 -22q85 0 154.5 41t124.5 109.5t95.5 157t67 183.5t39.5 189.5t13 175.5q0 100 -21 172t-57.5 119t-87.5 69.5t-111 22.5q-85 0 -154.5 -41t-124.5 -109.5t-96 -157t-67.5 -184t-39.5 -190t-13 -174.5zM881 1606q28 29 61 67 l65.5 78.5l63 80.5t54.5 75h321l-6 -21q-16 -18 -44 -42.5t-63.5 -52.5l-76 -57.5l-82.5 -57.5l-83 -53.5t-77 -43.5h-139z" />-<glyph glyph-name="Ocircumflex" unicode="Ô" horiz-adv-x="1622" d="M129 545q0 98 24.5 206t73.5 212.5t122 199t169.5 166t217.5 114t265 42.5q113 0 215.5 -33t180.5 -102t124 -175t46 -253q0 -94 -23.5 -201t-71 -212t-119 -201t-168 -169.5t-218 -117.5t-267.5 -44q-121 0 -225.5 36.5t-181 108.5t-120.5 178t-44 245zM463 496 q0 -101 21.5 -173.5t59 -119t88.5 -68.5t109 -22q85 0 154.5 41t124.5 109.5t95.5 157t67 183.5t39.5 189.5t13 175.5q0 100 -21 172t-57.5 119t-87.5 69.5t-111 22.5q-85 0 -154.5 -41t-124.5 -109.5t-96 -157t-67.5 -184t-39.5 -190t-13 -174.5zM652 1606l68 66.5l78 77.5 l78 80.5t67 76.5h276q13 -32 33 -72l42.5 -81l44.5 -80t40 -68l-6 -27h-129l-42 40l-49 48l-49.5 50t-42.5 46l-61 -46l-69 -50l-68 -48l-58 -40h-159z" />-<glyph glyph-name="Otilde" unicode="Õ" horiz-adv-x="1622" d="M129 545q0 98 24.5 206t73.5 212.5t122 199t169.5 166t217.5 114t265 42.5q113 0 215.5 -33t180.5 -102t124 -175t46 -253q0 -94 -23.5 -201t-71 -212t-119 -201t-168 -169.5t-218 -117.5t-267.5 -44q-121 0 -225.5 36.5t-181 108.5t-120.5 178t-44 245zM463 496 q0 -101 21.5 -173.5t59 -119t88.5 -68.5t109 -22q85 0 154.5 41t124.5 109.5t95.5 157t67 183.5t39.5 189.5t13 175.5q0 100 -21 172t-57.5 119t-87.5 69.5t-111 22.5q-85 0 -154.5 -41t-124.5 -109.5t-96 -157t-67.5 -184t-39.5 -190t-13 -174.5zM1184 1774q26 0 45 9 t32.5 24t22 34.5t13.5 40.5h123q-11 -49 -33.5 -102.5t-56 -98t-78 -73.5t-99.5 -29t-99 20t-81 44.5l-69.5 44.5t-65.5 20q-21 0 -40 -9t-34 -24t-25 -34.5t-14 -40.5h-122q9 46 32.5 99.5t59.5 98.5t82.5 75t101.5 30q54 0 96.5 -20t78 -44.5l67 -44.5t63.5 -20z" />-<glyph glyph-name="Odieresis" unicode="Ö" horiz-adv-x="1622" d="M129 545q0 98 24.5 206t73.5 212.5t122 199t169.5 166t217.5 114t265 42.5q113 0 215.5 -33t180.5 -102t124 -175t46 -253q0 -94 -23.5 -201t-71 -212t-119 -201t-168 -169.5t-218 -117.5t-267.5 -44q-121 0 -225.5 36.5t-181 108.5t-120.5 178t-44 245zM463 496 q0 -101 21.5 -173.5t59 -119t88.5 -68.5t109 -22q85 0 154.5 41t124.5 109.5t95.5 157t67 183.5t39.5 189.5t13 175.5q0 100 -21 172t-57.5 119t-87.5 69.5t-111 22.5q-85 0 -154.5 -41t-124.5 -109.5t-96 -157t-67.5 -184t-39.5 -190t-13 -174.5zM942 1755q0 -40 -11 -67 t-28 -44t-39 -24.5t-44 -7.5q-51 0 -82 27.5t-31 84.5q0 66 33 105t90 39q49 0 80.5 -28t31.5 -85zM1370 1755q0 -40 -11 -67t-28 -44t-39 -24.5t-44 -7.5q-51 0 -82 27.5t-31 84.5q0 66 33 105t90 39q49 0 80.5 -28t31.5 -85z" />-<glyph glyph-name="multiply" unicode="×" d="M604 631l-301 -303l-104 104l301 303l-301 301l104 103l301 -301l301 303l105 -105l-303 -303l303 -303l-103 -102z" />-<glyph glyph-name="Oslash" unicode="Ø" horiz-adv-x="1622" d="M129 545q0 98 24.5 206t73.5 212.5t122 199t169.5 166t217.5 114t265 42.5q162 0 295 -68l97 129h143l-145 -192q80 -69 128 -176t48 -256q0 -94 -23.5 -201t-71 -212t-119 -201t-168 -169.5t-218 -117.5t-267.5 -44q-81 0 -154 17t-138 49l-93 -125h-145l143 186 q-86 73 -135 182.5t-49 258.5zM463 496q0 -96 18 -166l686 915q-37 54 -90.5 80.5t-118.5 26.5q-85 0 -154.5 -41t-124.5 -109.5t-96 -157t-67.5 -184t-39.5 -190t-13 -174.5zM1235 969q0 43 -4 82t-12 71l-682 -909q37 -53 89.5 -76.5t114.5 -23.5q85 0 154.5 41 t124.5 109.5t95.5 157t67 183.5t39.5 189.5t13 175.5z" />-<glyph glyph-name="Ugrave" unicode="Ù" horiz-adv-x="1530" d="M772 125q93 0 157 27t107 74.5t69 113t42 143.5l158 740q3 17 5.5 35t2.5 26q0 22 -11 36t-30.5 21.5t-47 10t-59.5 2.5h-26l22 108h539l-23 -108h-26q-33 0 -64 -4.5t-57 -17.5t-44.5 -38t-27.5 -65l-164 -778q-24 -115 -71.5 -203t-121 -147.5t-173.5 -90t-230 -30.5 q-120 0 -212 23.5t-155 69t-95.5 111.5t-32.5 150q0 16 2 40t5 49.5t7 50t8 42.5l150 698q12 55 12 70q0 22 -11 36t-30.5 21.5t-46.5 10t-59 2.5h-27l23 108h708l-22 -108h-27q-34 0 -65.5 -4.5t-57.5 -17t-44.5 -36.5t-27.5 -63l-155 -721l-8.5 -40t-8 -45.5t-6 -47.5 t-2.5 -45q0 -103 64 -156t188 -53zM1015 1579q-44 26 -93.5 66t-95.5 83.5t-84.5 85.5t-59.5 72l6 21h301q12 -37 31.5 -80t40.5 -84t42.5 -77t38.5 -60l-6 -27h-121z" />-<glyph glyph-name="Uacute" unicode="Ú" horiz-adv-x="1530" d="M772 125q93 0 157 27t107 74.5t69 113t42 143.5l158 740q3 17 5.5 35t2.5 26q0 22 -11 36t-30.5 21.5t-47 10t-59.5 2.5h-26l22 108h539l-23 -108h-26q-33 0 -64 -4.5t-57 -17.5t-44.5 -38t-27.5 -65l-164 -778q-24 -115 -71.5 -203t-121 -147.5t-173.5 -90t-230 -30.5 q-120 0 -212 23.5t-155 69t-95.5 111.5t-32.5 150q0 16 2 40t5 49.5t7 50t8 42.5l150 698q12 55 12 70q0 22 -11 36t-30.5 21.5t-46.5 10t-59 2.5h-27l23 108h708l-22 -108h-27q-34 0 -65.5 -4.5t-57.5 -17t-44.5 -36.5t-27.5 -63l-155 -721l-8.5 -40t-8 -45.5t-6 -47.5 t-2.5 -45q0 -103 64 -156t188 -53zM914 1606q28 29 61 67l65.5 78.5l63 80.5t54.5 75h321l-6 -21q-16 -18 -44 -42.5t-63.5 -52.5l-76 -57.5l-82.5 -57.5l-83 -53.5t-77 -43.5h-139z" />-<glyph glyph-name="Ucircumflex" unicode="Û" horiz-adv-x="1530" d="M772 125q93 0 157 27t107 74.5t69 113t42 143.5l158 740q3 17 5.5 35t2.5 26q0 22 -11 36t-30.5 21.5t-47 10t-59.5 2.5h-26l22 108h539l-23 -108h-26q-33 0 -64 -4.5t-57 -17.5t-44.5 -38t-27.5 -65l-164 -778q-24 -115 -71.5 -203t-121 -147.5t-173.5 -90t-230 -30.5 q-120 0 -212 23.5t-155 69t-95.5 111.5t-32.5 150q0 16 2 40t5 49.5t7 50t8 42.5l150 698q12 55 12 70q0 22 -11 36t-30.5 21.5t-46.5 10t-59 2.5h-27l23 108h708l-22 -108h-27q-34 0 -65.5 -4.5t-57.5 -17t-44.5 -36.5t-27.5 -63l-155 -721l-8.5 -40t-8 -45.5t-6 -47.5 t-2.5 -45q0 -103 64 -156t188 -53zM697 1606l68 66.5l78 77.5l78 80.5t67 76.5h276q13 -32 33 -72l42.5 -81l44.5 -80t40 -68l-6 -27h-129l-42 40l-49 48l-49.5 50t-42.5 46l-61 -46l-69 -50l-68 -48l-58 -40h-159z" />-<glyph glyph-name="Udieresis" unicode="Ü" horiz-adv-x="1530" d="M772 125q93 0 157 27t107 74.5t69 113t42 143.5l158 740q3 17 5.5 35t2.5 26q0 22 -11 36t-30.5 21.5t-47 10t-59.5 2.5h-26l22 108h539l-23 -108h-26q-33 0 -64 -4.5t-57 -17.5t-44.5 -38t-27.5 -65l-164 -778q-24 -115 -71.5 -203t-121 -147.5t-173.5 -90t-230 -30.5 q-120 0 -212 23.5t-155 69t-95.5 111.5t-32.5 150q0 16 2 40t5 49.5t7 50t8 42.5l150 698q12 55 12 70q0 22 -11 36t-30.5 21.5t-46.5 10t-59 2.5h-27l23 108h708l-22 -108h-27q-34 0 -65.5 -4.5t-57.5 -17t-44.5 -36.5t-27.5 -63l-155 -721l-8.5 -40t-8 -45.5t-6 -47.5 t-2.5 -45q0 -103 64 -156t188 -53zM956 1755q0 -40 -11 -67t-28 -44t-39 -24.5t-44 -7.5q-51 0 -82 27.5t-31 84.5q0 66 33 105t90 39q49 0 80.5 -28t31.5 -85zM1384 1755q0 -40 -11 -67t-28 -44t-39 -24.5t-44 -7.5q-51 0 -82 27.5t-31 84.5q0 66 33 105t90 39 q49 0 80.5 -28t31.5 -85z" />-<glyph glyph-name="Yacute" unicode="Ý" horiz-adv-x="1419" d="M227 0l23 109h55q32 0 62 4.5t55 18t43 37.5t27 64l59 285l-221 727q-9 30 -18.5 51t-22.5 33.5t-32.5 18.5t-49.5 6h-27l23 108h665l-22 -108h-92q-47 0 -66.5 -16t-19.5 -44q0 -18 6 -52t18 -81l74 -289l15 -64t14.5 -68t11 -66.5t6.5 -57.5q19 41 58 104t92 138 l202 289q25 35 43.5 76t18.5 75q0 28 -28.5 42t-96.5 14h-43l24 108h537l-23 -108h-38q-23 0 -42.5 -6t-39.5 -21.5t-42.5 -41t-50.5 -64.5l-507 -705l-62 -291q-2 -11 -5 -25t-3 -22q0 -22 11 -35.5t31 -21t47.5 -10t59.5 -2.5h50l-23 -109h-756zM832 1606q28 29 61 67 l65.5 78.5l63 80.5t54.5 75h321l-6 -21q-16 -18 -44 -42.5t-63.5 -52.5l-76 -57.5l-82.5 -57.5l-83 -53.5t-77 -43.5h-139z" />-<glyph glyph-name="Thorn" unicode="Þ" horiz-adv-x="1307" d="M-47 0l22 109h27q32 0 62.5 4t55.5 17t43.5 37t26.5 64l213 1006q4 11 6.5 25t2.5 22q0 22 -11 36t-30.5 21.5t-47 10t-59.5 2.5h-26l22 108h707l-23 -108h-26q-34 0 -65.5 -4.5t-57.5 -17.5t-45 -38t-27 -65l-6 -31h67q121 0 217.5 -25.5t164.5 -76t104.5 -125 t36.5 -172.5q0 -52 -10 -110t-35.5 -114t-70.5 -106.5t-114.5 -89t-167.5 -61.5t-229 -23h-154l-14 -70q-2 -11 -5 -25t-3 -22q0 -22 11 -35.5t31 -21t47.5 -10t59.5 -2.5h27l-23 -109h-704zM551 414h84q93 0 157.5 28t104.5 78t58 120t18 153q0 76 -17.5 130t-49 88.5 t-75 50t-94.5 15.5h-45z" />-<glyph glyph-name="germandbls" unicode="ß" horiz-adv-x="1423" d="M895 1460q-55 0 -97.5 -19.5t-74 -56t-53 -89t-33.5 -119.5l-256 -1192q-20 -94 -54.5 -166t-78 -125t-94.5 -88.5t-105 -57t-109.5 -30.5t-107.5 -9q-40 0 -75.5 6.5t-55.5 14.5l29 113q17 -5 40.5 -9t55.5 -4q45 0 81 19.5t65.5 62t53.5 109.5t46 162l207 976h-166 l26 140h170l17 63q27 116 81.5 195.5t130.5 129t170 71.5t201 22q111 0 194 -23t138.5 -64.5t83 -100.5t27.5 -131q0 -26 -3.5 -58.5t-8.5 -66.5t-11.5 -67.5t-13.5 -61.5h-117q-61 0 -105 -12t-72.5 -33.5t-42 -51.5t-13.5 -67q0 -29 8.5 -53t28.5 -47t54.5 -47.5 t86.5 -55.5q99 -57 141.5 -129.5t42.5 -171.5q0 -81 -32.5 -147t-92 -113t-143 -72.5t-185.5 -25.5q-77 0 -140 15t-108 44t-69.5 70t-24.5 94q0 40 15 71.5t43 53t66.5 32.5t86.5 11q0 -48 8 -95t26.5 -85t48 -61.5t71.5 -23.5q68 0 103.5 45t35.5 119q0 32 -6 60.5 t-23 55.5t-47.5 53t-78.5 55q-96 55 -149.5 131t-53.5 176q0 83 31.5 142.5t83 98t116.5 56.5t133 18q10 47 15.5 85.5t5.5 72.5q0 84 -41 135t-127 51z" />-<glyph glyph-name="agrave" unicode="à" horiz-adv-x="1362" d="M969 -20q-91 0 -146 41.5t-55 121.5q0 32 3 62.5t11 70.5h-14q-38 -71 -75.5 -126.5t-83.5 -93t-102.5 -57t-131.5 -19.5q-73 0 -128 28.5t-91.5 78.5t-55 118.5t-18.5 148.5t18.5 167.5t55.5 172.5t91.5 162t126 135t159.5 92.5t192 34.5q40 0 81.5 -6.5t79.5 -16.5 t71.5 -22.5t56.5 -25.5l108 49h109l-135 -639q-4 -17 -10 -45t-11.5 -59.5t-9.5 -61t-4 -49.5q0 -51 20 -76t52 -25q29 0 55.5 12.5t58.5 30.5l45 -82q-26 -20 -59 -41.5t-73.5 -39.5t-88 -29.5t-102.5 -11.5zM393 352q0 -59 8.5 -99t23 -64.5t35 -35t44.5 -10.5q45 0 93 39 t90 102.5t75.5 143.5t50.5 162l72 340q-20 29 -55.5 43t-75.5 14q-60 0 -109.5 -31t-89.5 -83t-70.5 -118t-51 -137t-30.5 -140t-10 -126zM758 1241q-44 26 -93.5 66t-95.5 83.5t-84.5 85.5t-59.5 72l6 21h301q12 -37 31.5 -80t40.5 -84t42.5 -77t38.5 -60l-6 -27h-121z" />-<glyph glyph-name="aacute" unicode="á" horiz-adv-x="1362" d="M969 -20q-91 0 -146 41.5t-55 121.5q0 32 3 62.5t11 70.5h-14q-38 -71 -75.5 -126.5t-83.5 -93t-102.5 -57t-131.5 -19.5q-73 0 -128 28.5t-91.5 78.5t-55 118.5t-18.5 148.5t18.5 167.5t55.5 172.5t91.5 162t126 135t159.5 92.5t192 34.5q40 0 81.5 -6.5t79.5 -16.5 t71.5 -22.5t56.5 -25.5l108 49h109l-135 -639q-4 -17 -10 -45t-11.5 -59.5t-9.5 -61t-4 -49.5q0 -51 20 -76t52 -25q29 0 55.5 12.5t58.5 30.5l45 -82q-26 -20 -59 -41.5t-73.5 -39.5t-88 -29.5t-102.5 -11.5zM393 352q0 -59 8.5 -99t23 -64.5t35 -35t44.5 -10.5q45 0 93 39 t90 102.5t75.5 143.5t50.5 162l72 340q-20 29 -55.5 43t-75.5 14q-60 0 -109.5 -31t-89.5 -83t-70.5 -118t-51 -137t-30.5 -140t-10 -126zM637 1268q28 29 61 67l65.5 78.5l63 80.5t54.5 75h321l-6 -21q-16 -18 -44 -42.5t-63.5 -52.5l-76 -57.5l-82.5 -57.5l-83 -53.5 t-77 -43.5h-139z" />-<glyph glyph-name="acircumflex" unicode="â" horiz-adv-x="1362" d="M969 -20q-91 0 -146 41.5t-55 121.5q0 32 3 62.5t11 70.5h-14q-38 -71 -75.5 -126.5t-83.5 -93t-102.5 -57t-131.5 -19.5q-73 0 -128 28.5t-91.5 78.5t-55 118.5t-18.5 148.5t18.5 167.5t55.5 172.5t91.5 162t126 135t159.5 92.5t192 34.5q40 0 81.5 -6.5t79.5 -16.5 t71.5 -22.5t56.5 -25.5l108 49h109l-135 -639q-4 -17 -10 -45t-11.5 -59.5t-9.5 -61t-4 -49.5q0 -51 20 -76t52 -25q29 0 55.5 12.5t58.5 30.5l45 -82q-26 -20 -59 -41.5t-73.5 -39.5t-88 -29.5t-102.5 -11.5zM393 352q0 -59 8.5 -99t23 -64.5t35 -35t44.5 -10.5q45 0 93 39 t90 102.5t75.5 143.5t50.5 162l72 340q-20 29 -55.5 43t-75.5 14q-60 0 -109.5 -31t-89.5 -83t-70.5 -118t-51 -137t-30.5 -140t-10 -126zM389 1268l68 66.5l78 77.5l78 80.5t67 76.5h276q13 -32 33 -72l42.5 -81l44.5 -80t40 -68l-6 -27h-129l-42 40l-49 48l-49.5 50 t-42.5 46l-61 -46l-69 -50l-68 -48l-58 -40h-159z" />-<glyph glyph-name="atilde" unicode="ã" horiz-adv-x="1362" d="M969 -20q-91 0 -146 41.5t-55 121.5q0 32 3 62.5t11 70.5h-14q-38 -71 -75.5 -126.5t-83.5 -93t-102.5 -57t-131.5 -19.5q-73 0 -128 28.5t-91.5 78.5t-55 118.5t-18.5 148.5t18.5 167.5t55.5 172.5t91.5 162t126 135t159.5 92.5t192 34.5q40 0 81.5 -6.5t79.5 -16.5 t71.5 -22.5t56.5 -25.5l108 49h109l-135 -639q-4 -17 -10 -45t-11.5 -59.5t-9.5 -61t-4 -49.5q0 -51 20 -76t52 -25q29 0 55.5 12.5t58.5 30.5l45 -82q-26 -20 -59 -41.5t-73.5 -39.5t-88 -29.5t-102.5 -11.5zM393 352q0 -59 8.5 -99t23 -64.5t35 -35t44.5 -10.5q45 0 93 39 t90 102.5t75.5 143.5t50.5 162l72 340q-20 29 -55.5 43t-75.5 14q-60 0 -109.5 -31t-89.5 -83t-70.5 -118t-51 -137t-30.5 -140t-10 -126zM979 1436q26 0 45 9t32.5 24t22 34.5t13.5 40.5h123q-11 -49 -33.5 -102.5t-56 -98t-78 -73.5t-99.5 -29t-99 20t-81 44.5l-69.5 44.5 t-65.5 20q-21 0 -40 -9t-34 -24t-25 -34.5t-14 -40.5h-122q9 46 32.5 99.5t59.5 98.5t82.5 75t101.5 30q54 0 96.5 -20t78 -44.5l67 -44.5t63.5 -20z" />-<glyph glyph-name="adieresis" unicode="ä" horiz-adv-x="1362" d="M969 -20q-91 0 -146 41.5t-55 121.5q0 32 3 62.5t11 70.5h-14q-38 -71 -75.5 -126.5t-83.5 -93t-102.5 -57t-131.5 -19.5q-73 0 -128 28.5t-91.5 78.5t-55 118.5t-18.5 148.5t18.5 167.5t55.5 172.5t91.5 162t126 135t159.5 92.5t192 34.5q40 0 81.5 -6.5t79.5 -16.5 t71.5 -22.5t56.5 -25.5l108 49h109l-135 -639q-4 -17 -10 -45t-11.5 -59.5t-9.5 -61t-4 -49.5q0 -51 20 -76t52 -25q29 0 55.5 12.5t58.5 30.5l45 -82q-26 -20 -59 -41.5t-73.5 -39.5t-88 -29.5t-102.5 -11.5zM393 352q0 -59 8.5 -99t23 -64.5t35 -35t44.5 -10.5q45 0 93 39 t90 102.5t75.5 143.5t50.5 162l72 340q-20 29 -55.5 43t-75.5 14q-60 0 -109.5 -31t-89.5 -83t-70.5 -118t-51 -137t-30.5 -140t-10 -126zM659 1417q0 -40 -11 -67t-28 -44t-39 -24.5t-44 -7.5q-51 0 -82 27.5t-31 84.5q0 66 33 105t90 39q49 0 80.5 -28t31.5 -85z M1087 1417q0 -40 -11 -67t-28 -44t-39 -24.5t-44 -7.5q-51 0 -82 27.5t-31 84.5q0 66 33 105t90 39q49 0 80.5 -28t31.5 -85z" />-<glyph glyph-name="aring" unicode="å" horiz-adv-x="1362" d="M924 1432q0 30 -10 50.5t-26.5 34t-38.5 19.5t-46 6q-25 0 -47 -6t-38.5 -19.5t-26 -34t-9.5 -50.5t9.5 -51t26 -34.5t38.5 -19.5t47 -6q24 0 46 6t38.5 19.5t26.5 34.5t10 51zM1049 1432q0 -55 -19 -96.5t-52.5 -70t-78.5 -43t-96 -14.5t-96 14.5t-78.5 43t-52.5 70 t-19 96.5q0 54 19 96t52.5 70t78.5 42.5t96 14.5t96 -14.5t78.5 -42.5t52.5 -70t19 -96zM969 -20q-91 0 -146 41.5t-55 121.5q0 32 3 62.5t11 70.5h-14q-38 -71 -75.5 -126.5t-83.5 -93t-102.5 -57t-131.5 -19.5q-73 0 -128 28.5t-91.5 78.5t-55 118.5t-18.5 148.5 t18.5 167.5t55.5 172.5t91.5 162t126 135t159.5 92.5t192 34.5q40 0 81.5 -6.5t79.5 -16.5t71.5 -22.5t56.5 -25.5l108 49h109l-135 -639q-4 -17 -10 -45t-11.5 -59.5t-9.5 -61t-4 -49.5q0 -51 20 -76t52 -25q29 0 55.5 12.5t58.5 30.5l45 -82q-26 -20 -59 -41.5 t-73.5 -39.5t-88 -29.5t-102.5 -11.5zM393 352q0 -59 8.5 -99t23 -64.5t35 -35t44.5 -10.5q45 0 93 39t90 102.5t75.5 143.5t50.5 162l72 340q-20 29 -55.5 43t-75.5 14q-60 0 -109.5 -31t-89.5 -83t-70.5 -118t-51 -137t-30.5 -140t-10 -126z" />-<glyph glyph-name="ae" unicode="æ" horiz-adv-x="1802" d="M1325 135q50 0 95 14.5t83 38t69 53t52 60.5q12 -15 19.5 -38.5t7.5 -49.5q0 -37 -24 -78t-73.5 -75.5t-126.5 -57t-182 -22.5q-187 0 -299.5 75t-150.5 221h-19q-38 -71 -77 -126.5t-86 -93t-105 -57t-133 -19.5q-73 0 -128 28.5t-91.5 78.5t-55 118.5t-18.5 148.5 t18.5 167.5t55.5 172.5t91.5 162t126 135t159.5 92.5t192 34.5q103 0 186 -31.5t154 -93.5q80 62 181.5 95.5t226.5 33.5q154 0 236.5 -63.5t82.5 -177.5q0 -83 -46 -159.5t-127 -135t-193 -93t-244 -34.5h-37.5t-38.5 2q-3 -20 -3.5 -40.5t-0.5 -37.5q0 -119 55.5 -183.5 t167.5 -64.5zM393 352q0 -59 8.5 -99t23 -64.5t35 -35t44.5 -10.5q37 0 77 28t77.5 74.5t70.5 108t57 129.5q10 53 24 107.5t33.5 106.5t44 101t56.5 91q-10 15 -29.5 32t-45 31.5t-55 24.5t-60.5 10q-60 0 -109.5 -31t-89.5 -83t-70.5 -118t-51 -137t-30.5 -140t-10 -126z M1397 999q-33 0 -64.5 -17.5t-59.5 -48t-52 -71.5t-44 -88t-34.5 -97t-22.5 -99h19q76 0 141.5 21t113.5 58.5t75.5 90.5t27.5 118q0 63 -26 98t-74 35z" />-<glyph glyph-name="ccedilla" unicode="ç" horiz-adv-x="1081" d="M621 135q54 0 97.5 14.5t78.5 38.5t62 54t48 61q13 -12 22 -35.5t9 -54.5q0 -40 -23.5 -81.5t-73 -75t-125.5 -55t-181 -21.5q-93 0 -175.5 26t-144 79t-97.5 132t-36 186q0 76 19 158.5t57 162t94.5 151t131.5 125.5t168 86t204 32q85 0 146.5 -17.5t101 -47t58.5 -69 t19 -83.5q0 -32 -13.5 -62.5t-43.5 -55t-77.5 -39.5t-115.5 -15q0 54 -9.5 102.5t-28.5 85t-46.5 57.5t-64.5 21q-44 0 -82.5 -29.5t-70.5 -78t-56.5 -111t-42 -129t-26.5 -131t-9 -117.5q0 -125 57 -194.5t169 -69.5zM487 -70q41 -5 74.5 -20t57.5 -37.5t37 -51t13 -61.5 q0 -66 -30.5 -113.5t-81.5 -78.5t-116.5 -45.5t-135.5 -14.5q-32 0 -75.5 7.5t-78.5 19.5l27 121q35 -8 70 -12t63 -4q31 0 59 6t48 18.5t32 32t12 47.5q0 20 -10.5 36.5t-28.5 29t-43 19.5t-53 9l82 180h117z" />-<glyph glyph-name="egrave" unicode="è" horiz-adv-x="1096" d="M618 135q50 0 95 14.5t83.5 38t69 53t52.5 60.5q12 -15 19 -38.5t7 -49.5q0 -37 -24 -78t-73.5 -75.5t-126 -57t-181.5 -22.5q-94 0 -177 26t-145.5 78.5t-98.5 132t-36 186.5q0 76 20 159t59.5 162.5t98 151t135.5 125.5t172.5 85.5t208.5 31.5q147 0 228 -63.5 t81 -177.5q0 -83 -46 -159t-127 -133.5t-193 -91.5t-244 -34h-37t-39 2q-3 -20 -3.5 -40.5t-0.5 -37.5q0 -119 55.5 -183.5t167.5 -64.5zM690 995q-50 0 -95 -37.5t-81 -97.5t-62 -134.5t-38 -147.5h18q76 0 141.5 20t114 57.5t76 89.5t27.5 117q0 63 -26.5 98t-74.5 35z M762 1241q-44 26 -93.5 66t-95.5 83.5t-84.5 85.5t-59.5 72l6 21h301q12 -37 31.5 -80t40.5 -84t42.5 -77t38.5 -60l-6 -27h-121z" />-<glyph glyph-name="eacute" unicode="é" horiz-adv-x="1096" d="M618 135q50 0 95 14.5t83.5 38t69 53t52.5 60.5q12 -15 19 -38.5t7 -49.5q0 -37 -24 -78t-73.5 -75.5t-126 -57t-181.5 -22.5q-94 0 -177 26t-145.5 78.5t-98.5 132t-36 186.5q0 76 20 159t59.5 162.5t98 151t135.5 125.5t172.5 85.5t208.5 31.5q147 0 228 -63.5 t81 -177.5q0 -83 -46 -159t-127 -133.5t-193 -91.5t-244 -34h-37t-39 2q-3 -20 -3.5 -40.5t-0.5 -37.5q0 -119 55.5 -183.5t167.5 -64.5zM690 995q-50 0 -95 -37.5t-81 -97.5t-62 -134.5t-38 -147.5h18q76 0 141.5 20t114 57.5t76 89.5t27.5 117q0 63 -26.5 98t-74.5 35z M633 1268q28 29 61 67l65.5 78.5l63 80.5t54.5 75h321l-6 -21q-16 -18 -44 -42.5t-63.5 -52.5l-76 -57.5l-82.5 -57.5l-83 -53.5t-77 -43.5h-139z" />-<glyph glyph-name="ecircumflex" unicode="ê" horiz-adv-x="1096" d="M618 135q50 0 95 14.5t83.5 38t69 53t52.5 60.5q12 -15 19 -38.5t7 -49.5q0 -37 -24 -78t-73.5 -75.5t-126 -57t-181.5 -22.5q-94 0 -177 26t-145.5 78.5t-98.5 132t-36 186.5q0 76 20 159t59.5 162.5t98 151t135.5 125.5t172.5 85.5t208.5 31.5q147 0 228 -63.5 t81 -177.5q0 -83 -46 -159t-127 -133.5t-193 -91.5t-244 -34h-37t-39 2q-3 -20 -3.5 -40.5t-0.5 -37.5q0 -119 55.5 -183.5t167.5 -64.5zM690 995q-50 0 -95 -37.5t-81 -97.5t-62 -134.5t-38 -147.5h18q76 0 141.5 20t114 57.5t76 89.5t27.5 117q0 63 -26.5 98t-74.5 35z M371 1268l68 66.5l78 77.5l78 80.5t67 76.5h276q13 -32 33 -72l42.5 -81l44.5 -80t40 -68l-6 -27h-129l-42 40l-49 48l-49.5 50t-42.5 46l-61 -46l-69 -50l-68 -48l-58 -40h-159z" />-<glyph glyph-name="edieresis" unicode="ë" horiz-adv-x="1096" d="M618 135q50 0 95 14.5t83.5 38t69 53t52.5 60.5q12 -15 19 -38.5t7 -49.5q0 -37 -24 -78t-73.5 -75.5t-126 -57t-181.5 -22.5q-94 0 -177 26t-145.5 78.5t-98.5 132t-36 186.5q0 76 20 159t59.5 162.5t98 151t135.5 125.5t172.5 85.5t208.5 31.5q147 0 228 -63.5 t81 -177.5q0 -83 -46 -159t-127 -133.5t-193 -91.5t-244 -34h-37t-39 2q-3 -20 -3.5 -40.5t-0.5 -37.5q0 -119 55.5 -183.5t167.5 -64.5zM690 995q-50 0 -95 -37.5t-81 -97.5t-62 -134.5t-38 -147.5h18q76 0 141.5 20t114 57.5t76 89.5t27.5 117q0 63 -26.5 98t-74.5 35z M634 1417q0 -40 -11 -67t-28 -44t-39 -24.5t-44 -7.5q-51 0 -82 27.5t-31 84.5q0 66 33 105t90 39q49 0 80.5 -28t31.5 -85zM1062 1417q0 -40 -11 -67t-28 -44t-39 -24.5t-44 -7.5q-51 0 -82 27.5t-31 84.5q0 66 33 105t90 39q49 0 80.5 -28t31.5 -85z" />-<glyph glyph-name="igrave" unicode="ì" horiz-adv-x="725" d="M621 1098l-142 -666q-12 -57 -20 -104q-8 -49 -8 -92q0 -45 18.5 -69t52.5 -24q32 0 59 12t62 35l49 -69q-24 -26 -55 -51.5t-70 -45.5t-85.5 -32t-102.5 -12q-118 0 -181 55.5t-63 154.5q0 44 8 105q7 53 25 139l80 381q3 13 5.5 27t4.5 26.5t3 21.5t1 13q0 28 -6 45 t-19 26t-33.5 12t-49.5 3h-37l18 109h486zM457 1241q-44 26 -93.5 66t-95.5 83.5t-84.5 85.5t-59.5 72l6 21h301q12 -37 31.5 -80t40.5 -84t42.5 -77t38.5 -60l-6 -27h-121z" />-<glyph glyph-name="iacute" unicode="í" horiz-adv-x="725" d="M621 1098l-142 -666q-12 -57 -20 -104q-8 -49 -8 -92q0 -45 18.5 -69t52.5 -24q32 0 59 12t62 35l49 -69q-24 -26 -55 -51.5t-70 -45.5t-85.5 -32t-102.5 -12q-118 0 -181 55.5t-63 154.5q0 44 8 105q7 53 25 139l80 381q3 13 5.5 27t4.5 26.5t3 21.5t1 13q0 28 -6 45 t-19 26t-33.5 12t-49.5 3h-37l18 109h486zM345 1268q28 29 61 67l65.5 78.5l63 80.5t54.5 75h321l-6 -21q-16 -18 -44 -42.5t-63.5 -52.5l-76 -57.5l-82.5 -57.5l-83 -53.5t-77 -43.5h-139z" />-<glyph glyph-name="icircumflex" unicode="î" horiz-adv-x="725" d="M621 1098l-142 -666q-12 -57 -20 -104q-8 -49 -8 -92q0 -45 18.5 -69t52.5 -24q32 0 59 12t62 35l49 -69q-24 -26 -55 -51.5t-70 -45.5t-85.5 -32t-102.5 -12q-118 0 -181 55.5t-63 154.5q0 44 8 105q7 53 25 139l80 381q3 13 5.5 27t4.5 26.5t3 21.5t1 13q0 28 -6 45 t-19 26t-33.5 12t-49.5 3h-37l18 109h486zM69 1268l68 66.5l78 77.5l78 80.5t67 76.5h276q13 -32 33 -72l42.5 -81l44.5 -80t40 -68l-6 -27h-129l-42 40l-49 48l-49.5 50t-42.5 46l-61 -46l-69 -50l-68 -48l-58 -40h-159z" />-<glyph glyph-name="idieresis" unicode="ï" horiz-adv-x="725" d="M621 1098l-142 -666q-12 -57 -20 -104q-8 -49 -8 -92q0 -45 18.5 -69t52.5 -24q32 0 59 12t62 35l49 -69q-24 -26 -55 -51.5t-70 -45.5t-85.5 -32t-102.5 -12q-118 0 -181 55.5t-63 154.5q0 44 8 105q7 53 25 139l80 381q3 13 5.5 27t4.5 26.5t3 21.5t1 13q0 28 -6 45 t-19 26t-33.5 12t-49.5 3h-37l18 109h486zM355 1417q0 -40 -11 -67t-28 -44t-39 -24.5t-44 -7.5q-51 0 -82 27.5t-31 84.5q0 66 33 105t90 39q49 0 80.5 -28t31.5 -85zM783 1417q0 -40 -11 -67t-28 -44t-39 -24.5t-44 -7.5q-51 0 -82 27.5t-31 84.5q0 66 33 105t90 39 q49 0 80.5 -28t31.5 -85z" />-<glyph glyph-name="eth" unicode="ð" horiz-adv-x="1260" d="M82 410q0 63 16.5 135t49.5 143t83 137t118 116t153.5 80t189.5 30q51 0 98.5 -15t79.5 -41q-14 81 -46.5 146t-82.5 119l-321 -150l29 143l194 90q-48 35 -102 59.5t-113 43.5l39 125q91 -22 177.5 -59t164.5 -91l305 140l-31 -146l-170 -78q59 -56 108 -122t84 -144.5 t54 -170.5t19 -200q0 -89 -17.5 -177t-53 -168t-89.5 -148.5t-128 -119t-168.5 -79t-209.5 -28.5q-95 0 -174 26.5t-136 79.5t-88.5 134t-31.5 190zM393 393q0 -69 11.5 -117.5t33 -78.5t52.5 -44t69 -14q55 0 99 25.5t78 68t58.5 97.5t40 114t22.5 117.5t7 108.5 q0 125 -42.5 182t-121.5 57q-56 0 -100.5 -25.5t-78.5 -68t-58 -96.5t-39.5 -111.5t-23 -113.5t-7.5 -101z" />-<glyph glyph-name="ntilde" unicode="ñ" horiz-adv-x="1376" d="M800 190q0 88 33 244l33 162l10 49l12 60.5t10.5 63t4.5 56.5q0 19 -3.5 40t-13 38t-26 28t-43.5 11q-35 0 -69.5 -23.5t-66.5 -62t-61 -88t-52.5 -101.5t-40.5 -102t-26 -90l-104 -475h-307l172 815q3 13 5.5 27t4.5 26.5t3 21.5t1 13q0 28 -6 45t-19 26t-33.5 12 t-49.5 3h-37l18 109h455l-23 -219h13q39 61 80.5 106t89.5 74.5t104.5 44t124.5 14.5q65 0 109.5 -19t72 -51.5t39.5 -75t12 -89.5q0 -26 -3.5 -55.5t-9 -59.5t-12 -60t-11.5 -57l-46 -219q-12 -56 -20 -104.5t-8 -91.5q0 -45 18.5 -69t53.5 -24q32 0 58.5 12t61.5 35 l49 -69q-24 -26 -55 -51.5t-69.5 -45.5t-85.5 -32t-103 -12q-118 0 -181 55.5t-63 154.5zM995 1436q26 0 45 9t32.5 24t22 34.5t13.5 40.5h123q-11 -49 -33.5 -102.5t-56 -98t-78 -73.5t-99.5 -29t-99 20t-81 44.5l-69.5 44.5t-65.5 20q-21 0 -40 -9t-34 -24t-25 -34.5 t-14 -40.5h-122q9 46 32.5 99.5t59.5 98.5t82.5 75t101.5 30q54 0 96.5 -20t78 -44.5l67 -44.5t63.5 -20z" />-<glyph glyph-name="ograve" unicode="ò" horiz-adv-x="1266" d="M82 410q0 77 18 159t54.5 161t91.5 149.5t129.5 123.5t169 84t209.5 31q88 0 166 -24.5t136 -76.5t92 -133.5t34 -195.5q0 -74 -17 -155t-52.5 -159.5t-90 -150t-128.5 -125.5t-168 -86t-210 -32q-96 0 -176 26.5t-137 80t-89 134.5t-32 189zM553 127q55 0 99.5 31.5 t80 83t61.5 117.5t42.5 136t25 137.5t8.5 121.5q0 121 -41.5 174t-113.5 53q-52 0 -96 -30t-79.5 -80t-62.5 -114.5t-45.5 -133.5t-27.5 -137.5t-9 -125.5q0 -125 42 -179t116 -54zM758 1241q-44 26 -93.5 66t-95.5 83.5t-84.5 85.5t-59.5 72l6 21h301q12 -37 31.5 -80 t40.5 -84t42.5 -77t38.5 -60l-6 -27h-121z" />-<glyph glyph-name="oacute" unicode="ó" horiz-adv-x="1266" d="M82 410q0 77 18 159t54.5 161t91.5 149.5t129.5 123.5t169 84t209.5 31q88 0 166 -24.5t136 -76.5t92 -133.5t34 -195.5q0 -74 -17 -155t-52.5 -159.5t-90 -150t-128.5 -125.5t-168 -86t-210 -32q-96 0 -176 26.5t-137 80t-89 134.5t-32 189zM553 127q55 0 99.5 31.5 t80 83t61.5 117.5t42.5 136t25 137.5t8.5 121.5q0 121 -41.5 174t-113.5 53q-52 0 -96 -30t-79.5 -80t-62.5 -114.5t-45.5 -133.5t-27.5 -137.5t-9 -125.5q0 -125 42 -179t116 -54zM674 1268q28 29 61 67l65.5 78.5l63 80.5t54.5 75h321l-6 -21q-16 -18 -44 -42.5 t-63.5 -52.5l-76 -57.5l-82.5 -57.5l-83 -53.5t-77 -43.5h-139z" />-<glyph glyph-name="ocircumflex" unicode="ô" horiz-adv-x="1266" d="M82 410q0 77 18 159t54.5 161t91.5 149.5t129.5 123.5t169 84t209.5 31q88 0 166 -24.5t136 -76.5t92 -133.5t34 -195.5q0 -74 -17 -155t-52.5 -159.5t-90 -150t-128.5 -125.5t-168 -86t-210 -32q-96 0 -176 26.5t-137 80t-89 134.5t-32 189zM553 127q55 0 99.5 31.5 t80 83t61.5 117.5t42.5 136t25 137.5t8.5 121.5q0 121 -41.5 174t-113.5 53q-52 0 -96 -30t-79.5 -80t-62.5 -114.5t-45.5 -133.5t-27.5 -137.5t-9 -125.5q0 -125 42 -179t116 -54zM408 1268l68 66.5l78 77.5l78 80.5t67 76.5h276q13 -32 33 -72l42.5 -81l44.5 -80t40 -68 l-6 -27h-129l-42 40l-49 48l-49.5 50t-42.5 46l-61 -46l-69 -50l-68 -48l-58 -40h-159z" />-<glyph glyph-name="otilde" unicode="õ" horiz-adv-x="1266" d="M82 410q0 77 18 159t54.5 161t91.5 149.5t129.5 123.5t169 84t209.5 31q88 0 166 -24.5t136 -76.5t92 -133.5t34 -195.5q0 -74 -17 -155t-52.5 -159.5t-90 -150t-128.5 -125.5t-168 -86t-210 -32q-96 0 -176 26.5t-137 80t-89 134.5t-32 189zM553 127q55 0 99.5 31.5 t80 83t61.5 117.5t42.5 136t25 137.5t8.5 121.5q0 121 -41.5 174t-113.5 53q-52 0 -96 -30t-79.5 -80t-62.5 -114.5t-45.5 -133.5t-27.5 -137.5t-9 -125.5q0 -125 42 -179t116 -54zM958 1436q26 0 45 9t32.5 24t22 34.5t13.5 40.5h123q-11 -49 -33.5 -102.5t-56 -98 t-78 -73.5t-99.5 -29t-99 20t-81 44.5l-69.5 44.5t-65.5 20q-21 0 -40 -9t-34 -24t-25 -34.5t-14 -40.5h-122q9 46 32.5 99.5t59.5 98.5t82.5 75t101.5 30q54 0 96.5 -20t78 -44.5l67 -44.5t63.5 -20z" />-<glyph glyph-name="odieresis" unicode="ö" horiz-adv-x="1266" d="M82 410q0 77 18 159t54.5 161t91.5 149.5t129.5 123.5t169 84t209.5 31q88 0 166 -24.5t136 -76.5t92 -133.5t34 -195.5q0 -74 -17 -155t-52.5 -159.5t-90 -150t-128.5 -125.5t-168 -86t-210 -32q-96 0 -176 26.5t-137 80t-89 134.5t-32 189zM553 127q55 0 99.5 31.5 t80 83t61.5 117.5t42.5 136t25 137.5t8.5 121.5q0 121 -41.5 174t-113.5 53q-52 0 -96 -30t-79.5 -80t-62.5 -114.5t-45.5 -133.5t-27.5 -137.5t-9 -125.5q0 -125 42 -179t116 -54zM690 1417q0 -40 -11 -67t-28 -44t-39 -24.5t-44 -7.5q-51 0 -82 27.5t-31 84.5q0 66 33 105 t90 39q49 0 80.5 -28t31.5 -85zM1118 1417q0 -40 -11 -67t-28 -44t-39 -24.5t-44 -7.5q-51 0 -82 27.5t-31 84.5q0 66 33 105t90 39q49 0 80.5 -28t31.5 -85z" />-<glyph glyph-name="divide" unicode="÷" d="M492 1120q0 70 32.5 97.5t81.5 27.5q24 0 45 -6.5t36.5 -21t24.5 -38.5t9 -59t-9 -58.5t-24.5 -38.5t-36.5 -21.5t-45 -6.5q-49 0 -81.5 28t-32.5 97zM492 344q0 70 32.5 97.5t81.5 27.5q24 0 45 -6.5t36.5 -21t24.5 -38.5t9 -59t-9 -58.5t-24.5 -38.5t-36.5 -21.5 t-45 -6.5q-49 0 -81.5 28t-32.5 97zM1044 659h-878v146h878v-146z" />-<glyph glyph-name="oslash" unicode="ø" horiz-adv-x="1266" d="M82 410q0 77 18 159t54.5 161t91.5 149.5t129.5 123.5t169 84t209.5 31q60 0 114.5 -11t102.5 -34l92 107h154l-146 -179q51 -52 81 -129.5t30 -183.5q0 -74 -17 -155t-52.5 -159.5t-90 -150t-128.5 -125.5t-168 -86t-210 -32q-137 0 -240 53l-94 -115h-155l153 188 q-48 54 -73 129.5t-25 174.5zM553 127q55 0 99.5 31.5t80 83t61.5 117.5t42.5 136t25 137.5t8.5 121.5v4l-450 -549q20 -43 53.5 -62.5t79.5 -19.5zM715 981q-52 0 -95.5 -29.5t-79 -78t-62.5 -112t-45 -131.5t-28 -135.5t-10 -125.5l443 542q-20 35 -51.5 52.5t-71.5 17.5z " />-<glyph glyph-name="ugrave" unicode="ù" horiz-adv-x="1378" d="M793 176q0 9 1 21t2.5 25t3 24.5t3.5 19.5h-17q-45 -64 -88.5 -116t-92.5 -89.5t-106 -58t-130 -20.5q-66 0 -111 18.5t-72 49.5t-39 72.5t-12 86.5q0 51 12 112l23 113l74 361q8 42 13 76t5 51q0 41 -25 54t-83 13h-37l18 109h492l-135 -613q-6 -24 -12 -54t-11 -60 t-8.5 -57t-3.5 -46q0 -45 19 -71.5t69 -26.5q45 0 92.5 40.5t91 105t77.5 144t52 156.5l109 482h305l-139 -666q-13 -56 -21 -104.5t-8 -91.5q0 -45 18.5 -69t53.5 -24q32 0 58.5 12t61.5 35l50 -69q-25 -26 -56 -51.5t-69.5 -45.5t-85.5 -32t-103 -12q-68 0 -113.5 15.5 t-73.5 42t-40 62t-12 76.5zM752 1241q-44 26 -93.5 66t-95.5 83.5t-84.5 85.5t-59.5 72l6 21h301q12 -37 31.5 -80t40.5 -84t42.5 -77t38.5 -60l-6 -27h-121z" />-<glyph glyph-name="uacute" unicode="ú" horiz-adv-x="1378" d="M793 176q0 9 1 21t2.5 25t3 24.5t3.5 19.5h-17q-45 -64 -88.5 -116t-92.5 -89.5t-106 -58t-130 -20.5q-66 0 -111 18.5t-72 49.5t-39 72.5t-12 86.5q0 51 12 112l23 113l74 361q8 42 13 76t5 51q0 41 -25 54t-83 13h-37l18 109h492l-135 -613q-6 -24 -12 -54t-11 -60 t-8.5 -57t-3.5 -46q0 -45 19 -71.5t69 -26.5q45 0 92.5 40.5t91 105t77.5 144t52 156.5l109 482h305l-139 -666q-13 -56 -21 -104.5t-8 -91.5q0 -45 18.5 -69t53.5 -24q32 0 58.5 12t61.5 35l50 -69q-25 -26 -56 -51.5t-69.5 -45.5t-85.5 -32t-103 -12q-68 0 -113.5 15.5 t-73.5 42t-40 62t-12 76.5zM688 1268q28 29 61 67l65.5 78.5l63 80.5t54.5 75h321l-6 -21q-16 -18 -44 -42.5t-63.5 -52.5l-76 -57.5l-82.5 -57.5l-83 -53.5t-77 -43.5h-139z" />-<glyph glyph-name="ucircumflex" unicode="û" horiz-adv-x="1378" d="M793 176q0 9 1 21t2.5 25t3 24.5t3.5 19.5h-17q-45 -64 -88.5 -116t-92.5 -89.5t-106 -58t-130 -20.5q-66 0 -111 18.5t-72 49.5t-39 72.5t-12 86.5q0 51 12 112l23 113l74 361q8 42 13 76t5 51q0 41 -25 54t-83 13h-37l18 109h492l-135 -613q-6 -24 -12 -54t-11 -60 t-8.5 -57t-3.5 -46q0 -45 19 -71.5t69 -26.5q45 0 92.5 40.5t91 105t77.5 144t52 156.5l109 482h305l-139 -666q-13 -56 -21 -104.5t-8 -91.5q0 -45 18.5 -69t53.5 -24q32 0 58.5 12t61.5 35l50 -69q-25 -26 -56 -51.5t-69.5 -45.5t-85.5 -32t-103 -12q-68 0 -113.5 15.5 t-73.5 42t-40 62t-12 76.5zM451 1268l68 66.5l78 77.5l78 80.5t67 76.5h276q13 -32 33 -72l42.5 -81l44.5 -80t40 -68l-6 -27h-129l-42 40l-49 48l-49.5 50t-42.5 46l-61 -46l-69 -50l-68 -48l-58 -40h-159z" />-<glyph glyph-name="udieresis" unicode="ü" horiz-adv-x="1378" d="M793 176q0 9 1 21t2.5 25t3 24.5t3.5 19.5h-17q-45 -64 -88.5 -116t-92.5 -89.5t-106 -58t-130 -20.5q-66 0 -111 18.5t-72 49.5t-39 72.5t-12 86.5q0 51 12 112l23 113l74 361q8 42 13 76t5 51q0 41 -25 54t-83 13h-37l18 109h492l-135 -613q-6 -24 -12 -54t-11 -60 t-8.5 -57t-3.5 -46q0 -45 19 -71.5t69 -26.5q45 0 92.5 40.5t91 105t77.5 144t52 156.5l109 482h305l-139 -666q-13 -56 -21 -104.5t-8 -91.5q0 -45 18.5 -69t53.5 -24q32 0 58.5 12t61.5 35l50 -69q-25 -26 -56 -51.5t-69.5 -45.5t-85.5 -32t-103 -12q-68 0 -113.5 15.5 t-73.5 42t-40 62t-12 76.5zM731 1417q0 -40 -11 -67t-28 -44t-39 -24.5t-44 -7.5q-51 0 -82 27.5t-31 84.5q0 66 33 105t90 39q49 0 80.5 -28t31.5 -85zM1159 1417q0 -40 -11 -67t-28 -44t-39 -24.5t-44 -7.5q-51 0 -82 27.5t-31 84.5q0 66 33 105t90 39q49 0 80.5 -28 t31.5 -85z" />-<glyph glyph-name="yacute" unicode="ý" horiz-adv-x="1200" d="M160 881q-17 61 -49.5 84.5t-92.5 23.5h-20l25 109h401l135 -531q10 -40 21 -93.5l21 -107t17 -100t9 -71.5h6q61 82 115 165t94.5 161.5t64 149.5t23.5 130q0 69 -33.5 105t-93.5 36q0 40 12.5 72.5t34.5 55.5t52 35.5t65 12.5q83 0 125.5 -46t42.5 -128q0 -80 -27 -169 t-77 -189t-121.5 -212t-161.5 -237l-139 -191t-127.5 -157t-127 -122.5t-137 -88t-158 -53t-190.5 -17.5q-59 0 -114.5 10t-98.5 27l31 115q24 -8 67.5 -17.5t106.5 -9.5q94 0 172 25t144.5 67.5t125 99.5t113.5 122zM508 1268q28 29 61 67l65.5 78.5l63 80.5t54.5 75h321 l-6 -21q-16 -18 -44 -42.5t-63.5 -52.5l-76 -57.5l-82.5 -57.5l-83 -53.5t-77 -43.5h-139z" />-<glyph glyph-name="thorn" unicode="þ" horiz-adv-x="1276" d="M885 733q0 42 -5.5 81.5t-18 70.5t-34 50t-53.5 19q-34 0 -67.5 -19.5t-65 -52.5t-59 -76t-50 -89.5t-39 -93.5t-24.5 -88l-78 -367q9 -9 24 -19.5t34 -18.5t41.5 -13.5t46.5 -5.5q55 0 102.5 30t86 80t68.5 114.5t50 133.5t30.5 137.5t10.5 126.5zM506 825q35 64 72 118 t82 93t102 60.5t133 21.5q67 0 122.5 -24t94.5 -71t60.5 -116t21.5 -159q0 -74 -17.5 -159.5t-52.5 -171t-87.5 -164.5t-123.5 -139.5t-159.5 -97t-194.5 -36.5q-62 0 -115.5 12.5t-93.5 34.5q-1 -16 -4 -33q-2 -14 -5 -31.5t-7 -34.5l-29 -137q-3 -13 -5.5 -27t-4.5 -26.5 t-3 -21.5t-1 -13q0 -28 6 -45t19 -26t33.5 -12t49.5 -3h78l-18 -109h-520l376 1766l6 27t5 26.5t3 21.5t1 13q0 28 -6 45t-19 26t-34 12t-50 3h-47l19 108h497l-82 -389q-4 -19 -13 -48l-20 -62l-23.5 -68l-24 -66.5l-21 -57.5l-14.5 -40h14z" />-<glyph glyph-name="ydieresis" unicode="ÿ" horiz-adv-x="1200" d="M160 881q-17 61 -49.5 84.5t-92.5 23.5h-20l25 109h401l135 -531q10 -40 21 -93.5l21 -107t17 -100t9 -71.5h6q61 82 115 165t94.5 161.5t64 149.5t23.5 130q0 69 -33.5 105t-93.5 36q0 40 12.5 72.5t34.5 55.5t52 35.5t65 12.5q83 0 125.5 -46t42.5 -128q0 -80 -27 -169 t-77 -189t-121.5 -212t-161.5 -237l-139 -191t-127.5 -157t-127 -122.5t-137 -88t-158 -53t-190.5 -17.5q-59 0 -114.5 10t-98.5 27l31 115q24 -8 67.5 -17.5t106.5 -9.5q94 0 172 25t144.5 67.5t125 99.5t113.5 122zM556 1417q0 -40 -11 -67t-28 -44t-39 -24.5t-44 -7.5 q-51 0 -82 27.5t-31 84.5q0 66 33 105t90 39q49 0 80.5 -28t31.5 -85zM984 1417q0 -40 -11 -67t-28 -44t-39 -24.5t-44 -7.5q-51 0 -82 27.5t-31 84.5q0 66 33 105t90 39q49 0 80.5 -28t31.5 -85z" />-<glyph glyph-name="OE" unicode="Œ" horiz-adv-x="2030" d="M1001 1485q47 0 96.5 -6t92.5 -17h915l-71 -381h-146q1 7 2.5 25l3 37.5t3 36t1.5 20.5q0 28 -7.5 53t-25 44t-45 29.5t-67.5 10.5h-272l-109 -512h465l-27 -123h-464l-123 -577h323q48 0 86 13.5t66 36.5t48 53.5t32 64.5l32 88h146l-94 -381h-969q-45 -11 -93.5 -17 t-99.5 -6q-121 0 -225.5 36.5t-181 108.5t-120.5 178t-44 245q0 98 24.5 206t73.5 212.5t122 199t169.5 166t217.5 114t265 42.5zM741 113q45 0 86 8.5t78 28.5l244 1153q-35 21 -85 35t-106 14q-85 0 -154.5 -41t-124.5 -109.5t-96 -157t-67.5 -184t-39.5 -190t-13 -174.5 q0 -101 21.5 -173.5t59 -119t88.5 -68.5t109 -22z" />-<glyph glyph-name="oe" unicode="œ" horiz-adv-x="1845" d="M1368 135q50 0 95 14.5t83 38t69 53t52 60.5q12 -15 19.5 -38.5t7.5 -49.5q0 -37 -24 -78t-73.5 -75.5t-126.5 -57t-182 -22.5q-117 0 -208.5 40t-147.5 121q-74 -72 -177 -116.5t-239 -44.5q-96 0 -176 26t-137 79t-89 132t-32 186q0 76 18 158.5t54.5 162t92 151 t130.5 125.5t169.5 86t209.5 32t203.5 -38t138.5 -122q80 75 186 119.5t242 44.5q146 0 227.5 -63.5t81.5 -177.5q0 -83 -46 -159.5t-127 -135t-193 -93t-244 -34.5h-37t-39 2q-3 -20 -3.5 -40.5t-0.5 -37.5q0 -119 55.5 -183.5t167.5 -64.5zM553 127q55 0 100 32t80 84.5 t61.5 119.5t43.5 137t25.5 137.5t8.5 120.5q0 120 -41.5 171.5t-113.5 51.5q-52 0 -96.5 -30.5t-80 -81t-63 -116t-45.5 -135t-27.5 -138t-9.5 -124.5q0 -62 11 -105.5t31.5 -71t49.5 -40t66 -12.5zM1440 999q-33 0 -64.5 -17.5t-59.5 -48t-52 -71.5t-44 -88t-34.5 -97 t-22.5 -99h19q76 0 141.5 21t113.5 58.5t75.5 90.5t27.5 118q0 63 -26 98t-74 35z" />-<glyph glyph-name="Ydieresis" unicode="Ÿ" horiz-adv-x="1419" d="M227 0l23 109h55q32 0 62 4.5t55 18t43 37.5t27 64l59 285l-221 727q-9 30 -18.5 51t-22.5 33.5t-32.5 18.5t-49.5 6h-27l23 108h665l-22 -108h-92q-47 0 -66.5 -16t-19.5 -44q0 -18 6 -52t18 -81l74 -289l15 -64t14.5 -68t11 -66.5t6.5 -57.5q19 41 58 104t92 138 l202 289q25 35 43.5 76t18.5 75q0 28 -28.5 42t-96.5 14h-43l24 108h537l-23 -108h-38q-23 0 -42.5 -6t-39.5 -21.5t-42.5 -41t-50.5 -64.5l-507 -705l-62 -291q-2 -11 -5 -25t-3 -22q0 -22 11 -35.5t31 -21t47.5 -10t59.5 -2.5h50l-23 -109h-756zM919 1755q0 -40 -11 -67 t-28 -44t-39 -24.5t-44 -7.5q-51 0 -82 27.5t-31 84.5q0 66 33 105t90 39q49 0 80.5 -28t31.5 -85zM1347 1755q0 -40 -11 -67t-28 -44t-39 -24.5t-44 -7.5q-51 0 -82 27.5t-31 84.5q0 66 33 105t90 39q49 0 80.5 -28t31.5 -85z" />-<glyph glyph-name="circumflex" unicode="ˆ" horiz-adv-x="1182" d="M330 1268l68 66.5l78 77.5l78 80.5t67 76.5h276q13 -32 33 -72l42.5 -81l44.5 -80t40 -68l-6 -27h-129l-42 40l-49 48l-49.5 50t-42.5 46l-61 -46l-69 -50l-68 -48l-58 -40h-159z" />-<glyph glyph-name="tilde" unicode="˜" horiz-adv-x="1182" d="M905 1436q26 0 45 9t32.5 24t22 34.5t13.5 40.5h123q-11 -49 -33.5 -102.5t-56 -98t-78 -73.5t-99.5 -29t-99 20t-81 44.5l-69.5 44.5t-65.5 20q-21 0 -40 -9t-34 -24t-25 -34.5t-14 -40.5h-122q9 46 32.5 99.5t59.5 98.5t82.5 75t101.5 30q54 0 96.5 -20t78 -44.5 l67 -44.5t63.5 -20z" />-<glyph glyph-name="endash" unicode="–" horiz-adv-x="1024" d="M1018 477h-1043l29 146h1045z" />-<glyph glyph-name="emdash" unicode="—" horiz-adv-x="2048" d="M2042 477h-2067l29 146h2069z" />-<glyph glyph-name="quoteleft" unicode="‘" horiz-adv-x="573" d="M231 1028q0 78 28.5 148t85 127t141 98.5t196.5 62.5l-23 -108q-210 -50 -210 -170q0 -22 8.5 -37t21 -27.5t27.5 -24.5t27.5 -27.5t21 -36t8.5 -50.5q0 -33 -12.5 -59t-33.5 -44.5t-48.5 -28t-58.5 -9.5q-37 0 -69.5 12.5t-57 36t-38.5 58t-14 79.5z" />-<glyph glyph-name="quoteright" unicode="’" horiz-adv-x="573" d="M629 1278q0 -78 -28.5 -148t-85 -127t-141 -98.5t-196.5 -62.5l23 108q211 50 211 170q0 21 -8.5 36.5t-21.5 28t-27.5 24.5t-27.5 27.5t-21.5 36t-8.5 50.5q0 33 12.5 59t33.5 44.5t49 28t59 9.5q37 0 69.5 -12.5t56.5 -36t38 -58t14 -79.5z" />-<glyph glyph-name="quotesinglbase" unicode="‚" horiz-adv-x="602" d="M428 127q0 -78 -28.5 -148t-85 -127t-141 -98.5t-196.5 -62.5l23 108q211 50 211 170q0 21 -8.5 36.5t-21.5 28t-27.5 24.5t-27.5 27.5t-21.5 36t-8.5 50.5q0 33 12.5 59t33.5 44.5t49 28t59 9.5q37 0 69.5 -12.5t56.5 -36t38 -58t14 -79.5z" />-<glyph glyph-name="quotedblleft" unicode="“" horiz-adv-x="1001" d="M231 1028q0 78 28.5 148t85 127t141 98.5t196.5 62.5l-23 -108q-210 -50 -210 -170q0 -22 8.5 -37t21 -27.5t27.5 -24.5t27.5 -27.5t21 -36t8.5 -50.5q0 -33 -12.5 -59t-33.5 -44.5t-48.5 -28t-58.5 -9.5q-37 0 -69.5 12.5t-57 36t-38.5 58t-14 79.5zM659 1028 q0 78 28.5 148t85 127t141 98.5t196.5 62.5l-23 -108q-210 -50 -210 -170q0 -22 8.5 -37t21 -27.5t27.5 -24.5t27.5 -27.5t21 -36t8.5 -50.5q0 -33 -12.5 -59t-33.5 -44.5t-48.5 -28t-58.5 -9.5q-37 0 -69.5 12.5t-57 36t-38.5 58t-14 79.5z" />-<glyph glyph-name="quotedblright" unicode="”" horiz-adv-x="1001" d="M629 1278q0 -78 -28.5 -148t-85 -127t-141 -98.5t-196.5 -62.5l23 108q211 50 211 170q0 21 -8.5 36.5t-21.5 28t-27.5 24.5t-27.5 27.5t-21.5 36t-8.5 50.5q0 33 12.5 59t33.5 44.5t49 28t59 9.5q37 0 69.5 -12.5t56.5 -36t38 -58t14 -79.5zM1055 1278q0 -78 -28.5 -148 t-85 -127t-141 -98.5t-196.5 -62.5l23 108q211 50 211 170q0 21 -8.5 36.5t-21.5 28t-27.5 24.5t-27.5 27.5t-21.5 36t-8.5 50.5q0 33 12.5 59t33.5 44.5t49 28t59 9.5q37 0 69.5 -12.5t56.5 -36t38 -58t14 -79.5z" />-<glyph glyph-name="quotedblbase" unicode="„" horiz-adv-x="1001" d="M428 127q0 -78 -28.5 -148t-85 -127t-141 -98.5t-196.5 -62.5l23 108q211 50 211 170q0 21 -8.5 36.5t-21.5 28t-27.5 24.5t-27.5 27.5t-21.5 36t-8.5 50.5q0 33 12.5 59t33.5 44.5t49 28t59 9.5q37 0 69.5 -12.5t56.5 -36t38 -58t14 -79.5zM854 127q0 -78 -28.5 -148 t-85 -127t-141 -98.5t-196.5 -62.5l23 108q211 50 211 170q0 21 -8.5 36.5t-21.5 28t-27.5 24.5t-27.5 27.5t-21.5 36t-8.5 50.5q0 33 12.5 59t33.5 44.5t49 28t59 9.5q37 0 69.5 -12.5t56.5 -36t38 -58t14 -79.5z" />-<glyph glyph-name="bullet" unicode="•" horiz-adv-x="819" d="M760 766q0 -101 -27.5 -169.5t-72 -111t-100 -61t-111.5 -18.5q-65 0 -118.5 17.5t-92 53t-59.5 89t-21 126.5q0 81 22 148t62.5 114t98 73t128.5 26q62 0 115.5 -17.5t92.5 -53t61 -89.5t22 -127z" />-<glyph glyph-name="ellipsis" unicode="…" horiz-adv-x="1874" d="M414 164q0 -51 -14.5 -85.5t-37.5 -56t-52 -31t-58 -9.5q-69 0 -111.5 35t-42.5 106q0 83 44 132.5t118 49.5q32 0 60 -9t49 -26.5t33 -44t12 -61.5zM1038 164q0 -51 -14.5 -85.5t-37.5 -56t-51.5 -31t-57.5 -9.5q-70 0 -112 35t-42 106q0 83 43.5 132.5t118.5 49.5 q32 0 60 -9t48.5 -26.5t32.5 -44t12 -61.5zM1661 166q0 -51 -14.5 -85.5t-37.5 -56t-52 -31t-58 -9.5q-69 0 -111 35t-42 106q0 84 43.5 133t117.5 49q32 0 60 -9t49 -26.5t33 -44t12 -61.5z" />-<glyph glyph-name="guilsinglleft" unicode="‹" horiz-adv-x="723" d="M147 623l385 340h144l-293 -410l119 -410h-144l-233 373z" />-<glyph glyph-name="guilsinglright" unicode="›" horiz-adv-x="723" d="M573 485l-385 -342h-143l295 410l-121 410h143l234 -371z" />-<glyph glyph-name="Euro" unicode="€" d="M707 133q51 0 92 13.5t74.5 36.5t60 53t49.5 63q12 -12 24.5 -31.5t12.5 -54.5q0 -36 -24.5 -77t-74 -75.5t-124 -57.5t-174.5 -23q-98 0 -182 27.5t-146 84.5t-97.5 144.5t-35.5 207.5q0 26 0.5 49.5t3.5 49.5h-121l27 127h106q3 20 10.5 54t18.5 73h-107l27 127h117 q54 160 125 266.5t155.5 170.5t180 91t196.5 27q89 0 155.5 -18t110 -48.5t65 -70.5t21.5 -85q0 -89 -70.5 -138.5t-201.5 -49.5q0 51 -5.5 101t-20.5 90t-41 65t-66 25q-41 0 -82 -20t-80.5 -69t-75.5 -131t-67 -206h377l-27 -127h-385q-11 -37 -18 -73.5t-9 -53.5h301 l-26 -127h-291q-3 -25 -3.5 -54t-0.5 -51q0 -145 63.5 -225t182.5 -80z" />-<glyph glyph-name="trademark" unicode="™" horiz-adv-x="1710" d="M1459 754v55q35 0 56 10t24 45v457l-232 -567h-71l-232 569v-451q0 -41 21 -52t61 -11v-55h-248v55h21q31 0 54.5 10t25.5 47v482q-1 19 -7.5 30.5t-17.5 17.5t-25.5 7.5t-29.5 1.5h-21v57h236l225 -567l232 567h231v-57h-22q-17 0 -31.5 -2t-25 -8t-16 -18.5t-5.5 -33.5 v-471q0 -21 5.5 -33t16 -19t25 -9t31.5 -2h22v-55h-303zM560 872q0 -21 6.5 -33t17 -19t25 -9t29.5 -2h32v-55h-348v55h35q15 0 29 2t24.5 8t16.5 17.5t6 29.5v531h-90q-22 -1 -37 -6.5t-24 -15.5t-14 -22.5t-7 -27.5l-8 -49h-60l6 186h598l7 -186h-60l-8 49q-4 29 -21 50 t-61 22h-94v-525z" />-</font>-</defs></svg>
− extra/fonts/DroidSerif-BoldItalic.ttf
binary file changed (43996 → absent bytes)
− extra/fonts/DroidSerif-BoldItalic.woff
binary file changed (28364 → absent bytes)
− extra/fonts/DroidSerif-Italic.eot
binary file changed (38050 → absent bytes)
− extra/fonts/DroidSerif-Italic.svg
@@ -1,223 +0,0 @@-<?xml version="1.0" standalone="no"?>-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >-<svg xmlns="http://www.w3.org/2000/svg">-<defs>-<font id="DroidSerif-Italic" horiz-adv-x="1145" >-<font-face font-family="Droid Serif" font-weight="400" font-style="italic" font-stretch="normal" units-per-em="2048" panose-1="2 2 6 0 6 5 0 9 2 0" ascent="1638" descent="-410" x-height="1098" cap-height="1462" bbox="-332 -492 2306 1907" underline-thickness="102" underline-position="-103" slope="-12" unicode-range="U+0020-U+2122"/>-<missing-glyph horiz-adv-x="532" />-<glyph glyph-name=".notdef" horiz-adv-x="532" />-<glyph glyph-name=".null" horiz-adv-x="0" />-<glyph glyph-name="nonmarkingreturn" horiz-adv-x="682" />-<glyph glyph-name="space" unicode=" " horiz-adv-x="532" />-<glyph glyph-bame="tab" unicode="	" horiz-adv-x="532" />-<glyph glyph-bame="uni00A0" unicode=" " horiz-adv-x="532" />-<glyph glyph-name="exclam" unicode="!" horiz-adv-x="682" d="M410 1462h237l-295 -1050h-94zM250 -16q-127 0 -127 125q0 71 36.5 114t98.5 43q127 0 127 -125q0 -73 -36.5 -115t-98.5 -42z" />-<glyph glyph-name="quotedbl" unicode=""" horiz-adv-x="836" d="M694 1462h217l-163 -481h-95zM309 1462h217l-164 -481h-94z" />-<glyph glyph-name="numbersign" unicode="#" d="M1038 537l-14 -121h-258l-121 -416h-123l121 416h-285l-120 -416h-123l121 416h-168l14 121h190l119 401h-244l13 121h266l117 403h125l-119 -403h285l118 403h123l-119 -403h160l-12 -121h-182l-117 -401h233zM397 537h285l117 401h-285z" />-<glyph glyph-name="dollar" unicode="$" d="M412 76q-170 12 -266.5 88t-96.5 188q0 77 50.5 122.5t107.5 45.5q0 -135 60.5 -227.5t164.5 -114.5l107 502q-271 135 -271 350q0 167 111.5 264t314.5 107l33 155h102l-32 -155q295 -26 295 -205q0 -141 -166 -141q0 220 -150 254l-104 -492l22 -12q185 -101 235 -174 t50 -162q0 -168 -123.5 -275t-343.5 -120l-49 -236h-103zM580 864l94 447q-215 -36 -215 -252q0 -119 121 -195zM633 635l-98 -461q116 10 182.5 79t66.5 171q0 132 -151 211z" />-<glyph glyph-name="percent" unicode="%" horiz-adv-x="1835" d="M565 1481q273 0 273 -326q0 -207 -120.5 -393.5t-318.5 -186.5q-125 0 -194.5 83.5t-69.5 242.5q0 216 118 398t312 182zM297 870q0 -211 111 -211t185.5 177.5t74.5 351.5q0 211 -111 211t-185.5 -177t-74.5 -352zM494 0h-152l1028 1462h143zM1470 889q273 0 273 -326 q0 -207 -120.5 -393t-317.5 -186q-265 0 -265 325q0 216 118 398t312 182zM1202 279q0 -211 111 -211t185.5 176.5t74.5 351.5q0 211 -111 211t-185.5 -176.5t-74.5 -351.5z" />-<glyph glyph-name="ampersand" unicode="&" horiz-adv-x="1520" d="M1096 0l-123 170q-185 -190 -453 -190q-211 0 -329.5 104.5t-118.5 277.5q0 159 96 269t334 198q-99 167 -99 301q0 170 111.5 261.5t325.5 91.5q166 0 256 -72t90 -194q0 -133 -75 -215t-372 -189l287 -393q87 164 129 362l16 80h369l-18 -86h-19q-102 0 -148 -30.5 t-66.5 -59t-102.5 -199.5q-39 -81 -90 -161l112 -154q63 -86 201 -86h18l-18 -86h-313zM682 895q173 65 245 130.5t72 176.5q0 81 -47.5 130.5t-126.5 49.5q-225 0 -225 -237q0 -114 82 -250zM909 258l-348 483q-160 -73 -223.5 -155.5t-63.5 -218.5q0 -119 71.5 -196 t195.5 -77q212 0 368 164z" />-<glyph glyph-name="quotesingle" unicode="'" horiz-adv-x="451" d="M301 1462h217l-164 -481h-94z" />-<glyph glyph-name="parenleft" unicode="(" horiz-adv-x="709" d="M469 -168l-20 -94q-363 159 -363 643q0 413 198.5 746t551.5 429l-21 -92q-283 -154 -409.5 -517.5t-126.5 -661.5q0 -321 190 -453z" />-<glyph glyph-name="parenright" unicode=")" horiz-adv-x="709" d="M266 1446l21 94q362 -158 362 -643q0 -415 -200 -748.5t-549 -427.5l20 93q284 153 410.5 519.5t126.5 659.5q0 320 -191 453z" />-<glyph glyph-name="asterisk" unicode="*" horiz-adv-x="1024" d="M281 1354l104 143l266 -260l17 330l174 -37l-123 -305l350 123l37 -172l-361 -27l318 -170l-102 -139l-271 252l-12 -328l-174 37l119 305l-349 -125l-36 172l358 29z" />-<glyph glyph-name="plus" unicode="+" d="M649 672v-379h-123v379h-376v121h376v378h123v-378h379v-121h-379z" />-<glyph glyph-name="comma" unicode="," horiz-adv-x="512" d="M-57 -299l8 86q209 47 209 172q0 36 -66 78q-67 42 -67 104q0 55 35 88t92 33q69 0 114 -45.5t45 -122.5q0 -150 -95 -253.5t-275 -139.5z" />-<glyph glyph-name="hyphen" unicode="-" horiz-adv-x="635" d="M29 481l32 154h533l-33 -154h-532z" />-<glyph glyph-name="period" unicode="." horiz-adv-x="586" d="M217 -16q-127 0 -127 125q0 71 36.5 114t98.5 43q127 0 127 -125q0 -72 -36.5 -114.5t-98.5 -42.5z" />-<glyph glyph-name="slash" unicode="/" horiz-adv-x="590" d="M-61 -248h-129l841 1804h129z" />-<glyph glyph-name="zero" unicode="0" d="M696 1485q180 0 284.5 -123t104.5 -346q0 -394 -167 -716.5t-441 -322.5q-183 0 -289 131t-106 365q0 394 170 703t444 309zM494 90q178 0 285.5 295.5t107.5 669.5q0 319 -211 319q-179 0 -287 -297t-108 -663q0 -324 213 -324z" />-<glyph glyph-name="one" unicode="1" d="M872 1470l-264 -1245q-8 -38 -8 -57q0 -82 148 -82h110l-18 -86h-762l16 86h109q97 0 141 36.5t59 110.5l160 754q35 164 76 322h-8q-129 -127 -192 -170t-117 -43q-82 0 -82 110q140 34 335 158l166 106h131z" />-<glyph glyph-name="two" unicode="2" d="M-35 0l43 166l481 434q191 172 268 298t77 276q0 95 -51 150.5t-138 55.5q-236 0 -291 -309q-145 0 -145 131q0 114 123.5 197.5t322.5 83.5q185 0 286 -78t101 -221q0 -122 -87 -251.5t-339 -354.5l-460 -414h475q67 0 114.5 30.5t88.5 122.5l14 33h86l-82 -350h-887z " />-<glyph glyph-name="three" unicode="3" d="M362 705l21 104h66q174 0 286.5 107t112.5 278q0 186 -174 186q-228 0 -291 -305q-152 0 -152 131q0 119 127.5 198t323.5 79q174 0 275.5 -79t101.5 -214q0 -156 -110.5 -271t-301.5 -149q147 -15 227 -98.5t80 -210.5q0 -231 -159.5 -356t-403.5 -125q-197 0 -297 58.5 t-100 156.5q0 68 43 109.5t113 41.5q0 -112 67 -183t189 -71q146 0 239.5 93t93.5 253q0 127 -75.5 197t-212.5 70h-89z" />-<glyph glyph-name="four" unicode="4" d="M262 0l17 86h90q97 0 141 36.5t59 110.5l33 168h-631l19 84l839 977h197l-203 -946h254l-24 -115h-254l-35 -176q-8 -40 -8 -57q0 -82 147 -82h47l-18 -86h-670zM707 899q49 232 100 397q-85 -129 -180 -235l-465 -545h465z" />-<glyph glyph-name="five" unicode="5" d="M258 717l-70 14l220 731h684l-56 -309h-86q6 78 6 96q0 49 -94 49h-397l-135 -456q99 32 196 32q202 0 316 -100t114 -276q0 -228 -159.5 -373t-421.5 -145q-359 0 -359 204q0 127 154 127q7 -219 223 -219q165 0 257.5 103t92.5 288q0 132 -71.5 205.5t-190.5 73.5 q-110 0 -223 -45z" />-<glyph glyph-name="six" unicode="6" d="M360 782q152 107 302 107q162 0 254 -97.5t92 -261.5q0 -238 -134.5 -394t-365.5 -156q-173 0 -279 131.5t-106 363.5q0 413 198.5 710.5t501.5 297.5q146 0 230 -53t84 -132q0 -153 -179 -153q0 139 -35 189t-104 50q-308 0 -459 -602zM338 666q-29 -164 -29 -267 q0 -307 211 -307q124 0 204.5 120t80.5 306q0 266 -184 266q-137 0 -283 -118z" />-<glyph glyph-name="seven" unicode="7" d="M221 0l758 1298h-539q-112 0 -141 -104l-29 -102h-86l88 370h916l-12 -59l-801 -1403h-154z" />-<glyph glyph-name="eight" unicode="8" d="M426 748q-201 144 -201 339q0 187 126.5 291.5t363.5 104.5q174 0 275.5 -82.5t101.5 -216.5q0 -132 -80.5 -227.5t-276.5 -176.5q260 -151 260 -366q0 -209 -145 -321.5t-395 -112.5q-198 0 -316.5 98t-118.5 254q0 142 90 238t316 178zM637 829q272 116 272 340 q0 100 -56 155.5t-155 55.5q-125 0 -200.5 -74t-75.5 -196q0 -165 215 -281zM520 694q-303 -111 -303 -366q0 -115 71 -181.5t193 -66.5q144 0 232 76t88 209q0 106 -68 181.5t-213 147.5z" />-<glyph glyph-name="nine" unicode="9" d="M809 702q-134 -139 -313 -139q-159 0 -252.5 93t-93.5 247q0 250 157.5 415t382.5 165q173 0 274 -106t101 -298q0 -394 -188 -746.5t-519 -352.5q-286 0 -286 184q0 51 32 85t89 34q4 -93 52.5 -148t129.5 -55q142 0 243.5 145.5t190.5 476.5zM842 850q32 133 32 242 q0 278 -190 278q-135 0 -232.5 -144t-97.5 -329q0 -109 52.5 -165t140.5 -56q164 0 295 174z" />-<glyph glyph-name="colon" unicode=":" horiz-adv-x="588" d="M377 825q-127 0 -127 125q0 71 36.5 114.5t98.5 43.5q127 0 127 -125q0 -74 -36.5 -116t-98.5 -42zM193 -16q-127 0 -127 125q0 71 36.5 114t98.5 43q60 0 93.5 -34t33.5 -91q0 -73 -36.5 -115t-98.5 -42z" />-<glyph glyph-name="semicolon" unicode=";" horiz-adv-x="586" d="M381 825q-127 0 -127 125q0 71 36.5 114.5t98.5 43.5q127 0 127 -125q0 -74 -36.5 -116t-98.5 -42zM37 -299l8 86q209 47 209 172q0 36 -66 78q-67 42 -67 104q0 55 35 88t92 33q69 0 114.5 -46.5t45.5 -121.5q0 -151 -96.5 -254t-274.5 -139z" />-<glyph glyph-name="less" unicode="<" d="M150 696v72l878 473v-137l-688 -373l688 -369v-137z" />-<glyph glyph-name="equal" unicode="=" d="M1028 993v-121h-878v121h878zM1028 590v-121h-878v121h878z" />-<glyph glyph-name="greater" unicode=">" d="M150 225v137l688 369l-688 373v137l878 -473v-72z" />-<glyph glyph-name="question" unicode="?" horiz-adv-x="1024" d="M565 649l-51 -237h-125l64 303q415 154 415 444q0 100 -54 160.5t-144 60.5q-211 0 -254 -290q-148 0 -148 122q0 114 114.5 192.5t299.5 78.5q387 0 387 -340q0 -311 -504 -494zM406 -16q-127 0 -127 125q0 71 36.5 114t98.5 43q60 0 93.5 -34t33.5 -91 q0 -73 -36.5 -115t-98.5 -42z" />-<glyph glyph-name="at" unicode="@" horiz-adv-x="1886" d="M1135 344h-11q-97 -186 -276 -186q-139 0 -225 93t-86 257q0 218 141 396.5t381 178.5q114 0 192 -61l82 43h49l-104 -522q-14 -70 -14 -137q0 -63 33.5 -101.5t91.5 -38.5q101 0 182.5 152.5t81.5 390.5q0 255 -156 402t-424 147q-318 0 -548 -255.5t-230 -651.5 q0 -305 163 -470t447 -165q282 0 520 170l48 -74q-275 -203 -588 -203q-352 0 -552 194.5t-200 542.5q0 439 270 727.5t682 288.5q322 0 507.5 -179.5t185.5 -479.5q0 -278 -124.5 -461.5t-277.5 -183.5q-178 0 -241 186zM1182 938q-29 61 -115 61q-154 0 -247 -168 t-93 -352q0 -108 43.5 -160.5t112.5 -52.5q172 0 221 256z" />-<glyph glyph-name="A" unicode="A" horiz-adv-x="1444" d="M408 489l-123 -215q-39 -68 -39 -114q0 -74 114 -74h48l-17 -86h-506l17 86h39q55 0 92 28.5t98 131.5l713 1216h160l194 -1267q10 -64 35 -86.5t86 -22.5h27l-17 -86h-557l16 86h48q147 0 147 104q0 42 -4 70l-33 229h-538zM881 950q-31 218 -33 324q-47 -111 -160 -305 l-219 -377h463z" />-<glyph glyph-name="B" unicode="B" horiz-adv-x="1339" d="M-27 86h27q170 0 201 147l213 1004q8 38 8 57q0 82 -148 82h-26l18 86h531q479 0 479 -307q0 -303 -334 -381l-2 -8q250 -50 250 -299q0 -225 -151 -346t-439 -121h-643zM530 817h158q365 0 365 311q0 232 -283 232h-123zM379 102h199q391 0 391 379q0 234 -287 234h-172 z" />-<glyph glyph-name="C" unicode="C" horiz-adv-x="1284" d="M1073 299q27 -19 27 -68q0 -98 -114 -174.5t-314 -76.5q-267 0 -416.5 143t-149.5 405q0 388 242 671.5t602 283.5q179 0 279 -61.5t100 -166.5q0 -73 -55 -116t-146 -43q0 278 -204 278q-253 0 -423.5 -275.5t-170.5 -602.5q0 -383 364 -383q237 0 379 186z" />-<glyph glyph-name="D" unicode="D" horiz-adv-x="1485" d="M-27 86h27q170 0 201 147l213 1004q8 38 8 57q0 82 -148 82h-26l18 86h574q279 0 430 -144.5t151 -412.5q0 -392 -223 -648.5t-596 -256.5h-645zM557 104q296 0 468.5 227t172.5 597q0 432 -389 432h-162l-268 -1256h178z" />-<glyph glyph-name="E" unicode="E" horiz-adv-x="1276" d="M379 102h395q173 0 232 168l32 88h107l-90 -358h-1098l16 86h27q104 0 144.5 36.5t54.5 100.5l213 1006q6 28 6 49q0 48 -28 73t-116 25h-26l18 86h1032l-65 -358h-107l6 63q5 48 5 56q0 137 -146 137h-344l-117 -545h494l-20 -100h-494z" />-<glyph glyph-name="F" unicode="F" horiz-adv-x="1208" d="M-27 86h27q96 0 138 33t61 104l215 1014q8 38 8 57q0 82 -148 82h-26l18 86h1032l-65 -358h-107l4 36q1 19 3 36q4 34 4 47q0 137 -154 137h-336l-127 -592h494l-21 -100h-493l-92 -435q-7 -33 -7 -51q0 -96 142 -96h67l-16 -86h-637z" />-<glyph glyph-name="G" unicode="G" horiz-adv-x="1462" d="M1430 676l-19 -86h-8q-82 0 -120 -36.5t-54 -111.5l-78 -368q-217 -94 -463 -94q-280 0 -431 143.5t-151 404.5q0 407 250 681t631 274q204 0 318 -62t114 -159q0 -76 -61 -119t-156 -43q0 132 -67 203t-191 71q-253 0 -433.5 -261.5t-180.5 -616.5q0 -400 395 -400 q115 0 231 29l68 326q8 38 8 57q0 82 -147 82h-19l19 86h545z" />-<glyph glyph-name="H" unicode="H" horiz-adv-x="1624" d="M829 0l17 86h26q98 0 142 36.5t59 110.5l101 480h-666l-102 -488q-9 -43 -9 -57q0 -82 148 -82h26l-18 -86h-596l16 86h27q99 0 142 37.5t59 109.5l213 1004q8 38 8 57q0 82 -148 82h-26l18 86h596l-18 -86h-27q-98 0 -141 -37t-58 -110l-88 -414h666l90 422q8 38 8 57 q0 82 -147 82h-27l19 86h596l-19 -86h-26q-98 0 -141 -37.5t-58 -109.5l-213 -1004q-8 -38 -8 -57q0 -82 147 -82h27l-19 -86h-596z" />-<glyph glyph-name="I" unicode="I" horiz-adv-x="752" d="M862 1462l-18 -86h-27q-98 0 -141 -37t-58 -110l-212 -1004q-9 -43 -9 -57q0 -82 148 -82h26l-18 -86h-596l16 86h27q170 0 201 147l213 1004q8 38 8 57q0 82 -148 82h-26l18 86h596z" />-<glyph glyph-name="J" unicode="J" horiz-adv-x="731" d="M852 1462l-18 -86h-27q-98 0 -141 -37t-58 -110l-266 -1245q-54 -251 -171.5 -363.5t-320.5 -112.5q-91 0 -167 23l28 92q51 -18 133 -18q119 0 185 89t108 288l266 1255q9 42 9 57q0 82 -148 82h-26l18 86h596z" />-<glyph glyph-name="K" unicode="K" horiz-adv-x="1434" d="M-27 86h27q99 0 142 37.5t59 109.5l213 1004q8 38 8 57q0 82 -148 82h-26l18 86h596l-18 -86h-27q-98 0 -141 -37t-58 -110l-88 -414l-36 -151l528 485q154 141 154 184q0 45 -91 45l17 84h452l-16 -84q-53 0 -118 -35.5t-197 -156.5l-373 -338l248 -598 q68 -164 194 -164h6l-18 -86h-29q-162 0 -241 33.5t-127 156.5l-199 506l-211 -147l-67 -324q-9 -44 -9 -57q0 -82 148 -82h26l-18 -86h-596z" />-<glyph glyph-name="L" unicode="L" horiz-adv-x="1276" d="M-23 86h27q96 0 138 37.5t59 109.5l213 1004q8 38 8 57q0 82 -148 82h-26l18 86h596l-18 -86h-27q-97 0 -139 -34.5t-57 -104.5l-242 -1135h403q157 0 219 185l54 153h106l-106 -440h-1096z" />-<glyph glyph-name="M" unicode="M" horiz-adv-x="1921" d="M1178 0l18 86h6q87 0 122 32.5t52 96.5l234 1094l-748 -1309h-110l-199 1309l-229 -1084q-9 -43 -9 -57q0 -82 148 -82h26l-18 -86h-512l18 86h27q102 0 141.5 36t53.5 101l215 1014q8 38 8 57q0 82 -148 82h-26l18 86h465l174 -1167l670 1167h457l-19 -86h-26 q-98 0 -141 -37.5t-58 -109.5l-213 -1004q-8 -38 -8 -57q0 -82 147 -82h27l-19 -86h-544z" />-<glyph glyph-name="N" unicode="N" horiz-adv-x="1563" d="M1327 1237q8 38 8 57q0 82 -147 82h-27l19 86h514l-19 -86h-26q-98 0 -140.5 -37.5t-58.5 -109.5l-260 -1229h-133l-527 1200l-206 -975q-9 -43 -9 -57q0 -82 148 -82h26l-18 -86h-512l18 86h27q96 0 138 37.5t59 109.5l213 1004q8 38 8 57q0 82 -148 82h-26l18 86h373 l496 -1130z" />-<glyph glyph-name="O" unicode="O" horiz-adv-x="1520" d="M926 1485q238 0 384 -148t146 -415q0 -351 -228 -648t-585 -297q-243 0 -390 153.5t-147 414.5q0 352 230 646t590 294zM655 90q255 0 415.5 282.5t160.5 604.5q0 191 -84.5 294t-237.5 103q-256 0 -417.5 -283.5t-161.5 -603.5q0 -192 87 -294.5t238 -102.5z" />-<glyph glyph-name="P" unicode="P" horiz-adv-x="1270" d="M-41 0l18 86h27q96 0 139 37t58 110l213 1004q8 38 8 57q0 82 -148 82h-26l18 86h525q222 0 344 -103t122 -292q0 -233 -156.5 -365.5t-469.5 -132.5h-152l-73 -344q-9 -42 -9 -57q0 -82 148 -82h67l-18 -86h-635zM500 666h108q432 0 432 399q0 299 -299 299h-94z" />-<glyph glyph-name="Q" unicode="Q" horiz-adv-x="1520" d="M752 -14q9 -183 61.5 -265.5t159.5 -82.5q74 0 139 18l14 -86q-103 -33 -202 -33q-377 0 -394 451q-199 34 -311.5 183t-112.5 374q0 352 230 646t590 294q238 0 384 -148t146 -415q0 -311 -191 -598.5t-513 -337.5zM655 90q255 0 415.5 282.5t160.5 604.5 q0 191 -84.5 294t-237.5 103q-256 0 -417.5 -283.5t-161.5 -603.5q0 -192 87 -294.5t238 -102.5z" />-<glyph glyph-name="R" unicode="R" horiz-adv-x="1325" d="M461 645l-90 -420q-9 -42 -9 -57q0 -82 148 -82h68l-19 -86h-635l19 86h26q102 0 141.5 36t53.5 101l215 1014q8 38 8 57q0 82 -147 82h-27l18 86h537q469 0 469 -348q0 -338 -379 -428l148 -438q36 -105 73 -133.5t95 -28.5h16l-16 -86h-27q-176 0 -249 46.5t-116 193.5 l-118 405h-203zM481 741h158q183 0 282 93.5t99 267.5q0 136 -60.5 199t-207.5 63h-140z" />-<glyph glyph-name="S" unicode="S" horiz-adv-x="1114" d="M172 428q0 -157 83 -252.5t228 -95.5q143 0 227.5 71t84.5 193q0 97 -48.5 164t-209.5 158q-164 95 -227.5 195t-63.5 226q0 177 134 286.5t363 109.5q178 0 284 -62t106 -159q0 -142 -181 -142q0 120 -57 190t-168 70q-122 0 -200 -70t-78 -188q0 -87 41.5 -150.5 t211.5 -162.5q161 -94 232.5 -187t71.5 -212q0 -203 -142 -316.5t-395 -113.5q-209 0 -332 82.5t-123 209.5q0 71 44.5 113.5t113.5 42.5z" />-<glyph glyph-name="T" unicode="T" horiz-adv-x="1255" d="M657 225q-8 -38 -8 -57q0 -82 148 -82h47l-19 -86h-637l19 86h47q103 0 143 35.5t54 101.5l241 1137h-196q-146 0 -205 -168l-29 -88h-106l86 358h1153l-66 -358h-106q10 80 10 125q0 131 -135 131h-199z" />-<glyph glyph-name="U" unicode="U" horiz-adv-x="1495" d="M205 1376l18 86h596l-16 -86h-27q-96 0 -140 -36.5t-61 -110.5l-153 -723q-25 -118 -25 -191q0 -213 295 -213q166 0 263 74t135 246l174 815q8 37 8 57q0 82 -148 82h-26l18 86h514l-16 -86h-27q-96 0 -140 -36.5t-61 -110.5l-172 -811q-93 -438 -552 -438 q-226 0 -345.5 92t-119.5 262q0 83 22 182l152 721q8 38 8 57q0 82 -148 82h-26z" />-<glyph glyph-name="V" unicode="V" horiz-adv-x="1382" d="M639 201q76 177 166 336l364 651q39 70 39 115q0 73 -114 73h-47l16 86h506l-19 -86h-39q-69 0 -103.5 -35t-84.5 -124l-682 -1217h-149l-175 1268q-8 61 -33.5 84.5t-88.5 23.5h-27l18 86h557l-16 -86h-47q-66 0 -107 -27.5t-41 -76.5q0 -35 5 -70l81 -665 q21 -172 21 -336z" />-<glyph glyph-name="W" unicode="W" horiz-adv-x="2140" d="M1915 1188q39 78 39 115q0 73 -115 73h-49l16 86h500l-18 -86h-39q-66 0 -103.5 -32t-85.5 -127l-604 -1217h-188l-92 1135l-551 -1135h-187l-106 1268q-5 55 -30 81.5t-93 26.5h-27l19 86h557l-19 -86h-47q-147 0 -147 -104q0 -42 4 -70l49 -655q14 -187 14 -346 q86 202 168 368l430 879h148l67 -887q15 -201 11 -360q90 209 153 336z" />-<glyph glyph-name="X" unicode="X" horiz-adv-x="1352" d="M1458 1376h-18q-56 0 -100.5 -30t-131.5 -123l-387 -428l246 -600q45 -109 141 -109h27l-18 -86h-553l18 86h23q61 0 96 20.5t35 51.5q0 42 -39 141l-133 326l-306 -340q-82 -91 -82 -144q0 -55 121 -55h6l-18 -86h-485l18 86h8q67 0 119.5 35t122.5 112l453 494 l-220 545q-42 104 -159 104h-27l18 86h555l-18 -86h-47q-96 0 -96 -57q0 -29 37 -123l116 -299l256 295q78 90 78 133q0 51 -98 51h-6l18 86h449z" />-<glyph glyph-name="Y" unicode="Y" horiz-adv-x="1280" d="M729 1462l-18 -86h-103q-84 0 -84 -55q0 -40 27 -119l92 -274q49 -146 55 -246q20 38 154 215l195 260q75 100 75 166q0 53 -98 53h-76l19 86h506l-19 -86h-39q-39 0 -77.5 -31.5t-108.5 -123.5l-477 -625l-80 -371q-8 -37 -8 -57q0 -82 147 -82h47l-18 -86h-637l18 86 h47q103 0 143 35.5t54 101.5l80 377l-238 668q-23 65 -45.5 86.5t-77.5 21.5h-26l18 86h553z" />-<glyph glyph-name="Z" unicode="Z" horiz-adv-x="1239" d="M1276 1378l-1032 -1276h547q152 0 206 168l29 88h107l-86 -358h-1082l17 82l1030 1278h-477q-151 0 -205 -168l-29 -88h-106l86 358h1011z" />-<glyph glyph-name="bracketleft" unicode="[" horiz-adv-x="735" d="M10 -262l387 1818h457l-18 -86h-88q-96 0 -139.5 -36t-59.5 -111l-291 -1360q-8 -37 -8 -57q0 -82 147 -82h88l-18 -86h-457z" />-<glyph glyph-name="backslash" unicode="\" horiz-adv-x="590" d="M231 1556h119l88 -1804h-121z" />-<glyph glyph-name="bracketright" unicode="]" horiz-adv-x="737" d="M-96 -262l18 86h88q96 0 139 37t58 110l291 1360q8 37 8 58q0 81 -148 81h-88l19 86h456l-387 -1818h-454z" />-<glyph glyph-name="asciicircum" unicode="^" d="M552 1462h72l431 -917h-137l-330 729l-330 -729h-137z" />-<glyph glyph-name="underscore" unicode="_" horiz-adv-x="940" d="M772 -291h-958l26 121h959z" />-<glyph glyph-name="grave" unicode="`" horiz-adv-x="1182" d="M829 1241q-154 150 -264 307l6 21h205q60 -155 137 -301l-6 -27h-78z" />-<glyph glyph-name="a" unicode="a" horiz-adv-x="1186" d="M748 309h-13q-147 -329 -383 -329q-125 0 -194.5 98.5t-69.5 275.5q0 282 175.5 523t434.5 241q139 0 240 -74l90 52h66l-140 -656q-30 -141 -30 -227q0 -104 67 -104q58 0 148 53l37 -60q-147 -122 -289 -122q-160 0 -160 170q0 65 21 159zM864 952q-47 74 -155 74 q-175 0 -293.5 -222.5t-118.5 -457.5q0 -237 129 -237q102 0 214 151t148 330z" />-<glyph glyph-name="b" unicode="b" horiz-adv-x="1151" d="M213 53l-92 -59h-66l271 1272q18 84 18 108q0 96 -143 96h-17l19 86h387l-98 -460q-21 -99 -74 -271h10q151 293 375 293q122 0 191 -94t69 -276q0 -285 -176 -526.5t-434 -241.5q-147 0 -240 73zM285 145q62 -73 159 -73q178 0 294 227t116 451q0 239 -129 239 q-112 0 -218.5 -155.5t-133.5 -282.5z" />-<glyph glyph-name="c" unicode="c" horiz-adv-x="997" d="M821 256q21 -13 21 -51q0 -82 -96.5 -153.5t-258.5 -71.5q-179 0 -289 113t-110 310q0 268 173 491.5t437 223.5q141 0 220 -56t79 -140q0 -62 -49.5 -103t-132.5 -41q0 109 -42 175.5t-116 66.5q-143 0 -249.5 -214t-106.5 -419q0 -278 225 -278q179 0 295 147z" />-<glyph glyph-name="d" unicode="d" horiz-adv-x="1186" d="M748 309h-13q-147 -329 -383 -329q-125 0 -194.5 98.5t-69.5 275.5q0 282 175.5 523t434.5 241q115 0 201 -49q16 94 23 115l20 98q14 69 14 92q0 96 -143 96h-16l18 86h377l-238 -1116q-30 -141 -30 -227q0 -104 67 -104q58 0 148 53l37 -60q-147 -122 -289 -122 q-160 0 -160 170q0 65 21 159zM864 952q-47 74 -155 74q-175 0 -293.5 -222.5t-118.5 -457.5q0 -237 129 -237q102 0 214 151t148 330z" />-<glyph glyph-name="e" unicode="e" horiz-adv-x="1010" d="M821 256q21 -13 21 -51q0 -82 -96.5 -153.5t-258.5 -71.5q-179 0 -289 113t-110 310q0 268 173 491.5t437 223.5q130 0 201.5 -62t71.5 -173q0 -166 -172.5 -289t-421.5 -123h-35q-18 0 -37 2q-4 -28 -4 -86q0 -278 225 -278q175 0 295 147zM324 567h20q181 0 298.5 88 t117.5 228q0 137 -119 137q-99 0 -185 -124.5t-132 -328.5z" />-<glyph glyph-name="f" unicode="f" horiz-adv-x="651" d="M719 981h-230l-213 -997q-53 -247 -155 -361.5t-289 -114.5q-58 0 -119 17l19 94q51 -14 94 -14q99 0 156 85t102 292l215 999h-186l12 74l197 43l22 112q72 367 389 367q131 0 197.5 -41.5t66.5 -110.5q0 -133 -170 -133q0 191 -104 191q-75 0 -116.5 -62t-65.5 -192 l-27 -131h227z" />-<glyph glyph-name="g" unicode="g" horiz-adv-x="1139" d="M901 1053q97 145 221 145q103 0 103 -94q0 -127 -121 -127q0 74 -57 74q-40 0 -101 -47q45 -64 45 -158q0 -172 -129 -315.5t-340 -143.5q-47 0 -65 4q-146 -59 -146 -129q0 -51 105 -61l196 -21q318 -34 318 -268q0 -191 -144 -297.5t-411 -106.5q-447 0 -447 267 q0 110 89.5 187.5t244.5 107.5q-98 48 -98 141q0 63 40.5 107t153.5 102q-143 67 -143 242q0 173 130.5 314.5t344.5 141.5q131 0 211 -65zM545 485q117 0 188.5 116.5t71.5 254.5q0 168 -141 168q-120 0 -190.5 -117.5t-70.5 -253.5q0 -168 142 -168zM561 6l-199 19 q-15 1 -88.5 -20t-126.5 -78t-53 -140q0 -178 289 -178q180 0 274.5 66.5t94.5 179.5q0 134 -191 151z" />-<glyph glyph-name="h" unicode="h" horiz-adv-x="1227" d="M471 840q170 276 379 276q219 0 219 -227q0 -65 -37 -225l-53 -242q-27 -123 -27 -215q0 -98 72 -98q59 0 131 47l37 -60q-134 -116 -250 -116q-85 0 -135.5 55t-50.5 149t39 277l47 212q30 124 30 191q0 121 -94 121q-114 0 -234.5 -155.5t-152.5 -305.5l-110 -524h-191 l266 1266q19 90 19 108q0 96 -144 96h-16l18 86h377l-80 -376q-36 -168 -81 -340h22z" />-<glyph glyph-name="i" unicode="i" horiz-adv-x="623" d="M459 1303q-115 0 -115 112q0 65 33.5 104.5t89.5 39.5q115 0 115 -113q0 -70 -34.5 -106.5t-88.5 -36.5zM127 1012l18 86h367l-139 -658q-31 -147 -31 -227q0 -104 68 -104q59 0 147 53l37 -60q-142 -122 -277 -122q-76 0 -123 55t-47 139q0 92 46 322l79 376q7 33 7 54 q0 86 -125 86h-27z" />-<glyph glyph-name="j" unicode="j" horiz-adv-x="596" d="M461 1303q-115 0 -115 112q0 65 33.5 104.5t89.5 39.5q115 0 115 -113q0 -70 -34.5 -106.5t-88.5 -36.5zM-287 -475l19 94q51 -14 94 -14q103 0 161.5 91.5t98.5 287.5l188 888q7 33 7 54q0 86 -125 86h-27l18 86h367l-235 -1114q-52 -244 -155.5 -360t-291.5 -116 q-58 0 -119 17z" />-<glyph glyph-name="k" unicode="k" horiz-adv-x="1165" d="M694 786q151 131 151 193q0 31 -91 31l18 86h453l-19 -86q-119 0 -329 -191l-185 -170l187 -405q79 -172 208 -172l-18 -86q-170 0 -250 39.5t-135 171.5l-133 327l-186 -118l-84 -406h-191l266 1266q19 90 19 108q0 96 -144 96h-16l18 86h377l-80 -376 q-110 -517 -159 -674z" />-<glyph glyph-name="l" unicode="l" horiz-adv-x="623" d="M207 1470l18 86h387l-237 -1116q-31 -146 -31 -227q0 -104 68 -104q59 0 147 53l37 -60q-142 -122 -277 -122q-74 0 -121.5 54t-47.5 140q0 97 45 322l167 786q13 61 13 92q0 96 -144 96h-24z" />-<glyph glyph-name="m" unicode="m" horiz-adv-x="1835" d="M920 0h-195q0 46 26 212.5t44 248.5l47 212q30 124 30 191q0 121 -94 121q-114 0 -234.5 -155.5t-152.5 -305.5l-110 -524h-191l172 823q12 57 12 92q0 97 -143 97h-16l18 86h379l-55 -258h20q166 276 373 276q219 0 219 -227q0 -25 -4 -49h20q167 276 373 276 q219 0 219 -227q0 -65 -37 -225l-53 -242q-26 -119 -26 -215q0 -98 71 -98q59 0 131 47l37 -60q-130 -116 -250 -116q-84 0 -135 55t-51 149t39 277l35 160q43 197 43 243q0 121 -95 121q-105 0 -217 -134.5t-157 -281.5l-33 -147q-13 -56 -36 -219t-23 -203z" />-<glyph glyph-name="n" unicode="n" horiz-adv-x="1227" d="M281 0h-191l172 823q12 57 12 92q0 97 -143 97h-16l18 86h379l-55 -258h20q166 276 373 276q219 0 219 -227q0 -65 -37 -225l-53 -242q-27 -123 -27 -215q0 -98 72 -98q59 0 131 47l37 -60q-134 -116 -250 -116q-85 0 -135.5 55t-50.5 149t39 277l47 212q30 124 30 191 q0 121 -94 121q-114 0 -233.5 -154t-153.5 -307z" />-<glyph glyph-name="o" unicode="o" horiz-adv-x="1176" d="M698 1118q181 0 285 -109t104 -315q0 -273 -167 -493.5t-439 -220.5q-185 0 -289 112t-104 311q0 276 168.5 495.5t441.5 219.5zM504 80q169 0 269.5 200.5t100.5 444.5q0 285 -200 285q-167 0 -270 -201t-103 -447q0 -282 203 -282z" />-<glyph glyph-name="p" unicode="p" horiz-adv-x="1182" d="M389 -406l-18 -86h-385l272 1299q18 86 18 108q0 97 -143 97h-16l18 86h363l-49 -273h10q148 293 375 293q125 0 192.5 -95t67.5 -275q0 -275 -171.5 -521.5t-439.5 -246.5q-117 0 -200 47q-9 -66 -17 -99l-28 -125q-19 -85 -19 -114q0 -95 143 -95h27zM315 145 q66 -73 162 -73q171 0 289.5 223.5t118.5 454.5q0 239 -129 239q-106 0 -214 -147.5t-143 -306.5z" />-<glyph glyph-name="q" unicode="q" horiz-adv-x="1147" d="M735 309q-149 -329 -383 -329q-124 0 -194 98.5t-70 275.5q0 282 175.5 523t434.5 241q128 0 228 -55l50 28q48 27 52 27h72l-283 -1335q-12 -57 -12 -92q0 -97 141 -97h17l-19 -86h-373l86 410q8 39 91 391h-13zM864 952q-45 74 -155 74q-177 0 -294.5 -226t-117.5 -456 q0 -235 129 -235q101 0 212 148.5t150 332.5z" />-<glyph glyph-name="r" unicode="r" horiz-adv-x="958" d="M799 821q0 162 -82 162q-95 0 -201.5 -187t-157.5 -423l-77 -373h-191l172 823q12 57 12 92q0 97 -163 97h-17l19 86h362l-43 -295h21q82 183 162 249t186 66q168 0 168 -141q0 -156 -170 -156z" />-<glyph glyph-name="s" unicode="s" horiz-adv-x="950" d="M741 795q0 103 -48.5 165t-135.5 62q-92 0 -145.5 -49.5t-53.5 -126.5q0 -68 51 -116t158 -95q246 -110 246 -318q0 -151 -116 -244t-308 -93q-155 0 -246.5 62.5t-91.5 168.5q0 66 36.5 100.5t92.5 34.5q0 -127 61.5 -197.5t172.5 -70.5q221 0 221 190q0 73 -47 123.5 t-160 102.5q-147 70 -197.5 144.5t-50.5 172.5q0 134 109.5 219.5t285.5 85.5q151 0 235.5 -56t84.5 -138q0 -127 -154 -127z" />-<glyph glyph-name="t" unicode="t" horiz-adv-x="754" d="M616 154l35 -76q-138 -98 -276 -98q-223 0 -223 239q0 77 20 172l125 590h-156l19 82q200 0 313 287h94l-53 -252h248l-25 -117h-248l-124 -584q-25 -118 -25 -176q0 -112 109 -112q83 0 167 45z" />-<glyph glyph-name="u" unicode="u" horiz-adv-x="1227" d="M135 1098h381l-135 -613q-41 -186 -41 -247q0 -125 100 -125q105 0 226.5 151.5t158.5 323.5l107 510h198l-141 -658q-31 -145 -31 -227q0 -104 68 -104q58 0 131 45l37 -60q-130 -114 -260 -114q-77 0 -124.5 55t-47.5 139q0 53 10 121h-16q-111 -183 -198.5 -248 t-197.5 -65q-219 0 -219 227q0 65 37 225l80 359q18 81 18 122q0 97 -143 97h-16z" />-<glyph glyph-name="v" unicode="v" horiz-adv-x="1102" d="M197 903q-17 109 -142 109h-20l16 86h307l95 -606q36 -232 47 -363h6q153 147 265 343.5t112 315.5q0 72 -35 114t-92 42q0 74 40 119t99 45q139 0 139 -190q0 -186 -141.5 -435.5t-368.5 -480.5l-184 -20z" />-<glyph glyph-name="w" unicode="w" horiz-adv-x="1677" d="M246 -18l-15 882q-1 72 -28.5 110t-106.5 38h-20l28 86h308q6 -432 6 -934h8q267 469 457 915l170 8q43 -539 43 -937h14q167 205 276 405.5t109 318.5q0 94 -88 123q18 121 121 121q108 0 108 -153q0 -295 -514 -963l-202 -22l-31 886q-93 -221 -432 -860z" />-<glyph glyph-name="x" unicode="x" horiz-adv-x="1118" d="M-41 0l23 86h26q56 0 97.5 25.5t115.5 115.5l262 318l-133 319q-39 95 -64.5 121.5t-82.5 26.5h-27l17 86h272l156 -414l272 414h262l-33 -86h-26q-60 0 -100.5 -29t-112.5 -119l-215 -276l141 -355q36 -92 61.5 -119.5t85.5 -27.5h27l-16 -86h-275l-166 451l-307 -451 h-260z" />-<glyph glyph-name="y" unicode="y" horiz-adv-x="1081" d="M164 903q-18 60 -49 84.5t-92 24.5h-21l16 86h299l160 -553q74 -257 86 -371h6q277 421 277 680q0 111 -101 111q0 69 40 110t94 41q139 0 139 -172q0 -247 -276 -694.5t-442.5 -594.5t-438.5 -147q-108 0 -193 37l31 103q72 -23 160 -23q293 0 575 373z" />-<glyph glyph-name="z" unicode="z" horiz-adv-x="1047" d="M616 117q49 0 89.5 31t85.5 137l20 47h86l-80 -332h-819l16 82l697 899h-260q-71 0 -109.5 -34t-83.5 -132l-4 -8h-86l82 291h717l-17 -84l-698 -897h364z" />-<glyph glyph-name="braceleft" unicode="{" horiz-adv-x="877" d="M676 -262q-139 0 -217 77t-78 218v356q0 210 -242 217v86q242 5 242 215v357q0 292 295 292h135v-88h-61q-168 0 -168 -215v-350q0 -214 -228 -252v-2q228 -39 228 -254v-354q0 -215 168 -215h61v-88h-135z" />-<glyph glyph-name="bar" unicode="|" d="M649 -492h-123v2048h123v-2048z" />-<glyph glyph-name="braceright" unicode="}" horiz-adv-x="877" d="M129 -174h61q168 0 168 215v354q0 215 228 254v2q-228 38 -228 252v350q0 215 -168 215h-61v88h135q295 0 295 -292v-357q0 -210 242 -215v-86q-242 -7 -242 -217v-356q0 -141 -78 -218t-217 -77h-135v88z" />-<glyph glyph-name="asciitilde" unicode="~" horiz-adv-x="1149" d="M250 551h-121q28 358 258 358q94 0 248 -135q107 -102 168 -102q57 0 84 62t41 175h123q-39 -358 -258 -358q-97 0 -238 129q-118 108 -176 108q-60 0 -89 -58.5t-40 -178.5z" />-<glyph glyph-name="exclamdown" unicode="¡" horiz-adv-x="682" d="M420 1112q127 0 127 -125q0 -71 -36.5 -114.5t-98.5 -43.5q-127 0 -127 125q0 74 36.5 116t98.5 42zM260 -367h-237l294 1051h95z" />-<glyph glyph-name="cent" unicode="¢" d="M496 182q-156 26 -240 136t-84 282q0 249 155 468.5t406 244.5l33 149h102l-32 -151q116 -11 180.5 -64.5t64.5 -128.5q0 -62 -49.5 -102.5t-132.5 -40.5q0 166 -88 221l-188 -891q169 6 282 148q21 -15 21 -52q0 -79 -88 -149t-242 -76l-37 -176h-102zM524 319l189 893 q-138 -25 -233 -232t-95 -396q0 -214 139 -265z" />-<glyph glyph-name="sterling" unicode="£" d="M530 690q7 -44 7 -108q0 -237 -258 -389l2 -9q84 37 157 37q60 0 179 -39q118 -39 159 -39q114 0 205 93l55 -68q-185 -188 -348 -188q-65 0 -189 46t-198 46q-114 0 -238 -60l-28 -14l-37 82l72 41q295 168 295 442q0 50 -15 127h-227l22 103h189q-27 126 -27 206 q0 226 135.5 355t364.5 129q155 0 247.5 -56t92.5 -145q0 -162 -170 -162q0 260 -201 260q-139 0 -214 -96.5t-75 -271.5q0 -80 29 -219h348l-22 -103h-312z" />-<glyph glyph-name="currency" unicode="¤" d="M283 952l-136 135l86 87l134 -136q99 72 221 72q116 0 217 -72l137 138l86 -89l-135 -135q72 -99 72 -219t-72 -219l135 -135l-84 -86l-137 135q-104 -70 -219 -70q-124 0 -219 70l-136 -135l-86 86l134 135q-68 99 -68 219q0 124 70 219zM588 471q108 0 185 77.5 t77 184.5q0 110 -76 187t-186 77q-108 0 -184 -77t-76 -187q0 -107 76.5 -184.5t183.5 -77.5z" />-<glyph glyph-name="yen" unicode="¥" horiz-adv-x="1104" d="M582 225q-9 -44 -9 -57q0 -82 148 -82h47l-18 -86h-629l18 86h47q104 0 144 36t53 101l29 139h-287l20 97h287l31 143h-287l21 96h247l-157 570q-16 58 -39 83t-84 25h-6l18 86h479l-18 -86h-4q-69 0 -105 -13t-36 -56t24 -131l53 -220q35 -146 43 -249q22 39 107 167 l170 250q92 135 92 193q0 59 -111 59h-6l19 86h417l-18 -86h-18q-46 0 -78 -23.5t-111 -135.5l-360 -519h254l-21 -96h-286l-31 -143h287l-21 -97h-287z" />-<glyph glyph-name="brokenbar" unicode="¦" d="M649 737h-123v819h123v-819zM649 -492h-123v820h123v-820z" />-<glyph glyph-name="section" unicode="§" horiz-adv-x="1114" d="M170 131q0 -120 67 -193t183 -73q125 0 202.5 66.5t77.5 174.5q0 65 -51.5 125t-240.5 162q-228 124 -228 330q0 166 180 278q-49 83 -49 185q0 168 123 269t314 101q143 0 233 -60t90 -144q0 -74 -37 -108t-129 -34q0 115 -49.5 182.5t-144.5 67.5q-100 0 -171 -56 t-71 -153q0 -80 41.5 -133.5t228.5 -154.5q250 -134 250 -318q0 -172 -178 -309q41 -76 41 -152q0 -183 -127 -301.5t-336 -118.5q-157 0 -252.5 63.5t-95.5 168.5q0 65 36.5 100t92.5 35zM406 944q-82 -60 -82 -145q0 -76 50 -130t187 -130q121 -67 199 -138q76 83 76 156 q0 123 -211 238q-145 79 -219 149z" />-<glyph glyph-name="dieresis" unicode="¨" horiz-adv-x="1182" d="M514 1294q-102 0 -102 103q0 129 110 129q103 0 103 -103q0 -129 -111 -129zM903 1294q-102 0 -102 103q0 129 110 129q103 0 103 -103q0 -129 -111 -129z" />-<glyph glyph-name="copyright" unicode="©" horiz-adv-x="1731" d="M881 1485q309 0 531 -221.5t222 -532.5q0 -309 -221 -530t-532 -221q-312 0 -532 221t-220 530q0 311 221 532.5t531 221.5zM881 92q264 0 452.5 188t188.5 451q0 264 -188.5 452.5t-452.5 188.5q-265 0 -452 -189t-187 -452q0 -262 187 -450.5t452 -188.5zM1169 504 q29 -21 29 -64q0 -64 -83.5 -114t-223.5 -50q-194 0 -302 120t-108 337q0 210 117 331.5t322 121.5q132 0 206 -40t74 -106q0 -43 -39 -69.5t-100 -26.5q0 156 -146 156q-266 0 -266 -367q0 -177 69 -272.5t200 -95.5q170 0 251 139z" />-<glyph glyph-name="ordfeminine" unicode="ª" horiz-adv-x="805" d="M573 995h-8q-107 -213 -262 -213q-86 0 -133 60t-47 166q0 166 119.5 312t300.5 146q98 0 164 -43l63 29h45l-96 -379q-21 -83 -21 -131q0 -61 41 -61q35 0 88 32l23 -39q-93 -92 -178 -92q-113 0 -113 103q0 57 14 110zM629 1350q-29 41 -90 41q-102 0 -170 -125.5 t-68 -253.5q0 -133 70 -133q54 0 115.5 85.5t88.5 182.5z" />-<glyph glyph-name="guillemotleft" unicode="«" horiz-adv-x="1053" d="M139 586l389 356h103l-295 -399l129 -379h-103l-237 356zM510 586l389 356h102l-294 -399l129 -379h-103l-237 356z" />-<glyph glyph-name="logicalnot" unicode="¬" d="M1028 793v-541h-123v420h-755v121h878z" />-<glyph glyph-name="uni00AD" unicode="­" horiz-adv-x="635" d="M29 481l32 154h533l-33 -154h-532z" />-<glyph glyph-name="registered" unicode="®" horiz-adv-x="1731" d="M881 1485q309 0 531 -221.5t222 -532.5q0 -309 -221 -530t-532 -221q-312 0 -532 221t-220 530q0 311 221 532.5t531 221.5zM881 92q264 0 452.5 188t188.5 451q0 264 -188.5 452.5t-452.5 188.5q-265 0 -452 -189t-187 -452q0 -262 187 -450.5t452 -188.5zM494 358h26 q101 0 101 72v600q0 72 -101 72h-26v72h368q352 0 352 -244q0 -164 -186 -225l182 -293q20 -33 40.5 -43.5t74.5 -10.5v-69h-217l-225 381h-109v-240q0 -72 100 -72h29v-69h-409v69zM774 748h86q103 0 148 40t45 140q0 91 -46 128.5t-153 37.5h-80v-346z" />-<glyph glyph-name="macron" unicode="¯" horiz-adv-x="940" d="M950 1556h-960v121h960v-121z" />-<glyph glyph-name="degree" unicode="°" horiz-adv-x="819" d="M481 1462q128 0 220 -91t92 -220q0 -127 -91 -218t-221 -91q-128 0 -218.5 90.5t-90.5 218.5q0 130 91.5 220.5t217.5 90.5zM481 963q79 0 134 55t55 133q0 80 -55.5 135t-133.5 55q-75 0 -130.5 -54.5t-55.5 -135.5q0 -78 55.5 -133t130.5 -55z" />-<glyph glyph-name="plusminus" unicode="±" d="M649 672v-379h-123v379h-376v121h376v378h123v-378h379v-121h-379zM1028 0h-878v121h878v-121z" />-<glyph glyph-name="uni00B2" unicode="²" horiz-adv-x="819" d="M41 586l10 61l318 262q111 91 166 175.5t55 197.5q0 119 -103 119q-62 0 -107.5 -53.5t-57.5 -141.5q-62 0 -97 27.5t-35 71.5q0 77 84.5 127.5t227.5 50.5q124 0 196 -54t72 -141q0 -97 -62.5 -181t-220.5 -210l-245 -195h282q77 0 109 84l16 43h70l-57 -243h-621z" />-<glyph glyph-name="uni00B3" unicode="³" horiz-adv-x="819" d="M197 842q0 -75 45 -125.5t120 -50.5q195 0 195 182q0 69 -49 111t-137 42h-68l16 84h68q97 0 162 62t65 158t-94 96q-136 0 -172 -178q-135 0 -135 86q0 73 84.5 123.5t228.5 50.5q131 0 204 -51t73 -136q0 -189 -262 -241l-2 -11q206 -27 206 -188q0 -133 -107.5 -208 t-289.5 -75q-130 0 -199 43t-69 115q0 111 117 111z" />-<glyph glyph-name="acute" unicode="´" horiz-adv-x="1182" d="M508 1268q117 135 219 301h219l-6 -21q-157 -183 -356 -307h-82z" />-<glyph glyph-name="uni00B5" unicode="µ" horiz-adv-x="1237" d="M223 100q-33 -170 -33 -247q0 -149 113 -166q-19 -179 -174 -179q-133 0 -133 148q0 91 61 310q87 314 125 495l135 637h193l-135 -641q-23 -109 -23 -189q0 -157 150 -157q106 0 188 90t113 241l139 656h193l-164 -766q-25 -117 -25 -158q0 -65 60 -65q44 0 112 34 l35 -69q-136 -94 -238 -94q-145 0 -153 196h-10q-133 -196 -297 -196q-169 0 -232 120z" />-<glyph glyph-name="paragraph" unicode="¶" horiz-adv-x="1264" d="M877 1452h-195v-1679h-320v86h27q99 0 133.5 38t34.5 109v785h-145q-314 0 -314 401q0 364 318 364h780v-86h-27q-98 0 -133 -37.5t-35 -109.5v-1317q0 -72 35 -109.5t133 -37.5h27v-86h-319v1679z" />-<glyph glyph-name="periodcentered" unicode="·" horiz-adv-x="586" d="M315 588q-127 0 -127 125q0 71 36.5 114t98.5 43q127 0 127 -125q0 -73 -36.5 -115t-98.5 -42z" />-<glyph glyph-name="cedilla" unicode="¸" horiz-adv-x="682" d="M387 -70q74 -10 119 -58t45 -112q0 -119 -86.5 -185.5t-239.5 -66.5q-79 0 -153 23l22 104q70 -16 137 -16q152 0 152 125q0 102 -131 115l78 159h96z" />-<glyph glyph-name="uni00B9" unicode="¹" horiz-adv-x="819" d="M135 588l15 69h94q85 0 100 78l125 590h-190l14 70h102q143 0 187 78h102l-143 -678q-15 -71 -15 -86q0 -52 72 -52h94l-14 -69h-543z" />-<glyph glyph-name="ordmasculine" unicode="º" horiz-adv-x="813" d="M420 780q-133 0 -208 68.5t-75 187.5q0 162 117 297t303 135q130 0 204.5 -66.5t74.5 -184.5q0 -164 -115 -300.5t-301 -136.5zM543 1386q-98 0 -162 -119t-64 -253q0 -152 103 -152q99 0 161 118.5t62 260.5q0 145 -100 145z" />-<glyph glyph-name="guillemotright" unicode="»" horiz-adv-x="1053" d="M911 520l-389 -356h-102l299 399l-133 379h102l238 -356zM541 520l-389 -356h-103l299 399l-133 379h102l238 -356z" />-<glyph glyph-name="onequarter" unicode="¼" horiz-adv-x="1720" d="M1323 572q26 116 41 160l-43 -59l-291 -324h243zM1437 261l-18 -84q-8 -37 -8 -55q0 -52 71 -52h41l-14 -69h-451l15 69h57q85 0 100 78l25 113h-363l11 70l501 555h164l-112 -537h196l-18 -88h-197zM420 0h-143l1032 1462h139zM27 588l15 69h94q85 0 100 78l125 590 h-190l14 70h102q143 0 187 78h102l-143 -678q-15 -71 -15 -86q0 -52 72 -52h94l-14 -69h-543z" />-<glyph glyph-name="onehalf" unicode="½" horiz-adv-x="1720" d="M399 0h-143l1032 1462h139zM27 588l15 69h94q85 0 100 78l125 590h-190l14 70h102q143 0 187 78h102l-143 -678q-15 -71 -15 -86q0 -52 72 -52h94l-14 -69h-543zM922 1l10 61l318 262q111 91 166 175.5t55 197.5q0 119 -103 119q-62 0 -107.5 -53.5t-57.5 -141.5 q-62 0 -97 27.5t-35 71.5q0 77 84.5 127.5t227.5 50.5q124 0 196 -54t72 -141q0 -97 -62.5 -181t-220.5 -210l-245 -195h282q77 0 109 84l16 43h70l-57 -243h-621z" />-<glyph glyph-name="threequarters" unicode="¾" horiz-adv-x="1720" d="M1344 572q26 116 41 160l-43 -59l-291 -324h243zM1458 261l-18 -84q-8 -37 -8 -55q0 -52 71 -52h41l-14 -69h-451l15 69h57q85 0 100 78l25 113h-363l11 70l501 555h164l-112 -537h196l-18 -88h-197zM475 0h-143l1032 1462h139zM155 842q0 -75 45 -125.5t120 -50.5 q195 0 195 182q0 69 -49 111t-137 42h-68l16 84h68q97 0 162 62t65 158t-94 96q-136 0 -172 -178q-135 0 -135 86q0 73 84.5 123.5t228.5 50.5q131 0 204 -51t73 -136q0 -189 -262 -241l-2 -11q206 -27 206 -188q0 -133 -107.5 -208t-289.5 -75q-130 0 -199 43t-69 115 q0 111 117 111z" />-<glyph glyph-name="questiondown" unicode="¿" horiz-adv-x="1024" d="M666 1110q127 0 127 -125q0 -71 -37 -114.5t-99 -43.5q-60 0 -93.5 34.5t-33.5 90.5q0 75 37 116.5t99 41.5zM506 444l51 238h125l-64 -303q-415 -152 -415 -445q0 -100 54 -160.5t144 -60.5q212 0 254 291q148 0 148 -123q0 -113 -114.5 -191.5t-299.5 -78.5 q-387 0 -387 340q0 311 504 493z" />-<glyph glyph-name="Agrave" unicode="À" horiz-adv-x="1444" d="M408 489l-123 -215q-39 -68 -39 -114q0 -74 114 -74h48l-17 -86h-506l17 86h39q55 0 92 28.5t98 131.5l713 1216h160l194 -1267q10 -64 35 -86.5t86 -22.5h27l-17 -86h-557l16 86h48q147 0 147 104q0 42 -4 70l-33 229h-538zM881 950q-31 218 -33 324q-47 -111 -160 -305 l-219 -377h463zM923 1579q-154 150 -264 307l6 21h205q60 -155 137 -301l-6 -27h-78z" />-<glyph glyph-name="Aacute" unicode="Á" horiz-adv-x="1444" d="M408 489l-123 -215q-39 -68 -39 -114q0 -74 114 -74h48l-17 -86h-506l17 86h39q55 0 92 28.5t98 131.5l713 1216h160l194 -1267q10 -64 35 -86.5t86 -22.5h27l-17 -86h-557l16 86h48q147 0 147 104q0 42 -4 70l-33 229h-538zM881 950q-31 218 -33 324q-47 -111 -160 -305 l-219 -377h463zM838 1606q117 135 219 301h219l-6 -21q-157 -183 -356 -307h-82z" />-<glyph glyph-name="Acircumflex" unicode="Â" horiz-adv-x="1444" d="M408 489l-123 -215q-39 -68 -39 -114q0 -74 114 -74h48l-17 -86h-506l17 86h39q55 0 92 28.5t98 131.5l713 1216h160l194 -1267q10 -64 35 -86.5t86 -22.5h27l-17 -86h-557l16 86h48q147 0 147 104q0 42 -4 70l-33 229h-538zM881 950q-31 218 -33 324q-47 -111 -160 -305 l-219 -377h463zM617 1606q186 183 270 301h195q29 -117 139 -301l-6 -27h-72q-95 65 -182 184q-150 -127 -262 -184h-88z" />-<glyph glyph-name="Atilde" unicode="Ã" horiz-adv-x="1444" d="M408 489l-123 -215q-39 -68 -39 -114q0 -74 114 -74h48l-17 -86h-506l17 86h39q55 0 92 28.5t98 131.5l713 1216h160l194 -1267q10 -64 35 -86.5t86 -22.5h27l-17 -86h-557l16 86h48q147 0 147 104q0 42 -4 70l-33 229h-538zM881 950q-31 218 -33 324q-47 -111 -160 -305 l-219 -377h463zM1229 1821h92q-72 -242 -266 -242q-81 0 -169 65q-89 64 -126 64q-88 0 -123 -108h-92q72 241 268 241q82 0 169 -64q87 -65 124 -65q89 0 123 109z" />-<glyph glyph-name="Adieresis" unicode="Ä" horiz-adv-x="1444" d="M408 489l-123 -215q-39 -68 -39 -114q0 -74 114 -74h48l-17 -86h-506l17 86h39q55 0 92 28.5t98 131.5l713 1216h160l194 -1267q10 -64 35 -86.5t86 -22.5h27l-17 -86h-557l16 86h48q147 0 147 104q0 42 -4 70l-33 229h-538zM881 950q-31 218 -33 324q-47 -111 -160 -305 l-219 -377h463zM756 1632q-102 0 -102 103q0 129 110 129q103 0 103 -103q0 -129 -111 -129zM1145 1632q-102 0 -102 103q0 129 110 129q103 0 103 -103q0 -129 -111 -129z" />-<glyph glyph-name="Aring" unicode="Å" horiz-adv-x="1444" d="M914 1708q-135 0 -135 -123t135 -123t135 123t-135 123zM914 1362q-107 0 -176.5 59t-69.5 164q0 104 69 163.5t177 59.5t177 -59.5t69 -163.5q0 -105 -69.5 -164t-176.5 -59zM408 489l-123 -215q-39 -68 -39 -114q0 -74 114 -74h48l-17 -86h-506l17 86h39q55 0 92 28.5 t98 131.5l713 1216h160l194 -1267q10 -64 35 -86.5t86 -22.5h27l-17 -86h-557l16 86h48q147 0 147 104q0 42 -4 70l-33 229h-538zM881 950q-31 218 -33 324q-47 -111 -160 -305l-219 -377h463z" />-<glyph glyph-name="AE" unicode="Æ" horiz-adv-x="1948" d="M-127 0l29 86h39q54 0 94.5 28t116.5 132l815 1118l-174 12l18 86h1159l-65 -358h-107q10 70 10 119q0 137 -145 137h-344l-117 -545h494l-21 -100h-493l-131 -613h395q172 0 231 168l33 88h107l-91 -358h-1097l16 86h27q103 0 144 35.5t54 101.5l56 266h-463l-154 -215 q-55 -77 -55 -120q0 -68 104 -68h48l-29 -86h-504zM1110 1360h-39q-97 -168 -260 -391l-272 -377h409z" />-<glyph glyph-name="Ccedilla" unicode="Ç" horiz-adv-x="1284" d="M1073 299q27 -19 27 -68q0 -98 -114 -174.5t-314 -76.5q-267 0 -416.5 143t-149.5 405q0 388 242 671.5t602 283.5q179 0 279 -61.5t100 -166.5q0 -73 -55 -116t-146 -43q0 278 -204 278q-253 0 -423.5 -275.5t-170.5 -602.5q0 -383 364 -383q237 0 379 186zM649 -70 q74 -10 119 -58t45 -112q0 -119 -86.5 -185.5t-239.5 -66.5q-79 0 -153 23l22 104q70 -16 137 -16q152 0 152 125q0 102 -131 115l78 159h96z" />-<glyph glyph-name="Egrave" unicode="È" horiz-adv-x="1276" d="M379 102h395q173 0 232 168l32 88h107l-90 -358h-1098l16 86h27q104 0 144.5 36.5t54.5 100.5l213 1006q6 28 6 49q0 48 -28 73t-116 25h-26l18 86h1032l-65 -358h-107l6 63q5 48 5 56q0 137 -146 137h-344l-117 -545h494l-20 -100h-494zM849 1579q-154 150 -264 307 l6 21h205q60 -155 137 -301l-6 -27h-78z" />-<glyph glyph-name="Eacute" unicode="É" horiz-adv-x="1276" d="M379 102h395q173 0 232 168l32 88h107l-90 -358h-1098l16 86h27q104 0 144.5 36.5t54.5 100.5l213 1006q6 28 6 49q0 48 -28 73t-116 25h-26l18 86h1032l-65 -358h-107l6 63q5 48 5 56q0 137 -146 137h-344l-117 -545h494l-20 -100h-494zM725 1606q117 135 219 301h219 l-6 -21q-157 -183 -356 -307h-82z" />-<glyph glyph-name="Ecircumflex" unicode="Ê" horiz-adv-x="1276" d="M379 102h395q173 0 232 168l32 88h107l-90 -358h-1098l16 86h27q104 0 144.5 36.5t54.5 100.5l213 1006q6 28 6 49q0 48 -28 73t-116 25h-26l18 86h1032l-65 -358h-107l6 63q5 48 5 56q0 137 -146 137h-344l-117 -545h494l-20 -100h-494zM551 1606q186 183 270 301h195 q29 -117 139 -301l-6 -27h-72q-95 65 -182 184q-150 -127 -262 -184h-88z" />-<glyph glyph-name="Edieresis" unicode="Ë" horiz-adv-x="1276" d="M379 102h395q173 0 232 168l32 88h107l-90 -358h-1098l16 86h27q104 0 144.5 36.5t54.5 100.5l213 1006q6 28 6 49q0 48 -28 73t-116 25h-26l18 86h1032l-65 -358h-107l6 63q5 48 5 56q0 137 -146 137h-344l-117 -545h494l-20 -100h-494zM643 1632q-102 0 -102 103 q0 129 110 129q103 0 103 -103q0 -129 -111 -129zM1032 1632q-102 0 -102 103q0 129 110 129q103 0 103 -103q0 -129 -111 -129z" />-<glyph glyph-name="Igrave" unicode="Ì" horiz-adv-x="752" d="M862 1462l-18 -86h-27q-98 0 -141 -37t-58 -110l-212 -1004q-9 -43 -9 -57q0 -82 148 -82h26l-18 -86h-596l16 86h27q170 0 201 147l213 1004q8 38 8 57q0 82 -148 82h-26l18 86h596zM607 1579q-154 150 -264 307l6 21h205q60 -155 137 -301l-6 -27h-78z" />-<glyph glyph-name="Iacute" unicode="Í" horiz-adv-x="752" d="M862 1462l-18 -86h-27q-98 0 -141 -37t-58 -110l-212 -1004q-9 -43 -9 -57q0 -82 148 -82h26l-18 -86h-596l16 86h27q170 0 201 147l213 1004q8 38 8 57q0 82 -148 82h-26l18 86h596zM476 1606q117 135 219 301h219l-6 -21q-157 -183 -356 -307h-82z" />-<glyph glyph-name="Icircumflex" unicode="Î" horiz-adv-x="752" d="M862 1462l-18 -86h-27q-98 0 -141 -37t-58 -110l-212 -1004q-9 -43 -9 -57q0 -82 148 -82h26l-18 -86h-596l16 86h27q170 0 201 147l213 1004q8 38 8 57q0 82 -148 82h-26l18 86h596zM280 1606q186 183 270 301h195q29 -117 139 -301l-6 -27h-72q-95 65 -182 184 q-150 -127 -262 -184h-88z" />-<glyph glyph-name="Idieresis" unicode="Ï" horiz-adv-x="752" d="M862 1462l-18 -86h-27q-98 0 -141 -37t-58 -110l-212 -1004q-9 -43 -9 -57q0 -82 148 -82h26l-18 -86h-596l16 86h27q170 0 201 147l213 1004q8 38 8 57q0 82 -148 82h-26l18 86h596zM404 1632q-102 0 -102 103q0 129 110 129q103 0 103 -103q0 -129 -111 -129zM793 1632 q-102 0 -102 103q0 129 110 129q103 0 103 -103q0 -129 -111 -129z" />-<glyph glyph-name="Eth" unicode="Ð" horiz-adv-x="1530" d="M-27 86h27q170 0 201 147l102 482h-199l23 100h197l90 422q8 38 8 57q0 82 -148 82h-26l18 86h615q279 0 430 -144.5t151 -412.5q0 -392 -223 -648.5t-596 -256.5h-686zM598 104q294 0 467.5 226t173.5 598q0 432 -389 432h-203l-117 -545h388l-23 -100h-387l-129 -611 h219z" />-<glyph glyph-name="Ntilde" unicode="Ñ" horiz-adv-x="1563" d="M1327 1237q8 38 8 57q0 82 -147 82h-27l19 86h514l-19 -86h-26q-98 0 -140.5 -37.5t-58.5 -109.5l-260 -1229h-133l-527 1200l-206 -975q-9 -43 -9 -57q0 -82 148 -82h26l-18 -86h-512l18 86h27q96 0 138 37.5t59 109.5l213 1004q8 38 8 57q0 82 -148 82h-26l18 86h373 l496 -1130zM1347 1821h92q-72 -242 -266 -242q-81 0 -169 65q-89 64 -126 64q-88 0 -123 -108h-92q72 241 268 241q82 0 169 -64q87 -65 124 -65q89 0 123 109z" />-<glyph glyph-name="Ograve" unicode="Ò" horiz-adv-x="1520" d="M926 1485q238 0 384 -148t146 -415q0 -351 -228 -648t-585 -297q-243 0 -390 153.5t-147 414.5q0 352 230 646t590 294zM655 90q255 0 415.5 282.5t160.5 604.5q0 191 -84.5 294t-237.5 103q-256 0 -417.5 -283.5t-161.5 -603.5q0 -192 87 -294.5t238 -102.5zM1022 1579 q-154 150 -264 307l6 21h205q60 -155 137 -301l-6 -27h-78z" />-<glyph glyph-name="Oacute" unicode="Ó" horiz-adv-x="1520" d="M926 1485q238 0 384 -148t146 -415q0 -351 -228 -648t-585 -297q-243 0 -390 153.5t-147 414.5q0 352 230 646t590 294zM655 90q255 0 415.5 282.5t160.5 604.5q0 191 -84.5 294t-237.5 103q-256 0 -417.5 -283.5t-161.5 -603.5q0 -192 87 -294.5t238 -102.5zM827 1606 q117 135 219 301h219l-6 -21q-157 -183 -356 -307h-82z" />-<glyph glyph-name="Ocircumflex" unicode="Ô" horiz-adv-x="1520" d="M926 1485q238 0 384 -148t146 -415q0 -351 -228 -648t-585 -297q-243 0 -390 153.5t-147 414.5q0 352 230 646t590 294zM655 90q255 0 415.5 282.5t160.5 604.5q0 191 -84.5 294t-237.5 103q-256 0 -417.5 -283.5t-161.5 -603.5q0 -192 87 -294.5t238 -102.5zM639 1606 q186 183 270 301h195q29 -117 139 -301l-6 -27h-72q-95 65 -182 184q-150 -127 -262 -184h-88z" />-<glyph glyph-name="Otilde" unicode="Õ" horiz-adv-x="1520" d="M926 1485q238 0 384 -148t146 -415q0 -351 -228 -648t-585 -297q-243 0 -390 153.5t-147 414.5q0 352 230 646t590 294zM655 90q255 0 415.5 282.5t160.5 604.5q0 191 -84.5 294t-237.5 103q-256 0 -417.5 -283.5t-161.5 -603.5q0 -192 87 -294.5t238 -102.5zM1272 1821 h92q-72 -242 -266 -242q-81 0 -169 65q-89 64 -126 64q-88 0 -123 -108h-92q72 241 268 241q82 0 169 -64q87 -65 124 -65q89 0 123 109z" />-<glyph glyph-name="Odieresis" unicode="Ö" horiz-adv-x="1520" d="M926 1485q238 0 384 -148t146 -415q0 -351 -228 -648t-585 -297q-243 0 -390 153.5t-147 414.5q0 352 230 646t590 294zM655 90q255 0 415.5 282.5t160.5 604.5q0 191 -84.5 294t-237.5 103q-256 0 -417.5 -283.5t-161.5 -603.5q0 -192 87 -294.5t238 -102.5zM780 1632 q-102 0 -102 103q0 129 110 129q103 0 103 -103q0 -129 -111 -129zM1169 1632q-102 0 -102 103q0 129 110 129q103 0 103 -103q0 -129 -111 -129z" />-<glyph glyph-name="multiply" unicode="×" d="M571 647l-340 -340l-86 88l340 340l-340 338l86 86l340 -340l340 342l86 -88l-340 -340l340 -340l-86 -86z" />-<glyph glyph-name="Oslash" unicode="Ø" horiz-adv-x="1520" d="M1217 1409l106 137h119l-150 -194q164 -152 164 -430q0 -351 -228 -648t-585 -297q-165 0 -289 74l-96 -133h-121l137 191q-168 156 -168 436q0 352 230 646t590 294q165 0 291 -76zM379 250l751 1038q-84 86 -221 86q-256 0 -417.5 -283.5t-161.5 -603.5q0 -139 49 -237 zM1188 1204l-750 -1036q86 -78 217 -78q255 0 415.5 282.5t160.5 604.5q0 129 -43 227z" />-<glyph glyph-name="Ugrave" unicode="Ù" horiz-adv-x="1495" d="M205 1376l18 86h596l-16 -86h-27q-96 0 -140 -36.5t-61 -110.5l-153 -723q-25 -118 -25 -191q0 -213 295 -213q166 0 263 74t135 246l174 815q8 37 8 57q0 82 -148 82h-26l18 86h514l-16 -86h-27q-96 0 -140 -36.5t-61 -110.5l-172 -811q-93 -438 -552 -438 q-226 0 -345.5 92t-119.5 262q0 83 22 182l152 721q8 38 8 57q0 82 -148 82h-26zM981 1579q-154 150 -264 307l6 21h205q60 -155 137 -301l-6 -27h-78z" />-<glyph glyph-name="Uacute" unicode="Ú" horiz-adv-x="1495" d="M205 1376l18 86h596l-16 -86h-27q-96 0 -140 -36.5t-61 -110.5l-153 -723q-25 -118 -25 -191q0 -213 295 -213q166 0 263 74t135 246l174 815q8 37 8 57q0 82 -148 82h-26l18 86h514l-16 -86h-27q-96 0 -140 -36.5t-61 -110.5l-172 -811q-93 -438 -552 -438 q-226 0 -345.5 92t-119.5 262q0 83 22 182l152 721q8 38 8 57q0 82 -148 82h-26zM864 1606q117 135 219 301h219l-6 -21q-157 -183 -356 -307h-82z" />-<glyph glyph-name="Ucircumflex" unicode="Û" horiz-adv-x="1495" d="M205 1376l18 86h596l-16 -86h-27q-96 0 -140 -36.5t-61 -110.5l-153 -723q-25 -118 -25 -191q0 -213 295 -213q166 0 263 74t135 246l174 815q8 37 8 57q0 82 -148 82h-26l18 86h514l-16 -86h-27q-96 0 -140 -36.5t-61 -110.5l-172 -811q-93 -438 -552 -438 q-226 0 -345.5 92t-119.5 262q0 83 22 182l152 721q8 38 8 57q0 82 -148 82h-26zM680 1606q186 183 270 301h195q29 -117 139 -301l-6 -27h-72q-95 65 -182 184q-150 -127 -262 -184h-88z" />-<glyph glyph-name="Udieresis" unicode="Ü" horiz-adv-x="1495" d="M205 1376l18 86h596l-16 -86h-27q-96 0 -140 -36.5t-61 -110.5l-153 -723q-25 -118 -25 -191q0 -213 295 -213q166 0 263 74t135 246l174 815q8 37 8 57q0 82 -148 82h-26l18 86h514l-16 -86h-27q-96 0 -140 -36.5t-61 -110.5l-172 -811q-93 -438 -552 -438 q-226 0 -345.5 92t-119.5 262q0 83 22 182l152 721q8 38 8 57q0 82 -148 82h-26zM774 1632q-102 0 -102 103q0 129 110 129q103 0 103 -103q0 -129 -111 -129zM1163 1632q-102 0 -102 103q0 129 110 129q103 0 103 -103q0 -129 -111 -129z" />-<glyph glyph-name="Yacute" unicode="Ý" horiz-adv-x="1280" d="M729 1462l-18 -86h-103q-84 0 -84 -55q0 -40 27 -119l92 -274q49 -146 55 -246q20 38 154 215l195 260q75 100 75 166q0 53 -98 53h-76l19 86h506l-19 -86h-39q-39 0 -77.5 -31.5t-108.5 -123.5l-477 -625l-80 -371q-8 -37 -8 -57q0 -82 147 -82h47l-18 -86h-637l18 86 h47q103 0 143 35.5t54 101.5l80 377l-238 668q-23 65 -45.5 86.5t-77.5 21.5h-26l18 86h553zM770 1606q117 135 219 301h219l-6 -21q-157 -183 -356 -307h-82z" />-<glyph glyph-name="Thorn" unicode="Þ" horiz-adv-x="1237" d="M-41 0l18 86h27q96 0 139 37t58 110l213 1004q8 38 8 57q0 82 -148 82h-26l18 86h617l-19 -86h-47q-103 0 -144 -38.5t-55 -108.5l-4 -21h121q222 0 344.5 -103t122.5 -292q0 -233 -157.5 -365.5t-469.5 -132.5h-151l-18 -90q-9 -45 -9 -57q0 -82 148 -82h47l-19 -86 h-614zM444 412h109q432 0 432 399q0 299 -299 299h-94z" />-<glyph glyph-name="germandbls" unicode="ß" horiz-adv-x="1284" d="M-279 -475l19 94q51 -14 94 -14q102 0 159 87.5t99 289.5l217 1019h-166l21 97h166l14 61q94 408 512 408q373 0 373 -314q0 -109 -29 -247h-176q-225 0 -225 -168q0 -70 44.5 -117t125.5 -94q192 -113 192 -308q0 -162 -101.5 -250.5t-293.5 -88.5q-145 0 -228 55.5 t-83 152.5q0 60 43.5 97t109.5 37q0 -244 172 -244q91 0 146 52.5t55 141.5q0 138 -156 230q-204 126 -204 293q0 307 407 307q23 103 23 170q0 203 -201 203q-243 0 -307 -287l-258 -1204q-54 -250 -156.5 -363t-288.5 -113q-58 0 -119 17z" />-<glyph glyph-name="agrave" unicode="à" horiz-adv-x="1186" d="M748 309h-13q-147 -329 -383 -329q-125 0 -194.5 98.5t-69.5 275.5q0 282 175.5 523t434.5 241q139 0 240 -74l90 52h66l-140 -656q-30 -141 -30 -227q0 -104 67 -104q58 0 148 53l37 -60q-147 -122 -289 -122q-160 0 -160 170q0 65 21 159zM864 952q-47 74 -155 74 q-175 0 -293.5 -222.5t-118.5 -457.5q0 -237 129 -237q102 0 214 151t148 330zM705 1241q-154 150 -264 307l6 21h205q60 -155 137 -301l-6 -27h-78z" />-<glyph glyph-name="aacute" unicode="á" horiz-adv-x="1186" d="M748 309h-13q-147 -329 -383 -329q-125 0 -194.5 98.5t-69.5 275.5q0 282 175.5 523t434.5 241q139 0 240 -74l90 52h66l-140 -656q-30 -141 -30 -227q0 -104 67 -104q58 0 148 53l37 -60q-147 -122 -289 -122q-160 0 -160 170q0 65 21 159zM864 952q-47 74 -155 74 q-175 0 -293.5 -222.5t-118.5 -457.5q0 -237 129 -237q102 0 214 151t148 330zM637 1268q117 135 219 301h219l-6 -21q-157 -183 -356 -307h-82z" />-<glyph glyph-name="acircumflex" unicode="â" horiz-adv-x="1186" d="M748 309h-13q-147 -329 -383 -329q-125 0 -194.5 98.5t-69.5 275.5q0 282 175.5 523t434.5 241q139 0 240 -74l90 52h66l-140 -656q-30 -141 -30 -227q0 -104 67 -104q58 0 148 53l37 -60q-147 -122 -289 -122q-160 0 -160 170q0 65 21 159zM864 952q-47 74 -155 74 q-175 0 -293.5 -222.5t-118.5 -457.5q0 -237 129 -237q102 0 214 151t148 330zM410 1268q186 183 270 301h195q29 -117 139 -301l-6 -27h-72q-95 65 -182 184q-150 -127 -262 -184h-88z" />-<glyph glyph-name="atilde" unicode="ã" horiz-adv-x="1186" d="M748 309h-13q-147 -329 -383 -329q-125 0 -194.5 98.5t-69.5 275.5q0 282 175.5 523t434.5 241q139 0 240 -74l90 52h66l-140 -656q-30 -141 -30 -227q0 -104 67 -104q58 0 148 53l37 -60q-147 -122 -289 -122q-160 0 -160 170q0 65 21 159zM864 952q-47 74 -155 74 q-175 0 -293.5 -222.5t-118.5 -457.5q0 -237 129 -237q102 0 214 151t148 330zM1025 1483h92q-72 -242 -266 -242q-81 0 -169 65q-89 64 -126 64q-88 0 -123 -108h-92q72 241 268 241q82 0 169 -64q87 -65 124 -65q89 0 123 109z" />-<glyph glyph-name="adieresis" unicode="ä" horiz-adv-x="1186" d="M748 309h-13q-147 -329 -383 -329q-125 0 -194.5 98.5t-69.5 275.5q0 282 175.5 523t434.5 241q139 0 240 -74l90 52h66l-140 -656q-30 -141 -30 -227q0 -104 67 -104q58 0 148 53l37 -60q-147 -122 -289 -122q-160 0 -160 170q0 65 21 159zM864 952q-47 74 -155 74 q-175 0 -293.5 -222.5t-118.5 -457.5q0 -237 129 -237q102 0 214 151t148 330zM505 1294q-102 0 -102 103q0 129 110 129q103 0 103 -103q0 -129 -111 -129zM894 1294q-102 0 -102 103q0 129 110 129q103 0 103 -103q0 -129 -111 -129z" />-<glyph glyph-name="aring" unicode="å" horiz-adv-x="1186" d="M737 1567q-135 0 -135 -123t135 -123t135 123t-135 123zM737 1221q-107 0 -176.5 59t-69.5 164q0 104 69 163.5t177 59.5t177 -59.5t69 -163.5q0 -105 -69.5 -164t-176.5 -59zM748 309h-13q-147 -329 -383 -329q-125 0 -194.5 98.5t-69.5 275.5q0 282 175.5 523 t434.5 241q139 0 240 -74l90 52h66l-140 -656q-30 -141 -30 -227q0 -104 67 -104q58 0 148 53l37 -60q-147 -122 -289 -122q-160 0 -160 170q0 65 21 159zM864 952q-47 74 -155 74q-175 0 -293.5 -222.5t-118.5 -457.5q0 -237 129 -237q102 0 214 151t148 330z" />-<glyph glyph-name="ae" unicode="æ" horiz-adv-x="1665" d="M1477 256q20 -14 20 -51q0 -84 -96.5 -154.5t-257.5 -70.5q-141 0 -243 75t-136 219q-116 -294 -367 -294q-140 0 -224.5 108t-84.5 299q0 278 164.5 504.5t433.5 226.5q161 0 313 -127q153 127 355 127q130 0 201 -62t71 -173q0 -166 -172.5 -289t-421.5 -123h-35 q-18 0 -36 2q-5 -25 -5 -86q0 -278 226 -278q171 0 295 147zM905 893q-97 133 -209 133q-173 0 -286 -217t-113 -444q0 -122 45.5 -189t126.5 -67q102 0 177 99t104 267q38 233 155 418zM979 567h20q181 0 298.5 88t117.5 228q0 137 -119 137q-100 0 -186.5 -127.5 t-130.5 -325.5z" />-<glyph glyph-name="ccedilla" unicode="ç" horiz-adv-x="997" d="M821 256q21 -13 21 -51q0 -82 -96.5 -153.5t-258.5 -71.5q-179 0 -289 113t-110 310q0 268 173 491.5t437 223.5q141 0 220 -56t79 -140q0 -62 -49.5 -103t-132.5 -41q0 109 -42 175.5t-116 66.5q-143 0 -249.5 -214t-106.5 -419q0 -278 225 -278q179 0 295 147zM487 -70 q74 -10 119 -58t45 -112q0 -119 -86.5 -185.5t-239.5 -66.5q-79 0 -153 23l22 104q70 -16 137 -16q152 0 152 125q0 102 -131 115l78 159h96z" />-<glyph glyph-name="egrave" unicode="è" horiz-adv-x="1010" d="M821 256q21 -13 21 -51q0 -82 -96.5 -153.5t-258.5 -71.5q-179 0 -289 113t-110 310q0 268 173 491.5t437 223.5q130 0 201.5 -62t71.5 -173q0 -166 -172.5 -289t-421.5 -123h-35q-18 0 -37 2q-4 -28 -4 -86q0 -278 225 -278q175 0 295 147zM324 567h20q181 0 298.5 88 t117.5 228q0 137 -119 137q-99 0 -185 -124.5t-132 -328.5zM721 1241q-154 150 -264 307l6 21h205q60 -155 137 -301l-6 -27h-78z" />-<glyph glyph-name="eacute" unicode="é" horiz-adv-x="1010" d="M821 256q21 -13 21 -51q0 -82 -96.5 -153.5t-258.5 -71.5q-179 0 -289 113t-110 310q0 268 173 491.5t437 223.5q130 0 201.5 -62t71.5 -173q0 -166 -172.5 -289t-421.5 -123h-35q-18 0 -37 2q-4 -28 -4 -86q0 -278 225 -278q175 0 295 147zM324 567h20q181 0 298.5 88 t117.5 228q0 137 -119 137q-99 0 -185 -124.5t-132 -328.5zM614 1268q117 135 219 301h219l-6 -21q-157 -183 -356 -307h-82z" />-<glyph glyph-name="ecircumflex" unicode="ê" horiz-adv-x="1010" d="M821 256q21 -13 21 -51q0 -82 -96.5 -153.5t-258.5 -71.5q-179 0 -289 113t-110 310q0 268 173 491.5t437 223.5q130 0 201.5 -62t71.5 -173q0 -166 -172.5 -289t-421.5 -123h-35q-18 0 -37 2q-4 -28 -4 -86q0 -278 225 -278q175 0 295 147zM324 567h20q181 0 298.5 88 t117.5 228q0 137 -119 137q-99 0 -185 -124.5t-132 -328.5zM387 1268q186 183 270 301h195q29 -117 139 -301l-6 -27h-72q-95 65 -182 184q-150 -127 -262 -184h-88z" />-<glyph glyph-name="edieresis" unicode="ë" horiz-adv-x="1010" d="M821 256q21 -13 21 -51q0 -82 -96.5 -153.5t-258.5 -71.5q-179 0 -289 113t-110 310q0 268 173 491.5t437 223.5q130 0 201.5 -62t71.5 -173q0 -166 -172.5 -289t-421.5 -123h-35q-18 0 -37 2q-4 -28 -4 -86q0 -278 225 -278q175 0 295 147zM324 567h20q181 0 298.5 88 t117.5 228q0 137 -119 137q-99 0 -185 -124.5t-132 -328.5zM509 1294q-102 0 -102 103q0 129 110 129q103 0 103 -103q0 -129 -111 -129zM898 1294q-102 0 -102 103q0 129 110 129q103 0 103 -103q0 -129 -111 -129z" />-<glyph glyph-name="igrave" unicode="ì" horiz-adv-x="623" d="M127 1012l18 86h367l-139 -658q-31 -147 -31 -227q0 -104 68 -104q59 0 147 53l37 -60q-142 -122 -277 -122q-76 0 -123 55t-47 139q0 92 46 322l79 376q7 33 7 54q0 86 -125 86h-27zM455 1241q-154 150 -264 307l6 21h205q60 -155 137 -301l-6 -27h-78z" />-<glyph glyph-name="iacute" unicode="í" horiz-adv-x="623" d="M127 1012l18 86h367l-139 -658q-31 -147 -31 -227q0 -104 68 -104q59 0 147 53l37 -60q-142 -122 -277 -122q-76 0 -123 55t-47 139q0 92 46 322l79 376q7 33 7 54q0 86 -125 86h-27zM333 1268q117 135 219 301h219l-6 -21q-157 -183 -356 -307h-82z" />-<glyph glyph-name="icircumflex" unicode="î" horiz-adv-x="623" d="M127 1012l18 86h367l-139 -658q-31 -147 -31 -227q0 -104 68 -104q59 0 147 53l37 -60q-142 -122 -277 -122q-76 0 -123 55t-47 139q0 92 46 322l79 376q7 33 7 54q0 86 -125 86h-27zM79 1268q186 183 270 301h195q29 -117 139 -301l-6 -27h-72q-95 65 -182 184 q-150 -127 -262 -184h-88z" />-<glyph glyph-name="idieresis" unicode="ï" horiz-adv-x="623" d="M127 1012l18 86h367l-139 -658q-31 -147 -31 -227q0 -104 68 -104q59 0 147 53l37 -60q-142 -122 -277 -122q-76 0 -123 55t-47 139q0 92 46 322l79 376q7 33 7 54q0 86 -125 86h-27zM208 1294q-102 0 -102 103q0 129 110 129q103 0 103 -103q0 -129 -111 -129zM597 1294 q-102 0 -102 103q0 129 110 129q103 0 103 -103q0 -129 -111 -129z" />-<glyph glyph-name="eth" unicode="ð" horiz-adv-x="1182" d="M877 983q-45 185 -144 291l-293 -150l25 115l199 102q-89 68 -226 115l35 103q184 -47 324 -152l297 154l-27 -117l-201 -103q102 -101 161.5 -262t59.5 -338q0 -348 -161.5 -561t-444.5 -213q-180 0 -286.5 106.5t-106.5 295.5q0 254 168 461.5t442 207.5q109 0 179 -55 zM504 68q169 0 269.5 186.5t100.5 409.5q0 268 -200 268q-168 0 -270.5 -186.5t-102.5 -409.5q0 -268 203 -268z" />-<glyph glyph-name="ntilde" unicode="ñ" horiz-adv-x="1227" d="M281 0h-191l172 823q12 57 12 92q0 97 -143 97h-16l18 86h379l-55 -258h20q166 276 373 276q219 0 219 -227q0 -65 -37 -225l-53 -242q-27 -123 -27 -215q0 -98 72 -98q59 0 131 47l37 -60q-134 -116 -250 -116q-85 0 -135.5 55t-50.5 149t39 277l47 212q30 124 30 191 q0 121 -94 121q-114 0 -233.5 -154t-153.5 -307zM1046 1483h92q-72 -242 -266 -242q-81 0 -169 65q-89 64 -126 64q-88 0 -123 -108h-92q72 241 268 241q82 0 169 -64q87 -65 124 -65q89 0 123 109z" />-<glyph glyph-name="ograve" unicode="ò" horiz-adv-x="1176" d="M698 1118q181 0 285 -109t104 -315q0 -273 -167 -493.5t-439 -220.5q-185 0 -289 112t-104 311q0 276 168.5 495.5t441.5 219.5zM504 80q169 0 269.5 200.5t100.5 444.5q0 285 -200 285q-167 0 -270 -201t-103 -447q0 -282 203 -282zM734 1241q-154 150 -264 307l6 21 h205q60 -155 137 -301l-6 -27h-78z" />-<glyph glyph-name="oacute" unicode="ó" horiz-adv-x="1176" d="M698 1118q181 0 285 -109t104 -315q0 -273 -167 -493.5t-439 -220.5q-185 0 -289 112t-104 311q0 276 168.5 495.5t441.5 219.5zM504 80q169 0 269.5 200.5t100.5 444.5q0 285 -200 285q-167 0 -270 -201t-103 -447q0 -282 203 -282zM629 1268q117 135 219 301h219 l-6 -21q-157 -183 -356 -307h-82z" />-<glyph glyph-name="ocircumflex" unicode="ô" horiz-adv-x="1176" d="M698 1118q181 0 285 -109t104 -315q0 -273 -167 -493.5t-439 -220.5q-185 0 -289 112t-104 311q0 276 168.5 495.5t441.5 219.5zM504 80q169 0 269.5 200.5t100.5 444.5q0 285 -200 285q-167 0 -270 -201t-103 -447q0 -282 203 -282zM399 1268q186 183 270 301h195 q29 -117 139 -301l-6 -27h-72q-95 65 -182 184q-150 -127 -262 -184h-88z" />-<glyph glyph-name="otilde" unicode="õ" horiz-adv-x="1176" d="M698 1118q181 0 285 -109t104 -315q0 -273 -167 -493.5t-439 -220.5q-185 0 -289 112t-104 311q0 276 168.5 495.5t441.5 219.5zM504 80q169 0 269.5 200.5t100.5 444.5q0 285 -200 285q-167 0 -270 -201t-103 -447q0 -282 203 -282zM1023 1483h92q-72 -242 -266 -242 q-81 0 -169 65q-89 64 -126 64q-88 0 -123 -108h-92q72 241 268 241q82 0 169 -64q87 -65 124 -65q89 0 123 109z" />-<glyph glyph-name="odieresis" unicode="ö" horiz-adv-x="1176" d="M698 1118q181 0 285 -109t104 -315q0 -273 -167 -493.5t-439 -220.5q-185 0 -289 112t-104 311q0 276 168.5 495.5t441.5 219.5zM504 80q169 0 269.5 200.5t100.5 444.5q0 285 -200 285q-167 0 -270 -201t-103 -447q0 -282 203 -282zM518 1294q-102 0 -102 103 q0 129 110 129q103 0 103 -103q0 -129 -111 -129zM907 1294q-102 0 -102 103q0 129 110 129q103 0 103 -103q0 -129 -111 -129z" />-<glyph glyph-name="divide" unicode="÷" d="M590 1233q104 0 104 -113q0 -112 -104 -112q-105 0 -105 112q0 113 105 113zM1028 672h-878v121h878v-121zM590 457q104 0 104 -113t-104 -113q-105 0 -105 113t105 113z" />-<glyph glyph-name="oslash" unicode="ø" horiz-adv-x="1182" d="M942 1044l115 136h119l-166 -203q77 -106 77 -283q0 -275 -167 -497t-439 -222q-141 0 -243 72l-107 -129h-121l162 195q-84 110 -84 290q0 276 168.5 495.5t441.5 219.5q149 0 244 -74zM305 274l533 644q-49 92 -164 92q-167 0 -270 -201t-103 -447q0 -65 4 -88z M868 809l-530 -641q46 -92 166 -92q168 0 269 201.5t101 447.5q0 44 -6 84z" />-<glyph glyph-name="ugrave" unicode="ù" horiz-adv-x="1227" d="M135 1098h381l-135 -613q-41 -186 -41 -247q0 -125 100 -125q105 0 226.5 151.5t158.5 323.5l107 510h198l-141 -658q-31 -145 -31 -227q0 -104 68 -104q58 0 131 45l37 -60q-130 -114 -260 -114q-77 0 -124.5 55t-47.5 139q0 53 10 121h-16q-111 -183 -198.5 -248 t-197.5 -65q-219 0 -219 227q0 65 37 225l80 359q18 81 18 122q0 97 -143 97h-16zM750 1241q-154 150 -264 307l6 21h205q60 -155 137 -301l-6 -27h-78z" />-<glyph glyph-name="uacute" unicode="ú" horiz-adv-x="1227" d="M135 1098h381l-135 -613q-41 -186 -41 -247q0 -125 100 -125q105 0 226.5 151.5t158.5 323.5l107 510h198l-141 -658q-31 -145 -31 -227q0 -104 68 -104q58 0 131 45l37 -60q-130 -114 -260 -114q-77 0 -124.5 55t-47.5 139q0 53 10 121h-16q-111 -183 -198.5 -248 t-197.5 -65q-219 0 -219 227q0 65 37 225l80 359q18 81 18 122q0 97 -143 97h-16zM647 1268q117 135 219 301h219l-6 -21q-157 -183 -356 -307h-82z" />-<glyph glyph-name="ucircumflex" unicode="û" horiz-adv-x="1227" d="M135 1098h381l-135 -613q-41 -186 -41 -247q0 -125 100 -125q105 0 226.5 151.5t158.5 323.5l107 510h198l-141 -658q-31 -145 -31 -227q0 -104 68 -104q58 0 131 45l37 -60q-130 -114 -260 -114q-77 0 -124.5 55t-47.5 139q0 53 10 121h-16q-111 -183 -198.5 -248 t-197.5 -65q-219 0 -219 227q0 65 37 225l80 359q18 81 18 122q0 97 -143 97h-16zM434 1268q186 183 270 301h195q29 -117 139 -301l-6 -27h-72q-95 65 -182 184q-150 -127 -262 -184h-88z" />-<glyph glyph-name="udieresis" unicode="ü" horiz-adv-x="1227" d="M135 1098h381l-135 -613q-41 -186 -41 -247q0 -125 100 -125q105 0 226.5 151.5t158.5 323.5l107 510h198l-141 -658q-31 -145 -31 -227q0 -104 68 -104q58 0 131 45l37 -60q-130 -114 -260 -114q-77 0 -124.5 55t-47.5 139q0 53 10 121h-16q-111 -183 -198.5 -248 t-197.5 -65q-219 0 -219 227q0 65 37 225l80 359q18 81 18 122q0 97 -143 97h-16zM571 1294q-102 0 -102 103q0 129 110 129q103 0 103 -103q0 -129 -111 -129zM960 1294q-102 0 -102 103q0 129 110 129q103 0 103 -103q0 -129 -111 -129z" />-<glyph glyph-name="yacute" unicode="ý" horiz-adv-x="1081" d="M164 903q-18 60 -49 84.5t-92 24.5h-21l16 86h299l160 -553q74 -257 86 -371h6q277 421 277 680q0 111 -101 111q0 69 40 110t94 41q139 0 139 -172q0 -247 -276 -694.5t-442.5 -594.5t-438.5 -147q-108 0 -193 37l31 103q72 -23 160 -23q293 0 575 373zM535 1268 q117 135 219 301h219l-6 -21q-157 -183 -356 -307h-82z" />-<glyph glyph-name="thorn" unicode="þ" horiz-adv-x="1151" d="M248 27q-9 -78 -15 -99l-31 -151q-14 -58 -14 -94q0 -89 144 -89h26l-18 -86h-389l373 1758q18 85 18 108q0 96 -143 96h-17l19 86h377l-105 -491q-17 -79 -61 -240h12q148 293 375 293q125 0 194.5 -95t69.5 -275q0 -275 -173 -521.5t-439 -246.5q-120 0 -203 47z M281 145q65 -73 163 -73q171 0 290.5 223t119.5 455q0 113 -35.5 176t-97.5 63q-107 0 -216 -150.5t-140 -303.5z" />-<glyph glyph-name="ydieresis" unicode="ÿ" horiz-adv-x="1081" d="M164 903q-18 60 -49 84.5t-92 24.5h-21l16 86h299l160 -553q74 -257 86 -371h6q277 421 277 680q0 111 -101 111q0 69 40 110t94 41q139 0 139 -172q0 -247 -276 -694.5t-442.5 -594.5t-438.5 -147q-108 0 -193 37l31 103q72 -23 160 -23q293 0 575 373zM413 1294 q-102 0 -102 103q0 129 110 129q103 0 103 -103q0 -129 -111 -129zM802 1294q-102 0 -102 103q0 129 110 129q103 0 103 -103q0 -129 -111 -129z" />-<glyph glyph-name="OE" unicode="Œ" horiz-adv-x="1970" d="M1073 102h395q173 0 232 168l33 88h106l-90 -358h-881l-69 -12q-80 -11 -156 -11q-243 0 -390 153.5t-147 414.5q0 352 230 646t590 294q102 0 217 -23h850l-66 -358h-106q10 70 10 119q0 133 -145 137h-345l-116 -545h493l-20 -100h-494zM872 125l256 1208 q-113 41 -219 41q-256 0 -417.5 -283.5t-161.5 -603.5q0 -192 87 -294.5t238 -102.5q135 0 217 35z" />-<glyph glyph-name="oe" unicode="œ" horiz-adv-x="1767" d="M1579 256q20 -14 20 -51q0 -84 -96.5 -154.5t-257.5 -70.5q-239 0 -344 186q-169 -199 -420 -199q-182 0 -287.5 117t-105.5 319q0 277 168.5 501t441.5 224q248 0 344 -204q173 194 414 194q130 0 201.5 -62t71.5 -173q0 -92 -61 -181t-170.5 -145.5t-194 -70 t-240.5 -13.5q-6 -46 -6 -86q0 -278 227 -278q175 0 295 147zM504 68q168 0 269 205t101 452q0 295 -200 295q-167 0 -270 -205.5t-103 -452.5q0 -294 203 -294zM1081 567h21q181 0 298.5 89t117.5 227q0 137 -119 137q-100 0 -186.5 -126.5t-131.5 -326.5z" />-<glyph glyph-name="Ydieresis" unicode="Ÿ" horiz-adv-x="1280" d="M729 1462l-18 -86h-103q-84 0 -84 -55q0 -40 27 -119l92 -274q49 -146 55 -246q20 38 154 215l195 260q75 100 75 166q0 53 -98 53h-76l19 86h506l-19 -86h-39q-39 0 -77.5 -31.5t-108.5 -123.5l-477 -625l-80 -371q-8 -37 -8 -57q0 -82 147 -82h47l-18 -86h-637l18 86 h47q103 0 143 35.5t54 101.5l80 377l-238 668q-23 65 -45.5 86.5t-77.5 21.5h-26l18 86h553zM684 1632q-102 0 -102 103q0 129 110 129q103 0 103 -103q0 -129 -111 -129zM1073 1632q-102 0 -102 103q0 129 110 129q103 0 103 -103q0 -129 -111 -129z" />-<glyph glyph-name="circumflex" unicode="ˆ" horiz-adv-x="1182" d="M381 1268q186 183 270 301h195q29 -117 139 -301l-6 -27h-72q-95 65 -182 184q-150 -127 -262 -184h-88z" />-<glyph glyph-name="tilde" unicode="˜" horiz-adv-x="1182" d="M1028 1483h92q-72 -242 -266 -242q-81 0 -169 65q-89 64 -126 64q-88 0 -123 -108h-92q72 241 268 241q82 0 169 -64q87 -65 124 -65q89 0 123 109z" />-<glyph glyph-name="endash" unicode="–" horiz-adv-x="1024" d="M1036 489h-1042l24 121h1045z" />-<glyph glyph-name="emdash" unicode="—" horiz-adv-x="2048" d="M2060 489h-2066l24 121h2069z" />-<glyph glyph-name="quoteleft" unicode="‘" horiz-adv-x="512" d="M586 1462l-8 -86q-209 -46 -209 -172q0 -36 67 -78q66 -42 66 -104q0 -55 -35 -88t-92 -33q-67 0 -113.5 46.5t-46.5 121.5q0 148 95 252t276 141z" />-<glyph glyph-name="quoteright" unicode="’" horiz-adv-x="512" d="M168 901l8 86q209 47 209 172q0 36 -66 78q-67 42 -67 104q0 55 35 88t92 33q69 0 114.5 -46.5t45.5 -121.5q0 -151 -96.5 -254t-274.5 -139z" />-<glyph glyph-name="quotesinglbase" unicode="‚" horiz-adv-x="512" d="M-57 -299l8 86q209 47 209 172q0 36 -66 78q-67 42 -67 104q0 55 35 88t92 33q69 0 114 -45.5t45 -122.5q0 -150 -95 -253.5t-275 -139.5z" />-<glyph glyph-name="quotedblleft" unicode="“" horiz-adv-x="922" d="M586 1462l-8 -86q-209 -46 -209 -172q0 -36 67 -78q66 -42 66 -104q0 -55 -35 -88t-92 -33q-67 0 -113.5 46.5t-46.5 121.5q0 148 95 252t276 141zM995 1462l-8 -86q-209 -47 -209 -172q0 -36 67 -78q66 -42 66 -104q0 -55 -34.5 -88t-92.5 -33q-68 0 -113.5 46.5 t-45.5 121.5q0 150 95 253.5t275 139.5z" />-<glyph glyph-name="quotedblright" unicode="”" horiz-adv-x="922" d="M168 901l8 86q209 47 209 172q0 36 -66 78q-67 42 -67 104q0 55 35 88t92 33q69 0 114.5 -46.5t45.5 -121.5q0 -151 -96.5 -254t-274.5 -139zM578 901l8 86q209 47 209 172q0 36 -66 78q-67 42 -67 104q0 55 34 88t92 33q69 0 114.5 -46.5t45.5 -121.5q0 -152 -96.5 -255 t-273.5 -138z" />-<glyph glyph-name="quotedblbase" unicode="„" horiz-adv-x="922" d="M-57 -299l8 86q209 47 209 172q0 36 -66 78q-67 42 -67 104q0 55 35 88t92 33q69 0 114 -45.5t45 -122.5q0 -150 -95 -253.5t-275 -139.5zM350 -299l8 86q209 47 209 172q0 36 -66 78q-67 42 -67 104q0 55 35 88t92 33q69 0 114.5 -46.5t45.5 -121.5q0 -151 -96.5 -254 t-274.5 -139z" />-<glyph glyph-name="bullet" unicode="•" horiz-adv-x="741" d="M387 1026q270 0 270 -297t-270 -297t-270 297t270 297z" />-<glyph glyph-name="ellipsis" unicode="…" horiz-adv-x="1757" d="M217 -16q-127 0 -127 125q0 71 36.5 114t98.5 43q127 0 127 -125q0 -72 -36.5 -114.5t-98.5 -42.5zM1391 -16q-127 0 -127 125q0 71 36.5 114t98.5 43q60 0 93.5 -34t33.5 -91q0 -73 -36.5 -115t-98.5 -42zM799 -16q-127 0 -127 125q0 71 36.5 114t98.5 43 q127 0 127 -125q0 -73 -36.5 -115t-98.5 -42z" />-<glyph glyph-name="guilsinglleft" unicode="‹" horiz-adv-x="682" d="M139 586l389 356h103l-295 -397l129 -381h-103l-237 356z" />-<glyph glyph-name="guilsinglright" unicode="›" horiz-adv-x="682" d="M543 520l-389 -356h-103l299 397l-133 381h102l238 -356z" />-<glyph glyph-name="Euro" unicode="€" d="M936 223q25 -17 25 -49q0 -67 -95 -130.5t-254 -63.5q-197 0 -305 119t-108 345q0 72 4 103h-150l21 96h141q11 82 27 143h-140l21 97h145q178 596 637 596q141 0 224 -53t83 -138q0 -149 -180 -149q0 239 -166 239q-261 0 -401 -495h430l-21 -97h-434q-17 -63 -26 -143 h350l-21 -96h-342q-4 -31 -4 -117q0 -158 65 -244t187 -86q167 0 287 123z" />-<glyph glyph-name="trademark" unicode="™" horiz-adv-x="1710" d="M1479 754v55q74 0 80 55v457l-232 -567h-72l-231 569v-451q0 -41 21 -52t61 -11v-55h-248v55h21q76 0 79 57v482q0 57 -79 57h-21v57h236l225 -567l231 567h232v-57h-23q-78 0 -78 -62v-471q0 -63 78 -63h23v-55h-303zM580 872q0 -63 77 -63h33v-55h-348v55h35 q76 0 76 57v531h-91q-71 0 -81 -72l-9 -49h-59l6 186h598l6 -186h-59l-8 49q-11 70 -82 72h-94v-525z" />-</font>-</defs></svg>
− extra/fonts/DroidSerif-Italic.ttf
binary file changed (37848 → absent bytes)
− extra/fonts/DroidSerif-Italic.woff
binary file changed (25432 → absent bytes)
− extra/fonts/DroidSerif-Regular.eot
binary file changed (42054 → absent bytes)
− extra/fonts/DroidSerif-Regular.svg
@@ -1,223 +0,0 @@-<?xml version="1.0" standalone="no"?>-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >-<svg xmlns="http://www.w3.org/2000/svg">-<defs>-<font id="DroidSerif" horiz-adv-x="1145" >-<font-face font-family="Droid Serif" font-weight="400" font-stretch="normal" units-per-em="2048" panose-1="2 2 6 0 6 5 0 2 2 0" ascent="1638" descent="-410" x-height="1098" cap-height="1462" bbox="-25 -492 2126 1907" underline-thickness="102" underline-position="-103" unicode-range="U+0020-U+2122"/>-<missing-glyph horiz-adv-x="532" />-<glyph glyph-name=".notdef" horiz-adv-x="532" />-<glyph glyph-name=".null" horiz-adv-x="0" />-<glyph glyph-name="nonmarkingreturn" horiz-adv-x="682" />-<glyph glyph-name="space" unicode=" " horiz-adv-x="532" />-<glyph glyph-bame="tab" unicode="	" horiz-adv-x="532" />-<glyph glyph-bame="uni00A0" unicode=" " horiz-adv-x="532" />-<glyph glyph-name="exclam" unicode="!" horiz-adv-x="682" d="M221 1462h238l-72 -1050h-94zM211 125q0 40 10 66.5t27.5 42.5t41 23t50.5 7q26 0 49.5 -7t41 -23t28 -42.5t10.5 -66.5q0 -39 -10.5 -66t-28 -43t-41 -23t-49.5 -7q-27 0 -50.5 7t-41 23t-27.5 43t-10 66z" />-<glyph glyph-name="quotedbl" unicode=""" horiz-adv-x="836" d="M502 1462h217l-62 -481h-94zM117 1462h217l-62 -481h-94z" />-<glyph glyph-name="numbersign" unicode="#" d="M397 537h285l76 401h-285zM1038 537v-121h-258l-78 -416h-122l77 416h-284l-78 -416h-123l78 416h-168v121h190l78 401h-244v121h267l73 403h125l-75 -403h284l76 403h123l-76 -403h160v-121h-182l-76 -401h233z" />-<glyph glyph-name="dollar" unicode="$" d="M621 1401q92 -2 156.5 -20.5t105.5 -49t59.5 -71.5t18.5 -89q0 -66 -44 -99.5t-118 -33.5q0 45 -7.5 90.5t-27 83t-54.5 63.5t-89 32v-478q92 -40 163 -80t119 -86.5t73 -103t25 -129.5q0 -154 -99 -239.5t-281 -104.5v-248h-103v244q-51 0 -103 4.5t-101.5 15t-95 26.5 t-83.5 38v315h86q0 -57 21.5 -110.5t61 -96t94 -70t120.5 -32.5v506q-96 43 -162.5 85t-107.5 88.5t-59.5 100.5t-18.5 121q0 69 24.5 125t69.5 97.5t109.5 67t144.5 34.5v159h103v-155zM813 399q0 41 -10 73.5t-33 60.5t-59.5 52t-89.5 48v-455q92 16 142 74.5t50 146.5z M354 1090q0 -43 10.5 -75.5t31.5 -57.5t51.5 -45t70.5 -38v429q-40 -9 -71 -28.5t-51.5 -47t-31 -62.5t-10.5 -75z" />-<glyph glyph-name="percent" unicode="%" horiz-adv-x="1835" d="M764 1030q0 -103 -21 -187t-63 -144t-105 -93t-147 -33q-88 0 -151.5 33t-104.5 93t-60.5 144t-19.5 187t19.5 186.5t60.5 143t105 91.5t153 32q84 0 146.5 -32t104 -91.5t62.5 -143t21 -186.5zM276 1030q0 -88 8 -157.5t26 -117.5t47 -73t71 -25t71 25t46.5 73t25 117.5 t7.5 157.5q0 87 -7.5 155t-25 115.5t-45.5 72t-70 24.5t-71.5 -24.5t-48 -72t-26.5 -115.5t-8 -155zM1743 436q0 -103 -21 -187t-63 -144t-105 -92.5t-147 -32.5q-88 0 -152 32.5t-104.5 92.5t-60 144t-19.5 187t19.5 186.5t60.5 143t105 91.5t153 32q84 0 146.5 -32 t104 -91.5t62.5 -143t21 -186.5zM1255 436q0 -88 8 -157.5t26 -117.5t47 -73t71 -25t71 25t46 73t24.5 117.5t7.5 157.5q0 87 -7.5 155t-24.5 115.5t-45 72t-70 24.5t-71.5 -24.5t-48 -72t-26.5 -115.5t-8 -155zM616 0h-135l731 1462h134z" />-<glyph glyph-name="ampersand" unicode="&" horiz-adv-x="1520" d="M1174 0l-170 180q-37 -42 -81.5 -78.5t-98.5 -63.5t-119.5 -42.5t-143.5 -15.5q-123 0 -214.5 31t-152 87.5t-90.5 135.5t-30 176q0 87 23.5 151.5t66 113.5t103 86t134.5 68q-39 46 -68.5 88.5t-49 84.5t-29.5 86t-10 94q0 68 22 123t69 95t120.5 61.5t175.5 21.5 q98 0 167 -23t112.5 -63t63 -93t19.5 -112q0 -66 -19 -117t-60.5 -94t-107 -81.5t-159.5 -78.5l373 -395q14 46 23 93.5t14.5 93t7.5 89t2 80.5v80h369v-86h-19q-34 0 -67 -4t-61.5 -18.5t-51 -42t-35.5 -74.5q-18 -66 -41.5 -143t-58.5 -154l190 -203q15 -15 31 -25 t37.5 -16t51 -8t71.5 -2h18v-86h-327zM580 94q60 0 111.5 12.5t94.5 34.5t79 52.5t65 66.5l-453 485q-47 -26 -84.5 -57.5t-64 -73t-40.5 -93.5t-14 -118q0 -75 22 -132.5t62.5 -97t96.5 -59.5t125 -20zM813 1190q0 41 -10 76t-32.5 60.5t-59 40.5t-88.5 15q-91 0 -140 -53 t-49 -141q0 -39 8 -73t25.5 -67.5t44.5 -69.5t66 -79q60 26 104.5 55t73.5 63.5t43 76.5t14 96z" />-<glyph glyph-name="quotesingle" unicode="'" horiz-adv-x="451" d="M117 1462h217l-62 -481h-94z" />-<glyph glyph-name="parenleft" unicode="(" horiz-adv-x="709" d="M330 649q0 -122 9.5 -241t41.5 -225.5t94.5 -196.5t167.5 -154v-94q-141 56 -241 136t-163.5 190.5t-92.5 255t-29 329.5t29 328.5t92.5 253.5t163.5 189.5t241 135.5v-92q-105 -64 -167.5 -153.5t-94.5 -195.5t-41.5 -225t-9.5 -241z" />-<glyph glyph-name="parenright" unicode=")" horiz-adv-x="709" d="M379 649q0 122 -9.5 241t-42 225t-94.5 195.5t-167 153.5v92q141 -56 241 -135.5t163 -189.5t92.5 -253.5t29.5 -328.5t-29.5 -329.5t-92.5 -255t-163 -190.5t-241 -136v94q105 64 167 154t94.5 196.5t42 225.5t9.5 241z" />-<glyph glyph-name="asterisk" unicode="*" horiz-adv-x="1024" d="M84 1268l72 162l315 -199l-53 325h178l-57 -323l317 192l72 -159l-346 -101l346 -100l-72 -158l-317 191l57 -324h-178l51 324l-313 -195l-72 160l344 102z" />-<glyph glyph-name="plus" unicode="+" d="M633 672v-379h-123v379h-377v121h377v378h123v-378h379v-121h-379z" />-<glyph glyph-name="comma" unicode="," horiz-adv-x="512" d="M397 86q0 -65 -18 -126t-58.5 -113t-104 -93.5t-155.5 -68.5v86q101 32 148 78t47 106q0 20 -11 33.5t-28 24t-36 20.5t-36 24t-28 34.5t-11 51.5q0 59 38.5 90t95.5 31q32 0 60 -11.5t50 -34t34.5 -56t12.5 -76.5z" />-<glyph glyph-name="hyphen" unicode="-" horiz-adv-x="635" d="M51 481v154h533v-154h-533z" />-<glyph glyph-name="period" unicode="." horiz-adv-x="586" d="M164 125q0 40 10 66.5t27.5 42.5t41 23t50.5 7q26 0 49.5 -7t41 -23t28 -42.5t10.5 -66.5q0 -39 -10.5 -66t-28 -43t-41 -23t-49.5 -7q-27 0 -50.5 7t-41 23t-27.5 43t-10 66z" />-<glyph glyph-name="slash" unicode="/" horiz-adv-x="590" d="M121 -248h-121l471 1804h119z" />-<glyph glyph-name="zero" unicode="0" d="M1053 733q0 -169 -29.5 -308t-89.5 -238t-150 -153t-211 -54q-126 0 -217 54t-149.5 153t-86.5 238.5t-28 309.5t28 308t86.5 236t150 151t218.5 53q120 0 209.5 -53t149.5 -151t89.5 -236.5t29.5 -309.5zM305 733q0 -152 14 -271.5t45.5 -202t83 -126t125.5 -43.5 q75 0 126 43.5t82.5 126t45 202t13.5 271.5t-13.5 271t-45 201t-82 124.5t-124.5 42.5q-75 0 -126.5 -42.5t-83.5 -124.5t-46 -201t-14 -271z" />-<glyph glyph-name="one" unicode="1" d="M197 0v86h137q35 0 65.5 5t53 20.5t36 44.5t13.5 77v1076q-39 -48 -74 -87t-67 -67t-62.5 -43.5t-60.5 -15.5q-45 0 -74 30.5t-29 79.5q31 8 62.5 19.5t67 30t77 45t92.5 63.5l144 106h131v-1237q0 -48 13 -77t36 -44.5t53.5 -20.5t65.5 -5h100v-86h-780z" />-<glyph glyph-name="two" unicode="2" d="M944 1141q0 -72 -23 -140t-67 -139.5t-107.5 -148.5t-144.5 -166l-346 -383h473q46 0 76 13t49 34t29.5 48.5t17.5 57.5l8 37h86l-10 -354h-881v150l342 395q82 95 137.5 170t89.5 141.5t49 130t15 133.5q0 59 -12.5 106.5t-38 82t-65 53t-93.5 18.5q-70 0 -113.5 -25 t-68.5 -67t-34 -97t-9 -116q-37 0 -69 6t-56 21.5t-37.5 41t-13.5 64.5q0 59 25.5 109.5t76 87.5t125.5 57.5t174 20.5q97 0 174 -24.5t131 -69t82.5 -107.5t28.5 -141z" />-<glyph glyph-name="three" unicode="3" d="M508 90q61 0 116 17t97 56t67 102t25 155q0 65 -25.5 117.5t-73.5 89.5t-117 57.5t-155 20.5h-65v104h65q68 0 126.5 22.5t102 63.5t68.5 99.5t25 131.5q0 60 -12 107t-38.5 80t-68 50t-100.5 17q-70 0 -114 -25t-69 -67t-34 -97t-9 -116q-37 0 -69 6t-56 21.5t-37.5 41 t-13.5 64.5q0 59 25.5 109.5t76.5 87.5t126 57.5t174 20.5q97 0 176 -22t135.5 -63.5t87.5 -102t31 -138.5q0 -69 -24.5 -130t-68.5 -110.5t-104 -84.5t-131 -52q38 -4 81 -13.5t85 -27.5t80.5 -45t68 -66t47 -90.5t17.5 -119.5q0 -82 -22 -147t-60.5 -113.5t-89.5 -82.5 t-109 -55t-119 -30.5t-120 -9.5q-101 0 -174 18.5t-120.5 49.5t-70 72.5t-22.5 86.5q0 62 35 96.5t94 34.5q0 -54 17.5 -99.5t50.5 -78.5t81.5 -51.5t110.5 -18.5z" />-<glyph glyph-name="four" unicode="4" d="M862 401v-168q0 -48 13.5 -77t36 -44.5t53.5 -20.5t65 -5h27v-86h-617v86h58q35 0 65.5 5t53 20.5t36 44.5t13.5 77v168h-631v84l633 977h194v-946h254v-115h-254zM666 895q0 45 1 99t2.5 110t4 111.5t6.5 103.5q-7 -14 -20 -37.5l-29.5 -52.5l-35 -61l-37 -62.5 l-34.5 -57t-28 -44.5l-314 -488h484v379z" />-<glyph glyph-name="five" unicode="5" d="M489 100q63 0 116.5 17.5t93 58t61.5 107t22 163.5q0 81 -22 141t-63 100t-98 59.5t-128 19.5q-53 0 -91.5 -4t-68 -10.5t-51.5 -14.5t-41 -16l-49 14l66 725h684l10 -309h-86l-8 61q-3 22 -8.5 37.5t-17.5 26t-32.5 15.5t-52.5 5h-397l-39 -450q31 11 89.5 22t149.5 11 q99 0 184.5 -27t148.5 -81t98.5 -135.5t35.5 -189.5q0 -104 -33.5 -190t-97 -147.5t-155.5 -95t-209 -33.5q-114 0 -188.5 19.5t-118.5 49.5t-62 66.5t-18 70.5q0 57 31.5 89t97.5 32q0 -44 15.5 -82t46 -65.5t77 -43.5t108.5 -16z" />-<glyph glyph-name="six" unicode="6" d="M659 1384q-154 0 -233 -151.5t-90 -450.5q25 21 55.5 40.5t66.5 34t78.5 23.5t92.5 9q94 0 170 -29t129.5 -83t82.5 -132t29 -176q0 -108 -29.5 -198t-86 -154.5t-138 -100.5t-186.5 -36q-102 0 -189 42.5t-150.5 134t-99.5 235t-36 345.5q0 97 14.5 190t43.5 176t73 153 t103.5 120.5t134.5 78.5t165 28q86 0 149.5 -18t105.5 -48t62.5 -69t20.5 -80q0 -59 -40 -91t-117 -32q0 50 -9.5 93.5t-31.5 76t-56 51t-84 18.5zM588 784q-40 0 -77 -10.5t-69.5 -27t-59.5 -38t-48 -42.5q2 -154 21.5 -263t54.5 -178t85 -101t111 -32q110 0 167.5 85.5 t57.5 271.5q0 175 -62.5 255t-180.5 80z" />-<glyph glyph-name="seven" unicode="7" d="M389 0l483 1296h-538q-110 0 -119 -104l-8 -102h-86l10 370h916v-59l-504 -1401h-154z" />-<glyph glyph-name="eight" unicode="8" d="M94 367q0 74 23.5 129.5t65.5 99t99 78.5t124 67q-58 33 -107 71.5t-84 84.5t-54.5 100.5t-19.5 118.5q0 71 25 137t78 117t137.5 82t202.5 31q96 0 171 -26.5t126.5 -73.5t78.5 -111.5t27 -141.5q0 -67 -20 -118t-56 -91.5t-87 -73t-113 -63.5q76 -36 138.5 -77 t107.5 -89t69.5 -104t24.5 -123q0 -97 -35 -173.5t-99 -129.5t-155 -80.5t-203 -27.5q-115 0 -202 30t-145.5 82.5t-88 123t-29.5 151.5zM569 74q67 0 121.5 20.5t93 56.5t59.5 86t21 109q0 52 -19 96.5t-62 86t-111.5 81t-167.5 82.5q-97 -52 -154 -135t-57 -199 q0 -64 17.5 -116t52 -89.5t86.5 -58t120 -20.5zM805 1135q0 47 -12.5 92t-40 80.5t-72 57t-109.5 21.5q-57 0 -101.5 -18.5t-74.5 -51t-45.5 -77.5t-15.5 -98q0 -55 17.5 -99t52 -80t86.5 -67.5t120 -63.5q58 29 95.5 59.5t59.5 67t31 80t9 97.5z" />-<glyph glyph-name="nine" unicode="9" d="M467 90q170 0 251 152t91 460q-22 -27 -51.5 -52t-67 -44.5t-83 -31t-99.5 -11.5q-89 0 -163.5 26t-128 78t-83 130t-29.5 182q0 111 30 203.5t86.5 159t138 104t184.5 37.5q104 0 191 -40t151 -124t99.5 -214t35.5 -310q0 -107 -13.5 -209.5t-42 -193.5t-73 -167 t-106 -131.5t-141.5 -86t-179 -30.5q-87 0 -144.5 15.5t-91 40.5t-47.5 57t-14 64q0 38 19 64t42 34q14 -32 34.5 -61.5t50 -52t67.5 -35.5t86 -13zM553 684q50 1 91.5 14.5t73.5 36t55.5 52.5t37.5 63v4q-2 135 -22 232.5t-55 160.5t-83.5 93t-107.5 30q-109 0 -168.5 -95 t-59.5 -276q0 -84 15 -144t45 -98t74.5 -55.5t103.5 -17.5z" />-<glyph glyph-name="colon" unicode=":" horiz-adv-x="588" d="M166 125q0 40 10 66.5t27.5 42.5t41 23t50.5 7q26 0 49.5 -7t41 -23t28 -42.5t10.5 -66.5q0 -39 -10.5 -66t-28 -43t-41 -23t-49.5 -7q-27 0 -50.5 7t-41 23t-27.5 43t-10 66zM166 969q0 40 10 66.5t27.5 42.5t41 23t50.5 7q26 0 49.5 -7t41 -23t28 -42.5t10.5 -66.5 q0 -39 -10.5 -66t-28 -43.5t-41 -23.5t-49.5 -7q-27 0 -50.5 7t-41 23.5t-27.5 43.5t-10 66z" />-<glyph glyph-name="semicolon" unicode=";" horiz-adv-x="512" d="M397 86q0 -65 -18 -126t-58.5 -113t-104 -93.5t-155.5 -68.5v86q101 32 148 78t47 106q0 20 -11 33.5t-28 24t-36 20.5t-36 24t-28 34.5t-11 51.5q0 59 38.5 90t95.5 31q32 0 60 -11.5t50 -34t34.5 -56t12.5 -76.5zM129 969q0 40 10 66.5t27.5 42.5t41 23t50.5 7 q26 0 49.5 -7t41 -23t28 -42.5t10.5 -66.5q0 -39 -10.5 -66t-28 -43.5t-41 -23.5t-49.5 -7q-27 0 -50.5 7t-41 23.5t-27.5 43.5t-10 66z" />-<glyph glyph-name="less" unicode="<" d="M133 696v72l879 473v-137l-688 -373l688 -369v-137z" />-<glyph glyph-name="equal" unicode="=" d="M1012 590v-121h-879v121h879zM1012 993v-121h-879v121h879z" />-<glyph glyph-name="greater" unicode=">" d="M133 225v137l688 369l-688 373v137l879 -473v-72z" />-<glyph glyph-name="question" unicode="?" horiz-adv-x="1024" d="M522 412h-125v311q89 36 149.5 83.5t97 103t52 116t15.5 121.5q0 53 -13 96.5t-40 74t-69.5 47.5t-101.5 17q-64 0 -107.5 -21t-70.5 -57t-39 -83.5t-12 -100.5q-37 0 -68 8.5t-53.5 25t-35.5 41.5t-13 58q0 49 22.5 91t70 73t119.5 48.5t171 17.5q97 0 179 -25 t142 -72.5t94 -116.5t34 -157q0 -84 -26.5 -153t-77.5 -126t-125 -104t-169 -86v-231zM328 125q0 40 10 66.5t27.5 42.5t41 23t50.5 7q26 0 49.5 -7t41 -23t28 -42.5t10.5 -66.5q0 -39 -10.5 -66t-28 -43t-41 -23t-49.5 -7q-27 0 -50.5 7t-41 23t-27.5 43t-10 66z" />-<glyph glyph-name="at" unicode="@" horiz-adv-x="1886" d="M1778 803q0 -148 -36 -267.5t-94 -203.5t-129.5 -129t-142.5 -45q-91 0 -151.5 49t-89.5 137h-11q-21 -40 -47.5 -74t-60 -59t-75 -39t-93.5 -14q-62 0 -118.5 21t-99 64t-68 109t-25.5 156q0 55 12.5 119t39 127.5t68 123t99.5 105t133.5 73t169.5 27.5q66 0 113.5 -18 t78.5 -43l82 43h49l-104 -522q-3 -24 -7 -48q-3 -20 -5 -44t-2 -45q0 -36 10.5 -62.5t27.5 -44t39.5 -25.5t47.5 -8q45 0 92 36t85 105.5t62.5 170.5t24.5 231q0 134 -43 236.5t-119.5 172t-183 105t-234.5 35.5q-100 0 -196.5 -28t-182.5 -82.5t-159.5 -133t-126.5 -179 t-83 -222.5t-30 -262q0 -171 49 -291.5t132.5 -197t194 -111.5t234.5 -35q84 0 159 14.5t140.5 38.5t121 54.5t99.5 62.5l48 -74q-51 -38 -114 -74.5t-136.5 -65t-158 -46t-179.5 -17.5q-165 0 -303 45.5t-237.5 137.5t-155.5 230t-56 324q0 145 31.5 276.5t91 244.5 t144 204.5t190.5 156t230.5 99.5t264.5 35q172 0 302 -51t217 -139.5t130.5 -209t43.5 -259.5zM727 479q0 -108 43.5 -160.5t112.5 -52.5q52 0 90 22.5t64 58.5t42 82t25 93l78 416q-13 29 -45.5 45t-69.5 16q-61 0 -110.5 -26.5t-87 -69.5t-64.5 -98.5t-44.5 -113.5 t-25.5 -113t-8 -99z" />-<glyph glyph-name="A" unicode="A" horiz-adv-x="1444" d="M414 489l-78 -215q-9 -24 -14 -47.5t-5 -40.5q0 -52 33.5 -76t102.5 -24h47v-86h-500v86h39q30 0 51 6.5t38 24.5t32.5 49t34.5 80l454 1216h160l463 -1267q12 -32 24.5 -53t29.5 -33.5t39 -17.5t52 -5h27v-86h-563v86h47q123 0 123 98q0 17 -4.5 35t-12.5 41l-82 229 h-538zM788 950l-58.5 170.5t-43.5 153.5q-7 -37 -16.5 -71.5t-20.5 -70.5t-25 -75.5l-32 -87.5l-139 -377h462z" />-<glyph glyph-name="B" unicode="B" horiz-adv-x="1339" d="M1153 1096q0 -69 -19.5 -121t-53.5 -91t-80.5 -65.5t-100.5 -44.5v-8q70 -12 128 -41t99.5 -75.5t64.5 -110t23 -144.5q0 -198 -130 -296.5t-396 -98.5h-610v86h26q35 0 65.5 5t53.5 20.5t36 44.5t13 77v1004q0 45 -13.5 72.5t-36.5 42.5t-53.5 19.5t-64.5 4.5h-26v86 h549q266 0 396 -88.5t130 -277.5zM479 102h199q87 0 147 16t98 51.5t55 92.5t17 139q0 80 -15 139t-51.5 98t-96.5 58t-150 19h-203v-613zM479 817h142q90 0 150 16t96 49.5t51.5 86.5t15.5 127q0 75 -17 125.5t-55 81.5t-98.5 44t-147.5 13h-137v-543z" />-<glyph glyph-name="C" unicode="C" horiz-adv-x="1257" d="M774 1483q105 0 182.5 -17.5t129 -48.5t77 -73t25.5 -91q0 -33 -13.5 -59.5t-37.5 -45t-56.5 -28.5t-70.5 -10q0 48 -12.5 95t-41 84.5t-74 61t-112.5 23.5q-116 0 -197 -42.5t-132 -124t-74 -201t-23 -273.5q0 -137 24 -251t76 -195.5t132.5 -126.5t193.5 -45 q75 0 132.5 14.5t101.5 38.5t77.5 55.5t59.5 65.5q17 -11 28 -30t11 -50q0 -39 -26.5 -79.5t-80.5 -73.5t-137 -54.5t-197 -21.5q-153 0 -269 54t-194.5 153t-118.5 238t-40 308q0 166 42 304t124.5 237t205.5 154t285 55z" />-<glyph glyph-name="D" unicode="D" horiz-adv-x="1489" d="M1372 733q0 -168 -42 -303.5t-125 -231t-208 -147t-292 -51.5h-627v86h26q35 0 65.5 5t53.5 20.5t36 44.5t13 77v1004q0 45 -13.5 72.5t-36.5 42.5t-53.5 19.5t-64.5 4.5h-26v86h627q158 0 282 -48.5t209.5 -141.5t130.5 -229t45 -310zM657 104q122 0 213.5 41.5 t152.5 121.5t91.5 197t30.5 269q0 305 -121.5 466t-364.5 161h-180v-1256h178z" />-<glyph glyph-name="E" unicode="E" horiz-adv-x="1276" d="M479 102h395q49 0 83.5 13.5t57.5 36.5t36 53.5t18 64.5l14 88h107l-14 -358h-1098v86h26q34 0 64.5 4.5t53.5 18.5t36.5 41.5t13.5 72.5v1006q0 48 -13 77t-36 44.5t-53.5 20.5t-65.5 5h-26v86h1032l10 -358h-106l-10 88q-4 34 -15.5 64.5t-33 53.5t-54 36.5t-78.5 13.5 h-344v-545h494v-100h-494v-613z" />-<glyph glyph-name="F" unicode="F" horiz-adv-x="1208" d="M479 1360v-592h494v-100h-494v-435q0 -48 13.5 -77t36 -44.5t53.5 -20.5t65 -5h68v-86h-637v86h26q34 0 64.5 4.5t53.5 18.5t36.5 41.5t13.5 72.5v1014q0 45 -13.5 72.5t-36.5 42.5t-53.5 19.5t-64.5 4.5h-26v86h1032l10 -358h-106l-10 88q-4 35 -16 65.5t-34.5 53 t-56.5 36t-82 13.5h-336z" />-<glyph glyph-name="G" unicode="G" horiz-adv-x="1462" d="M821 -20q-178 0 -310.5 54t-220 153t-130.5 238t-43 308q0 166 45.5 304t136 237t224.5 154t311 55q114 0 198.5 -17.5t141 -48.5t84.5 -73t28 -91q0 -33 -15 -59.5t-40.5 -45t-60 -28.5t-74.5 -10q0 48 -14 95t-46 84.5t-84 61t-127 23.5q-130 0 -221.5 -42.5 t-149.5 -124t-84 -201t-26 -273.5q0 -153 27 -272t88 -200t158 -123t237 -42q59 0 114.5 6t98.5 19v330q0 45 -13.5 72.5t-36.5 42t-53.5 19.5t-64.5 5h-8v86h532v-86h-8q-29 0 -55 -5t-45 -20.5t-30 -45t-11 -77.5v-368q-103 -48 -212 -71t-241 -23z" />-<glyph glyph-name="H" unicode="H" horiz-adv-x="1624" d="M950 0v86h27q35 0 65.5 5t53 20.5t36 44.5t13.5 77v480h-666v-480q0 -48 13.5 -77t36 -44.5t53.5 -20.5t65 -5h27v-86h-596v86h26q35 0 65.5 5t53.5 20.5t36 44.5t13 77v1004q0 45 -13.5 72.5t-36.5 42.5t-53.5 19.5t-64.5 4.5h-26v86h596v-86h-27q-34 0 -65 -5 t-53.5 -20.5t-36 -44.5t-13.5 -77v-414h666v414q0 48 -13.5 77t-36 44.5t-53 20.5t-65.5 5h-27v86h596v-86h-26q-35 0 -65.5 -5t-53.5 -20.5t-36 -44.5t-13 -77v-1006q0 -45 13.5 -72.5t36.5 -41.5t53.5 -18.5t64.5 -4.5h26v-86h-596z" />-<glyph glyph-name="I" unicode="I" horiz-adv-x="752" d="M78 0v86h26q35 0 65.5 5t53.5 20.5t36 44.5t13 77v996q0 48 -13 77t-36 44.5t-53.5 20.5t-65.5 5h-26v86h596v-86h-27q-34 0 -65 -5t-53.5 -20.5t-36 -44.5t-13.5 -77v-996q0 -48 13.5 -77t36 -44.5t53.5 -20.5t65 -5h27v-86h-596z" />-<glyph glyph-name="J" unicode="J" horiz-adv-x="731" d="M-23 -395h19q66 0 117.5 18.5t86.5 62t53.5 116t18.5 180.5v1255q0 45 -13.5 72.5t-36.5 42.5t-53.5 19.5t-64.5 4.5h-26v86h596v-86h-27q-34 0 -65 -5t-53.5 -20.5t-36 -44.5t-13.5 -77v-1245q0 -138 -36 -229.5t-99 -146t-148.5 -77.5t-183.5 -23h-35v97z" />-<glyph glyph-name="K" unicode="K" horiz-adv-x="1434" d="M903 1137q33 38 55 67.5t34.5 53.5t18 42t5.5 33q0 26 -25 35.5t-76 9.5v84h453v-84q-35 0 -67.5 -13.5t-65 -38t-67 -60l-74.5 -80.5l-297 -334l432 -602q59 -81 113.5 -122.5t111.5 -41.5h4v-86h-22q-103 0 -169 8t-111 29t-78.5 56.5t-71.5 90.5l-353 506l-174 -141 v-316q0 -48 13.5 -77t36 -44.5t53.5 -20.5t65 -5h27v-86h-596v86h26q34 0 64.5 4.5t53.5 18.5t36.5 41.5t13.5 72.5v1014q0 45 -13.5 72.5t-36.5 42.5t-53.5 19.5t-64.5 4.5h-26v86h596v-86h-27q-34 0 -65 -5t-53.5 -20.5t-36 -44.5t-13.5 -77v-578z" />-<glyph glyph-name="L" unicode="L" horiz-adv-x="1276" d="M78 0v86h26q35 0 65.5 5t53.5 20.5t36 44.5t13 77v996q0 48 -13 77t-36 44.5t-53.5 20.5t-65.5 5h-26v86h596v-86h-27q-34 0 -64.5 -4.5t-53.5 -19.5t-36.5 -42.5t-13.5 -72.5v-1135h404q46 0 78.5 16.5t53.5 43t32.5 59.5t15.5 66l20 153h107l-14 -440h-1098z" />-<glyph glyph-name="M" unicode="M" horiz-adv-x="1921" d="M1298 0v86h7q33 0 59.5 4.5t45 18t29.5 39t13 67.5v1094l-469 -1309h-111l-475 1305v-1072q0 -48 10 -77t29.5 -44.5t47 -20.5t61.5 -5h6v-86h-473v86h26q34 0 64.5 4.5t53.5 18.5t36.5 41.5t13.5 72.5v1014q0 45 -13.5 72.5t-36.5 42.5t-53.5 19.5t-64.5 4.5h-26v86h467 l424 -1171l420 1171h454v-86h-26q-35 0 -65.5 -5t-53.5 -20.5t-36 -44.5t-13 -77v-996q0 -48 13 -77t36 -44.5t53.5 -20.5t65.5 -5h26v-86h-545z" />-<glyph glyph-name="N" unicode="N" horiz-adv-x="1563" d="M1165 0l-768 1180v-947q0 -48 13.5 -77t36 -44.5t53.5 -20.5t65 -5h27v-86h-514v86h26q35 0 65.5 5t53.5 20.5t36 44.5t13 77v1004q0 45 -13.5 72.5t-36.5 42.5t-53.5 19.5t-64.5 4.5h-26v86h375l733 -1132v907q0 45 -13.5 72.5t-36.5 42.5t-53.5 19.5t-64.5 4.5h-27v86 h514v-86h-26q-35 0 -65.5 -5t-53.5 -20.5t-36 -44.5t-13 -77v-1229h-146z" />-<glyph glyph-name="O" unicode="O" horiz-adv-x="1520" d="M1403 733q0 -169 -42 -308t-124 -238t-202 -153t-275 -54q-163 0 -284 54t-201 153t-120 238.5t-40 309.5t40 308.5t120 236.5t202 151.5t285 53.5q155 0 274 -53.5t201 -152t124 -237t42 -309.5zM342 733q0 -152 22.5 -271.5t72.5 -202t129.5 -126t193.5 -43.5 t193.5 43.5t129 126t71.5 202t22 271.5t-22 271.5t-71.5 201.5t-128 125t-192.5 43t-194 -43t-130.5 -125t-73 -201.5t-22.5 -271.5z" />-<glyph glyph-name="P" unicode="P" horiz-adv-x="1237" d="M78 0v86h26q35 0 65.5 5t53.5 20.5t36 44.5t13 77v1004q0 45 -13.5 72.5t-36.5 42.5t-53.5 19.5t-64.5 4.5h-26v86h575q129 0 224 -29.5t157 -85t92.5 -134.5t30.5 -179q0 -91 -28.5 -175t-93.5 -148.5t-170 -103t-257 -38.5h-129v-346q0 -45 13.5 -72.5t36.5 -41.5 t53.5 -18.5t64.5 -4.5h68v-86h-637zM479 666h109q93 0 159 19.5t108 63t61.5 112t19.5 165.5q0 86 -17 149.5t-55 105.5t-98.5 62.5t-147.5 20.5h-139v-698z" />-<glyph glyph-name="Q" unicode="Q" horiz-adv-x="1520" d="M1403 733q0 -150 -33 -276t-97.5 -222t-159 -158.5t-216.5 -84.5q9 -105 34 -175.5t64 -113t91 -60.5t114 -18h33v-96h-88q-81 0 -161.5 21.5t-146.5 74t-110.5 140t-54.5 219.5q-141 15 -245.5 74.5t-173.5 157t-103.5 229t-34.5 290.5q0 170 40 308.5t120 236.5 t202 151.5t285 53.5q155 0 274 -53.5t201 -152t124 -237t42 -309.5zM342 733q0 -152 22.5 -271.5t72.5 -202t129.5 -126t193.5 -43.5t193.5 43.5t129 126t71.5 202t22 271.5t-22 271.5t-71.5 201.5t-128 125t-192.5 43t-194 -43t-130.5 -125t-73 -201.5t-22.5 -271.5z" />-<glyph glyph-name="R" unicode="R" horiz-adv-x="1343" d="M1135 250q52 -82 101 -123t114 -41h6v-86h-29q-96 0 -157.5 7t-104.5 27t-73.5 56t-65.5 94l-277 461h-170v-422q0 -45 13.5 -72.5t36.5 -41.5t53.5 -18.5t64.5 -4.5h27v-86h-596v86h26q34 0 64.5 4.5t53.5 18.5t36.5 41.5t13.5 72.5v1014q0 45 -13.5 72.5t-36.5 42.5 t-53.5 19.5t-64.5 4.5h-26v86h551q266 0 396 -98.5t130 -296.5q0 -82 -26.5 -143.5t-69.5 -106.5t-97 -75t-108 -48zM479 741h144q90 0 150 20t96 60t51.5 100t15.5 140q0 82 -17 139.5t-55 94t-98.5 53t-147.5 16.5h-139v-623z" />-<glyph glyph-name="S" unicode="S" horiz-adv-x="1114" d="M506 -20q-100 0 -176.5 21t-128 58.5t-77.5 91t-26 117.5q0 62 38.5 100t109.5 38q3 -64 19.5 -123t49.5 -104t84 -72t123 -27q138 0 215.5 66.5t77.5 191.5q0 56 -16 99t-53 79.5t-97.5 70t-150.5 70.5q-95 39 -166 83.5t-118 99t-70 121.5t-23 151q0 88 33.5 157 t93.5 116.5t142 72.5t179 25q92 0 163 -19t120 -51t74.5 -73.5t25.5 -86.5q0 -66 -43.5 -99.5t-117.5 -33.5q0 48 -11.5 94.5t-38.5 83t-71 59.5t-109 23q-59 0 -105.5 -16.5t-78.5 -47t-49 -73.5t-17 -96q0 -61 16 -107.5t53.5 -84t98.5 -71t150 -69.5q90 -37 160.5 -78 t119.5 -91.5t75 -113t26 -141.5q0 -97 -35.5 -173.5t-101.5 -129.5t-159 -80.5t-208 -27.5z" />-<glyph glyph-name="T" unicode="T" horiz-adv-x="1255" d="M729 233q0 -48 13.5 -77t36 -44.5t53.5 -20.5t65 -5h47v-86h-637v86h47q34 0 64.5 4.5t53.5 18.5t36.5 41.5t13.5 72.5v1137h-196q-45 0 -75.5 -13.5t-50 -36.5t-29 -53.5t-13.5 -64.5l-11 -88h-106l10 358h1153l10 -358h-106l-10 88q-4 34 -14 64.5t-29 53.5t-50 36.5 t-77 13.5h-199v-1127z" />-<glyph glyph-name="U" unicode="U" horiz-adv-x="1468" d="M741 -20q-121 0 -217 25.5t-162.5 81t-101.5 143t-35 212.5v795q0 45 -13.5 72.5t-36.5 42.5t-53.5 19.5t-64.5 4.5h-26v86h596v-86h-27q-34 0 -65 -5t-53.5 -20.5t-36 -44.5t-13.5 -77v-799q0 -89 25.5 -151.5t71 -101.5t108 -57t137.5 -18q89 0 153.5 24t107 66.5 t63 101t20.5 128.5v815q0 45 -13.5 72.5t-36.5 42.5t-53.5 19.5t-64.5 4.5h-26v86h514v-86h-27q-34 0 -65 -5t-53.5 -20.5t-36 -44.5t-13.5 -77v-811q0 -102 -32.5 -183.5t-96 -138t-157.5 -86.5t-216 -30z" />-<glyph glyph-name="V" unicode="V" horiz-adv-x="1382" d="M614 0l-442 1268q-11 32 -24 53t-30 33t-39 17t-52 5h-27v86h563v-86h-47q-62 0 -92.5 -23.5t-30.5 -74.5q0 -17 4.5 -35t12.5 -41l225 -665q31 -91 53 -176.5t39 -159.5q16 74 38 157t56 179l226 651q8 25 13 48t5 40q0 53 -33.5 76.5t-101.5 23.5h-47v86h499v-86h-39 q-30 0 -51.5 -6t-39 -23.5t-32.5 -48.5t-32 -81l-424 -1217h-150z" />-<glyph glyph-name="W" unicode="W" horiz-adv-x="2144" d="M1180 1448l256 -887q29 -102 51.5 -195.5t36.5 -164.5q15 74 34.5 155.5t47.5 180.5l180 632l7 27t7 30l5.5 28t2.5 22q0 53 -33.5 76.5t-101.5 23.5h-47v86h500v-86h-39q-30 0 -52.5 -6t-40.5 -23t-33 -48t-30 -82l-346 -1217h-184l-334 1147l-313 -1147h-189l-375 1268 q-10 32 -22.5 53t-29.5 33t-40 17t-53 5h-27v86h564v-86h-47q-62 0 -92.5 -23.5t-30.5 -74.5q0 -17 5 -35l11 -41l188 -655l49 -182.5t40 -163.5q15 80 35.5 174.5t47.5 193.5l244 879h148z" />-<glyph glyph-name="X" unicode="X" horiz-adv-x="1352" d="M954 1317q0 18 -8.5 29.5t-23 18t-34.5 9t-44 2.5h-6v86h448v-86h-18q-30 0 -54.5 -7t-47.5 -24.5t-47.5 -45.5t-53.5 -70l-297 -428l377 -606q45 -60 85 -84.5t79 -24.5h26v-86h-553v86h11q122 0 122 68q0 11 -2.5 22t-9.5 26t-20.5 37l-34.5 56l-203 326l-227 -336 q-9 -13 -18 -29.5t-17 -34.5t-13 -36.5t-5 -34.5q0 -35 32.5 -49.5t100.5 -14.5h6v-86h-488v86h13q37 0 64.5 7.5t51 24.5t46.5 44t51 67l342 490l-338 549q-37 59 -83.5 83.5t-92.5 24.5h-27v86h555v-86h-6q-35 0 -58 -4.5t-36 -12.5t-18 -19t-5 -23q0 -11 1.5 -21 t6.5 -23.5t14.5 -31.5t26.5 -45l185 -299l194 295q23 35 37 66t14 59z" />-<glyph glyph-name="Y" unicode="Y" horiz-adv-x="1280" d="M326 0v86h47q34 0 64.5 4.5t53.5 18.5t36.5 41.5t13.5 72.5v387l-379 658q-17 29 -32 49.5t-31.5 33.5t-36.5 19t-46 6h-26v86h563v-86h-76q-28 0 -45.5 -5t-27.5 -14t-13.5 -21t-3.5 -25q0 -29 12.5 -58t24.5 -51l151 -274q36 -63 61 -124.5t42 -109.5q9 22 22.5 49 l30 57.5l34.5 63l36 64.5l131 235q20 35 28.5 66.5t8.5 56.5q0 45 -32 67.5t-91 22.5h-60v86h500v-86h-24q-22 0 -41.5 -7.5t-39.5 -26t-41.5 -49t-47.5 -76.5l-344 -607v-377q0 -48 13 -77t36 -44.5t53.5 -20.5t64.5 -5h48v-86h-637z" />-<glyph glyph-name="Z" unicode="Z" horiz-adv-x="1212" d="M1094 1378l-770 -1276h516q46 0 77 13.5t50 36.5t29 53.5t14 64.5l10 88h106l-10 -358h-1042v82l768 1278h-445q-45 0 -75.5 -13.5t-49.5 -36.5t-29 -53.5t-14 -64.5l-10 -88h-106l10 358h971v-84z" />-<glyph glyph-name="bracketleft" unicode="[" horiz-adv-x="735" d="M205 -262v1818h457v-86h-89q-34 0 -64.5 -5t-53.5 -20.5t-36 -44.5t-13 -77v-1352q0 -48 13 -77t36 -44.5t53.5 -20.5t64.5 -5h89v-86h-457z" />-<glyph glyph-name="backslash" unicode="\" horiz-adv-x="590" d="M0 1556h119l471 -1804h-121z" />-<glyph glyph-name="bracketright" unicode="]" horiz-adv-x="737" d="M76 -262v86h88q35 0 65.5 5t53 20.5t36 44.5t13.5 77v1352q0 48 -13.5 77t-36 44.5t-53 20.5t-65.5 5h-88v86h456v-1818h-456z" />-<glyph glyph-name="asciicircum" unicode="^" d="M535 1462h71l432 -917h-137l-332 729l-327 -729h-138z" />-<glyph glyph-name="underscore" unicode="_" horiz-adv-x="940" d="M950 -291h-960v121h960v-121z" />-<glyph glyph-name="grave" unicode="`" horiz-adv-x="1182" d="M791 1241h-78q-41 28 -89 69.5t-93 85.5t-82 84.5t-56 67.5v21h219q16 -34 37.5 -74.5t45.5 -81t49 -78.5t47 -67v-27z" />-<glyph glyph-name="a" unicode="a" horiz-adv-x="1153" d="M301 297q0 -96 40.5 -143.5t125.5 -47.5q62 0 112.5 20t86 57t54.5 90t19 118v166l-131 -6q-87 -4 -145.5 -21.5t-94.5 -49t-51.5 -77.5t-15.5 -106zM549 1016q-59 0 -95.5 -16.5t-57 -46.5t-27.5 -71t-7 -89q-85 0 -129.5 29t-44.5 100q0 53 29 90t79.5 60.5t117.5 34.5 t143 11q94 0 164 -18.5t117 -60.5t70.5 -109.5t23.5 -165.5v-531q0 -43 7 -71t22 -45t39.5 -24t58.5 -7h6v-86h-277l-32 176h-17q-32 -43 -62 -79t-66 -62t-81.5 -40.5t-109.5 -14.5q-68 0 -126.5 19.5t-101 60t-66.5 102t-24 145.5q0 163 116 242t351 86l170 6v123 q0 55 -6 101.5t-26 80t-57.5 52t-100.5 18.5z" />-<glyph glyph-name="b" unicode="b" horiz-adv-x="1257" d="M1145 551q0 -148 -27.5 -256t-80.5 -178t-131 -103.5t-179 -33.5q-59 0 -107 13t-86.5 37t-68 57t-51.5 73h-13l-36 -160h-328v86h16q35 0 65.5 5t53 20.5t36 44.5t13.5 77v1098q0 45 -13.5 72.5t-36.5 42.5t-53.5 19.5t-64.5 4.5h-16v86h377v-376q0 -35 -1 -80.5 t-3 -87.5l-4 -99h8q23 47 52 85t67 64.5t86 41t108 14.5q101 0 179 -33.5t131 -103t80.5 -176.5t27.5 -254zM692 987q-80 0 -133.5 -26.5t-85.5 -80.5t-45.5 -136t-13.5 -193q0 -107 13.5 -189t46 -138t86 -84.5t134.5 -28.5q68 0 115.5 28.5t78 84.5t44.5 138.5t14 190.5 q0 109 -14 190.5t-44.5 135.5t-79 81t-116.5 27z" />-<glyph glyph-name="c" unicode="c" horiz-adv-x="1008" d="M580 -20q-102 0 -188 31.5t-148 99.5t-96.5 174.5t-34.5 257.5q0 164 34.5 274.5t96 177.5t145 95t180.5 28q64 0 127.5 -12.5t114.5 -38.5t83 -65.5t32 -92.5q0 -71 -46 -100t-137 -29q0 48 -8 91t-27.5 75.5t-53 51t-85.5 18.5q-59 0 -106.5 -22t-81.5 -76t-52 -145 t-18 -228q0 -218 72.5 -325t237.5 -107q95 0 166 40t106 103q15 -12 25 -32t10 -48q0 -35 -22 -70t-65.5 -63t-108.5 -45.5t-152 -17.5z" />-<glyph glyph-name="d" unicode="d" horiz-adv-x="1257" d="M1036 225q0 -45 13.5 -72.5t36.5 -42.5t53.5 -19.5t64.5 -4.5h17v-86h-347l-22 184h-8q-23 -47 -52.5 -84.5t-67.5 -64t-86 -41t-108 -14.5q-101 0 -179 33.5t-131 103t-80 176.5t-27 254q0 148 27 256t80 178t131 103.5t179 33.5q59 0 107 -13t86.5 -37t68.5 -57t52 -73 h12q-3 50 -6 94l-4 75.5t-2 55.5v168q0 45 -13.5 72.5t-36.5 42.5t-53.5 19.5t-64.5 4.5h-17v86h377v-1331zM565 111q80 0 133.5 26.5t86 80.5t46 136t13.5 193q0 107 -13.5 189t-46 137.5t-86.5 84.5t-135 29q-67 0 -115 -29t-78.5 -85t-44.5 -138.5t-14 -189.5 q0 -218 59 -326t195 -108z" />-<glyph glyph-name="e" unicode="e" horiz-adv-x="1096" d="M563 1008q-114 0 -175.5 -92.5t-72.5 -270.5h471q0 81 -12 148t-38 115t-68.5 74t-104.5 26zM588 -20q-111 0 -199.5 37.5t-149.5 109.5t-93.5 176.5t-32.5 237.5q0 287 118 432t336 145q99 0 178 -31t134 -92.5t84.5 -153t29.5 -212.5v-94h-682q2 -112 22.5 -192.5 t59 -132.5t94.5 -76.5t129 -24.5q53 0 98.5 12t82.5 32t65.5 45.5t46.5 53.5q14 -6 26.5 -25t12.5 -45q0 -31 -22 -66.5t-67 -65.5t-112.5 -50t-158.5 -20z" />-<glyph glyph-name="f" unicode="f" horiz-adv-x="756" d="M688 86v-86h-633v86h27q35 0 65.5 5t53 20.5t36 44.5t13.5 77v768h-187v97h187v102q0 92 25 162t72 118t114 72.5t151 24.5q80 0 135 -10.5t88.5 -29.5t48.5 -45t15 -58q0 -28 -12.5 -49.5t-35 -36t-53 -22t-67.5 -7.5q0 31 -6.5 60.5t-22 52.5t-41.5 37t-65 14 q-44 0 -73.5 -17t-47.5 -50.5t-25.5 -82.5t-7.5 -114v-121h289v-97h-289v-768q0 -48 13.5 -77t36 -44.5t53.5 -20.5t65 -5h78z" />-<glyph glyph-name="g" unicode="g" horiz-adv-x="1102" d="M1077 1055q0 -22 -6.5 -41.5t-19.5 -34t-33.5 -23t-48.5 -8.5q0 12 -3 24t-10.5 22.5t-20.5 17t-34 6.5q-26 0 -47 -6t-41 -19q35 -43 57.5 -101.5t22.5 -146.5q0 -76 -23 -140.5t-68.5 -111.5t-115 -73.5t-162.5 -26.5q-12 0 -27 0.5l-30 1t-28 2l-21 2.5 q-20 -10 -38 -22t-31.5 -27t-21.5 -34t-8 -42q0 -25 9.5 -40t26.5 -23.5t40.5 -11t50.5 -2.5h232q92 0 156 -23t104.5 -63.5t59 -97t18.5 -122.5q0 -88 -32.5 -159t-99.5 -120.5t-169.5 -76.5t-241.5 -27q-214 0 -320 79t-106 220q0 60 21 105t56.5 77t81.5 52t97 29 q-21 9 -41 24t-36 36t-26 48t-10 60q0 61 32 105.5t102 86.5q-44 18 -78.5 49.5t-58 72t-36 88.5t-12.5 99q0 89 24 159t72 119t120 75t169 26q37 0 73 -5.5t66.5 -15t55 -21.5t39.5 -25q15 16 35 35.5t45 36t54.5 27.5t63.5 11q31 0 53.5 -8.5t37.5 -23t22.5 -34t7.5 -40.5 zM213 -180q0 -45 12 -84t42 -67t81 -44t129 -16q110 0 181.5 18.5t113.5 51.5t59 78.5t17 99.5q0 47 -13.5 77.5t-40 48t-65.5 24.5t-90 7h-201q-43 0 -83.5 -7.5t-72 -28.5t-50.5 -59t-19 -99zM332 745q0 -131 43.5 -192t142.5 -61q50 0 84.5 15t56.5 46t31.5 79.5 t9.5 115.5q0 138 -42 204t-142 66q-99 0 -141.5 -67.5t-42.5 -205.5z" />-<glyph glyph-name="h" unicode="h" horiz-adv-x="1300" d="M588 86v-86h-551v86h16q35 0 65.5 5t53 20.5t36 44.5t13.5 77v1098q0 45 -13.5 72.5t-36.5 42.5t-53.5 19.5t-64.5 4.5h-16v86h377v-450q0 -28 -1 -58.5t-3 -56.5l-4 -59h10q92 186 301 186q87 0 154.5 -23.5t113.5 -72t70 -124.5t24 -181v-484q0 -48 12 -77t33 -44.5 t50 -20.5t63 -5h6v-86h-356v707q0 67 -12 119t-38.5 88t-69.5 54.5t-105 18.5q-64 0 -110.5 -23.5t-77 -67t-45.5 -105t-15 -136.5v-432q0 -45 13.5 -72.5t36.5 -41.5t53.5 -18.5t64.5 -4.5h6z" />-<glyph glyph-name="i" unicode="i" horiz-adv-x="655" d="M74 86q34 0 64.5 4.5t53.5 18.5t36.5 41.5t13.5 72.5v649q0 45 -13.5 72.5t-36.5 42.5t-53.5 20t-64.5 5h-6v86h366v-865q0 -48 13.5 -77t36 -44.5t53.5 -20.5t65 -5h27v-86h-582v86h27zM213 1430q0 36 9 60t25 39t37 21t46 6q24 0 45 -6t36.5 -21t25 -39t9.5 -60 t-9.5 -60t-25 -39t-36.5 -21.5t-45 -6.5q-25 0 -46 6.5t-37 21.5t-25 39t-9 60z" />-<glyph glyph-name="j" unicode="j" horiz-adv-x="614" d="M414 1098v-1114q0 -138 -30 -229.5t-84 -146t-128.5 -77.5t-163.5 -23h-33v97h15q55 0 98 18.5t72.5 62t45 116t15.5 180.5v890q0 45 -13.5 72.5t-36.5 42.5t-53.5 20t-64.5 5h-6v86h367zM193 1430q0 36 9 60t24.5 39t37 21t45.5 6t45.5 -6t37 -21t25 -39t9.5 -60 t-9.5 -60t-25 -39t-37 -21.5t-45.5 -6.5t-45.5 6.5t-37 21.5t-24.5 39t-9 60z" />-<glyph glyph-name="k" unicode="k" horiz-adv-x="1198" d="M694 659l273 -405q53 -82 105.5 -125t121.5 -43h6v-86h-29q-89 0 -146.5 5.5t-99.5 26t-77 60t-78 107.5l-205 321l-151 -112v-185q0 -45 13.5 -72.5t36.5 -41.5t53.5 -18.5t64.5 -4.5h6v-86h-551v86h16q35 0 65.5 5t53 20.5t36 44.5t13.5 77v1098q0 45 -13.5 72.5 t-36.5 42.5t-53.5 19.5t-64.5 4.5h-16v86h377v-790q0 -28 -1 -71.5t-3 -85.5l-4 -101l256 280q33 37 54.5 65t34.5 49t18 37.5t5 31.5q0 26 -24.5 33.5t-75.5 7.5v86h452v-86q-71 0 -139.5 -50t-148.5 -141z" />-<glyph glyph-name="l" unicode="l" horiz-adv-x="635" d="M53 86q35 0 65.5 5t53 20.5t36 44.5t13.5 77v1098q0 45 -13.5 72.5t-36.5 42.5t-53.5 19.5t-64.5 4.5h-26v86h387v-1323q0 -48 13 -77t36 -44.5t53.5 -20.5t65.5 -5h26v-86h-581v86h26z" />-<glyph glyph-name="m" unicode="m" horiz-adv-x="1935" d="M608 86v-86h-551v86h27q35 0 64 5t50 20.5t32.5 44.5t11.5 77v639q0 45 -12 72.5t-33 42.5t-50 20t-63 5h-6v86h321l27 -166h10q31 57 65 93.5t72.5 57t81 28t90.5 7.5q50 0 95 -10t83.5 -32t69 -57.5t49.5 -86.5h17q31 57 67 93.5t76.5 57t85 28t92.5 7.5 q78 0 140 -23.5t105 -72t66 -124.5t23 -181v-484q0 -48 12 -77t33 -44.5t50 -20.5t63 -5h6v-86h-356v707q0 67 -11.5 119t-37 88t-66.5 54.5t-100 18.5q-64 0 -107.5 -24.5t-70.5 -66.5t-38.5 -97t-11.5 -117v-449q0 -48 12 -77t33 -44.5t50 -20.5t63 -5h6v-86h-356v707 q0 67 -11.5 119t-37 88t-66.5 54.5t-100 18.5q-67 0 -112.5 -27t-73.5 -73t-40 -106t-12 -126v-432q0 -45 13.5 -72.5t36.5 -41.5t53.5 -18.5t64.5 -4.5h6z" />-<glyph glyph-name="n" unicode="n" horiz-adv-x="1321" d="M608 86v-86h-551v86h17q35 0 65.5 5t53 20.5t36 44.5t13.5 77v639q0 45 -13.5 72.5t-36.5 42.5t-53.5 20t-64.5 5h-6v86h331l27 -166h10q32 57 67.5 93.5t75 57t84 28t93.5 7.5q81 0 144.5 -23.5t108 -72t68 -124.5t23.5 -181v-484q0 -48 11.5 -77t32.5 -44.5t50 -20.5 t63 -5h7v-86h-357v707q0 67 -12 119t-38.5 88t-69.5 54.5t-105 18.5q-70 0 -117.5 -27t-76.5 -73t-41.5 -106t-12.5 -126v-432q0 -45 13.5 -72.5t36.5 -41.5t53.5 -18.5t64.5 -4.5h6z" />-<glyph glyph-name="o" unicode="o" horiz-adv-x="1182" d="M1069 551q0 -289 -122.5 -430t-358.5 -141q-111 0 -199 35t-149.5 106t-94 178.5t-32.5 251.5q0 287 121.5 427t359.5 140q111 0 199 -34.5t149.5 -105t94 -177t32.5 -250.5zM311 551q0 -114 15 -201t48.5 -145.5t87 -88.5t130.5 -30t130 30t86 88.5t47.5 145.5t14.5 201 t-15 200t-48 143.5t-86.5 86.5t-130.5 29t-130 -29t-86 -86.5t-48 -143.5t-15 -200z" />-<glyph glyph-name="p" unicode="p" horiz-adv-x="1257" d="M692 987q-80 0 -133.5 -26.5t-85.5 -80.5t-45.5 -136t-13.5 -193q0 -107 13.5 -189t46 -138t86 -84.5t134.5 -28.5q68 0 115.5 28.5t78 84.5t44.5 138.5t14 190.5q0 109 -14 190.5t-44.5 135.5t-79 81t-116.5 27zM1145 551q0 -148 -27.5 -256t-80.5 -178t-131 -103.5 t-179 -33.5q-59 0 -107 13t-86.5 37t-68 57t-51.5 73h-8q2 -53 4 -99q1 -19 1.5 -39.5t1 -38.5t1 -32.5t0.5 -22.5v-196q0 -45 13.5 -72.5t36.5 -42t53.5 -19t64.5 -4.5h6v-86h-551v86h16q35 0 65.5 5t53 20.5t36 45t13.5 77.5v1130q0 45 -13.5 72.5t-36.5 42.5t-53.5 20 t-64.5 5h-26v86h358l21 -185h8q23 47 52 85t67 64.5t86 41t108 14.5q101 0 179 -33.5t131 -103t80.5 -176.5t27.5 -254z" />-<glyph glyph-name="q" unicode="q" horiz-adv-x="1257" d="M1221 -492h-613v86h68q35 0 65.5 5t53 20.5t36 45t13.5 77.5v176q0 35 1 80.5t3 87.5l4 98h-8q-23 -47 -52.5 -84.5t-67.5 -64t-86 -41t-108 -14.5q-101 0 -179 33.5t-131 103t-80 176.5t-27 254q0 148 27 256t80 178t131 103.5t179 33.5q59 0 107 -13t86.5 -37t68.5 -57 t52 -73h12l37 160h328v-86h-17q-34 0 -65 -5t-53.5 -20.5t-36 -45t-13.5 -77.5v-1130q0 -45 13.5 -72.5t36.5 -42.5t53.5 -20t64.5 -5h17v-86zM565 111q80 0 133.5 26.5t86 80.5t46 136t13.5 193q0 107 -13.5 189t-46 137.5t-86.5 84.5t-135 29q-67 0 -115 -29t-78.5 -85 t-44.5 -138.5t-14 -189.5q0 -218 59 -326t195 -108z" />-<glyph glyph-name="r" unicode="r" horiz-adv-x="965" d="M659 0h-591v86h6q35 0 65.5 5t53 20.5t36 44.5t13.5 77v639q0 45 -13.5 72.5t-36.5 42.5t-53.5 20t-64.5 5h-6v86h315l39 -203h10q20 46 41 86.5t51.5 71t75.5 48t113 17.5q112 0 166.5 -39t54.5 -110q0 -32 -10.5 -59t-33.5 -46.5t-59 -30t-88 -10.5q0 85 -24 122.5 t-84 37.5q-38 0 -68 -21.5t-52.5 -57t-38 -81.5t-25 -94.5t-13.5 -96.5t-4 -87v-322q0 -45 13.5 -72.5t36.5 -41.5t53.5 -18.5t64.5 -4.5h57v-86z" />-<glyph glyph-name="s" unicode="s" horiz-adv-x="924" d="M430 -20q-77 0 -139.5 13.5t-106.5 41t-68 68t-24 94.5q0 41 13.5 68.5t33 43.5t42 22.5t40.5 6.5q0 -54 11 -101t37 -82.5t68.5 -56t104.5 -20.5q55 0 97 13.5t70.5 38t43.5 59t15 75.5q0 38 -11.5 65.5t-40 51.5t-77.5 49t-123 57q-79 35 -137.5 67.5t-96.5 70.5 t-57 86.5t-19 115.5q0 70 27.5 124t78.5 90.5t122 55.5t158 19q73 0 129 -15t94 -40.5t57 -60t19 -72.5q0 -56 -38.5 -89.5t-109.5 -33.5q0 103 -42.5 160t-133.5 57q-52 0 -89 -12t-60.5 -34t-34.5 -52t-11 -66q0 -39 14 -67.5t44.5 -52.5t79 -46.5t116.5 -50.5 q81 -34 139 -67t95.5 -72t55.5 -88t18 -113q0 -80 -29 -140t-82 -100.5t-127.5 -60.5t-165.5 -20z" />-<glyph glyph-name="t" unicode="t" horiz-adv-x="721" d="M543 88q37 0 68 4t63 10v-90q-13 -6 -34 -12t-45.5 -10.5t-51.5 -7t-51 -2.5q-78 0 -134 16.5t-92 53.5t-53.5 98t-17.5 149v684h-156v82q37 0 81.5 15t78.5 50q35 38 56.5 90.5t37.5 131.5h94v-252h268v-117h-268v-690q0 -105 42.5 -154t113.5 -49z" />-<glyph glyph-name="u" unicode="u" horiz-adv-x="1300" d="M1079 223q0 -45 13.5 -72.5t36.5 -41.5t53.5 -18.5t64.5 -4.5h6v-86h-325l-27 166h-10q-31 -58 -68.5 -94t-79.5 -56.5t-88 -28t-96 -7.5q-81 0 -144 23t-106 72t-65.5 125t-22.5 181v491q0 45 -13.5 72.5t-36.5 42.5t-53.5 20t-64.5 5h-6v86h367v-707q0 -67 10.5 -119 t35 -88t66 -54.5t103.5 -18.5q68 0 116.5 24t80 67.5t46.5 104.5t15 135v422q0 48 -13.5 77.5t-36 45t-53 20.5t-65.5 5h-6v86h366v-875z" />-<glyph glyph-name="v" unicode="v" horiz-adv-x="1186" d="M8 1012v86h512v-86h-26q-62 0 -92.5 -24t-30.5 -75q0 -17 4 -34.5t12 -40.5l139 -387q13 -36 27 -79.5l26.5 -85.5t22.5 -79.5t14 -63.5h7q5 22 16.5 54.5l26 71l30 78.5l29.5 77l147 399q10 25 14.5 48t4.5 40q0 53 -33.5 77t-101.5 24h-15v86h439v-86h-25 q-30 0 -51 -6.5t-38 -24.5t-32.5 -49t-34.5 -80l-323 -852h-187l-329 903q-12 32 -25 53t-30 33.5t-39 17.5t-52 5h-6z" />-<glyph glyph-name="w" unicode="w" horiz-adv-x="1765" d="M975 1092l205 -607l25.5 -79.5l25.5 -84l22 -78.5t15 -63h6q9 51 29 125.5t51 173.5l102 330q8 26 13.5 55.5t5.5 46.5q0 53 -33.5 77t-102.5 24h-14v86h438v-86h-26q-30 0 -52 -6t-39.5 -23.5t-32.5 -48.5t-30 -82l-264 -852h-164l-244 721l-37 135l-290 -856h-162 l-268 903q-12 32 -25 53t-30 33.5t-39 17.5t-52 5h-6v86h506v-86h-27q-61 0 -92 -18t-31 -68q0 -17 5.5 -41t11.5 -47l96 -344l21 -81t20.5 -87.5l17.5 -81.5t13 -64h6q5 26 13.5 60.5t20 74l24.5 80.5t26 78l213 619h129z" />-<glyph glyph-name="x" unicode="x" horiz-adv-x="1184" d="M786 952q0 37 -31.5 48.5t-78.5 11.5h-6v86h448v-86h-18q-30 0 -54.5 -5t-48 -19t-48.5 -40t-56 -67l-211 -281l301 -405q45 -60 85.5 -84.5t78.5 -24.5h27v-86h-529v86h10q123 0 123 68q0 11 -3 22.5t-12 27t-24 37.5t-39 54l-137 184l-143 -200q-9 -12 -19 -28t-18 -33 t-13 -34.5t-5 -33.5q0 -35 29 -49.5t98 -14.5h6v-86h-488v86h19q37 0 65 6t53 21t50 40.5t55 63.5l258 334l-258 352q-45 53 -87.5 81t-88.5 28h-27v86h520v-86h-6q-35 0 -58 -4.5t-36 -13t-18 -19.5t-5 -23q0 -21 12.5 -41.5t35.5 -52.5l135 -184l106 153q23 35 37 66 t14 59z" />-<glyph glyph-name="y" unicode="y" horiz-adv-x="1157" d="M1157 1098v-86h-6q-30 0 -51.5 -6.5t-39 -24.5t-32.5 -49t-33 -80l-311 -860q-36 -99 -68 -174t-67 -128.5t-76.5 -88.5t-94.5 -56t-122 -29t-160 -8h-18v97q104 0 179 31t129.5 85t91 127.5t61.5 159.5l-379 895q-14 32 -27 52.5t-29.5 33.5t-38 18t-51.5 5h-6v86h492 v-86h-6q-62 0 -92.5 -24t-30.5 -75q0 -17 3.5 -34.5t12.5 -40.5l150 -365l29.5 -76.5t29 -82l24.5 -76.5t15 -62h6q11 45 30.5 109.5t45.5 138.5l137 399q9 25 13.5 48t4.5 40q0 53 -33.5 77t-101.5 24h-6v86h426z" />-<glyph glyph-name="z" unicode="z" horiz-adv-x="1047" d="M696 117q36 0 59.5 14t39 37t25 53.5t16.5 63.5l10 47h86l-10 -332h-836v82l594 899h-320q-40 0 -66 -10.5t-42.5 -31.5t-27 -52t-21.5 -72l-2 -8h-86l20 291h785v-84l-596 -897h372z" />-<glyph glyph-name="braceleft" unicode="{" horiz-adv-x="877" d="M639 -262q-72 0 -127.5 21t-92.5 59t-56 92.5t-19 122.5v356q0 63 -18.5 104t-51.5 65.5t-77 35t-95 12.5v86q51 2 95 12t77 34.5t51.5 65.5t18.5 103v357q0 140 74.5 216t220.5 76h135v-88h-61q-91 0 -129.5 -54t-38.5 -161v-350q0 -100 -56.5 -166.5t-171.5 -85.5v-2 q118 -20 173 -86t55 -168v-354q0 -105 38.5 -160t129.5 -55h61v-88h-135z" />-<glyph glyph-name="bar" unicode="|" d="M633 -492h-123v2048h123v-2048z" />-<glyph glyph-name="braceright" unicode="}" horiz-adv-x="877" d="M102 -174h62q91 0 129.5 55t38.5 160v354q0 102 54.5 168t172.5 86v2q-114 19 -170.5 85.5t-56.5 166.5v350q0 107 -38.5 161t-129.5 54h-62v88h136q145 0 219.5 -76t74.5 -216v-357q0 -62 19 -103t51.5 -65.5t76.5 -34.5t95 -12v-86q-51 -2 -95 -12.5t-76.5 -35 t-51.5 -65.5t-19 -104v-356q0 -68 -19 -122.5t-56 -92.5t-92 -59t-127 -21h-136v88z" />-<glyph glyph-name="asciitilde" unicode="~" horiz-adv-x="1149" d="M776 551q-30 0 -62 12.5t-63.5 31.5t-60 41.5t-51.5 43.5l-45 40.5t-43.5 34.5t-43.5 24t-45 9q-60 0 -89 -58t-40 -179h-120q6 64 18.5 128t40.5 115t75.5 83t123.5 32q34 0 68 -13t66 -33t60.5 -44l52.5 -45l43 -38.5t41.5 -32.5t41.5 -22.5t42 -8.5q57 0 84 61.5 t41 175.5h123q-7 -64 -21 -128t-42.5 -115t-75 -83t-119.5 -32z" />-<glyph glyph-name="exclamdown" unicode="¡" horiz-adv-x="682" d="M459 -367h-238l72 1051h94zM469 971q0 -40 -10 -67t-27.5 -43t-41 -23t-50.5 -7q-26 0 -49.5 7t-41 23t-28 43t-10.5 67q0 39 10.5 65.5t28 43t41 23.5t49.5 7q27 0 50.5 -7t41 -23.5t27.5 -43t10 -65.5z" />-<glyph glyph-name="cent" unicode="¢" d="M584 180q-93 10 -170.5 45.5t-133.5 102t-87 168t-31 243.5q0 152 33 258t90 173.5t134 101t165 41.5v149h102v-149q59 -5 118 -19.5t106 -40t76.5 -62.5t29.5 -85q0 -71 -45.5 -100t-136.5 -29q0 42 -9 80.5t-27 69t-46 51.5t-66 28v-909q42 0 84.5 6t81.5 15.5t73 21 t58 22.5v-112q-53 -28 -129.5 -50t-167.5 -24v-176h-102v180zM360 741q0 -183 54.5 -292t169.5 -140v897q-49 -9 -90 -37.5t-71 -83t-46.5 -139t-16.5 -205.5z" />-<glyph glyph-name="sterling" unicode="£" d="M500 690q15 -51 28 -104t13 -115q0 -88 -49.5 -155.5t-131.5 -122.5l5 -9q42 20 78.5 28.5t70.5 8.5q41 0 90 -12t97.5 -27t93 -27t75.5 -12q36 0 66.5 10.5t57.5 27.5t49.5 38.5t41.5 44.5v-133q-18 -25 -43 -52t-58 -49t-74.5 -36t-92.5 -14t-100 14.5t-99 31.5 t-101.5 31.5t-106.5 14.5q-68 0 -127 -19.5t-125 -50.5l-27 -12v108l37 23q38 23 75.5 55.5t67.5 75t49 94.5t19 113q0 60 -15 117.5t-36 113.5h-228v96h189q-14 32 -28 71t-25 81.5t-18 87t-7 86.5q0 88 29 157t83 116.5t131.5 72.5t174.5 25q99 0 168 -17.5t112 -48.5 t62.5 -73t19.5 -91q0 -66 -43.5 -99.5t-118.5 -33.5q0 48 -8.5 94.5t-31 83t-63 59.5t-105.5 23q-119 0 -174.5 -68t-55.5 -196q0 -48 8 -94t20 -88.5t26 -80l26 -67.5h348v-96h-319z" />-<glyph glyph-name="currency" unicode="¤" d="M197 733q0 62 17.5 117.5t51.5 101.5l-135 135l86 87l133 -136q47 34 102.5 53t118.5 19q60 0 115.5 -19t101.5 -53l138 138l86 -89l-135 -135q34 -46 52.5 -101.5t18.5 -117.5t-18.5 -117.5t-52.5 -101.5l135 -135l-84 -86l-137 135q-47 -32 -102.5 -51t-117.5 -19 t-117 18t-102 52l-135 -135l-86 86l133 135q-32 46 -49.5 101.5t-17.5 117.5zM311 733q0 -54 20.5 -101.5t56 -83t82.5 -56.5t101 -21q56 0 104 21t83 56.5t55.5 83t20.5 101.5q0 56 -20.5 104t-55.5 83.5t-83 56t-104 20.5q-54 0 -101 -20.5t-82.5 -56t-56 -83.5 t-20.5 -104z" />-<glyph glyph-name="yen" unicode="¥" d="M264 0v86h47q34 0 64 4.5t52.5 18.5t36 41.5t15.5 72.5v139h-286v97h286v143h-286v96h247l-278 570q-15 30 -28 50.5t-29.5 33.5t-37.5 18.5t-51 5.5h-6v86h498v-86h-14q-62 0 -89.5 -19t-27.5 -61q0 -24 8 -54.5t25 -65.5l98 -218q15 -32 29 -69l26.5 -73l22.5 -67 l16 -51q4 11 13 32.5l20.5 47l24 52l22.5 47.5l117 249q24 54 36.5 99.5t12.5 68.5q0 42 -28.5 62t-96.5 20h-6v86h418v-86h-19q-26 0 -45 -7t-35.5 -25t-34 -49t-40.5 -78l-250 -519h254v-96h-287v-143h287v-97h-287v-129q0 -48 13.5 -77t36 -44.5t53 -20.5t65.5 -5h47v-86 h-629z" />-<glyph glyph-name="brokenbar" unicode="¦" d="M633 737h-123v819h123v-819zM633 -492h-123v820h123v-820z" />-<glyph glyph-name="section" unicode="§" horiz-adv-x="1114" d="M598 795q-74 35 -138.5 69t-117.5 76q-22 -20 -36.5 -48t-14.5 -69t14 -73.5t48 -64t91.5 -64.5t145.5 -76l122 -62.5t109 -74.5q17 24 28 53t11 63q0 35 -10 66t-38.5 63t-80 66.5t-133.5 75.5zM571 -133q126 0 188 55.5t62 149.5q0 45 -11 78.5t-48.5 67t-109 74 t-191.5 99.5q-81 40 -138.5 81.5t-93.5 87.5t-52.5 98.5t-16.5 113.5q0 77 34 134.5t87 94.5q-38 45 -57 100.5t-19 129.5q0 78 28.5 138.5t78.5 102t119.5 63t150.5 21.5q84 0 146.5 -17.5t104 -48t62 -72.5t20.5 -91q0 -66 -39.5 -95.5t-105.5 -29.5q0 48 -10 93 t-33.5 80.5t-61 57t-92.5 21.5q-102 0 -158.5 -51t-56.5 -148q0 -54 20.5 -90.5t61 -66t99.5 -57.5l137 -66q87 -42 150 -84t104 -87.5t60.5 -96.5t19.5 -111q0 -37 -10.5 -73.5t-27.5 -70t-39 -63t-46 -53.5q29 -40 46 -90t17 -113q0 -81 -22.5 -148t-70 -115.5t-122 -75.5 t-178.5 -27q-79 0 -143.5 15.5t-110.5 45.5t-71 74t-25 102q0 42 13.5 69.5t33 44t42 23t40.5 6.5q0 -54 13.5 -104.5t42 -89.5t73 -62.5t106.5 -23.5z" />-<glyph glyph-name="dieresis" unicode="¨" horiz-adv-x="1182" d="M291 1409q0 32 8 54t22 35.5t33 19.5t41 6t41 -6t33.5 -19.5t22.5 -35.5t8 -54q0 -31 -8 -53t-22.5 -36t-33.5 -20t-41 -6q-45 0 -74.5 26t-29.5 89zM680 1409q0 32 8 54t22 35.5t33 19.5t41 6t41 -6t33.5 -19.5t22.5 -35.5t8 -54q0 -31 -8 -53t-22.5 -36t-33.5 -20 t-41 -6q-45 0 -74.5 26t-29.5 89z" />-<glyph glyph-name="copyright" unicode="©" horiz-adv-x="1731" d="M901 365q47 0 87 12t72 32t55.5 45t37.5 50q12 -9 20.5 -25.5t8.5 -38.5q0 -28 -19.5 -57t-58 -53t-96 -39t-134.5 -15q-102 0 -178.5 33t-128 93t-77 144t-25.5 187q0 100 28 183t83.5 143t137 93.5t189.5 33.5q71 0 123.5 -11.5t87.5 -31t52.5 -46t17.5 -57.5 q0 -22 -11 -40t-30 -30.5t-44.5 -19t-54.5 -6.5q0 29 -7.5 56.5t-24.5 50t-44.5 36t-68.5 13.5q-72 0 -122.5 -24t-82.5 -71t-46.5 -115t-14.5 -157q0 -177 69 -272.5t199 -95.5zM113 731q0 104 27 200.5t75.5 180t117 152.5t152 118t179.5 76t200 27t200.5 -27t180 -76 t152.5 -118t118 -152.5t76 -180t27 -200.5t-27 -200t-76 -179.5t-118 -152t-152.5 -117.5t-180 -75.5t-200.5 -26.5t-200 26.5t-179.5 75.5t-152 117.5t-117 152t-75.5 179.5t-27 200zM225 731q0 -88 23 -169.5t64.5 -152.5t100 -129.5t129.5 -100t152.5 -64.5t169.5 -23 q89 0 171 23t153 64.5t129.5 100t100 129.5t64.5 152.5t23 169.5t-23 170t-64.5 153.5t-100 130t-129.5 100t-153 64.5t-171 23q-88 0 -169.5 -23t-152.5 -64.5t-129.5 -100t-100 -130t-64.5 -153t-23 -170.5z" />-<glyph glyph-name="ordfeminine" unicode="ª" horiz-adv-x="782" d="M250 985q0 -64 27 -88.5t67 -24.5q34 0 61 12t45.5 34t28.5 52t10 66v97l-63 -3q-50 -1 -83.5 -11.5t-54 -29t-29.5 -44.5t-9 -60zM387 1397q-31 0 -50 -11t-29.5 -30t-14.5 -44.5t-4 -54.5q-78 0 -120 20.5t-42 68.5q0 35 21 59t57 39t84 22t102 7q68 0 118 -12 t83.5 -38.5t50 -68.5t16.5 -103v-311q0 -46 19.5 -63t69.5 -17v-69h-199l-37 94h-10q-18 -20 -39.5 -39.5t-48 -34.5t-58.5 -24t-71 -9q-99 0 -156 53t-57 156q0 102 81 150.5t248 56.5l88 4v59q0 31 -3.5 56.5t-14 44t-30.5 29t-54 10.5z" />-<glyph glyph-name="guillemotleft" unicode="«" horiz-adv-x="1053" d="M133 586l313 356h103l-207 -389l207 -389h-103l-313 356v66zM504 586l313 356h103l-207 -389l207 -389h-103l-313 356v66z" />-<glyph glyph-name="logicalnot" unicode="¬" d="M1012 793v-541h-123v420h-756v121h879z" />-<glyph glyph-name="uni00AD" unicode="­" horiz-adv-x="635" d="M51 481v154h533v-154h-533z" />-<glyph glyph-name="registered" unicode="®" horiz-adv-x="1731" d="M477 358h27q20 0 38 2.5t31.5 10t21.5 21.5t9 38v600q-1 24 -9 38.5t-21.5 22t-31.5 9.5t-38 2h-27v72h369q352 0 352 -244q0 -49 -16 -86t-42 -64t-59.5 -45.5t-68.5 -29.5l182 -293q10 -17 19.5 -27.5t22.5 -16.5t30.5 -8t42.5 -2v-69h-217l-226 381h-108v-240 q2 -24 10 -38t21.5 -21.5t31 -10t37.5 -2.5h29v-69h-410v69zM758 748h86q55 0 92 11t59 33t31.5 56t9.5 80q0 45 -11 77t-34.5 51.5t-61 28.5t-91.5 9h-80v-346zM113 731q0 104 27 200.5t75.5 180t117 152.5t152 118t179.5 76t200 27t200.5 -27t180 -76t152.5 -118 t118 -152.5t76 -180t27 -200.5t-27 -200t-76 -179.5t-118 -152t-152.5 -117.5t-180 -75.5t-200.5 -26.5t-200 26.5t-179.5 75.5t-152 117.5t-117 152t-75.5 179.5t-27 200zM225 731q0 -88 23 -169.5t64.5 -152.5t100 -129.5t129.5 -100t152.5 -64.5t169.5 -23q89 0 171 23 t153 64.5t129.5 100t100 129.5t64.5 152.5t23 169.5t-23 170t-64.5 153.5t-100 130t-129.5 100t-153 64.5t-171 23q-88 0 -169.5 -23t-152.5 -64.5t-129.5 -100t-100 -130t-64.5 -153t-23 -170.5z" />-<glyph glyph-name="macron" unicode="¯" horiz-adv-x="940" d="M950 1556h-960v121h960v-121z" />-<glyph glyph-name="degree" unicode="°" horiz-adv-x="819" d="M98 1151q0 65 24.5 121.5t66.5 98.5t98.5 66.5t120.5 24.5q65 0 121.5 -24.5t98.5 -66.5t66.5 -98.5t24.5 -121.5q0 -64 -24.5 -120.5t-66.5 -98.5t-98.5 -66t-121.5 -24q-64 0 -120.5 24t-98.5 66t-66.5 98.5t-24.5 120.5zM221 1151q0 -38 14.5 -72.5t40 -60t59.5 -40.5 t73 -15q40 0 74 15t59.5 40.5t40 60t14.5 72.5q0 40 -14.5 74.5t-40 60t-59.5 40.5t-74 15q-39 0 -73 -15t-59.5 -40.5t-40 -60t-14.5 -74.5z" />-<glyph glyph-name="plusminus" unicode="±" d="M633 672v-379h-123v379h-377v121h377v378h123v-378h379v-121h-379zM1012 0h-879v121h879v-121z" />-<glyph glyph-name="uni00B2" unicode="²" horiz-adv-x="819" d="M690 1262q0 -48 -11 -90t-37 -84t-68.5 -87t-106.5 -100l-217 -186h297q47 0 65.5 25t24.5 59l8 43h70l-6 -256h-623v92l260 233q45 41 76 83t49.5 84.5t26.5 84.5t8 82q0 34 -6.5 62.5t-21 49.5t-37.5 32.5t-56 11.5q-37 0 -63 -14.5t-42 -39t-23 -57t-7 -67.5 q-69 0 -108.5 17t-39.5 71q0 37 18 68.5t53.5 54.5t88.5 36t123 13q143 0 224 -56.5t81 -164.5z" />-<glyph glyph-name="uni00B3" unicode="³" horiz-adv-x="819" d="M356 573q-134 0 -204 46.5t-70 121.5q0 45 25.5 73t68.5 28q0 -32 10.5 -63.5t32 -56.5t54 -40.5t77.5 -15.5q85 0 133.5 41.5t48.5 136.5q0 72 -52 114.5t-165 42.5h-67v84h67q39 0 72 12.5t58 35.5t39 55.5t14 73.5q0 68 -27 103.5t-90 35.5q-39 0 -65 -14.5t-41.5 -39 t-22 -57t-6.5 -67.5q-35 0 -63.5 3.5t-48 13.5t-30 27t-10.5 44q0 37 18.5 68.5t55 54.5t90 36t123.5 13q68 0 124 -14t96 -40t61.5 -63.5t21.5 -85.5q0 -45 -17 -80.5t-45.5 -62.5t-66.5 -46.5t-80 -31.5v-10q41 -7 84 -19.5t78.5 -35.5t58.5 -59.5t23 -92.5 q0 -75 -33.5 -126.5t-86.5 -83t-117 -45.5t-126 -14z" />-<glyph glyph-name="acute" unicode="´" horiz-adv-x="1182" d="M391 1268q23 29 47.5 67t48 78.5l45 81t37.5 74.5h219v-21q-19 -27 -56 -67.5t-82 -84.5t-92.5 -85.5t-88.5 -69.5h-78v27z" />-<glyph glyph-name="uni00B5" unicode="µ" horiz-adv-x="1260" d="M569 -20q-86 0 -147.5 31.5t-102.5 88.5q2 -85 7 -157t21.5 -126t48.5 -87.5t89 -42.5q0 -39 -8.5 -71.5t-26 -56.5t-44.5 -37.5t-64 -13.5q-32 0 -62 12t-53 40.5t-37 75.5t-14 116q0 61 3.5 131.5l7.5 156l8.5 189.5t5.5 232v637h192v-688q0 -67 10 -122t33.5 -94.5 t63 -61t98.5 -21.5q57 0 103 24t78 67.5t49.5 104.5t17.5 135v656h192v-834q0 -45 12 -78.5t30.5 -55.5t41.5 -33t45 -11h7v-86h-111q-84 0 -132 41.5t-67 122.5h-8q-24 -41 -51.5 -75t-62 -58t-77 -37.5t-96.5 -13.5z" />-<glyph glyph-name="paragraph" unicode="¶" horiz-adv-x="1264" d="M860 1452h-194v-1679h-320v86h27q35 0 65.5 5t53 20.5t36 44.5t13.5 77v785h-146q-90 0 -150 32.5t-96 88t-51.5 128t-15.5 152.5q0 82 17 149t55 115t98 74t147 26h781v-86h-27q-34 0 -65 -5t-53.5 -20.5t-36 -44.5t-13.5 -77v-1317q0 -48 13.5 -77t36 -44.5t53.5 -20.5 t65 -5h27v-86h-320v1679z" />-<glyph glyph-name="periodcentered" unicode="·" horiz-adv-x="586" d="M166 729q0 40 10 67t27.5 43t41 22.5t50.5 6.5q26 0 49.5 -6.5t41 -22.5t28 -43t10.5 -67q0 -39 -10.5 -65.5t-28 -43t-41 -23.5t-49.5 -7q-27 0 -50.5 7t-41 23.5t-27.5 43t-10 65.5z" />-<glyph glyph-name="cedilla" unicode="¸" horiz-adv-x="682" d="M551 -258q0 -109 -69.5 -171.5t-200.5 -62.5q-16 0 -36 1.5t-41 4.5t-42 7.5t-39 9.5v104q37 -8 74 -12t65 -4q59 0 92 31t33 84q0 60 -43.5 90t-110.5 35l43 159h97l-21 -88q100 -8 149.5 -57.5t49.5 -130.5z" />-<glyph glyph-name="uni00B9" unicode="¹" horiz-adv-x="819" d="M145 588v69h95q18 0 33 2.5t26.5 10.5t18 23.5t6.5 41.5v590h-191v70h103q23 0 49 3t50 12t43 24t28 39h102v-738q0 -26 7 -41.5t18.5 -23.5t27 -10.5t33.5 -2.5h94v-69h-543z" />-<glyph glyph-name="ordmasculine" unicode="º" horiz-adv-x="813" d="M250 1126q0 -133 35 -199.5t123 -66.5q86 0 121.5 66.5t35.5 199.5q0 131 -36.5 195.5t-122.5 64.5t-121 -64.5t-35 -195.5zM741 1126q0 -176 -85 -262t-250 -86q-158 0 -246 86.5t-88 261.5q0 176 85 261.5t253 85.5q77 0 138 -21.5t104 -64.5t66 -108t23 -153z" />-<glyph glyph-name="guillemotright" unicode="»" horiz-adv-x="1053" d="M920 520l-314 -356h-102l207 389l-207 389h102l314 -356v-66zM549 520l-313 -356h-103l207 389l-207 389h103l313 -356v-66z" />-<glyph glyph-name="onequarter" unicode="¼" horiz-adv-x="1720" d="M1294 580q0 32 3 73t8 79q-8 -17 -17 -34t-14 -25l-223 -324h243v231zM1477 261v-113q0 -26 7 -41.5t19 -23.5t27.5 -10.5t32.5 -2.5h41v-69h-451v69h57q17 0 32 2.5t26.5 10.5t18.5 23.5t7 41.5v113h-364v61l383 564h164v-537h196v-88h-196zM566 0h-136l731 1462h134z M60 588v69h95q18 0 33 2.5t26.5 10.5t18 23.5t6.5 41.5v590h-191v70h103q23 0 49 3t50 12t43 24t28 39h102v-738q0 -26 7 -41.5t18.5 -23.5t27 -10.5t33.5 -2.5h94v-69h-543z" />-<glyph glyph-name="onehalf" unicode="½" horiz-adv-x="1720" d="M525 0h-136l731 1462h134zM60 588v69h95q18 0 33 2.5t26.5 10.5t18 23.5t6.5 41.5v590h-191v70h103q23 0 49 3t50 12t43 24t28 39h102v-738q0 -26 7 -41.5t18.5 -23.5t27 -10.5t33.5 -2.5h94v-69h-543zM1620 677q0 -48 -11 -90t-37 -84t-68.5 -87t-106.5 -100l-217 -186 h297q47 0 65.5 25t24.5 59l8 43h70l-6 -256h-623v92l260 233q45 41 76 83t49.5 84.5t26.5 84.5t8 82q0 34 -6.5 62.5t-21 49.5t-37.5 32.5t-56 11.5q-37 0 -63 -14.5t-42 -39t-23 -57t-7 -67.5q-69 0 -108.5 17t-39.5 71q0 37 18 68.5t53.5 54.5t88.5 36t123 13 q143 0 224 -56.5t81 -164.5z" />-<glyph glyph-name="threequarters" unicode="¾" horiz-adv-x="1720" d="M1294 580q0 32 3 73t8 79q-8 -17 -17 -34t-14 -25l-223 -324h243v231zM1477 261v-113q0 -26 7 -41.5t19 -23.5t27.5 -10.5t32.5 -2.5h41v-69h-451v69h57q17 0 32 2.5t26.5 10.5t18.5 23.5t7 41.5v113h-364v61l383 564h164v-537h196v-88h-196zM607 0h-136l731 1462h134z M356 573q-134 0 -204 46.5t-70 121.5q0 45 25.5 73t68.5 28q0 -32 10.5 -63.5t32 -56.5t54 -40.5t77.5 -15.5q85 0 133.5 41.5t48.5 136.5q0 72 -52 114.5t-165 42.5h-67v84h67q39 0 72 12.5t58 35.5t39 55.5t14 73.5q0 68 -27 103.5t-90 35.5q-39 0 -65 -14.5t-41.5 -39 t-22 -57t-6.5 -67.5q-35 0 -63.5 3.5t-48 13.5t-30 27t-10.5 44q0 37 18.5 68.5t55 54.5t90 36t123.5 13q68 0 124 -14t96 -40t61.5 -63.5t21.5 -85.5q0 -45 -17 -80.5t-45.5 -62.5t-66.5 -46.5t-80 -31.5v-10q41 -7 84 -19.5t78.5 -35.5t58.5 -59.5t23 -92.5 q0 -75 -33.5 -126.5t-86.5 -83t-117 -45.5t-126 -14z" />-<glyph glyph-name="questiondown" unicode="¿" horiz-adv-x="1024" d="M502 682h125v-311q-89 -36 -149.5 -84t-97 -103t-52 -115t-15.5 -122q0 -53 13 -96.5t40 -74.5t69.5 -48t101.5 -17q64 0 107.5 21t70.5 57t39 83.5t12 100.5q37 0 68 -8.5t53.5 -25t35.5 -41.5t13 -58q0 -49 -22.5 -91t-70 -72.5t-119.5 -48t-171 -17.5q-97 0 -179 25 t-142 72.5t-94 116.5t-34 157q0 84 26.5 153.5t77.5 126.5t125 103.5t169 85.5v231zM696 969q0 -40 -10 -67t-27.5 -43t-41 -23t-50.5 -7q-26 0 -49.5 7t-41 23t-28 43t-10.5 67q0 39 10.5 65.5t28 43t41 23.5t49.5 7q27 0 50.5 -7t41 -23.5t27.5 -43t10 -65.5z" />-<glyph glyph-name="Agrave" unicode="À" horiz-adv-x="1444" d="M414 489l-78 -215q-9 -24 -14 -47.5t-5 -40.5q0 -52 33.5 -76t102.5 -24h47v-86h-500v86h39q30 0 51 6.5t38 24.5t32.5 49t34.5 80l454 1216h160l463 -1267q12 -32 24.5 -53t29.5 -33.5t39 -17.5t52 -5h27v-86h-563v86h47q123 0 123 98q0 17 -4.5 35t-12.5 41l-82 229 h-538zM788 950l-58.5 170.5t-43.5 153.5q-7 -37 -16.5 -71.5t-20.5 -70.5t-25 -75.5l-32 -87.5l-139 -377h462zM805 1579h-78q-41 28 -89 69.5t-93 85.5t-82 84.5t-56 67.5v21h219q16 -34 37.5 -74.5t45.5 -81t49 -78.5t47 -67v-27z" />-<glyph glyph-name="Aacute" unicode="Á" horiz-adv-x="1444" d="M414 489l-78 -215q-9 -24 -14 -47.5t-5 -40.5q0 -52 33.5 -76t102.5 -24h47v-86h-500v86h39q30 0 51 6.5t38 24.5t32.5 49t34.5 80l454 1216h160l463 -1267q12 -32 24.5 -53t29.5 -33.5t39 -17.5t52 -5h27v-86h-563v86h47q123 0 123 98q0 17 -4.5 35t-12.5 41l-82 229 h-538zM788 950l-58.5 170.5t-43.5 153.5q-7 -37 -16.5 -71.5t-20.5 -70.5t-25 -75.5l-32 -87.5l-139 -377h462zM637 1606q23 29 47.5 67t48 78.5l45 81t37.5 74.5h219v-21q-19 -27 -56 -67.5t-82 -84.5t-92.5 -85.5t-88.5 -69.5h-78v27z" />-<glyph glyph-name="Acircumflex" unicode="Â" horiz-adv-x="1444" d="M414 489l-78 -215q-9 -24 -14 -47.5t-5 -40.5q0 -52 33.5 -76t102.5 -24h47v-86h-500v86h39q30 0 51 6.5t38 24.5t32.5 49t34.5 80l454 1216h160l463 -1267q12 -32 24.5 -53t29.5 -33.5t39 -17.5t52 -5h27v-86h-563v86h47q123 0 123 98q0 17 -4.5 35t-12.5 41l-82 229 h-538zM788 950l-58.5 170.5t-43.5 153.5q-7 -37 -16.5 -71.5t-20.5 -70.5t-25 -75.5l-32 -87.5l-139 -377h462zM422 1606l53 67l57.5 78.5t53.5 81t41 74.5h194q16 -34 41 -74.5t53.5 -81l57.5 -78.5l53 -67v-27h-80q-59 34 -118 84.5t-105 99.5q-47 -49 -105 -99.5 t-116 -84.5h-80v27z" />-<glyph glyph-name="Atilde" unicode="Ã" horiz-adv-x="1444" d="M414 489l-78 -215q-9 -24 -14 -47.5t-5 -40.5q0 -52 33.5 -76t102.5 -24h47v-86h-500v86h39q30 0 51 6.5t38 24.5t32.5 49t34.5 80l454 1216h160l463 -1267q12 -32 24.5 -53t29.5 -33.5t39 -17.5t52 -5h27v-86h-563v86h47q123 0 123 98q0 17 -4.5 35t-12.5 41l-82 229 h-538zM788 950l-58.5 170.5t-43.5 153.5q-7 -37 -16.5 -71.5t-20.5 -70.5t-25 -75.5l-32 -87.5l-139 -377h462zM895 1712q30 0 48.5 9t29 24.5t15 35t7.5 40.5h92q-3 -47 -16.5 -90.5t-39.5 -77.5t-65 -54t-94 -20t-99.5 20t-83 44.5l-72 44.5t-66.5 20q-31 0 -49 -9 t-28.5 -24t-15.5 -34.5t-8 -40.5h-92q3 47 16.5 90t40 77t66 54t94.5 20t99.5 -20t83 -44.5l72 -44.5t65.5 -20z" />-<glyph glyph-name="Adieresis" unicode="Ä" horiz-adv-x="1444" d="M414 489l-78 -215q-9 -24 -14 -47.5t-5 -40.5q0 -52 33.5 -76t102.5 -24h47v-86h-500v86h39q30 0 51 6.5t38 24.5t32.5 49t34.5 80l454 1216h160l463 -1267q12 -32 24.5 -53t29.5 -33.5t39 -17.5t52 -5h27v-86h-563v86h47q123 0 123 98q0 17 -4.5 35t-12.5 41l-82 229 h-538zM788 950l-58.5 170.5t-43.5 153.5q-7 -37 -16.5 -71.5t-20.5 -70.5t-25 -75.5l-32 -87.5l-139 -377h462zM424 1747q0 32 8 54t22 35.5t33 19.5t41 6t41 -6t33.5 -19.5t22.5 -35.5t8 -54q0 -31 -8 -53t-22.5 -36t-33.5 -20t-41 -6q-45 0 -74.5 26t-29.5 89zM813 1747 q0 32 8 54t22 35.5t33 19.5t41 6t41 -6t33.5 -19.5t22.5 -35.5t8 -54q0 -31 -8 -53t-22.5 -36t-33.5 -20t-41 -6q-45 0 -74.5 26t-29.5 89z" />-<glyph glyph-name="Aring" unicode="Å" horiz-adv-x="1444" d="M860 1585q0 33 -10.5 56.5t-29 38t-43 21.5t-52.5 7t-52.5 -7t-43 -21.5t-29 -38t-10.5 -56.5t10.5 -56.5t29 -38t43 -21.5t52.5 -7t52.5 7t43 21.5t29 38t10.5 56.5zM971 1585q0 -55 -19 -96.5t-52.5 -69.5t-78.5 -42.5t-96 -14.5t-96 14.5t-78.5 42.5t-52.5 69.5 t-19 96.5q0 54 19 96t52.5 70t78.5 42.5t96 14.5t96 -14.5t78.5 -42.5t52.5 -70t19 -96zM414 489l-78 -215q-9 -24 -14 -47.5t-5 -40.5q0 -52 33.5 -76t102.5 -24h47v-86h-500v86h39q30 0 51 6.5t38 24.5t32.5 49t34.5 80l454 1216h160l463 -1267q12 -32 24.5 -53 t29.5 -33.5t39 -17.5t52 -5h27v-86h-563v86h47q123 0 123 98q0 17 -4.5 35t-12.5 41l-82 229h-538zM788 950l-58.5 170.5t-43.5 153.5q-7 -37 -16.5 -71.5t-20.5 -70.5t-25 -75.5l-32 -87.5l-139 -377h462z" />-<glyph glyph-name="AE" unicode="Æ" horiz-adv-x="1948" d="M1151 102h395q48 0 82.5 13.5t57.5 36.5t36 53.5t19 64.5l14 88h107l-15 -358h-1097v86h26q34 0 64 4.5t52.5 18.5t36.5 41.5t15 72.5v266h-471l-113 -215q-17 -32 -24.5 -61t-7.5 -47q0 -42 31.5 -61t91.5 -19h47v-86h-498v86h33q54 0 96.5 38t85.5 122l592 1120 l-160 10v86h1135l10 -358h-106l-11 88q-4 34 -15 64t-32.5 52.5t-54 36.5t-78.5 15h-344v-545h494v-100h-494v-613zM849 1221l-92.5 -183.5l-110.5 -214.5l-120 -231h418v768h-29q-25 -56 -66 -139z" />-<glyph glyph-name="Ccedilla" unicode="Ç" horiz-adv-x="1257" d="M774 1483q105 0 182.5 -17.5t129 -48.5t77 -73t25.5 -91q0 -33 -13.5 -59.5t-37.5 -45t-56.5 -28.5t-70.5 -10q0 48 -12.5 95t-41 84.5t-74 61t-112.5 23.5q-116 0 -197 -42.5t-132 -124t-74 -201t-23 -273.5q0 -137 24 -251t76 -195.5t132.5 -126.5t193.5 -45 q75 0 132.5 14.5t101.5 38.5t77.5 55.5t59.5 65.5q17 -11 28 -30t11 -50q0 -39 -26.5 -79.5t-80.5 -73.5t-137 -54.5t-197 -21.5q-153 0 -269 54t-194.5 153t-118.5 238t-40 308q0 166 42 304t124.5 237t205.5 154t285 55zM942 -258q0 -109 -69.5 -171.5t-200.5 -62.5 q-16 0 -36 1.5t-41 4.5t-42 7.5t-39 9.5v104q37 -8 74 -12t65 -4q59 0 92 31t33 84q0 60 -43.5 90t-110.5 35l43 159h97l-21 -88q100 -8 149.5 -57.5t49.5 -130.5z" />-<glyph glyph-name="Egrave" unicode="È" horiz-adv-x="1276" d="M479 102h395q49 0 83.5 13.5t57.5 36.5t36 53.5t18 64.5l14 88h107l-14 -358h-1098v86h26q34 0 64.5 4.5t53.5 18.5t36.5 41.5t13.5 72.5v1006q0 48 -13 77t-36 44.5t-53.5 20.5t-65.5 5h-26v86h1032l10 -358h-106l-10 88q-4 34 -15.5 64.5t-33 53.5t-54 36.5t-78.5 13.5 h-344v-545h494v-100h-494v-613zM743 1579h-78q-41 28 -89 69.5t-93 85.5t-82 84.5t-56 67.5v21h219q16 -34 37.5 -74.5t45.5 -81t49 -78.5t47 -67v-27z" />-<glyph glyph-name="Eacute" unicode="É" horiz-adv-x="1276" d="M479 102h395q49 0 83.5 13.5t57.5 36.5t36 53.5t18 64.5l14 88h107l-14 -358h-1098v86h26q34 0 64.5 4.5t53.5 18.5t36.5 41.5t13.5 72.5v1006q0 48 -13 77t-36 44.5t-53.5 20.5t-65.5 5h-26v86h1032l10 -358h-106l-10 88q-4 34 -15.5 64.5t-33 53.5t-54 36.5t-78.5 13.5 h-344v-545h494v-100h-494v-613zM545 1606q23 29 47.5 67t48 78.5l45 81t37.5 74.5h219v-21q-19 -27 -56 -67.5t-82 -84.5t-92.5 -85.5t-88.5 -69.5h-78v27z" />-<glyph glyph-name="Ecircumflex" unicode="Ê" horiz-adv-x="1276" d="M479 102h395q49 0 83.5 13.5t57.5 36.5t36 53.5t18 64.5l14 88h107l-14 -358h-1098v86h26q34 0 64.5 4.5t53.5 18.5t36.5 41.5t13.5 72.5v1006q0 48 -13 77t-36 44.5t-53.5 20.5t-65.5 5h-26v86h1032l10 -358h-106l-10 88q-4 34 -15.5 64.5t-33 53.5t-54 36.5t-78.5 13.5 h-344v-545h494v-100h-494v-613zM350 1606l53 67l57.5 78.5t53.5 81t41 74.5h194q16 -34 41 -74.5t53.5 -81l57.5 -78.5l53 -67v-27h-80q-59 34 -118 84.5t-105 99.5q-47 -49 -105 -99.5t-116 -84.5h-80v27z" />-<glyph glyph-name="Edieresis" unicode="Ë" horiz-adv-x="1276" d="M479 102h395q49 0 83.5 13.5t57.5 36.5t36 53.5t18 64.5l14 88h107l-14 -358h-1098v86h26q34 0 64.5 4.5t53.5 18.5t36.5 41.5t13.5 72.5v1006q0 48 -13 77t-36 44.5t-53.5 20.5t-65.5 5h-26v86h1032l10 -358h-106l-10 88q-4 34 -15.5 64.5t-33 53.5t-54 36.5t-78.5 13.5 h-344v-545h494v-100h-494v-613zM342 1747q0 32 8 54t22 35.5t33 19.5t41 6t41 -6t33.5 -19.5t22.5 -35.5t8 -54q0 -31 -8 -53t-22.5 -36t-33.5 -20t-41 -6q-45 0 -74.5 26t-29.5 89zM731 1747q0 32 8 54t22 35.5t33 19.5t41 6t41 -6t33.5 -19.5t22.5 -35.5t8 -54 q0 -31 -8 -53t-22.5 -36t-33.5 -20t-41 -6q-45 0 -74.5 26t-29.5 89z" />-<glyph glyph-name="Igrave" unicode="Ì" horiz-adv-x="752" d="M78 0v86h26q35 0 65.5 5t53.5 20.5t36 44.5t13 77v996q0 48 -13 77t-36 44.5t-53.5 20.5t-65.5 5h-26v86h596v-86h-27q-34 0 -65 -5t-53.5 -20.5t-36 -44.5t-13.5 -77v-996q0 -48 13.5 -77t36 -44.5t53.5 -20.5t65 -5h27v-86h-596zM473 1579h-78q-41 28 -89 69.5 t-93 85.5t-82 84.5t-56 67.5v21h219q16 -34 37.5 -74.5t45.5 -81t49 -78.5t47 -67v-27z" />-<glyph glyph-name="Iacute" unicode="Í" horiz-adv-x="752" d="M78 0v86h26q35 0 65.5 5t53.5 20.5t36 44.5t13 77v996q0 48 -13 77t-36 44.5t-53.5 20.5t-65.5 5h-26v86h596v-86h-27q-34 0 -65 -5t-53.5 -20.5t-36 -44.5t-13.5 -77v-996q0 -48 13.5 -77t36 -44.5t53.5 -20.5t65 -5h27v-86h-596zM271 1606q23 29 47.5 67t48 78.5l45 81 t37.5 74.5h219v-21q-19 -27 -56 -67.5t-82 -84.5t-92.5 -85.5t-88.5 -69.5h-78v27z" />-<glyph glyph-name="Icircumflex" unicode="Î" horiz-adv-x="752" d="M78 0v86h26q35 0 65.5 5t53.5 20.5t36 44.5t13 77v996q0 48 -13 77t-36 44.5t-53.5 20.5t-65.5 5h-26v86h596v-86h-27q-34 0 -65 -5t-53.5 -20.5t-36 -44.5t-13.5 -77v-996q0 -48 13.5 -77t36 -44.5t53.5 -20.5t65 -5h27v-86h-596zM73 1606l53 67l57.5 78.5t53.5 81 t41 74.5h194q16 -34 41 -74.5t53.5 -81l57.5 -78.5l53 -67v-27h-80q-59 34 -118 84.5t-105 99.5q-47 -49 -105 -99.5t-116 -84.5h-80v27z" />-<glyph glyph-name="Idieresis" unicode="Ï" horiz-adv-x="752" d="M78 0v86h26q35 0 65.5 5t53.5 20.5t36 44.5t13 77v996q0 48 -13 77t-36 44.5t-53.5 20.5t-65.5 5h-26v86h596v-86h-27q-34 0 -65 -5t-53.5 -20.5t-36 -44.5t-13.5 -77v-996q0 -48 13.5 -77t36 -44.5t53.5 -20.5t65 -5h27v-86h-596zM73 1747q0 32 8 54t22 35.5t33 19.5 t41 6t41 -6t33.5 -19.5t22.5 -35.5t8 -54q0 -31 -8 -53t-22.5 -36t-33.5 -20t-41 -6q-45 0 -74.5 26t-29.5 89zM462 1747q0 32 8 54t22 35.5t33 19.5t41 6t41 -6t33.5 -19.5t22.5 -35.5t8 -54q0 -31 -8 -53t-22.5 -36t-33.5 -20t-41 -6q-45 0 -74.5 26t-29.5 89z" />-<glyph glyph-name="Eth" unicode="Ð" horiz-adv-x="1489" d="M78 815h194v422q0 45 -13.5 72.5t-36.5 42.5t-53.5 19.5t-64.5 4.5h-26v86h627q158 0 282 -48.5t209.5 -141.5t130.5 -229t45 -310q0 -168 -42 -303.5t-125 -231t-208.5 -147t-291.5 -51.5h-627v86h26q35 0 65.5 5t53.5 20.5t36 44.5t13 77v482h-194v100zM657 104 q122 0 213.5 41.5t152.5 121.5t91.5 197t30.5 269t-30.5 269t-91 196.5t-152 120.5t-212.5 41h-180v-545h348v-100h-348v-611h178z" />-<glyph glyph-name="Ntilde" unicode="Ñ" horiz-adv-x="1563" d="M1165 0l-768 1180v-947q0 -48 13.5 -77t36 -44.5t53.5 -20.5t65 -5h27v-86h-514v86h26q35 0 65.5 5t53.5 20.5t36 44.5t13 77v1004q0 45 -13.5 72.5t-36.5 42.5t-53.5 19.5t-64.5 4.5h-26v86h375l733 -1132v907q0 45 -13.5 72.5t-36.5 42.5t-53.5 19.5t-64.5 4.5h-27v86 h514v-86h-26q-35 0 -65.5 -5t-53.5 -20.5t-36 -44.5t-13 -77v-1229h-146zM979 1712q30 0 48.5 9t29 24.5t15 35t7.5 40.5h92q-3 -47 -16.5 -90.5t-39.5 -77.5t-65 -54t-94 -20t-99.5 20t-83 44.5l-72 44.5t-66.5 20q-31 0 -49 -9t-28.5 -24t-15.5 -34.5t-8 -40.5h-92 q3 47 16.5 90t40 77t66 54t94.5 20t99.5 -20t83 -44.5l72 -44.5t65.5 -20z" />-<glyph glyph-name="Ograve" unicode="Ò" horiz-adv-x="1520" d="M1403 733q0 -169 -42 -308t-124 -238t-202 -153t-275 -54q-163 0 -284 54t-201 153t-120 238.5t-40 309.5t40 308.5t120 236.5t202 151.5t285 53.5q155 0 274 -53.5t201 -152t124 -237t42 -309.5zM342 733q0 -152 22.5 -271.5t72.5 -202t129.5 -126t193.5 -43.5 t193.5 43.5t129 126t71.5 202t22 271.5t-22 271.5t-71.5 201.5t-128 125t-192.5 43t-194 -43t-130.5 -125t-73 -201.5t-22.5 -271.5zM861 1579h-78q-41 28 -89 69.5t-93 85.5t-82 84.5t-56 67.5v21h219q16 -34 37.5 -74.5t45.5 -81t49 -78.5t47 -67v-27z" />-<glyph glyph-name="Oacute" unicode="Ó" horiz-adv-x="1520" d="M1403 733q0 -169 -42 -308t-124 -238t-202 -153t-275 -54q-163 0 -284 54t-201 153t-120 238.5t-40 309.5t40 308.5t120 236.5t202 151.5t285 53.5q155 0 274 -53.5t201 -152t124 -237t42 -309.5zM342 733q0 -152 22.5 -271.5t72.5 -202t129.5 -126t193.5 -43.5 t193.5 43.5t129 126t71.5 202t22 271.5t-22 271.5t-71.5 201.5t-128 125t-192.5 43t-194 -43t-130.5 -125t-73 -201.5t-22.5 -271.5zM649 1606q23 29 47.5 67t48 78.5l45 81t37.5 74.5h219v-21q-19 -27 -56 -67.5t-82 -84.5t-92.5 -85.5t-88.5 -69.5h-78v27z" />-<glyph glyph-name="Ocircumflex" unicode="Ô" horiz-adv-x="1520" d="M1403 733q0 -169 -42 -308t-124 -238t-202 -153t-275 -54q-163 0 -284 54t-201 153t-120 238.5t-40 309.5t40 308.5t120 236.5t202 151.5t285 53.5q155 0 274 -53.5t201 -152t124 -237t42 -309.5zM342 733q0 -152 22.5 -271.5t72.5 -202t129.5 -126t193.5 -43.5 t193.5 43.5t129 126t71.5 202t22 271.5t-22 271.5t-71.5 201.5t-128 125t-192.5 43t-194 -43t-130.5 -125t-73 -201.5t-22.5 -271.5zM459 1606l53 67l57.5 78.5t53.5 81t41 74.5h194q16 -34 41 -74.5t53.5 -81l57.5 -78.5l53 -67v-27h-80q-59 34 -118 84.5t-105 99.5 q-47 -49 -105 -99.5t-116 -84.5h-80v27z" />-<glyph glyph-name="Otilde" unicode="Õ" horiz-adv-x="1520" d="M1403 733q0 -169 -42 -308t-124 -238t-202 -153t-275 -54q-163 0 -284 54t-201 153t-120 238.5t-40 309.5t40 308.5t120 236.5t202 151.5t285 53.5q155 0 274 -53.5t201 -152t124 -237t42 -309.5zM342 733q0 -152 22.5 -271.5t72.5 -202t129.5 -126t193.5 -43.5 t193.5 43.5t129 126t71.5 202t22 271.5t-22 271.5t-71.5 201.5t-128 125t-192.5 43t-194 -43t-130.5 -125t-73 -201.5t-22.5 -271.5zM918 1712q30 0 48.5 9t29 24.5t15 35t7.5 40.5h92q-3 -47 -16.5 -90.5t-39.5 -77.5t-65 -54t-94 -20t-99.5 20t-83 44.5l-72 44.5t-66.5 20 q-31 0 -49 -9t-28.5 -24t-15.5 -34.5t-8 -40.5h-92q3 47 16.5 90t40 77t66 54t94.5 20t99.5 -20t83 -44.5l72 -44.5t65.5 -20z" />-<glyph glyph-name="Odieresis" unicode="Ö" horiz-adv-x="1520" d="M1403 733q0 -169 -42 -308t-124 -238t-202 -153t-275 -54q-163 0 -284 54t-201 153t-120 238.5t-40 309.5t40 308.5t120 236.5t202 151.5t285 53.5q155 0 274 -53.5t201 -152t124 -237t42 -309.5zM342 733q0 -152 22.5 -271.5t72.5 -202t129.5 -126t193.5 -43.5 t193.5 43.5t129 126t71.5 202t22 271.5t-22 271.5t-71.5 201.5t-128 125t-192.5 43t-194 -43t-130.5 -125t-73 -201.5t-22.5 -271.5zM455 1747q0 32 8 54t22 35.5t33 19.5t41 6t41 -6t33.5 -19.5t22.5 -35.5t8 -54q0 -31 -8 -53t-22.5 -36t-33.5 -20t-41 -6q-45 0 -74.5 26 t-29.5 89zM844 1747q0 32 8 54t22 35.5t33 19.5t41 6t41 -6t33.5 -19.5t22.5 -35.5t8 -54q0 -31 -8 -53t-22.5 -36t-33.5 -20t-41 -6q-45 0 -74.5 26t-29.5 89z" />-<glyph glyph-name="multiply" unicode="×" d="M571 647l-340 -340l-86 88l340 340l-340 338l86 86l340 -340l340 342l86 -88l-340 -340l340 -340l-86 -86z" />-<glyph glyph-name="Oslash" unicode="Ø" horiz-adv-x="1520" d="M1163 1354q118 -97 179 -255t61 -366q0 -169 -42 -308t-124 -238t-202 -153t-275 -54q-93 0 -172.5 17.5t-145.5 51.5l-71 -131h-121l104 188q-121 98 -180 258.5t-59 370.5q0 170 40 308.5t120 236.5t202 151.5t285 53.5q91 0 169 -19.5t144 -54.5l76 135h119zM342 733 q0 -155 22.5 -273t73.5 -202l572 1034q-94 82 -248 82q-114 0 -194 -43t-130.5 -125t-73 -201.5t-22.5 -271.5zM1176 733q0 153 -22.5 271.5t-72.5 201.5l-571 -1036q95 -80 250 -80q114 0 193.5 43.5t129 126t71.5 202t22 271.5z" />-<glyph glyph-name="Ugrave" unicode="Ù" horiz-adv-x="1468" d="M741 -20q-121 0 -217 25.5t-162.5 81t-101.5 143t-35 212.5v795q0 45 -13.5 72.5t-36.5 42.5t-53.5 19.5t-64.5 4.5h-26v86h596v-86h-27q-34 0 -65 -5t-53.5 -20.5t-36 -44.5t-13.5 -77v-799q0 -89 25.5 -151.5t71 -101.5t108 -57t137.5 -18q89 0 153.5 24t107 66.5 t63 101t20.5 128.5v815q0 45 -13.5 72.5t-36.5 42.5t-53.5 19.5t-64.5 4.5h-26v86h514v-86h-27q-34 0 -65 -5t-53.5 -20.5t-36 -44.5t-13.5 -77v-811q0 -102 -32.5 -183.5t-96 -138t-157.5 -86.5t-216 -30zM852 1579h-78q-41 28 -89 69.5t-93 85.5t-82 84.5t-56 67.5v21h219 q16 -34 37.5 -74.5t45.5 -81t49 -78.5t47 -67v-27z" />-<glyph glyph-name="Uacute" unicode="Ú" horiz-adv-x="1468" d="M741 -20q-121 0 -217 25.5t-162.5 81t-101.5 143t-35 212.5v795q0 45 -13.5 72.5t-36.5 42.5t-53.5 19.5t-64.5 4.5h-26v86h596v-86h-27q-34 0 -65 -5t-53.5 -20.5t-36 -44.5t-13.5 -77v-799q0 -89 25.5 -151.5t71 -101.5t108 -57t137.5 -18q89 0 153.5 24t107 66.5 t63 101t20.5 128.5v815q0 45 -13.5 72.5t-36.5 42.5t-53.5 19.5t-64.5 4.5h-26v86h514v-86h-27q-34 0 -65 -5t-53.5 -20.5t-36 -44.5t-13.5 -77v-811q0 -102 -32.5 -183.5t-96 -138t-157.5 -86.5t-216 -30zM649 1606q23 29 47.5 67t48 78.5l45 81t37.5 74.5h219v-21 q-19 -27 -56 -67.5t-82 -84.5t-92.5 -85.5t-88.5 -69.5h-78v27z" />-<glyph glyph-name="Ucircumflex" unicode="Û" horiz-adv-x="1468" d="M741 -20q-121 0 -217 25.5t-162.5 81t-101.5 143t-35 212.5v795q0 45 -13.5 72.5t-36.5 42.5t-53.5 19.5t-64.5 4.5h-26v86h596v-86h-27q-34 0 -65 -5t-53.5 -20.5t-36 -44.5t-13.5 -77v-799q0 -89 25.5 -151.5t71 -101.5t108 -57t137.5 -18q89 0 153.5 24t107 66.5 t63 101t20.5 128.5v815q0 45 -13.5 72.5t-36.5 42.5t-53.5 19.5t-64.5 4.5h-26v86h514v-86h-27q-34 0 -65 -5t-53.5 -20.5t-36 -44.5t-13.5 -77v-811q0 -102 -32.5 -183.5t-96 -138t-157.5 -86.5t-216 -30zM471 1606l53 67l57.5 78.5t53.5 81t41 74.5h194q16 -34 41 -74.5 t53.5 -81l57.5 -78.5l53 -67v-27h-80q-59 34 -118 84.5t-105 99.5q-47 -49 -105 -99.5t-116 -84.5h-80v27z" />-<glyph glyph-name="Udieresis" unicode="Ü" horiz-adv-x="1468" d="M741 -20q-121 0 -217 25.5t-162.5 81t-101.5 143t-35 212.5v795q0 45 -13.5 72.5t-36.5 42.5t-53.5 19.5t-64.5 4.5h-26v86h596v-86h-27q-34 0 -65 -5t-53.5 -20.5t-36 -44.5t-13.5 -77v-799q0 -89 25.5 -151.5t71 -101.5t108 -57t137.5 -18q89 0 153.5 24t107 66.5 t63 101t20.5 128.5v815q0 45 -13.5 72.5t-36.5 42.5t-53.5 19.5t-64.5 4.5h-26v86h514v-86h-27q-34 0 -65 -5t-53.5 -20.5t-36 -44.5t-13.5 -77v-811q0 -102 -32.5 -183.5t-96 -138t-157.5 -86.5t-216 -30zM477 1747q0 32 8 54t22 35.5t33 19.5t41 6t41 -6t33.5 -19.5 t22.5 -35.5t8 -54q0 -31 -8 -53t-22.5 -36t-33.5 -20t-41 -6q-45 0 -74.5 26t-29.5 89zM866 1747q0 32 8 54t22 35.5t33 19.5t41 6t41 -6t33.5 -19.5t22.5 -35.5t8 -54q0 -31 -8 -53t-22.5 -36t-33.5 -20t-41 -6q-45 0 -74.5 26t-29.5 89z" />-<glyph glyph-name="Yacute" unicode="Ý" horiz-adv-x="1280" d="M326 0v86h47q34 0 64.5 4.5t53.5 18.5t36.5 41.5t13.5 72.5v387l-379 658q-17 29 -32 49.5t-31.5 33.5t-36.5 19t-46 6h-26v86h563v-86h-76q-28 0 -45.5 -5t-27.5 -14t-13.5 -21t-3.5 -25q0 -29 12.5 -58t24.5 -51l151 -274q36 -63 61 -124.5t42 -109.5q9 22 22.5 49 l30 57.5l34.5 63l36 64.5l131 235q20 35 28.5 66.5t8.5 56.5q0 45 -32 67.5t-91 22.5h-60v86h500v-86h-24q-22 0 -41.5 -7.5t-39.5 -26t-41.5 -49t-47.5 -76.5l-344 -607v-377q0 -48 13 -77t36 -44.5t53.5 -20.5t64.5 -5h48v-86h-637zM567 1606q23 29 47.5 67t48 78.5l45 81 t37.5 74.5h219v-21q-19 -27 -56 -67.5t-82 -84.5t-92.5 -85.5t-88.5 -69.5h-78v27z" />-<glyph glyph-name="Thorn" unicode="Þ" horiz-adv-x="1237" d="M78 0v86h26q35 0 65.5 5t53.5 20.5t36 44.5t13 77v996q0 48 -13 77t-36 44.5t-53.5 20.5t-65.5 5h-26v86h616v-86h-47q-34 0 -65 -5t-53.5 -20.5t-36 -44.5t-13.5 -77v-62h174q258 0 381 -103.5t123 -303.5q0 -91 -28.5 -172t-93.5 -142t-170 -96t-257 -35h-129v-82 q0 -48 13.5 -77t36 -44.5t53.5 -20.5t65 -5h47v-86h-616zM479 412h109q93 0 159 16.5t108 56t61.5 105t19.5 162.5q0 86 -17 146t-55 98t-98.5 55.5t-147.5 17.5h-139v-657z" />-<glyph glyph-name="germandbls" unicode="ß" horiz-adv-x="1325" d="M887 78q75 0 123.5 46t48.5 140q0 34 -7.5 63t-29.5 56t-61 54t-101 57q-64 32 -113 63t-81.5 69t-49 86t-16.5 113q0 62 20.5 113t62 87.5t105 56.5t150.5 20q0 97 -14 166.5t-45 114.5t-79.5 66t-117.5 21q-62 0 -108.5 -20t-77.5 -56.5t-46.5 -88.5t-15.5 -117v-1188 h-387v86h27q35 0 65.5 5t53 20.5t36 44.5t13.5 77v768h-166v97h166v61q0 113 34 190t93.5 124.5t140 68.5t174.5 21q128 0 213 -36.5t136.5 -107t73.5 -174.5t23 -239h-149q-109 0 -160 -41.5t-51 -124.5q0 -31 10 -56.5t35 -49t66 -47t102 -52.5q77 -35 126 -74.5t77 -82.5 t38.5 -88t10.5 -91q0 -78 -24 -138t-68.5 -100t-108.5 -60.5t-145 -20.5q-72 0 -131 13.5t-101 41t-65 68t-23 94.5q0 41 12 68.5t30 43.5t38.5 22.5t36.5 6.5q0 -54 10.5 -101t34 -82.5t62 -56t94.5 -20.5z" />-<glyph glyph-name="agrave" unicode="à" horiz-adv-x="1153" d="M301 297q0 -96 40.5 -143.5t125.5 -47.5q62 0 112.5 20t86 57t54.5 90t19 118v166l-131 -6q-87 -4 -145.5 -21.5t-94.5 -49t-51.5 -77.5t-15.5 -106zM549 1016q-59 0 -95.5 -16.5t-57 -46.5t-27.5 -71t-7 -89q-85 0 -129.5 29t-44.5 100q0 53 29 90t79.5 60.5t117.5 34.5 t143 11q94 0 164 -18.5t117 -60.5t70.5 -109.5t23.5 -165.5v-531q0 -43 7 -71t22 -45t39.5 -24t58.5 -7h6v-86h-277l-32 176h-17q-32 -43 -62 -79t-66 -62t-81.5 -40.5t-109.5 -14.5q-68 0 -126.5 19.5t-101 60t-66.5 102t-24 145.5q0 163 116 242t351 86l170 6v123 q0 55 -6 101.5t-26 80t-57.5 52t-100.5 18.5zM667 1241h-78q-41 28 -89 69.5t-93 85.5t-82 84.5t-56 67.5v21h219q16 -34 37.5 -74.5t45.5 -81t49 -78.5t47 -67v-27z" />-<glyph glyph-name="aacute" unicode="á" horiz-adv-x="1153" d="M301 297q0 -96 40.5 -143.5t125.5 -47.5q62 0 112.5 20t86 57t54.5 90t19 118v166l-131 -6q-87 -4 -145.5 -21.5t-94.5 -49t-51.5 -77.5t-15.5 -106zM549 1016q-59 0 -95.5 -16.5t-57 -46.5t-27.5 -71t-7 -89q-85 0 -129.5 29t-44.5 100q0 53 29 90t79.5 60.5t117.5 34.5 t143 11q94 0 164 -18.5t117 -60.5t70.5 -109.5t23.5 -165.5v-531q0 -43 7 -71t22 -45t39.5 -24t58.5 -7h6v-86h-277l-32 176h-17q-32 -43 -62 -79t-66 -62t-81.5 -40.5t-109.5 -14.5q-68 0 -126.5 19.5t-101 60t-66.5 102t-24 145.5q0 163 116 242t351 86l170 6v123 q0 55 -6 101.5t-26 80t-57.5 52t-100.5 18.5zM471 1268q23 29 47.5 67t48 78.5l45 81t37.5 74.5h219v-21q-19 -27 -56 -67.5t-82 -84.5t-92.5 -85.5t-88.5 -69.5h-78v27z" />-<glyph glyph-name="acircumflex" unicode="â" horiz-adv-x="1153" d="M301 297q0 -96 40.5 -143.5t125.5 -47.5q62 0 112.5 20t86 57t54.5 90t19 118v166l-131 -6q-87 -4 -145.5 -21.5t-94.5 -49t-51.5 -77.5t-15.5 -106zM549 1016q-59 0 -95.5 -16.5t-57 -46.5t-27.5 -71t-7 -89q-85 0 -129.5 29t-44.5 100q0 53 29 90t79.5 60.5t117.5 34.5 t143 11q94 0 164 -18.5t117 -60.5t70.5 -109.5t23.5 -165.5v-531q0 -43 7 -71t22 -45t39.5 -24t58.5 -7h6v-86h-277l-32 176h-17q-32 -43 -62 -79t-66 -62t-81.5 -40.5t-109.5 -14.5q-68 0 -126.5 19.5t-101 60t-66.5 102t-24 145.5q0 163 116 242t351 86l170 6v123 q0 55 -6 101.5t-26 80t-57.5 52t-100.5 18.5zM261 1268l53 67l57.5 78.5t53.5 81t41 74.5h194q16 -34 41 -74.5t53.5 -81l57.5 -78.5l53 -67v-27h-80q-59 34 -118 84.5t-105 99.5q-47 -49 -105 -99.5t-116 -84.5h-80v27z" />-<glyph glyph-name="atilde" unicode="ã" horiz-adv-x="1153" d="M301 297q0 -96 40.5 -143.5t125.5 -47.5q62 0 112.5 20t86 57t54.5 90t19 118v166l-131 -6q-87 -4 -145.5 -21.5t-94.5 -49t-51.5 -77.5t-15.5 -106zM549 1016q-59 0 -95.5 -16.5t-57 -46.5t-27.5 -71t-7 -89q-85 0 -129.5 29t-44.5 100q0 53 29 90t79.5 60.5t117.5 34.5 t143 11q94 0 164 -18.5t117 -60.5t70.5 -109.5t23.5 -165.5v-531q0 -43 7 -71t22 -45t39.5 -24t58.5 -7h6v-86h-277l-32 176h-17q-32 -43 -62 -79t-66 -62t-81.5 -40.5t-109.5 -14.5q-68 0 -126.5 19.5t-101 60t-66.5 102t-24 145.5q0 163 116 242t351 86l170 6v123 q0 55 -6 101.5t-26 80t-57.5 52t-100.5 18.5zM743 1374q30 0 48.5 9t29 24.5t15 35t7.5 40.5h92q-3 -47 -16.5 -90.5t-39.5 -77.5t-65 -54t-94 -20t-99.5 20t-83 44.5l-72 44.5t-66.5 20q-31 0 -49 -9t-28.5 -24t-15.5 -34.5t-8 -40.5h-92q3 47 16.5 90t40 77t66 54t94.5 20 t99.5 -20t83 -44.5l72 -44.5t65.5 -20z" />-<glyph glyph-name="adieresis" unicode="ä" horiz-adv-x="1153" d="M301 297q0 -96 40.5 -143.5t125.5 -47.5q62 0 112.5 20t86 57t54.5 90t19 118v166l-131 -6q-87 -4 -145.5 -21.5t-94.5 -49t-51.5 -77.5t-15.5 -106zM549 1016q-59 0 -95.5 -16.5t-57 -46.5t-27.5 -71t-7 -89q-85 0 -129.5 29t-44.5 100q0 53 29 90t79.5 60.5t117.5 34.5 t143 11q94 0 164 -18.5t117 -60.5t70.5 -109.5t23.5 -165.5v-531q0 -43 7 -71t22 -45t39.5 -24t58.5 -7h6v-86h-277l-32 176h-17q-32 -43 -62 -79t-66 -62t-81.5 -40.5t-109.5 -14.5q-68 0 -126.5 19.5t-101 60t-66.5 102t-24 145.5q0 163 116 242t351 86l170 6v123 q0 55 -6 101.5t-26 80t-57.5 52t-100.5 18.5zM257 1409q0 32 8 54t22 35.5t33 19.5t41 6t41 -6t33.5 -19.5t22.5 -35.5t8 -54q0 -31 -8 -53t-22.5 -36t-33.5 -20t-41 -6q-45 0 -74.5 26t-29.5 89zM646 1409q0 32 8 54t22 35.5t33 19.5t41 6t41 -6t33.5 -19.5t22.5 -35.5 t8 -54q0 -31 -8 -53t-22.5 -36t-33.5 -20t-41 -6q-45 0 -74.5 26t-29.5 89z" />-<glyph glyph-name="aring" unicode="å" horiz-adv-x="1153" d="M708 1444q0 33 -10.5 56.5t-29 38t-43 21.5t-52.5 7t-52.5 -7t-43 -21.5t-29 -38t-10.5 -56.5t10.5 -56.5t29 -38t43 -21.5t52.5 -7t52.5 7t43 21.5t29 38t10.5 56.5zM819 1444q0 -55 -19 -96.5t-52.5 -69.5t-78.5 -42.5t-96 -14.5t-96 14.5t-78.5 42.5t-52.5 69.5 t-19 96.5q0 54 19 96t52.5 70t78.5 42.5t96 14.5t96 -14.5t78.5 -42.5t52.5 -70t19 -96zM301 297q0 -96 40.5 -143.5t125.5 -47.5q62 0 112.5 20t86 57t54.5 90t19 118v166l-131 -6q-87 -4 -145.5 -21.5t-94.5 -49t-51.5 -77.5t-15.5 -106zM549 1016q-59 0 -95.5 -16.5 t-57 -46.5t-27.5 -71t-7 -89q-85 0 -129.5 29t-44.5 100q0 53 29 90t79.5 60.5t117.5 34.5t143 11q94 0 164 -18.5t117 -60.5t70.5 -109.5t23.5 -165.5v-531q0 -43 7 -71t22 -45t39.5 -24t58.5 -7h6v-86h-277l-32 176h-17q-32 -43 -62 -79t-66 -62t-81.5 -40.5t-109.5 -14.5 q-68 0 -126.5 19.5t-101 60t-66.5 102t-24 145.5q0 163 116 242t351 86l170 6v123q0 55 -6 101.5t-26 80t-57.5 52t-100.5 18.5z" />-<glyph glyph-name="ae" unicode="æ" horiz-adv-x="1722" d="M301 297q0 -96 40.5 -143.5t125.5 -47.5q62 0 112.5 20t86 57t54.5 90t19 118v123v27v16l-131 -6q-87 -4 -145.5 -21.5t-94.5 -49t-51.5 -77.5t-15.5 -106zM1190 1008q-115 0 -176 -92.5t-72 -270.5h471q0 81 -12 148t-38 115t-68.5 74t-104.5 26zM1221 -20 q-150 0 -254.5 59t-161.5 174q-19 -46 -53 -88t-82.5 -74.5t-111 -51.5t-138.5 -19q-68 0 -126.5 19.5t-101 60t-66.5 102t-24 145.5q0 163 116 242t351 86l170 6v123q0 55 -6 101.5t-26 80t-57.5 52t-100.5 18.5q-59 0 -95.5 -16.5t-57 -46.5t-27.5 -71t-7 -89 q-85 0 -129.5 29t-44.5 100q0 53 29 90t79.5 60.5t117.5 34.5t143 11q116 0 195.5 -29.5t124.5 -95.5q114 125 317 125q99 0 178 -31t134 -92.5t84.5 -153t29.5 -212.5v-94h-682q2 -112 21.5 -192.5t58 -132.5t97.5 -76.5t140 -24.5q45 0 87 11t78.5 30.5t66 45.5t49.5 56 q14 -6 26.5 -25t12.5 -45q0 -31 -22 -66.5t-66 -65.5t-110.5 -50t-155.5 -20z" />-<glyph glyph-name="ccedilla" unicode="ç" horiz-adv-x="1008" d="M580 -20q-102 0 -188 31.5t-148 99.5t-96.5 174.5t-34.5 257.5q0 164 34.5 274.5t96 177.5t145 95t180.5 28q64 0 127.5 -12.5t114.5 -38.5t83 -65.5t32 -92.5q0 -71 -46 -100t-137 -29q0 48 -8 91t-27.5 75.5t-53 51t-85.5 18.5q-59 0 -106.5 -22t-81.5 -76t-52 -145 t-18 -228q0 -218 72.5 -325t237.5 -107q95 0 166 40t106 103q15 -12 25 -32t10 -48q0 -35 -22 -70t-65.5 -63t-108.5 -45.5t-152 -17.5zM784 -258q0 -109 -69.5 -171.5t-200.5 -62.5q-16 0 -36 1.5t-41 4.5t-42 7.5t-39 9.5v104q37 -8 74 -12t65 -4q59 0 92 31t33 84 q0 60 -43.5 90t-110.5 35l43 159h97l-21 -88q100 -8 149.5 -57.5t49.5 -130.5z" />-<glyph glyph-name="egrave" unicode="è" horiz-adv-x="1096" d="M563 1008q-114 0 -175.5 -92.5t-72.5 -270.5h471q0 81 -12 148t-38 115t-68.5 74t-104.5 26zM588 -20q-111 0 -199.5 37.5t-149.5 109.5t-93.5 176.5t-32.5 237.5q0 287 118 432t336 145q99 0 178 -31t134 -92.5t84.5 -153t29.5 -212.5v-94h-682q2 -112 22.5 -192.5 t59 -132.5t94.5 -76.5t129 -24.5q53 0 98.5 12t82.5 32t65.5 45.5t46.5 53.5q14 -6 26.5 -25t12.5 -45q0 -31 -22 -66.5t-67 -65.5t-112.5 -50t-158.5 -20zM683 1241h-78q-41 28 -89 69.5t-93 85.5t-82 84.5t-56 67.5v21h219q16 -34 37.5 -74.5t45.5 -81t49 -78.5t47 -67 v-27z" />-<glyph glyph-name="eacute" unicode="é" horiz-adv-x="1096" d="M563 1008q-114 0 -175.5 -92.5t-72.5 -270.5h471q0 81 -12 148t-38 115t-68.5 74t-104.5 26zM588 -20q-111 0 -199.5 37.5t-149.5 109.5t-93.5 176.5t-32.5 237.5q0 287 118 432t336 145q99 0 178 -31t134 -92.5t84.5 -153t29.5 -212.5v-94h-682q2 -112 22.5 -192.5 t59 -132.5t94.5 -76.5t129 -24.5q53 0 98.5 12t82.5 32t65.5 45.5t46.5 53.5q14 -6 26.5 -25t12.5 -45q0 -31 -22 -66.5t-67 -65.5t-112.5 -50t-158.5 -20zM473 1268q23 29 47.5 67t48 78.5l45 81t37.5 74.5h219v-21q-19 -27 -56 -67.5t-82 -84.5t-92.5 -85.5t-88.5 -69.5 h-78v27z" />-<glyph glyph-name="ecircumflex" unicode="ê" horiz-adv-x="1096" d="M563 1008q-114 0 -175.5 -92.5t-72.5 -270.5h471q0 81 -12 148t-38 115t-68.5 74t-104.5 26zM588 -20q-111 0 -199.5 37.5t-149.5 109.5t-93.5 176.5t-32.5 237.5q0 287 118 432t336 145q99 0 178 -31t134 -92.5t84.5 -153t29.5 -212.5v-94h-682q2 -112 22.5 -192.5 t59 -132.5t94.5 -76.5t129 -24.5q53 0 98.5 12t82.5 32t65.5 45.5t46.5 53.5q14 -6 26.5 -25t12.5 -45q0 -31 -22 -66.5t-67 -65.5t-112.5 -50t-158.5 -20zM255 1268l53 67l57.5 78.5t53.5 81t41 74.5h194q16 -34 41 -74.5t53.5 -81l57.5 -78.5l53 -67v-27h-80 q-59 34 -118 84.5t-105 99.5q-47 -49 -105 -99.5t-116 -84.5h-80v27z" />-<glyph glyph-name="edieresis" unicode="ë" horiz-adv-x="1096" d="M563 1008q-114 0 -175.5 -92.5t-72.5 -270.5h471q0 81 -12 148t-38 115t-68.5 74t-104.5 26zM588 -20q-111 0 -199.5 37.5t-149.5 109.5t-93.5 176.5t-32.5 237.5q0 287 118 432t336 145q99 0 178 -31t134 -92.5t84.5 -153t29.5 -212.5v-94h-682q2 -112 22.5 -192.5 t59 -132.5t94.5 -76.5t129 -24.5q53 0 98.5 12t82.5 32t65.5 45.5t46.5 53.5q14 -6 26.5 -25t12.5 -45q0 -31 -22 -66.5t-67 -65.5t-112.5 -50t-158.5 -20zM253 1409q0 32 8 54t22 35.5t33 19.5t41 6t41 -6t33.5 -19.5t22.5 -35.5t8 -54q0 -31 -8 -53t-22.5 -36t-33.5 -20 t-41 -6q-45 0 -74.5 26t-29.5 89zM642 1409q0 32 8 54t22 35.5t33 19.5t41 6t41 -6t33.5 -19.5t22.5 -35.5t8 -54q0 -31 -8 -53t-22.5 -36t-33.5 -20t-41 -6q-45 0 -74.5 26t-29.5 89z" />-<glyph glyph-name="igrave" unicode="ì" horiz-adv-x="655" d="M74 86q34 0 64.5 4.5t53.5 18.5t36.5 41.5t13.5 72.5v649q0 45 -13.5 72.5t-36.5 42.5t-53.5 20t-64.5 5h-6v86h366v-865q0 -48 13.5 -77t36 -44.5t53.5 -20.5t65 -5h27v-86h-582v86h27zM436 1241h-78q-41 28 -89 69.5t-93 85.5t-82 84.5t-56 67.5v21h219 q16 -34 37.5 -74.5t45.5 -81t49 -78.5t47 -67v-27z" />-<glyph glyph-name="iacute" unicode="í" horiz-adv-x="655" d="M74 86q34 0 64.5 4.5t53.5 18.5t36.5 41.5t13.5 72.5v649q0 45 -13.5 72.5t-36.5 42.5t-53.5 20t-64.5 5h-6v86h366v-865q0 -48 13.5 -77t36 -44.5t53.5 -20.5t65 -5h27v-86h-582v86h27zM240 1268q23 29 47.5 67t48 78.5l45 81t37.5 74.5h219v-21q-19 -27 -56 -67.5 t-82 -84.5t-92.5 -85.5t-88.5 -69.5h-78v27z" />-<glyph glyph-name="icircumflex" unicode="î" horiz-adv-x="655" d="M74 86q34 0 64.5 4.5t53.5 18.5t36.5 41.5t13.5 72.5v649q0 45 -13.5 72.5t-36.5 42.5t-53.5 20t-64.5 5h-6v86h366v-865q0 -48 13.5 -77t36 -44.5t53.5 -20.5t65 -5h27v-86h-582v86h27zM22 1268l53 67l57.5 78.5t53.5 81t41 74.5h194q16 -34 41 -74.5t53.5 -81 l57.5 -78.5l53 -67v-27h-80q-59 34 -118 84.5t-105 99.5q-47 -49 -105 -99.5t-116 -84.5h-80v27z" />-<glyph glyph-name="idieresis" unicode="ï" horiz-adv-x="655" d="M74 86q34 0 64.5 4.5t53.5 18.5t36.5 41.5t13.5 72.5v649q0 45 -13.5 72.5t-36.5 42.5t-53.5 20t-64.5 5h-6v86h366v-865q0 -48 13.5 -77t36 -44.5t53.5 -20.5t65 -5h27v-86h-582v86h27zM18 1409q0 32 8 54t22 35.5t33 19.5t41 6t41 -6t33.5 -19.5t22.5 -35.5t8 -54 q0 -31 -8 -53t-22.5 -36t-33.5 -20t-41 -6q-45 0 -74.5 26t-29.5 89zM407 1409q0 32 8 54t22 35.5t33 19.5t41 6t41 -6t33.5 -19.5t22.5 -35.5t8 -54q0 -31 -8 -53t-22.5 -36t-33.5 -20t-41 -6q-45 0 -74.5 26t-29.5 89z" />-<glyph glyph-name="eth" unicode="ð" horiz-adv-x="1182" d="M307 1559q94 -23 184 -60.5t171 -91.5l264 154v-117l-181 -103q72 -60 132 -137.5t102.5 -171.5t66 -203.5t23.5 -232.5q0 -312 -122.5 -464t-358.5 -152q-111 0 -199 32.5t-149.5 98.5t-94 165.5t-32.5 233.5q0 266 121.5 396t359.5 130q68 0 126 -14t109 -39 q-21 68 -74.5 143.5t-131.5 143.5l-256 -146v115l172 98q-51 34 -109 62.5t-123 48.5v111zM311 510q0 -105 15 -184t48.5 -132.5t87 -80.5t130.5 -27t130 27t86 80.5t47.5 132.5t14.5 184q0 104 -15 182.5t-48 131t-86.5 78.5t-130.5 26t-130 -26t-86 -78.5t-48 -131 t-15 -182.5z" />-<glyph glyph-name="ntilde" unicode="ñ" horiz-adv-x="1321" d="M608 86v-86h-551v86h17q35 0 65.5 5t53 20.5t36 44.5t13.5 77v639q0 45 -13.5 72.5t-36.5 42.5t-53.5 20t-64.5 5h-6v86h331l27 -166h10q32 57 67.5 93.5t75 57t84 28t93.5 7.5q81 0 144.5 -23.5t108 -72t68 -124.5t23.5 -181v-484q0 -48 11.5 -77t32.5 -44.5t50 -20.5 t63 -5h7v-86h-357v707q0 67 -12 119t-38.5 88t-69.5 54.5t-105 18.5q-70 0 -117.5 -27t-76.5 -73t-41.5 -106t-12.5 -126v-432q0 -45 13.5 -72.5t36.5 -41.5t53.5 -18.5t64.5 -4.5h6zM842 1374q30 0 48.5 9t29 24.5t15 35t7.5 40.5h92q-3 -47 -16.5 -90.5t-39.5 -77.5 t-65 -54t-94 -20t-99.5 20t-83 44.5l-72 44.5t-66.5 20q-31 0 -49 -9t-28.5 -24t-15.5 -34.5t-8 -40.5h-92q3 47 16.5 90t40 77t66 54t94.5 20t99.5 -20t83 -44.5l72 -44.5t65.5 -20z" />-<glyph glyph-name="ograve" unicode="ò" horiz-adv-x="1182" d="M1069 551q0 -289 -122.5 -430t-358.5 -141q-111 0 -199 35t-149.5 106t-94 178.5t-32.5 251.5q0 287 121.5 427t359.5 140q111 0 199 -34.5t149.5 -105t94 -177t32.5 -250.5zM311 551q0 -114 15 -201t48.5 -145.5t87 -88.5t130.5 -30t130 30t86 88.5t47.5 145.5t14.5 201 t-15 200t-48 143.5t-86.5 86.5t-130.5 29t-130 -29t-86 -86.5t-48 -143.5t-15 -200zM696 1241h-78q-41 28 -89 69.5t-93 85.5t-82 84.5t-56 67.5v21h219q16 -34 37.5 -74.5t45.5 -81t49 -78.5t47 -67v-27z" />-<glyph glyph-name="oacute" unicode="ó" horiz-adv-x="1182" d="M1069 551q0 -289 -122.5 -430t-358.5 -141q-111 0 -199 35t-149.5 106t-94 178.5t-32.5 251.5q0 287 121.5 427t359.5 140q111 0 199 -34.5t149.5 -105t94 -177t32.5 -250.5zM311 551q0 -114 15 -201t48.5 -145.5t87 -88.5t130.5 -30t130 30t86 88.5t47.5 145.5t14.5 201 t-15 200t-48 143.5t-86.5 86.5t-130.5 29t-130 -29t-86 -86.5t-48 -143.5t-15 -200zM512 1268q23 29 47.5 67t48 78.5l45 81t37.5 74.5h219v-21q-19 -27 -56 -67.5t-82 -84.5t-92.5 -85.5t-88.5 -69.5h-78v27z" />-<glyph glyph-name="ocircumflex" unicode="ô" horiz-adv-x="1182" d="M1069 551q0 -289 -122.5 -430t-358.5 -141q-111 0 -199 35t-149.5 106t-94 178.5t-32.5 251.5q0 287 121.5 427t359.5 140q111 0 199 -34.5t149.5 -105t94 -177t32.5 -250.5zM311 551q0 -114 15 -201t48.5 -145.5t87 -88.5t130.5 -30t130 30t86 88.5t47.5 145.5t14.5 201 t-15 200t-48 143.5t-86.5 86.5t-130.5 29t-130 -29t-86 -86.5t-48 -143.5t-15 -200zM284 1268l53 67l57.5 78.5t53.5 81t41 74.5h194q16 -34 41 -74.5t53.5 -81l57.5 -78.5l53 -67v-27h-80q-59 34 -118 84.5t-105 99.5q-47 -49 -105 -99.5t-116 -84.5h-80v27z" />-<glyph glyph-name="otilde" unicode="õ" horiz-adv-x="1182" d="M1069 551q0 -289 -122.5 -430t-358.5 -141q-111 0 -199 35t-149.5 106t-94 178.5t-32.5 251.5q0 287 121.5 427t359.5 140q111 0 199 -34.5t149.5 -105t94 -177t32.5 -250.5zM311 551q0 -114 15 -201t48.5 -145.5t87 -88.5t130.5 -30t130 30t86 88.5t47.5 145.5t14.5 201 t-15 200t-48 143.5t-86.5 86.5t-130.5 29t-130 -29t-86 -86.5t-48 -143.5t-15 -200zM757 1374q30 0 48.5 9t29 24.5t15 35t7.5 40.5h92q-3 -47 -16.5 -90.5t-39.5 -77.5t-65 -54t-94 -20t-99.5 20t-83 44.5l-72 44.5t-66.5 20q-31 0 -49 -9t-28.5 -24t-15.5 -34.5t-8 -40.5 h-92q3 47 16.5 90t40 77t66 54t94.5 20t99.5 -20t83 -44.5l72 -44.5t65.5 -20z" />-<glyph glyph-name="odieresis" unicode="ö" horiz-adv-x="1182" d="M1069 551q0 -289 -122.5 -430t-358.5 -141q-111 0 -199 35t-149.5 106t-94 178.5t-32.5 251.5q0 287 121.5 427t359.5 140q111 0 199 -34.5t149.5 -105t94 -177t32.5 -250.5zM311 551q0 -114 15 -201t48.5 -145.5t87 -88.5t130.5 -30t130 30t86 88.5t47.5 145.5t14.5 201 t-15 200t-48 143.5t-86.5 86.5t-130.5 29t-130 -29t-86 -86.5t-48 -143.5t-15 -200zM284 1409q0 32 8 54t22 35.5t33 19.5t41 6t41 -6t33.5 -19.5t22.5 -35.5t8 -54q0 -31 -8 -53t-22.5 -36t-33.5 -20t-41 -6q-45 0 -74.5 26t-29.5 89zM673 1409q0 32 8 54t22 35.5t33 19.5 t41 6t41 -6t33.5 -19.5t22.5 -35.5t8 -54q0 -31 -8 -53t-22.5 -36t-33.5 -20t-41 -6q-45 0 -74.5 26t-29.5 89z" />-<glyph glyph-name="divide" unicode="÷" d="M1012 672h-879v121h879v-121zM469 1120q0 32 8 54t22 35t33 18.5t41 5.5t41 -5.5t33.5 -18.5t22.5 -35t8 -54q0 -31 -8 -52.5t-22.5 -35t-33.5 -19t-41 -5.5t-41 5.5t-33 19t-22 35t-8 52.5zM469 344q0 32 8 54t22 35t33 18.5t41 5.5t41 -5.5t33.5 -18.5t22.5 -35t8 -54 q0 -31 -8 -53t-22.5 -35t-33.5 -19t-41 -6q-45 0 -74.5 25t-29.5 88z" />-<glyph glyph-name="oslash" unicode="ø" horiz-adv-x="1182" d="M930 991q68 -69 103.5 -178.5t35.5 -261.5q0 -289 -122.5 -430t-358.5 -141q-71 0 -133 14t-113 43l-74 -119h-121l111 180q-71 72 -108 183.5t-37 269.5q0 287 121.5 427t359.5 140q74 0 137.5 -16t116.5 -47l78 125h118zM311 551q0 -94 10 -169t31 -132l424 686 q-68 74 -186 74q-77 0 -130 -29t-86 -86.5t-48 -143.5t-15 -200zM870 551q0 177 -36 285l-422 -684q32 -32 76.5 -49t103.5 -17q77 0 130 30t86 88.5t47.5 145.5t14.5 201z" />-<glyph glyph-name="ugrave" unicode="ù" horiz-adv-x="1300" d="M1079 223q0 -45 13.5 -72.5t36.5 -41.5t53.5 -18.5t64.5 -4.5h6v-86h-325l-27 166h-10q-31 -58 -68.5 -94t-79.5 -56.5t-88 -28t-96 -7.5q-81 0 -144 23t-106 72t-65.5 125t-22.5 181v491q0 45 -13.5 72.5t-36.5 42.5t-53.5 20t-64.5 5h-6v86h367v-707q0 -67 10.5 -119 t35 -88t66 -54.5t103.5 -18.5q68 0 116.5 24t80 67.5t46.5 104.5t15 135v422q0 48 -13.5 77.5t-36 45t-53 20.5t-65.5 5h-6v86h366v-875zM712 1241h-78q-41 28 -89 69.5t-93 85.5t-82 84.5t-56 67.5v21h219q16 -34 37.5 -74.5t45.5 -81t49 -78.5t47 -67v-27z" />-<glyph glyph-name="uacute" unicode="ú" horiz-adv-x="1300" d="M1079 223q0 -45 13.5 -72.5t36.5 -41.5t53.5 -18.5t64.5 -4.5h6v-86h-325l-27 166h-10q-31 -58 -68.5 -94t-79.5 -56.5t-88 -28t-96 -7.5q-81 0 -144 23t-106 72t-65.5 125t-22.5 181v491q0 45 -13.5 72.5t-36.5 42.5t-53.5 20t-64.5 5h-6v86h367v-707q0 -67 10.5 -119 t35 -88t66 -54.5t103.5 -18.5q68 0 116.5 24t80 67.5t46.5 104.5t15 135v422q0 48 -13.5 77.5t-36 45t-53 20.5t-65.5 5h-6v86h366v-875zM541 1268q23 29 47.5 67t48 78.5l45 81t37.5 74.5h219v-21q-19 -27 -56 -67.5t-82 -84.5t-92.5 -85.5t-88.5 -69.5h-78v27z" />-<glyph glyph-name="ucircumflex" unicode="û" horiz-adv-x="1300" d="M1079 223q0 -45 13.5 -72.5t36.5 -41.5t53.5 -18.5t64.5 -4.5h6v-86h-325l-27 166h-10q-31 -58 -68.5 -94t-79.5 -56.5t-88 -28t-96 -7.5q-81 0 -144 23t-106 72t-65.5 125t-22.5 181v491q0 45 -13.5 72.5t-36.5 42.5t-53.5 20t-64.5 5h-6v86h367v-707q0 -67 10.5 -119 t35 -88t66 -54.5t103.5 -18.5q68 0 116.5 24t80 67.5t46.5 104.5t15 135v422q0 48 -13.5 77.5t-36 45t-53 20.5t-65.5 5h-6v86h366v-875zM342 1268l53 67l57.5 78.5t53.5 81t41 74.5h194q16 -34 41 -74.5t53.5 -81l57.5 -78.5l53 -67v-27h-80q-59 34 -118 84.5t-105 99.5 q-47 -49 -105 -99.5t-116 -84.5h-80v27z" />-<glyph glyph-name="udieresis" unicode="ü" horiz-adv-x="1300" d="M1079 223q0 -45 13.5 -72.5t36.5 -41.5t53.5 -18.5t64.5 -4.5h6v-86h-325l-27 166h-10q-31 -58 -68.5 -94t-79.5 -56.5t-88 -28t-96 -7.5q-81 0 -144 23t-106 72t-65.5 125t-22.5 181v491q0 45 -13.5 72.5t-36.5 42.5t-53.5 20t-64.5 5h-6v86h367v-707q0 -67 10.5 -119 t35 -88t66 -54.5t103.5 -18.5q68 0 116.5 24t80 67.5t46.5 104.5t15 135v422q0 48 -13.5 77.5t-36 45t-53 20.5t-65.5 5h-6v86h366v-875zM348 1409q0 32 8 54t22 35.5t33 19.5t41 6t41 -6t33.5 -19.5t22.5 -35.5t8 -54q0 -31 -8 -53t-22.5 -36t-33.5 -20t-41 -6 q-45 0 -74.5 26t-29.5 89zM737 1409q0 32 8 54t22 35.5t33 19.5t41 6t41 -6t33.5 -19.5t22.5 -35.5t8 -54q0 -31 -8 -53t-22.5 -36t-33.5 -20t-41 -6q-45 0 -74.5 26t-29.5 89z" />-<glyph glyph-name="yacute" unicode="ý" horiz-adv-x="1157" d="M1157 1098v-86h-6q-30 0 -51.5 -6.5t-39 -24.5t-32.5 -49t-33 -80l-311 -860q-36 -99 -68 -174t-67 -128.5t-76.5 -88.5t-94.5 -56t-122 -29t-160 -8h-18v97q104 0 179 31t129.5 85t91 127.5t61.5 159.5l-379 895q-14 32 -27 52.5t-29.5 33.5t-38 18t-51.5 5h-6v86h492 v-86h-6q-62 0 -92.5 -24t-30.5 -75q0 -17 3.5 -34.5t12.5 -40.5l150 -365l29.5 -76.5t29 -82l24.5 -76.5t15 -62h6q11 45 30.5 109.5t45.5 138.5l137 399q9 25 13.5 48t4.5 40q0 53 -33.5 77t-101.5 24h-6v86h426zM504 1268q23 29 47.5 67t48 78.5l45 81t37.5 74.5h219v-21 q-19 -27 -56 -67.5t-82 -84.5t-92.5 -85.5t-88.5 -69.5h-78v27z" />-<glyph glyph-name="thorn" unicode="þ" horiz-adv-x="1257" d="M692 987q-80 0 -133.5 -26.5t-85.5 -80.5t-45.5 -136t-13.5 -193q0 -107 13.5 -189t46 -138t86 -84.5t134.5 -28.5q67 0 115 28.5t78.5 84.5t44.5 138.5t14 190.5q0 218 -59 326t-195 108zM1145 551q0 -148 -27.5 -256t-80.5 -178t-131 -103.5t-179 -33.5q-59 0 -107 13 t-86.5 37t-68 57t-51.5 73h-8q2 -53 4 -99q1 -20 1.5 -40t1 -38t1 -32.5t0.5 -22.5v-196q0 -45 13.5 -72.5t36.5 -42t53.5 -19t64.5 -4.5h6v-86h-551v86h16q34 0 65 5t53.5 20.5t36 45t13.5 77.5v1589q0 45 -13.5 72.5t-36.5 42.5t-53.5 19.5t-64.5 4.5h-16v86h377v-376 q0 -35 -1 -80.5t-3 -87.5l-4 -99h8q23 47 52 85t67 64.5t86 41t108 14.5q101 0 179 -33.5t131 -103t80.5 -176.5t27.5 -254z" />-<glyph glyph-name="ydieresis" unicode="ÿ" horiz-adv-x="1157" d="M1157 1098v-86h-6q-30 0 -51.5 -6.5t-39 -24.5t-32.5 -49t-33 -80l-311 -860q-36 -99 -68 -174t-67 -128.5t-76.5 -88.5t-94.5 -56t-122 -29t-160 -8h-18v97q104 0 179 31t129.5 85t91 127.5t61.5 159.5l-379 895q-14 32 -27 52.5t-29.5 33.5t-38 18t-51.5 5h-6v86h492 v-86h-6q-62 0 -92.5 -24t-30.5 -75q0 -17 3.5 -34.5t12.5 -40.5l150 -365l29.5 -76.5t29 -82l24.5 -76.5t15 -62h6q11 45 30.5 109.5t45.5 138.5l137 399q9 25 13.5 48t4.5 40q0 53 -33.5 77t-101.5 24h-6v86h426zM320 1409q0 32 8 54t22 35.5t33 19.5t41 6t41 -6 t33.5 -19.5t22.5 -35.5t8 -54q0 -31 -8 -53t-22.5 -36t-33.5 -20t-41 -6q-45 0 -74.5 26t-29.5 89zM709 1409q0 32 8 54t22 35.5t33 19.5t41 6t41 -6t33.5 -19.5t22.5 -35.5t8 -54q0 -31 -8 -53t-22.5 -36t-33.5 -20t-41 -6q-45 0 -74.5 26t-29.5 89z" />-<glyph glyph-name="OE" unicode="Œ" horiz-adv-x="1964" d="M1167 102h396q48 0 82.5 13.5t57.5 36.5t36 53.5t18 64.5l15 88h106l-14 -358h-893q-21 -5 -47.5 -8.5t-55 -6t-56.5 -4t-52 -1.5q-163 0 -284 54t-201 153t-120 238.5t-40 309.5t40 308.5t120 236.5t202 151.5t285 53.5q24 0 52.5 -1.5t57 -4.5t54.5 -7t47 -10h825 l10 -358h-106l-10 88q-4 34 -15.5 64.5t-33 53.5t-54 36.5t-78.5 13.5h-344v-545h494v-100h-494v-613zM760 90q60 0 110 8.5t91 22.5v1218q-41 17 -90 26t-109 9q-114 0 -194 -43t-130.5 -125t-73 -201.5t-22.5 -271.5t22.5 -271.5t72.5 -202t129.5 -126t193.5 -43.5z" />-<glyph glyph-name="oe" unicode="œ" horiz-adv-x="1853" d="M311 551q0 -114 15 -201t48.5 -145.5t87 -88.5t130.5 -30q76 0 128.5 29t85.5 86.5t48 142.5t16 197v28q-1 110 -17 192.5t-49 138t-86 83t-128 27.5q-77 0 -130 -29t-86 -86.5t-48 -143.5t-15 -200zM1321 1008q-115 0 -176 -92.5t-72 -270.5h471q0 81 -12 148t-38 115 t-68.5 74t-104.5 26zM1346 -20q-130 0 -221.5 52t-151.5 152q-119 -204 -385 -204q-111 0 -199 35t-149.5 106t-94 178.5t-32.5 251.5q0 287 121.5 427t359.5 140q124 0 217.5 -49t155.5 -149q58 100 147.5 149t210.5 49q99 0 178 -31t134 -92.5t84.5 -153t29.5 -212.5v-94 h-682q2 -99 19 -177.5t53 -134t93 -85t140 -29.5q53 0 98.5 12t82.5 32t65.5 45.5t46.5 53.5q14 -6 26.5 -25t12.5 -45q0 -31 -22 -66.5t-67 -65.5t-112.5 -50t-158.5 -20z" />-<glyph glyph-name="Ydieresis" unicode="Ÿ" horiz-adv-x="1280" d="M326 0v86h47q34 0 64.5 4.5t53.5 18.5t36.5 41.5t13.5 72.5v387l-379 658q-17 29 -32 49.5t-31.5 33.5t-36.5 19t-46 6h-26v86h563v-86h-76q-28 0 -45.5 -5t-27.5 -14t-13.5 -21t-3.5 -25q0 -29 12.5 -58t24.5 -51l151 -274q36 -63 61 -124.5t42 -109.5q9 22 22.5 49 l30 57.5l34.5 63l36 64.5l131 235q20 35 28.5 66.5t8.5 56.5q0 45 -32 67.5t-91 22.5h-60v86h500v-86h-24q-22 0 -41.5 -7.5t-39.5 -26t-41.5 -49t-47.5 -76.5l-344 -607v-377q0 -48 13 -77t36 -44.5t53.5 -20.5t64.5 -5h48v-86h-637zM359 1747q0 32 8 54t22 35.5t33 19.5 t41 6t41 -6t33.5 -19.5t22.5 -35.5t8 -54q0 -31 -8 -53t-22.5 -36t-33.5 -20t-41 -6q-45 0 -74.5 26t-29.5 89zM748 1747q0 32 8 54t22 35.5t33 19.5t41 6t41 -6t33.5 -19.5t22.5 -35.5t8 -54q0 -31 -8 -53t-22.5 -36t-33.5 -20t-41 -6q-45 0 -74.5 26t-29.5 89z" />-<glyph glyph-name="circumflex" unicode="ˆ" horiz-adv-x="1182" d="M289 1268l53 67l57.5 78.5t53.5 81t41 74.5h194q16 -34 41 -74.5t53.5 -81l57.5 -78.5l53 -67v-27h-80q-59 34 -118 84.5t-105 99.5q-47 -49 -105 -99.5t-116 -84.5h-80v27z" />-<glyph glyph-name="tilde" unicode="˜" horiz-adv-x="1182" d="M762 1374q30 0 48.5 9t29 24.5t15 35t7.5 40.5h92q-3 -47 -16.5 -90.5t-39.5 -77.5t-65 -54t-94 -20t-99.5 20t-83 44.5l-72 44.5t-66.5 20q-31 0 -49 -9t-28.5 -24t-15.5 -34.5t-8 -40.5h-92q3 47 16.5 90t40 77t66 54t94.5 20t99.5 -20t83 -44.5l72 -44.5t65.5 -20z " />-<glyph glyph-name="endash" unicode="–" horiz-adv-x="1024" d="M1034 489h-1044v121h1044v-121z" />-<glyph glyph-name="emdash" unicode="—" horiz-adv-x="2048" d="M2058 489h-2068v121h2068v-121z" />-<glyph glyph-name="quoteleft" unicode="‘" horiz-adv-x="512" d="M115 1063q0 65 18 126t58.5 113t104.5 93.5t155 68.5v-86q-101 -32 -148 -78t-47 -106q0 -20 11 -33.5t28 -24t36 -20.5t36 -24t28 -34.5t11 -51.5q0 -59 -38.5 -90t-95.5 -31q-31 0 -59.5 11.5t-50.5 34t-34.5 56t-12.5 76.5z" />-<glyph glyph-name="quoteright" unicode="’" horiz-adv-x="512" d="M397 1284q0 -65 -18 -126t-58.5 -113t-104 -93.5t-155.5 -68.5v86q101 32 148 78t47 106q0 20 -11 33.5t-28 24t-36 20.5t-36 24t-28 34.5t-11 51.5q0 59 38.5 90t95.5 31q32 0 60 -11.5t50 -34t34.5 -56t12.5 -76.5z" />-<glyph glyph-name="quotesinglbase" unicode="‚" horiz-adv-x="512" d="M397 86q0 -65 -18 -126t-58.5 -113t-104 -93.5t-155.5 -68.5v86q101 32 148 78t47 106q0 20 -11 33.5t-28 24t-36 20.5t-36 24t-28 34.5t-11 51.5q0 59 38.5 90t95.5 31q32 0 60 -11.5t50 -34t34.5 -56t12.5 -76.5z" />-<glyph glyph-name="quotedblleft" unicode="“" horiz-adv-x="922" d="M524 1063q0 65 18 126t58.5 113t104.5 93.5t155 68.5v-86q-100 -32 -147 -78t-47 -106q0 -20 11 -33.5t27.5 -24t36 -20.5t36 -24t27.5 -34.5t11 -51.5q0 -59 -38 -90t-95 -31q-31 0 -60 11.5t-50.5 34t-34.5 56t-13 76.5zM115 1063q0 65 18 126t58.5 113t104.5 93.5 t155 68.5v-86q-101 -32 -148 -78t-47 -106q0 -20 11 -33.5t28 -24t36 -20.5t36 -24t28 -34.5t11 -51.5q0 -59 -38.5 -90t-95.5 -31q-31 0 -59.5 11.5t-50.5 34t-34.5 56t-12.5 76.5z" />-<glyph glyph-name="quotedblright" unicode="”" horiz-adv-x="922" d="M397 1284q0 -65 -18 -126t-58.5 -113t-104 -93.5t-155.5 -68.5v86q101 32 148 78t47 106q0 20 -11 33.5t-28 24t-36 20.5t-36 24t-28 34.5t-11 51.5q0 59 38.5 90t95.5 31q32 0 60 -11.5t50 -34t34.5 -56t12.5 -76.5zM807 1284q0 -65 -18 -126t-58.5 -113t-104 -93.5 t-155.5 -68.5v86q100 32 147.5 78t47.5 106q0 20 -11 33.5t-28 24t-36 20.5t-36 24t-28 34.5t-11 51.5q0 59 38 90t95 31q32 0 60.5 -11.5t50 -34t34.5 -56t13 -76.5z" />-<glyph glyph-name="quotedblbase" unicode="„" horiz-adv-x="922" d="M397 86q0 -65 -18 -126t-58.5 -113t-104 -93.5t-155.5 -68.5v86q101 32 148 78t47 106q0 20 -11 33.5t-28 24t-36 20.5t-36 24t-28 34.5t-11 51.5q0 59 38.5 90t95.5 31q32 0 60 -11.5t50 -34t34.5 -56t12.5 -76.5zM807 86q0 -65 -18 -126t-58.5 -113t-104 -93.5 t-155.5 -68.5v86q100 32 147.5 78t47.5 106q0 20 -11 33.5t-28 24t-36 20.5t-36 24t-28 34.5t-11 51.5q0 59 38 90t95 31q32 0 60.5 -11.5t50 -34t34.5 -56t13 -76.5z" />-<glyph glyph-name="bullet" unicode="•" horiz-adv-x="741" d="M100 729q0 84 20.5 141t56.5 91.5t86 49.5t108 15q56 0 105 -15t86 -49.5t58 -91.5t21 -141t-21 -140.5t-58 -91.5t-86 -50t-105 -15q-58 0 -108 15t-86 50t-56.5 91.5t-20.5 140.5z" />-<glyph glyph-name="ellipsis" unicode="…" horiz-adv-x="1757" d="M164 125q0 40 10 66.5t27.5 42.5t41 23t50.5 7q26 0 49.5 -7t41 -23t28 -42.5t10.5 -66.5q0 -39 -10.5 -66t-28 -43t-41 -23t-49.5 -7q-27 0 -50.5 7t-41 23t-27.5 43t-10 66zM750 125q0 40 10 66.5t27.5 42.5t41 23t50.5 7q26 0 49.5 -7t41 -23t28 -42.5t10.5 -66.5 q0 -39 -10.5 -66t-28 -43t-41 -23t-49.5 -7q-27 0 -50.5 7t-41 23t-27.5 43t-10 66zM1335 125q0 40 10 66.5t27.5 42.5t41 23t50.5 7q26 0 49.5 -7t41 -23t28 -42.5t10.5 -66.5q0 -39 -10.5 -66t-28 -43t-41 -23t-49.5 -7q-27 0 -50.5 7t-41 23t-27.5 43t-10 66z" />-<glyph glyph-name="guilsinglleft" unicode="‹" horiz-adv-x="682" d="M133 586l313 356h103l-207 -389l207 -389h-103l-313 356v66z" />-<glyph glyph-name="guilsinglright" unicode="›" horiz-adv-x="682" d="M551 520l-313 -356h-103l207 389l-207 389h103l313 -356v-66z" />-<glyph glyph-name="Euro" unicode="€" d="M776 100q52 0 92.5 10t71.5 27t54.5 39.5t41.5 46.5q13 -8 25 -23t12 -46q0 -27 -18.5 -57.5t-56.5 -56.5t-96 -43t-138 -17q-96 0 -184 30.5t-159 98t-120 175.5t-68 263h-149v96h141q-2 23 -3 50t-1 48v45h-137v97h145q14 171 59.5 285.5t114 183.5t155 98t181.5 29 q78 0 141.5 -15.5t109 -43t70.5 -65.5t25 -83q0 -71 -45.5 -100t-132.5 -29q0 48 -10 91t-32 75t-57.5 50.5t-86.5 18.5q-53 0 -102 -23t-88.5 -80t-67 -152.5t-37.5 -239.5h430v-97h-434v-49v-48t2 -46h350v-96h-342q13 -109 42.5 -192.5t73.5 -140t101.5 -85.5t126.5 -29z " />-<glyph glyph-name="trademark" unicode="™" horiz-adv-x="1710" d="M1321 754v55q35 0 56 10t24 45v457l-232 -567h-71l-232 569v-451q0 -41 21 -52t61 -11v-55h-248v55h21q31 0 54.5 10t25.5 47v482q-1 19 -7.5 30.5t-17.5 17.5t-25.5 7.5t-29.5 1.5h-21v57h236l225 -567l232 567h231v-57h-22q-17 0 -31.5 -2t-25 -8t-16 -18.5t-5.5 -33.5 v-471q0 -21 5.5 -33t16 -19t25 -9t31.5 -2h22v-55h-303zM422 872q0 -21 6.5 -33t17 -19t25 -9t29.5 -2h32v-55h-348v55h35q15 0 29 2t24.5 8t16.5 17.5t6 29.5v531h-90q-22 -1 -37 -6.5t-24 -15.5t-14 -22.5t-7 -27.5l-8 -49h-60l6 186h598l7 -186h-60l-8 49q-4 29 -21 50 t-61 22h-94v-525z" />-</font>-</defs></svg>
− extra/fonts/DroidSerif-Regular.ttf
binary file changed (41852 → absent bytes)
− extra/fonts/DroidSerif-Regular.woff
binary file changed (26972 → absent bytes)
extra/haddock.css view
@@ -1,47 +1,3 @@-/* -------- Global things --------- */--@font-face {- font-family: 'DroidSerif';- src: url('fonts/DroidSerif-Regular.eot');- src: local('Droid Serif'),- url('fonts/DroidSerif-Regular.woff') format('woff'),- url('fonts/DroidSerif-Regular.ttf') format('truetype'),- url('fonts/DroidSerif-Regular.svg#DroidSerif') format('svg');-}--@font-face {- font-family: 'DroidSerif';- font-style: italic;- src: url('fonts/DroidSerif-Italic.eot');- src: local('Droid Serif'),- url('fonts/DroidSerif-Italic.woff') format('woff'),- url('fonts/DroidSerif-Italic.ttf') format('truetype'),- url('fonts/DroidSerif-Italic.svg#DroidSerif-Italic') format('svg');-}--@font-face {- font-family: 'DroidSerif';- font-weight: bold;- src: url('fonts/DroidSerif-Bold.eot');- src: local('Droid Serif'),- url('fonts/DroidSerif-Bold.woff') format('woff'),- url('fonts/DroidSerif-Bold.ttf') format('truetype'),- url('fonts/DroidSerif-Bold.svg#DroidSerif-Bold') format('svg');-}--@font-face {- font-family: 'DroidSerif';- src: url('fonts/DroidSerif-BoldItalic.eot');- font-weight: bold;- font-style: italic;- src: local('Droid Serif'),- url('fonts/DroidSerif-BoldItalic.woff') format('woff'),- url('fonts/DroidSerif-BoldItalic.ttf') format('truetype'),- url('fonts/DroidSerif-BoldItalic.svg#DroidSerif-BoldItalic') format('svg');-}--- HTML { background-color: #f0f3ff; width: 100%;@@ -56,7 +12,7 @@ background-color: #ffffff; color: #000000; font-size: 110%;- font-family: DroidSerif, Georgia, serif;+ font-family: Georgia, serif; } A:link { color: #5200A3; text-decoration: none }
haddock.sh view
@@ -4,10 +4,9 @@ rm -Rf dist/doc -HADDOCK_OPTS='--html-location=http://hackage.haskell.org/packages/archive/$pkg/latest/doc/html --css=extra/haddock.css'+HADDOCK_OPTS='--html-location=http://hackage.haskell.org/packages/archive/$pkg/latest/doc/html' cabal haddock $HADDOCK_OPTS --hyperlink-source $@ -cp -r extra/fonts dist/doc/html/snap-core/ cp extra/logo.gif dist/doc/html/snap-core/haskell_icon.gif cp extra/hscolour.css dist/doc/html/snap-core/src/
− project_template/barebones/foo.cabal
@@ -1,35 +0,0 @@-Name: projname-Version: 0.1-Synopsis: Project Synopsis Here-Description: Project Description Here-License: AllRightsReserved-Author: Author-Maintainer: maintainer@example.com-Stability: Experimental-Category: Web-Build-type: Simple-Cabal-version: >=1.2--Executable projname- hs-source-dirs: src- main-is: Main.hs-- Build-depends:- base >= 4,- haskell98,- monads-fd >= 0.1 && <0.2,- bytestring >= 0.9.1 && <0.10,- snap-core >= 0.2 && <0.3,- snap-server >= 0.2 && <0.3,- xhtml-combinators,- unix,- text,- containers,- MonadCatchIO-transformers,- filepath >= 1.1 && <1.2-- if impl(ghc >= 6.12.0)- ghc-options: -threaded -Wall -fwarn-tabs -funbox-strict-fields -O2- -fno-warn-unused-do-bind- else- ghc-options: -threaded -Wall -fwarn-tabs -funbox-strict-fields -O2
− project_template/barebones/src/Main.hs
@@ -1,22 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-module Main where--import Control.Applicative-import Snap.Types-import Snap.Util.FileServe--import Server--main :: IO ()-main = quickServer $- ifTop (writeBS "hello world") <|>- route [ ("foo", writeBS "bar")- , ("echo/:echoparam", echoHandler)- ] <|>- dir "static" (fileServe ".")--echoHandler :: Snap ()-echoHandler = do- param <- getParam "echoparam"- maybe (writeBS "must specify echo/param in URL")- writeBS param
− project_template/barebones/src/Server.hs
@@ -1,111 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-module Server- ( ServerConfig(..)- , emptyServerConfig- , commandLineConfig- , server- , quickServer- ) where-import qualified Data.ByteString.Char8 as B-import Data.ByteString.Char8 (ByteString)-import Data.Char-import Control.Concurrent-import Control.Exception (SomeException)-import Control.Monad.CatchIO-import qualified Data.Text as T-import Prelude hiding (catch)-import Snap.Http.Server-import Snap.Types-import Snap.Util.GZip-import System hiding (getEnv)-import System.Posix.Env-import qualified Text.XHtmlCombinators.Escape as XH---data ServerConfig = ServerConfig- { locale :: String- , interface :: ByteString- , port :: Int- , hostname :: ByteString- , accessLog :: Maybe FilePath- , errorLog :: Maybe FilePath- , compression :: Bool- , error500Handler :: SomeException -> Snap ()- }---emptyServerConfig :: ServerConfig-emptyServerConfig = ServerConfig- { locale = "en_US"- , interface = "0.0.0.0"- , port = 8000- , hostname = "myserver"- , accessLog = Just "access.log"- , errorLog = Just "error.log"- , compression = True- , error500Handler = \e -> do- let t = T.pack $ show e- r = setContentType "text/html; charset=utf-8" $- setResponseStatus 500 "Internal Server Error" emptyResponse- putResponse r- writeBS "<html><head><title>Internal Server Error</title></head>"- writeBS "<body><h1>Internal Server Error</h1>"- writeBS "<p>A web handler threw an exception. Details:</p>"- writeBS "<pre>\n"- writeText $ XH.escape t- writeBS "\n</pre></body></html>"- }---commandLineConfig :: IO ServerConfig-commandLineConfig = do- args <- getArgs- let conf = case args of- [] -> emptyServerConfig- (port':_) -> emptyServerConfig { port = read port' }- locale' <- getEnv "LANG"- return $ case locale' of- Nothing -> conf- Just l -> conf {locale = takeWhile (\c -> isAlpha c || c == '_') l}--server :: ServerConfig -> Snap () -> IO ()-server config handler = do- putStrLn $ "Listening on " ++ (B.unpack $ interface config)- ++ ":" ++ show (port config)- setUTF8Locale (locale config)- try $ httpServe- (interface config)- (port config)- (hostname config)- (accessLog config)- (errorLog config)- (catch500 $ compress $ handler)- :: IO (Either SomeException ())- threadDelay 1000000- putStrLn "Shutting down"- where- catch500 = (`catch` (error500Handler config))- compress = if compression config then withCompression else id---quickServer :: Snap () -> IO ()-quickServer = (commandLineConfig >>=) . flip server---setUTF8Locale :: String -> IO ()-setUTF8Locale locale' = do- mapM_ (\k -> setEnv k (locale' ++ ".UTF-8") True)- [ "LANG"- , "LC_CTYPE"- , "LC_NUMERIC"- , "LC_TIME"- , "LC_COLLATE"- , "LC_MONETARY"- , "LC_MESSAGES"- , "LC_PAPER"- , "LC_NAME"- , "LC_ADDRESS"- , "LC_TELEPHONE"- , "LC_MEASUREMENT"- , "LC_IDENTIFICATION"- , "LC_ALL" ]
− project_template/default/foo.cabal
@@ -1,37 +0,0 @@-Name: projname-Version: 0.1-Synopsis: Project Synopsis Here-Description: Project Description Here-License: AllRightsReserved-Author: Author-Maintainer: maintainer@example.com-Stability: Experimental-Category: Web-Build-type: Simple-Cabal-version: >=1.2--Executable projname- hs-source-dirs: src- main-is: Main.hs-- Build-depends:- base >= 4,- haskell98,- monads-fd >= 0.1 && <0.2,- bytestring >= 0.9.1 && <0.10,- snap-core >= 0.2 && <0.3,- snap-server >= 0.2 && <0.3,- heist >= 0.2.4 && <0.3,- hexpat >= 0.19 && <0.20,- xhtml-combinators,- unix,- text,- containers,- MonadCatchIO-transformers,- filepath >= 1.1 && <1.2-- if impl(ghc >= 6.12.0)- ghc-options: -threaded -Wall -fwarn-tabs -funbox-strict-fields -O2- -fno-warn-unused-do-bind- else- ghc-options: -threaded -Wall -fwarn-tabs -funbox-strict-fields -O2
− project_template/default/src/Glue.hs
@@ -1,46 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-module Glue- ( templateHandler- , defaultReloadHandler- , templateServe- , render- ) where--import Control.Applicative-import Control.Monad-import Data.ByteString.Char8 (ByteString)-import qualified Data.ByteString.Char8 as B-import Prelude hiding (catch)-import Snap.Types hiding (dir)-import Snap.Util.FileServe-import Text.Templating.Heist-import Text.Templating.Heist.TemplateDirectory---templateHandler :: TemplateDirectory Snap- -> (TemplateDirectory Snap -> Snap ())- -> (TemplateState Snap -> Snap ())- -> Snap ()-templateHandler td reload f = reload td <|> (f =<< getDirectoryTS td)---defaultReloadHandler :: TemplateDirectory Snap -> Snap ()-defaultReloadHandler td = path "admin/reload" $ do- e <- reloadTemplateDirectory td- modifyResponse $ setContentType "text/plain; charset=utf-8"- writeBS . B.pack $ either id (const "Templates loaded successfully.") e---render :: TemplateState Snap -> ByteString -> Snap ()-render ts template = do- bytes <- renderTemplate ts template- flip (maybe pass) bytes $ \x -> do- modifyResponse $ setContentType "text/html; charset=utf-8"- writeBS x---templateServe :: TemplateState Snap -> Snap ()-templateServe ts = ifTop (render ts "index") <|> do- path' <- getSafePath- when (head path' == '_') pass- render ts $ B.pack path'
− project_template/default/src/Main.hs
@@ -1,30 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-module Main where--import Control.Applicative-import Snap.Types-import Snap.Util.FileServe-import Text.Templating.Heist-import Text.Templating.Heist.TemplateDirectory--import Glue-import Server---main :: IO ()-main = do- td <- newTemplateDirectory' "templates" emptyTemplateState- quickServer $ templateHandler td defaultReloadHandler $ \ts ->- ifTop (writeBS "hello world") <|>- route [ ("foo", writeBS "bar")- , ("echo/:echoparam", echoHandler)- ] <|>- templateServe ts <|>- dir "static" (fileServe ".")---echoHandler :: Snap ()-echoHandler = do- param <- getParam "echoparam"- maybe (writeBS "must specify echo/param in URL")- writeBS param
− project_template/default/src/Server.hs
@@ -1,111 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-module Server- ( ServerConfig(..)- , emptyServerConfig- , commandLineConfig- , server- , quickServer- ) where-import qualified Data.ByteString.Char8 as B-import Data.ByteString.Char8 (ByteString)-import Data.Char-import Control.Concurrent-import Control.Exception (SomeException)-import Control.Monad.CatchIO-import qualified Data.Text as T-import Prelude hiding (catch)-import Snap.Http.Server-import Snap.Types-import Snap.Util.GZip-import System hiding (getEnv)-import System.Posix.Env-import qualified Text.XHtmlCombinators.Escape as XH---data ServerConfig = ServerConfig- { locale :: String- , interface :: ByteString- , port :: Int- , hostname :: ByteString- , accessLog :: Maybe FilePath- , errorLog :: Maybe FilePath- , compression :: Bool- , error500Handler :: SomeException -> Snap ()- }---emptyServerConfig :: ServerConfig-emptyServerConfig = ServerConfig- { locale = "en_US"- , interface = "0.0.0.0"- , port = 8000- , hostname = "myserver"- , accessLog = Just "access.log"- , errorLog = Just "error.log"- , compression = True- , error500Handler = \e -> do- let t = T.pack $ show e- r = setContentType "text/html; charset=utf-8" $- setResponseStatus 500 "Internal Server Error" emptyResponse- putResponse r- writeBS "<html><head><title>Internal Server Error</title></head>"- writeBS "<body><h1>Internal Server Error</h1>"- writeBS "<p>A web handler threw an exception. Details:</p>"- writeBS "<pre>\n"- writeText $ XH.escape t- writeBS "\n</pre></body></html>"- }---commandLineConfig :: IO ServerConfig-commandLineConfig = do- args <- getArgs- let conf = case args of- [] -> emptyServerConfig- (port':_) -> emptyServerConfig { port = read port' }- locale' <- getEnv "LANG"- return $ case locale' of- Nothing -> conf- Just l -> conf {locale = takeWhile (\c -> isAlpha c || c == '_') l}--server :: ServerConfig -> Snap () -> IO ()-server config handler = do- putStrLn $ "Listening on " ++ (B.unpack $ interface config)- ++ ":" ++ show (port config)- setUTF8Locale (locale config)- try $ httpServe- (interface config)- (port config)- (hostname config)- (accessLog config)- (errorLog config)- (catch500 $ compress $ handler)- :: IO (Either SomeException ())- threadDelay 1000000- putStrLn "Shutting down"- where- catch500 = (`catch` (error500Handler config))- compress = if compression config then withCompression else id---quickServer :: Snap () -> IO ()-quickServer = (commandLineConfig >>=) . flip server---setUTF8Locale :: String -> IO ()-setUTF8Locale locale' = do- mapM_ (\k -> setEnv k (locale' ++ ".UTF-8") True)- [ "LANG"- , "LC_CTYPE"- , "LC_NUMERIC"- , "LC_TIME"- , "LC_COLLATE"- , "LC_MONETARY"- , "LC_MESSAGES"- , "LC_PAPER"- , "LC_NAME"- , "LC_ADDRESS"- , "LC_TELEPHONE"- , "LC_MEASUREMENT"- , "LC_IDENTIFICATION"- , "LC_ALL" ]
+ runTestsAndCoverage.sh view
@@ -0,0 +1,62 @@+#!/bin/sh++set -e++export LC_ALL=C+export LANG=C++rm -f testsuite.tix++# TODO How do we find the executable without knowing the version number in dist-newstyle?+./dist-newstyle/build/snap-core-1.0.0.0/build/testsuite/testsuite -j4 -a1000 $*++DIR="./dist-newstyle/hpc"++rm -Rf $DIR+mkdir -p $DIR+mkdir -p out++# NOTE+# Snap.Internal.Util.FileUploads shouldn't be in the excludes list. This is a+# temporary workaround so we can release.++EXCLUDES='Main+Snap.Core.Tests+Snap.Internal.Debug+Snap.Internal.Http.Parser.Tests+Snap.Internal.Http.Server.Tests+Snap.Internal.Http.Types.Tests+Snap.Internal.Parsing.Tests+Snap.Internal.Routing.Tests+Snap.Internal.Test.Assertions+Snap.Internal.Test.RequestBuilder+Snap.Internal.Util.FileUploads+Snap.Test+Snap.Test.Common+Snap.Test.Tests+Snap.Types.Tests+Snap.Types.Headers.Tests+Snap.Util.FileServe.Tests+Snap.Util.FileUploads.Tests+Snap.Util.GZip.Tests+Snap.Util.Proxy.Tests+Snap.Util.Readable.Tests+Text.Snap.Templates.Tests'++EXCL=""++for m in $EXCLUDES; do+ EXCL="$EXCL --exclude=$m"+done++hpc markup $EXCL --destdir=$DIR testsuite++rm -f testsuite.tix++#TODO only copy hpc results if this script is called from deploy_hpc.sh+cp -r $DIR out/++cat <<EOF++Test coverage report written to $DIR.+EOF
snap-core.cabal view
@@ -1,12 +1,12 @@+cabal-version: 2.2 name: snap-core-version: 0.2.16-synopsis: Snap: A Haskell Web Framework (Core)+version: 1.0.5.1+synopsis: Snap: A Haskell Web Framework (core interfaces and types) description:- This is the first developer prerelease of the Snap framework. Snap is a- simple and fast web development framework and server written in Haskell. For- more information or to download the latest version, you can visit the Snap- project website at <http://snapframework.com/>.+ Snap is a simple and fast web development framework and server written in+ Haskell. For more information or to download the latest version, you can+ visit the Snap project website at <http://snapframework.com/>. . This library contains the core definitions and types for the Snap framework, including:@@ -14,10 +14,7 @@ 1. Primitive types and functions for HTTP (requests, responses, cookies, post/query parameters, etc) .- 2. Type aliases and helper functions for Iteratee I/O- .- 3. A monad for programming web handlers called \"Snap\", inspired by- happstack's (<http://happstack.com/index.html>), which allows:+ 2. A monad for programming web handlers called \"Snap\", which allows: . * Stateful access to the HTTP request and response objects .@@ -27,76 +24,57 @@ * Early termination of the computation if you know early what you want to return and want to prevent further monadic processing .- /Quick start/: The 'Snap' monad and HTTP definitions are in "Snap.Types",- some iteratee utilities are in "Snap.Iteratee".- .- Higher-level facilities for building web applications (like user/session- management, component interfaces, data modeling, etc.) are planned but not- yet implemented, so this release will mostly be of interest for those who:- .- * need a fast and minimal HTTP API at roughly the same level of abstraction- as Java servlets, or- .- * are interested in contributing to the Snap Framework project.+ /Quick start/: The 'Snap' monad and HTTP definitions are in "Snap.Core". -license: BSD3+license: BSD-3-Clause license-file: LICENSE-author: James Sanders, Shu-yu Guo, Gregory Collins, Doug Beardsley+author: Snap Framework Authors (see CONTRIBUTORS) maintainer: snap@snapframework.com build-type: Simple-cabal-version: >= 1.6 homepage: http://snapframework.com/-category: Web+bug-reports: https://github.com/snapframework/snap-core/issues+category: Web, Snap, IO-Streams +tested-with:+ GHC == 8.6.5+ GHC == 8.8.4+ GHC == 8.10.7+ GHC == 9.0.2+ GHC == 9.2.5+ GHC == 9.4.4+ extra-source-files:- test/suite/TestSuite.hs,+ test/TestSuite.hs, cbits/timefuncs.c, CONTRIBUTORS,- extra/fonts/DroidSerif-Bold.eot,- extra/fonts/DroidSerif-Bold.svg,- extra/fonts/DroidSerif-Bold.ttf,- extra/fonts/DroidSerif-Bold.woff,- extra/fonts/DroidSerif-BoldItalic.eot,- extra/fonts/DroidSerif-BoldItalic.svg,- extra/fonts/DroidSerif-BoldItalic.ttf,- extra/fonts/DroidSerif-BoldItalic.woff,- extra/fonts/DroidSerif-Italic.eot,- extra/fonts/DroidSerif-Italic.svg,- extra/fonts/DroidSerif-Italic.ttf,- extra/fonts/DroidSerif-Italic.woff,- extra/fonts/DroidSerif-Regular.eot,- extra/fonts/DroidSerif-Regular.svg,- extra/fonts/DroidSerif-Regular.ttf,- extra/fonts/DroidSerif-Regular.woff,+ changelog.md, extra/haddock.css, extra/hscolour.css, extra/logo.gif, haddock.sh, LICENSE,- project_template/barebones/foo.cabal,- project_template/barebones/src/Main.hs,- project_template/barebones/src/Server.hs,- project_template/default/foo.cabal,- project_template/default/src/Glue.hs,- project_template/default/src/Main.hs,- project_template/default/src/Server.hs, README.md, README.SNAP.md, Setup.hs,+ runTestsAndCoverage.sh, test/data/fileServe/foo.bin, test/data/fileServe/foo.bin.bin.bin, test/data/fileServe/foo.html, test/data/fileServe/foo.txt,- test/runTestsAndCoverage.sh,- test/snap-core-testsuite.cabal,- test/suite/Snap/Internal/Http/Types/Tests.hs,- test/suite/Snap/Internal/Routing/Tests.hs,- test/suite/Snap/Iteratee/Tests.hs,- test/suite/Snap/Test/Common.hs,- test/suite/Snap/Types/Tests.hs,- test/suite/Snap/Util/FileServe/Tests.hs,- test/suite/Snap/Util/GZip/Tests.hs-+ test/data/fileServe/mydir1/index.txt,+ test/data/fileServe/mydir2/dir/foo.txt,+ test/data/fileServe/mydir2/foo.txt,+ test/data/fileServe/mydir3/altindex.html,+ test/Snap/Core/Tests.hs,+ test/Snap/Internal/Http/Types/Tests.hs,+ test/Snap/Internal/Parsing/Tests.hs,+ test/Snap/Internal/Routing/Tests.hs,+ test/Snap/Test/Common.hs,+ test/Snap/Types/Headers/Tests.hs,+ test/Snap/Util/FileServe/Tests.hs,+ test/Snap/Util/FileUploads/Tests.hs,+ test/Snap/Util/GZip/Tests.hs,+ test/Snap/Util/Proxy/Tests.hs Flag portable Description: Compile in cross-platform mode. No platform-specific code or@@ -104,121 +82,248 @@ Default: False -Flag no-debug- Description: Disable any debug logging code. Without this flag, Snap will+Flag debug+ Description: Enable debug logging code. With this flag, Snap will test the DEBUG environment variable to decide whether to do logging, and this introduces a tiny amount of overhead (a call into a function pointer) because the calls to 'debug'- cannot be inlined. Users who want to squeeze out maximum- performance can set the no-debug flag to get a version of Snap- which has debug calls that should be inlined away.- + cannot be inlined.+ Default: False +Flag network-uri+ Description: Get Network.URI from the network-uri package+ Default: True++common universal+ default-language: Haskell2010++ build-depends:+ , base >= 4 && < 5+ Library+ import: universal hs-source-dirs: src - if flag(no-debug)+ if !flag(debug) cpp-options: -DNODEBUG if flag(portable) || os(windows) cpp-options: -DPORTABLE+ build-depends: time-locale-compat == 0.1.* else c-sources: cbits/timefuncs.c include-dirs: cbits- build-depends: bytestring-mmap >= 0.2.1 && <0.3+ build-depends: old-locale >= 1 && <2 exposed-modules:- Data.CIByteString,- Snap.Types,- Snap.Iteratee,+ Snap.Core,+ Snap.Internal.Core, Snap.Internal.Debug, Snap.Internal.Http.Types,- Snap.Internal.Iteratee.Debug,+ Snap.Internal.Parsing,+ Snap.Internal.Util.FileServe,+ Snap.Test,+ Snap.Types.Headers,+ Snap.Util.CORS, Snap.Util.FileServe,- Snap.Util.GZip+ Snap.Util.FileUploads,+ Snap.Util.GZip,+ Snap.Util.Proxy other-modules:- Snap.Internal.Parsing,+ Snap.Internal.Instances, Snap.Internal.Routing,- Snap.Internal.Types+ Snap.Internal.Test.RequestBuilder,+ Snap.Internal.Test.Assertions,+ Snap.Internal.Util.FileUploads + build-depends:- attoparsec >= 0.8.0.2 && < 0.9,- base >= 4 && < 5,- bytestring,- bytestring-nums,- bytestring-show >= 0.3.2 && < 0.4,- cereal >= 0.3 && < 0.4,- containers,- deepseq >= 1.1 && <1.2,- directory,- dlist >= 0.5 && < 0.6,- filepath,- iteratee >= 0.3.1 && < 0.4,- ListLike >= 1 && < 2,- MonadCatchIO-transformers >= 0.2.1 && < 0.3,- monads-fd < 0.1.0.3,- old-locale,- old-time,- text >= 0.10 && <0.11,- time,- transformers,- unix-compat,- zlib+ HUnit >= 1.2 && < 2,+ attoparsec >= 0.12 && < 0.15,+ bytestring >= 0.9 && < 0.12,+ bytestring-builder >= 0.10.4 && < 0.11,+ case-insensitive >= 1.1 && < 1.3,+ containers >= 0.3 && < 1.0,+ directory >= 1 && < 2,+ filepath >= 1.1 && < 2.0,+ lifted-base >= 0.1 && < 0.3,+ io-streams >= 1.3 && < 1.6,+ hashable >= 1.2.0.6 && < 1.5,+ monad-control >= 1.0 && < 1.1,+ mtl >= 2.0 && < 2.4,+ random >= 1 && < 2,+ readable >= 0.1 && < 0.4,+ regex-posix >= 0.95 && < 1,+ text >= 0.11 && < 2.1,+ time >= 1.0 && < 1.14,+ transformers >= 0.3 && < 0.7,+ transformers-base >= 0.4 && < 0.5,+ unix-compat >= 0.3 && < 0.7,+ unordered-containers >= 0.1.4.3 && < 0.3,+ vector >= 0.6 && < 0.14 - ghc-prof-options: -prof -auto-all+ other-extensions:+ BangPatterns,+ CPP,+ DeriveDataTypeable,+ EmptyDataDecls,+ ExistentialQuantification,+ FlexibleContexts,+ FlexibleInstances,+ ForeignFunctionInterface,+ GeneralizedNewtypeDeriving,+ MultiParamTypeClasses,+ OverloadedStrings,+ OverloadedStrings,+ PackageImports,+ Rank2Types,+ ScopedTypeVariables,+ TypeSynonymInstances if impl(ghc >= 6.12.0)- ghc-options: -Wall -fwarn-tabs -funbox-strict-fields -O2- -fno-warn-unused-do-bind+ ghc-options: -Wall -fwarn-tabs -fno-warn-unused-do-bind else- ghc-options: -Wall -fwarn-tabs -funbox-strict-fields -O2+ ghc-options: -Wall -fwarn-tabs + -- See https://ghc.haskell.org/trac/ghc/wiki/Migration/8.0#base-4.9.0.0 -Executable snap- hs-source-dirs: src- main-is: Snap/Starter.hs+ if impl(ghc < 8.8)+ if impl(ghc >= 8.0)+ ghc-options: -Wcompat -Wnoncanonical-monad-instances -Wnoncanonical-monadfail-instances+ else+ build-depends: fail == 4.9.*, semigroups >= 0.18 && < 0.20 - other-modules: Snap.StarterTH+ if flag(network-uri)+ -- Leaving network-uri-2.7.0.0 out for now because it is marked deprecated+ build-depends: network-uri >= 2.6 && < 2.7,+ network >= 2.6 && < 3.2+ else+ build-depends: network-uri >= 2.5 && < 2.6,+ network >= 2.3 && < 2.6 - if flag(no-debug)- cpp-options: -DNODEBUG+ if !impl(ghc >= 7.6)+ build-depends: unix-compat >= 0.3 && < 0.5.3 ++Test-suite testsuite+ import: universal+ hs-source-dirs: src test+ Type: exitcode-stdio-1.0+ Main-is: TestSuite.hs++ if flag(portable) || os(windows)+ cpp-options: -DPORTABLE+ build-depends: time-locale-compat == 0.1.*+ else+ c-sources: cbits/timefuncs.c+ include-dirs: cbits+ build-depends: old-locale >= 1 && <2++ other-modules:+ Snap.Core,+ Snap.Internal.Debug,+ Snap.Internal.Http.Types,+ Snap.Internal.Parsing,+ Snap.Test,+ Snap.Types.Headers,+ Snap.Util.CORS,+ Snap.Util.FileServe,+ Snap.Util.FileUploads,+ Snap.Util.GZip,+ Snap.Util.Proxy,+ Snap.Internal.Core,+ Snap.Internal.Instances,+ Snap.Internal.Routing,+ Snap.Internal.Test.RequestBuilder,+ Snap.Internal.Test.Assertions,+ Snap.Internal.Util.FileServe,+ Snap.Internal.Util.FileUploads,+ --------------------------------------------------------------------------+ Snap.Core.Tests,+ Snap.Internal.Http.Types.Tests,+ Snap.Internal.Parsing.Tests,+ Snap.Internal.Routing.Tests,+ Snap.Test.Common,+ Snap.Test.Tests,+ Snap.Types.Headers.Tests,+ Snap.Util.CORS.Tests,+ Snap.Util.FileServe.Tests,+ Snap.Util.FileUploads.Tests,+ Snap.Util.GZip.Tests,+ Snap.Util.Proxy.Tests+ build-depends:- attoparsec >= 0.8.1 && < 0.9,- base >= 4 && < 5,+ HUnit,+ attoparsec, bytestring,- bytestring-nums,- bytestring-show >= 0.3.2 && < 0.4,- cereal >= 0.3 && < 0.4,+ bytestring-builder,+ case-insensitive, containers,- deepseq >= 1.1 && <1.2, directory,- directory-tree,- dlist >= 0.5 && < 0.6, filepath,- haskell98,- iteratee >= 0.3.1 && <0.4,- monads-fd,- old-locale,- old-time,- template-haskell,- text >= 0.10 && <0.11,+ hashable,+ lifted-base,+ io-streams,+ monad-control,+ mtl,+ random,+ readable,+ regex-posix,+ text, time, transformers,+ transformers-base, unix-compat,- zlib-- ghc-prof-options: -prof -auto-all+ unordered-containers,+ vector,+ --------------------------------------------------------------------------+ QuickCheck >= 2.3.0.2 && <3,+ deepseq >= 1.1 && < 1.5,+ parallel >= 3 && <4,+ test-framework >= 0.8.0.3 && <0.9,+ test-framework-hunit >= 0.2.7 && <0.4,+ test-framework-quickcheck2 >= 0.2.12.1 && <0.4,+ zlib >= 0.5 && <0.7 - if impl(ghc >= 6.12.0)- ghc-options: -Wall -fwarn-tabs -funbox-strict-fields -O2- -fno-warn-unused-do-bind+ if flag(network-uri)+ build-depends: network-uri,+ network else- ghc-options: -Wall -fwarn-tabs -funbox-strict-fields -O2+ build-depends: network-uri,+ network + ghc-options: -Wall -fwarn-tabs -funbox-strict-fields -threaded+ -fno-warn-unused-do-bind++ -- See https://ghc.haskell.org/trac/ghc/wiki/Migration/8.0#base-4.9.0.0+ if impl(ghc < 8.8)+ if impl(ghc >= 8.0)+ ghc-options: -Wcompat -Wnoncanonical-monad-instances -Wnoncanonical-monadfail-instances+ else+ build-depends: fail == 4.9.*, semigroups >= 0.18 && < 0.20++ other-extensions:+ BangPatterns,+ CPP,+ DeriveDataTypeable,+ EmptyDataDecls,+ ExistentialQuantification,+ FlexibleContexts,+ FlexibleInstances,+ ForeignFunctionInterface,+ GeneralizedNewtypeDeriving,+ MagicHash,+ MultiParamTypeClasses,+ OverloadedStrings,+ Rank2Types,+ ScopedTypeVariables,+ StandaloneDeriving,+ TypeFamilies,+ TypeSynonymInstances+ source-repository head type: git- location: http://git.snapframework.com/snap-core.git+ location: git://github.com/snapframework/snap-core.git
− src/Data/CIByteString.hs
@@ -1,60 +0,0 @@-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE OverloadedStrings #-}---- | "Data.CIByteString" is a module containing 'CIByteString', a wrapper for--- 'ByteString' which provides case-insensitive (ASCII-wise) 'Ord' and 'Eq'--- instances.------ 'CIByteString' also has an 'IsString' instance, so if you use the--- \"OverloadedStrings\" LANGUAGE pragma you can write case-insensitive string--- literals, e.g.:------ @--- \> let a = \"Foo\" in--- putStrLn $ (show $ unCI a) ++ \"==\\\"FoO\\\" is \" ++ show (a == \"FoO\")--- \"Foo\"==\"FoO\" is True--- @--module Data.CIByteString- ( CIByteString- , toCI- , unCI- , ciToLower- ) where---- for IsString instance-import Data.ByteString.Char8 ()-import Data.ByteString (ByteString)-import Data.ByteString.Internal (c2w, w2c)-import qualified Data.ByteString as S-import Data.Char-import Data.String----- | A case-insensitive newtype wrapper for 'ByteString'-data CIByteString = CIByteString { unCI :: !ByteString- , _lowercased :: !ByteString }--toCI :: ByteString -> CIByteString-toCI s = CIByteString s t- where- t = lowercase s--ciToLower :: CIByteString -> ByteString-ciToLower = _lowercased--instance Show CIByteString where- show (CIByteString s _) = show s--lowercase :: ByteString -> ByteString-lowercase = S.map (c2w . toLower . w2c)--instance Eq CIByteString where- (CIByteString _ a) == (CIByteString _ b) = a == b- (CIByteString _ a) /= (CIByteString _ b) = a /= b--instance Ord CIByteString where- (CIByteString _ a) <= (CIByteString _ b) = a <= b--instance IsString CIByteString where- fromString = toCI . fromString
+ src/Snap/Core.hs view
@@ -0,0 +1,174 @@+{-|++This module contains the core type definitions, class instances, and functions+for HTTP as well as the 'Snap' monad, which is used for web handlers.++-}+module Snap.Core+ (+ -- * The Snap Monad+ Snap+ , runSnap+ , MonadSnap(..)+ , NoHandlerException(..)++ -- ** Functions for control flow and early termination+ , bracketSnap+ , finishWith+ , catchFinishWith+ , pass++ -- *** Escaping HTTP+ , EscapeHttpHandler+ , EscapeSnap(..)+ , escapeHttp+ , terminateConnection++ -- ** Routing+ , method+ , methods+ , path+ , pathArg+ , dir+ , ifTop+ , route+ , routeLocal++ -- ** Access to state+ , getRequest+ , getsRequest+ , getResponse+ , getsResponse+ , putRequest+ , putResponse+ , modifyRequest+ , modifyResponse+ , localRequest+ , withRequest+ , withResponse++ -- ** Logging+ , logError++ -- ** Grabbing/transforming request bodies+ , runRequestBody+ , readRequestBody+ , transformRequestBody++ -- * HTTP Datatypes and Functions+ -- $httpDoc+ --+ , Request+ , Response+ , Headers+ , HasHeaders(..)+ , Params+ , Method(..)+ , Cookie(..)+ , HttpVersion++ -- ** Headers+ , addHeader+ , setHeader+ , getHeader+ , listHeaders+ , deleteHeader+ , ipHeaderFilter+ , ipHeaderFilter'++ -- ** Requests+ , rqHeaders+ , rqHostName+ , rqClientAddr+ , rqClientPort+ , rqServerAddr+ , rqServerPort+ , rqLocalHostname+ , rqIsSecure+ , rqContentLength+ , rqMethod+ , rqVersion+ , rqCookies+ , rqPathInfo+ , rqContextPath+ , rqURI+ , rqQueryString+ , rqParams+ , rqQueryParams+ , rqPostParams+ , rqParam+ , rqPostParam+ , rqQueryParam+ , getParam+ , getPostParam+ , getQueryParam+ , getParams+ , getPostParams+ , getQueryParams+ , rqModifyParams+ , rqSetParam++ -- *** Deprecated functions+ , rqRemoteAddr+ , rqRemotePort++ -- ** Responses+ , emptyResponse+ , setResponseCode+ , setResponseStatus+ , rspStatus+ , rspStatusReason+ , setContentType+ , addResponseCookie+ , getResponseCookie+ , getResponseCookies+ , deleteResponseCookie+ , modifyResponseCookie+ , expireCookie+ , getCookie+ , readCookie+ , setContentLength+ , clearContentLength+ , redirect+ , redirect'++ -- *** Response I/O+ , setResponseBody+ , modifyResponseBody+ , addToOutput+ , writeBuilder+ , writeBS+ , writeLazyText+ , writeText+ , writeLBS+ , sendFile+ , sendFilePartial++ -- ** Timeouts+ , setTimeout+ , extendTimeout+ , modifyTimeout+ , getTimeoutModifier++ -- * HTTP utilities+ , formatHttpTime+ , parseHttpTime+ , parseUrlEncoded+ , buildUrlEncoded+ , printUrlEncoded+ , urlEncode+ , urlEncodeBuilder+ , urlDecode+ ) where++------------------------------------------------------------------------------+import Snap.Internal.Core (EscapeHttpHandler, EscapeSnap (..), MonadSnap (..), NoHandlerException (..), Snap, addToOutput, bracketSnap, catchFinishWith, dir, escapeHttp, expireCookie, extendTimeout, finishWith, getCookie, getParam, getParams, getPostParam, getPostParams, getQueryParam, getQueryParams, getRequest, getResponse, getTimeoutModifier, getsRequest, getsResponse, ifTop, ipHeaderFilter, ipHeaderFilter', localRequest, logError, method, methods, modifyRequest, modifyResponse, modifyTimeout, pass, path, pathArg, putRequest, putResponse, readCookie, readRequestBody, redirect, redirect', runRequestBody, runSnap, sendFile, sendFilePartial, setTimeout, terminateConnection, transformRequestBody, withRequest, withResponse, writeBS, writeBuilder, writeLBS, writeLazyText, writeText)+import Snap.Internal.Http.Types (Cookie (..), HasHeaders (..), HttpVersion, Method (..), Params, Request (rqClientAddr, rqClientPort, rqContentLength, rqContextPath, rqCookies, rqHeaders, rqHostName, rqIsSecure, rqLocalHostname, rqMethod, rqParams, rqPathInfo, rqPostParams, rqQueryParams, rqQueryString, rqServerAddr, rqServerPort, rqURI, rqVersion), Response (rspStatus, rspStatusReason), addHeader, addResponseCookie, clearContentLength, deleteHeader, deleteResponseCookie, emptyResponse, formatHttpTime, getHeader, getResponseCookie, getResponseCookies, listHeaders, modifyResponseBody, modifyResponseCookie, parseHttpTime, rqModifyParams, rqParam, rqPostParam, rqQueryParam, rqRemoteAddr, rqRemotePort, rqSetParam, setContentLength, setContentType, setHeader, setResponseBody, setResponseCode, setResponseStatus)+import Snap.Internal.Instances ()+import Snap.Internal.Parsing (buildUrlEncoded, parseUrlEncoded, printUrlEncoded, urlDecode, urlEncode, urlEncodeBuilder)+import Snap.Internal.Routing (route, routeLocal)+import Snap.Types.Headers (Headers)+------------------------------------------------------------------------------++-- $httpDoc+-- HTTP-related datatypes: 'Request', 'Response', 'Cookie', etc.
+ src/Snap/Internal/Core.hs view
@@ -0,0 +1,2073 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE Rank2Types #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeSynonymInstances #-}+#if __GLASGOW_HASKELL__ >= 708+{-# LANGUAGE StandaloneDeriving #-}+#endif++module Snap.Internal.Core+ ( MonadSnap(..)+ , SnapResult(..)+ , EscapeHttpHandler+ , EscapeSnap(..)+ , Zero(..)+ , Snap(..)+ , SnapState(..)+ , runRequestBody+ , readRequestBody+ , transformRequestBody+ , finishWith+ , catchFinishWith+ , pass+ , method+ , methods+ , updateContextPath+ , pathWith+ , dir+ , path+ , pathArg+ , ifTop+ , sget+ , smodify+ , getRequest+ , getResponse+ , getsRequest+ , getsResponse+ , putRequest+ , putResponse+ , modifyRequest+ , modifyResponse+ , redirect+ , redirect'+ , logError+ , addToOutput+ , writeBuilder+ , writeBS+ , writeLBS+ , writeText+ , writeLazyText+ , sendFile+ , sendFilePartial+ , localRequest+ , withRequest+ , withResponse+ , ipHeaderFilter+ , ipHeaderFilter'+ , bracketSnap+ , NoHandlerException(..)+ , terminateConnection+ , escapeHttp+ , runSnap+ , fixupResponse+ , evalSnap+ , getParamFrom+ , getParam+ , getPostParam+ , getQueryParam+ , getParams+ , getPostParams+ , getQueryParams+ , getCookie+ , readCookie+ , expireCookie+ , setTimeout+ , extendTimeout+ , modifyTimeout+ , getTimeoutModifier+ , module Snap.Internal.Http.Types+ ) where++------------------------------------------------------------------------------+import Control.Applicative (Alternative ((<|>), empty), Applicative ((<*>), pure), (<$>))+import Control.Exception.Lifted (ErrorCall (..), Exception, Handler (..), SomeException (..), catch, catches, mask, onException, throwIO)+import Control.Monad (Functor (..), Monad (..), MonadPlus (..), ap, liftM, unless, (=<<))+import qualified Control.Monad.Fail as Fail+import Control.Monad.Base (MonadBase (..))+import Control.Monad.IO.Class (MonadIO (..))+import Control.Monad.Trans.Control (MonadBaseControl (..))+import Control.Monad.Trans.State (StateT (..))+import Data.ByteString.Builder (Builder, byteString, lazyByteString)+import Data.ByteString.Char8 (ByteString)+import qualified Data.ByteString.Char8 as S (break, concat, drop, dropWhile, intercalate, length, take, takeWhile)+import qualified Data.ByteString.Internal as S (create)+import qualified Data.ByteString.Lazy.Char8 as L (ByteString, fromChunks)+import Data.CaseInsensitive (CI)+import Data.Maybe (Maybe (..), listToMaybe, maybe)+import qualified Data.Text as T (Text)+import qualified Data.Text.Encoding as T (encodeUtf8)+import qualified Data.Text.Lazy.Encoding as LT (encodeUtf8)+import qualified Data.Text.Lazy as LT (Text)+import Data.Time (Day (ModifiedJulianDay), UTCTime (UTCTime))+#if __GLASGOW_HASKELL__ < 708+import Data.Typeable (TyCon, Typeable, Typeable1 (..), mkTyCon3, mkTyConApp)+#else+import Data.Typeable (Typeable)+#endif+import Data.Word (Word64, Word8)+import Foreign.Ptr (Ptr, plusPtr)+import Foreign.Storable (poke)+import Prelude (Bool (..), Either (..), Eq (..), FilePath, IO, Int, Num (..), Ord (..), Show (..), String, const, divMod, elem, filter, fromIntegral, id, map, max, otherwise, quot, ($), ($!), (++), (.), (||))+import System.IO.Streams (InputStream, OutputStream)+import qualified System.IO.Streams as Streams+import System.Posix.Types (FileOffset)+import System.PosixCompat.Files (fileSize, getFileStatus)+#if !MIN_VERSION_bytestring(0,10,6)+import qualified Data.ByteString.Internal as S (inlinePerformIO)+#else+import qualified Data.ByteString.Internal as S (accursedUnutterablePerformIO)+#endif+------------------------------------------------------------------------------+import qualified Data.Readable as R+import Snap.Internal.Http.Types (Cookie (..), HasHeaders (..), HttpVersion, Method (..), Params, Request (..), Response (..), ResponseBody (..), StreamProc, addHeader, addResponseCookie, clearContentLength, deleteHeader, deleteResponseCookie, emptyResponse, formatHttpTime, formatLogTime, getHeader, getResponseCookie, getResponseCookies, listHeaders, modifyResponseBody, modifyResponseCookie, normalizeMethod, parseHttpTime, rqModifyParams, rqParam, rqPostParam, rqQueryParam, rqSetParam, rspBodyMap, rspBodyToEnum, setContentLength, setContentType, setHeader, setResponseBody, setResponseCode, setResponseStatus, statusReasonMap)+import Snap.Internal.Parsing (urlDecode)+import qualified Snap.Types.Headers as H+------------------------------------------------------------------------------+++------------------------------------------------------------------------------+-- | 'MonadSnap' is a type class, analogous to 'MonadIO' for 'IO', that makes it+-- easy to wrap 'Snap' inside monad transformers.+class (Monad m, MonadIO m, MonadBaseControl IO m, MonadPlus m, Functor m,+ Applicative m, Alternative m) => MonadSnap m where+ -- | Lift a computation from the 'Snap' monad.+ liftSnap :: Snap a -> m a+++------------------------------------------------------------------------------+data SnapResult a = SnapValue a+ | Zero Zero+++------------------------------------------------------------------------------+-- | Type of external handler passed to 'escapeHttp'.+type EscapeHttpHandler = ((Int -> Int) -> IO ()) -- ^ timeout modifier+ -> InputStream ByteString -- ^ socket read end+ -> OutputStream Builder -- ^ socket write end+ -> IO ()+++------------------------------------------------------------------------------+-- | Used internally to implement 'escapeHttp'.+data EscapeSnap = TerminateConnection SomeException+ | EscapeHttp EscapeHttpHandler+ deriving (Typeable)++instance Exception EscapeSnap++instance Show EscapeSnap where+ show (TerminateConnection e) = "<terminated: " ++ show e ++ ">"+ show (EscapeHttp _) = "<escape http>"+++------------------------------------------------------------------------------+data Zero = PassOnProcessing+ | EarlyTermination Response+ | EscapeSnap EscapeSnap++ --------------------+ -- The Snap Monad --+ --------------------+{-|+'Snap' is the 'Monad' that user web handlers run in. 'Snap' gives you:++1. Stateful access to fetch or modify an HTTP 'Request'.++ @+ printRqContextPath :: Snap ()+ printRqContextPath = 'writeBS' . 'rqContextPath' =<< 'getRequest'+ @++2. Stateful access to fetch or modify an HTTP 'Response'.++ @+ printRspStatusReason :: Snap ()+ printRspStatusReason = 'writeBS' . 'rspStatusReason' =<< 'getResponse'+ @++3. Failure \/ 'Alternative' \/ 'MonadPlus' semantics: a 'Snap' handler can+choose not to handle a given request, using 'empty' or its synonym 'pass', and+you can try alternative handlers with the '<|>' operator:++ @+ a :: Snap String+ a = 'pass'++ b :: Snap String+ b = return \"foo\"++ c :: Snap String+ c = a '<|>' b -- try running a, if it fails then try b+ @++4. Convenience functions ('writeBS', 'writeLBS', 'writeText', 'writeLazyText',+'addToOutput') for queueing output to be written to the 'Response', or for+streaming to the response using+<http://hackage.haskell.org/package/io-streams io-streams>:++ @+ example :: ('OutputStream' 'Builder' -> IO ('OutputStream' 'Builder')) -> Snap ()+ example streamProc = do+ 'writeBS' \"I\'m a strict bytestring\"+ 'writeLBS' \"I\'m a lazy bytestring\"+ 'writeText' \"I\'m strict text\"+ 'addToOutput' streamProc+ @++5. Early termination: if you call 'finishWith':++ @+ a :: Snap ()+ a = do+ 'modifyResponse' $ 'setResponseStatus' 500 \"Internal Server Error\"+ 'writeBS' \"500 error\"+ r <- 'getResponse'+ 'finishWith' r+ @++ then any subsequent processing will be skipped and the supplied 'Response'+ value will be returned from 'runSnap' as-is.++6. Access to the 'IO' monad through a 'MonadIO' instance:++ @+ a :: Snap ()+ a = 'liftIO' fireTheMissiles+ @++7. The ability to set or extend a timeout which will kill the handler thread+after @N@ seconds of inactivity (the default is 20 seconds):++ @+ a :: Snap ()+ a = 'setTimeout' 30+ @++8. Throw and catch exceptions using a 'MonadBaseControl' instance:++ @+ import "Control.Exception.Lifted" ('SomeException', 'throwIO', 'catch')++ foo :: Snap ()+ foo = bar \`catch\` \(e::'SomeException') -> baz+ where+ bar = 'throwIO' FooException+ @++9. Log a message to the error log:++ @+ foo :: Snap ()+ foo = 'logError' \"grumble.\"+ @+-}++-- Haddock comment broken in two to work around https://github.com/haskell/haddock/issues/313++-- | You may notice that most of the type signatures in this module contain a+-- @('MonadSnap' m) => ...@ typeclass constraint. 'MonadSnap' is a typeclass+-- which, in essence, says \"you can get back to the 'Snap' monad from+-- here\". Using 'MonadSnap' you can extend the 'Snap' monad with additional+-- functionality and still have access to most of the 'Snap' functions without+-- writing 'Control.Monad.Trans.Class.lift' everywhere. Instances are already+-- provided for most of the common monad transformers+-- ('Control.Monad.Trans.Reader.ReaderT', 'Control.Monad.Trans.Writer.WriterT',+-- 'Control.Monad.Trans.State.StateT', etc.).+newtype Snap a = Snap {+ unSnap :: forall r . (a -> SnapState -> IO r) -- success continuation+ -> (Zero -> SnapState -> IO r) -- mzero continuation+ -> SnapState -- state for the monad+ -> IO r+ }+++------------------------------------------------------------------------------+data SnapState = SnapState+ { _snapRequest :: Request+ , _snapResponse :: Response+ , _snapLogError :: ByteString -> IO ()+ , _snapModifyTimeout :: (Int -> Int) -> IO ()+ }++-- TODO(greg): error log action and timeout modifier are never modified.+-- Splitting them out into their own datatype would save 16 bytes of allocation+-- every time you modify the request or response, but would gobble a register.+-- Benchmark it both ways.++------------------------------------------------------------------------------+instance Monad Snap where+ (>>=) = snapBind+#if !MIN_VERSION_base(4,8,0)+ -- pre-AMP+ return = pure+ {-# INLINE return #-}+#endif+#if !MIN_VERSION_base(4,13,0)+ fail = Fail.fail+#endif++instance Fail.MonadFail Snap where+ fail = snapFail++------------------------------------------------------------------------------+snapBind :: Snap a -> (a -> Snap b) -> Snap b+snapBind m f = Snap $ \sk fk st -> unSnap m (\a st' -> unSnap (f a) sk fk st') fk st+{-# INLINE snapBind #-}++snapFail :: String -> Snap a+snapFail !_ = Snap $ \_ fk st -> fk PassOnProcessing st+{-# INLINE snapFail #-}+++------------------------------------------------------------------------------+instance MonadIO Snap where+ liftIO m = Snap $ \sk _ st -> do x <- m+ sk x st+++------------------------------------------------------------------------------+instance (MonadBase IO) Snap where+ liftBase = liftIO+++------------------------------------------------------------------------------+newtype StSnap a = StSnap {+ unStSnap :: StM (StateT SnapState IO) (SnapResult a)+ }++instance (MonadBaseControl IO) Snap where+ type StM Snap a = StSnap a++ liftBaseWith f = stateTToSnap $ liftM SnapValue $+ liftBaseWith $ \g' -> f $ \m ->+ liftM StSnap $ g' $ snapToStateT m+ {-# INLINE liftBaseWith #-}++ restoreM = stateTToSnap . restoreM . unStSnap+ {-# INLINE restoreM #-}++------------------------------------------------------------------------------+snapToStateT :: Snap a -> StateT SnapState IO (SnapResult a)+snapToStateT m = StateT $ \st -> do+ unSnap m (\a st' -> return (SnapValue a, st'))+ (\z st' -> return (Zero z, st')) st+{-# INLINE snapToStateT #-}+++------------------------------------------------------------------------------+{-# INLINE stateTToSnap #-}+stateTToSnap :: StateT SnapState IO (SnapResult a) -> Snap a+stateTToSnap m = Snap $ \sk fk st -> do+ (a, st') <- runStateT m st+ case a of+ SnapValue x -> sk x st'+ Zero z -> fk z st'+++------------------------------------------------------------------------------+instance MonadPlus Snap where+ mzero = Snap $ \_ fk st -> fk PassOnProcessing st++ a `mplus` b =+ Snap $ \sk fk st ->+ let fk' z st' = case z of+ PassOnProcessing -> unSnap b sk fk st'+ _ -> fk z st'+ in unSnap a sk fk' st+++------------------------------------------------------------------------------+instance Functor Snap where+ fmap f m = Snap $ \sk fk st -> unSnap m (sk . f) fk st++------------------------------------------------------------------------------+instance Applicative Snap where+ pure x = Snap $ \sk _ st -> sk x st+ (<*>) = ap+++------------------------------------------------------------------------------+instance Alternative Snap where+ empty = mzero+ (<|>) = mplus+++------------------------------------------------------------------------------+instance MonadSnap Snap where+ liftSnap = id+++------------------------------------------------------------------------------+-- | The Typeable instance is here so Snap can be dynamically executed with+-- Hint.+#if __GLASGOW_HASKELL__ < 708+snapTyCon :: TyCon+#if MIN_VERSION_base(4,4,0)+snapTyCon = mkTyCon3 "snap-core" "Snap.Core" "Snap"+#else+snapTyCon = mkTyCon "Snap.Core.Snap"+#endif+{-# NOINLINE snapTyCon #-}++instance Typeable1 Snap where+ typeOf1 _ = mkTyConApp snapTyCon []+#else+deriving instance Typeable Snap+#endif++------------------------------------------------------------------------------+-- | Pass the request body stream to a consuming procedure, returning the+-- result.+--+-- If the consuming procedure you pass in here throws an exception, Snap will+-- attempt to clear the rest of the unread request body (using+-- 'System.IO.Streams.Combinators.skipToEof') before rethrowing the+-- exception. If you used 'terminateConnection', however, Snap will give up and+-- immediately close the socket.+--+-- To prevent slowloris attacks, the connection will be also terminated if the+-- input socket produces data too slowly (500 bytes per second is the default+-- limit).+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.ByteString.Char8" as B8+-- ghci> import qualified "Data.ByteString.Lazy" as L+-- ghci> import "Data.Char" (toUpper)+-- ghci> import qualified "Data.Map" as M+-- ghci> import qualified "Snap.Test" as T+-- ghci> import qualified "System.IO.Streams" as Streams+-- ghci> let r = T.put \"\/foo\" \"text\/plain\" \"some text\"+-- ghci> :{+-- ghci| let f s = do u \<- Streams.map (B8.map toUpper) s+-- ghci| l \<- Streams.toList u+-- ghci| return $ L.fromChunks l+-- ghci| :}+-- ghci> T.runHandler r ('runRequestBody' f >>= 'writeLBS')+-- HTTP/1.1 200 OK+-- server: Snap/test+-- date: Thu, 07 Aug 2014 20:48:40 GMT+--+-- SOME TEXT+-- @+runRequestBody :: MonadSnap m =>+ (InputStream ByteString -> IO a)+ -> m a+runRequestBody proc = do+ bumpTimeout <- liftM ($ max 5) getTimeoutModifier+ req <- getRequest+ body <- liftIO $ Streams.throwIfTooSlow bumpTimeout 500 5 $+ rqBody req+ run body++ where+ skip body = liftIO (Streams.skipToEof body) `catch` tooSlow++ tooSlow (e :: Streams.RateTooSlowException) =+ terminateConnection e++ run body = (liftIO $ do+ x <- proc body+ Streams.skipToEof body+ return x) `catches` handlers+ where+ handlers = [ Handler tooSlow, Handler other ]+ other (e :: SomeException) = skip body >> throwIO e+++------------------------------------------------------------------------------+-- | Returns the request body as a lazy bytestring. /Note that the request is+-- not actually provided lazily!/+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> import qualified "Snap.Test" as T+-- ghci> let r = T.put \"\/foo\" \"text\/plain\" \"some text\"+-- ghci> T.runHandler r ('readRequestBody' 2048 >>= 'writeLBS')+-- HTTP\/1.1 200 OK+-- server: Snap\/test+-- date: Thu, 07 Aug 2014 20:08:44 GMT+--+-- some text+-- @+--+-- /Since: 0.6/+readRequestBody :: MonadSnap m =>+ Word64 -- ^ size of the largest request body we're willing+ -- to accept. If a request body longer than this is+ -- received, a 'TooManyBytesReadException' is+ -- thrown. See 'takeNoMoreThan'.+ -> m L.ByteString+readRequestBody sz = liftM L.fromChunks $ runRequestBody f+ where+ f str = Streams.throwIfProducesMoreThan (fromIntegral sz) str >>=+ Streams.toList+++------------------------------------------------------------------------------+-- | Normally Snap is careful to ensure that the request body is fully+-- consumed after your web handler runs, but before the 'Response' body+-- is streamed out the socket. If you want to transform the request body into+-- some output in O(1) space, you should use this function.+--+-- Take care: in order for this to work, the HTTP client must be written with+-- input-to-output streaming in mind.+--+-- Note that upon calling this function, response processing finishes early as+-- if you called 'finishWith'. Make sure you set any content types, headers,+-- cookies, etc. before you call this function.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.ByteString.Char8" as B8+-- ghci> import "Data.Char" (toUpper)+-- ghci> import qualified "Data.Map" as M+-- ghci> import qualified "Snap.Test" as T+-- ghci> import qualified "System.IO.Streams" as Streams+-- ghci> let r = T.put \"\/foo\" \"text\/plain\" \"some text\"+-- ghci> let f = Streams.map (B8.map toUpper)+-- ghci> T.runHandler r ('transformRequestBody' f >> 'readRequestBody' 2048 >>= 'writeLBS')+-- HTTP\/1.1 200 OK+-- server: Snap\/test+-- date: Thu, 07 Aug 2014 20:30:15 GMT+--+-- SOME TEXT+-- @+transformRequestBody :: (InputStream ByteString -> IO (InputStream ByteString))+ -- ^ the 'InputStream' from the 'Request' is passed to+ -- this function, and then the resulting 'InputStream'+ -- is fed to the output.+ -> Snap ()+transformRequestBody trans = do+ req <- getRequest+ is <- liftIO ((trans $ rqBody req) >>=+ Streams.mapM (return . byteString))+ origRsp <- getResponse+ let rsp = setResponseBody (\out -> Streams.connect is out >> return out) $+ origRsp { rspTransformingRqBody = True }+ finishWith rsp+++------------------------------------------------------------------------------+-- | Short-circuits a 'Snap' monad action early, storing the given+-- 'Response' value in its state.+--+-- IMPORTANT: Be vary careful when using this with things like a DB library's+-- `withTransaction` function or any other kind of setup/teardown block, as it+-- can prevent the cleanup from being called and result in resource leaks.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> import qualified "Snap.Test" as T+-- ghci> import "Control.Applicative"+-- ghci> let r = T.get \"\/\" M.empty+-- ghci> T.runHandler r (('ifTop' $ 'writeBS' \"TOP\") \<|> 'finishWith' 'emptyResponse')+-- HTTP\/1.1 200 OK+-- server: Snap\/test+-- date: Thu, 07 Aug 2014 16:58:57 GMT+--+-- TOP+-- ghci> let r\' = T.get \"\/foo\/bar\" M.empty+-- ghci> T.runHandler r\' (('ifTop' $ 'writeBS' \"TOP\") \<|> 'finishWith' 'emptyResponse')+-- HTTP\/1.1 200 OK+-- server: Snap\/test+-- date: Thu, 07 Aug 2014 17:50:50 GMT+--+--+-- @+finishWith :: MonadSnap m => Response -> m a+finishWith r = liftSnap $ Snap $ \_ fk st -> fk (EarlyTermination r) st+{-# INLINE finishWith #-}+++------------------------------------------------------------------------------+-- | Capture the flow of control in case a handler calls 'finishWith'.+--+-- /WARNING/: in the event of a call to 'transformRequestBody' it is possible+-- to violate HTTP protocol safety when using this function. If you call+-- 'catchFinishWith' it is suggested that you do not modify the body of the+-- 'Response' which was passed to the 'finishWith' call.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.ByteString.Char8" as B8+-- ghci> import qualified "Data.Map" as M+-- ghci> import qualified "Snap.Test" as T+-- ghci> import "Control.Applicative"+-- ghci> let r = T.get \"\/foo\/bar\" M.empty+-- ghci> let h = ('ifTop' $ 'writeBS' \"TOP\") \<|> 'finishWith' 'emptyResponse'+-- ghci> T.runHandler r ('catchFinishWith' h >>= 'writeBS' . B8.pack . show)+-- HTTP\/1.1 200 OK+-- server: Snap\/test+-- date: Thu, 07 Aug 2014 18:35:42 GMT+--+-- Left HTTP\/1.1 200 OK+--+--+-- @+catchFinishWith :: Snap a -> Snap (Either Response a)+catchFinishWith (Snap m) = Snap $ \sk fk st -> do+ let sk' v s = sk (Right v) s+ let fk' z s = case z of+ (EarlyTermination resp) -> sk (Left resp) s+ _ -> fk z s+ m sk' fk' st+{-# INLINE catchFinishWith #-}+++------------------------------------------------------------------------------+-- | Fails out of a 'Snap' monad action. This is used to indicate+-- that you choose not to handle the given request within the given+-- handler.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> import qualified "Snap.Test" as T+-- ghci> let r = T.get \"\/foo\/bar\" M.empty+-- ghci> T.runHandler r 'pass'+-- HTTP\/1.1 404 Not Found+-- server: Snap\/test+-- date: Thu, 07 Aug 2014 13:35:42 GMT+--+-- \<!DOCTYPE html>+-- \<html>+-- \<head>+-- \<title>Not found\<\/title>+-- \<\/head>+-- \<body>+-- \<code>No handler accepted \"\/foo\/bar\"<\/code>+-- \<\/body>\<\/html>+-- @+pass :: MonadSnap m => m a+pass = empty+++------------------------------------------------------------------------------+-- | Runs a 'Snap' monad action only if the request's HTTP method matches+-- the given method.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> import qualified "Snap.Test" as T+-- ghci> let r = T.get \"\/foo\/bar\" M.empty+-- ghci> T.runHandler r ('method' 'GET' $ 'writeBS' \"OK\")+-- HTTP\/1.1 200 OK+-- server: Snap\/test+-- date: Thu, 07 Aug 2014 13:38:48 GMT+--+-- OK+-- ghci> T.runHandler r ('method' 'POST' $ 'writeBS' \"OK\")+-- HTTP\/1.1 404 Not Found+-- ...+-- @+method :: MonadSnap m => Method -> m a -> m a+method m action = do+ req <- getRequest+ unless (rqMethod req == m) pass+ action+{-# INLINE method #-}+++------------------------------------------------------------------------------+-- | Runs a 'Snap' monad action only if the request's HTTP method matches+-- one of the given methods.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> import qualified "Snap.Test" as T+-- ghci> let r = T.get \"\/foo\/bar\" M.empty+-- ghci> T.runHandler r ('methods' ['GET', 'POST'] $ 'writeBS' \"OK\")+-- HTTP\/1.1 200 OK+-- server: Snap\/test+-- date: Thu, 07 Aug 2014 13:38:48 GMT+--+-- OK+-- ghci> T.runHandler r ('methods' ['POST'] $ 'writeBS' \"OK\")+-- HTTP\/1.1 404 Not Found+-- ...+-- @+methods :: MonadSnap m => [Method] -> m a -> m a+methods ms action = do+ req <- getRequest+ unless (rqMethod req `elem` ms) pass+ action+{-# INLINE methods #-}+++------------------------------------------------------------------------------+-- Appends n bytes of the path info to the context path with a+-- trailing slash.+updateContextPath :: Int -> Request -> Request+updateContextPath n req | n > 0 = req { rqContextPath = ctx+ , rqPathInfo = pinfo }+ | otherwise = req+ where+ ctx' = S.take n (rqPathInfo req)+ ctx = S.concat [rqContextPath req, ctx', "/"]+ pinfo = S.drop (n+1) (rqPathInfo req)+++------------------------------------------------------------------------------+-- Runs a 'Snap' monad action only if the 'rqPathInfo' matches the given+-- predicate.+pathWith :: MonadSnap m+ => (ByteString -> ByteString -> Bool)+ -> ByteString+ -> m a+ -> m a+pathWith c p action = do+ req <- getRequest+ unless (c p (rqPathInfo req)) pass+ localRequest (updateContextPath $ S.length p) action+++------------------------------------------------------------------------------+-- | Runs a 'Snap' monad action only when the 'rqPathInfo' of the request+-- starts with the given path. For example,+--+-- > dir "foo" handler+--+-- Will fail if 'rqPathInfo' is not \"@\/foo@\" or \"@\/foo\/...@\", and will+-- add @\"foo\/\"@ to the handler's local 'rqContextPath'.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> import qualified "Snap.Test" as T+-- ghci> let r = T.get \"\/foo\/bar\" M.empty+-- ghci> T.runHandler r ('dir' \"foo\" $ 'writeBS' \"OK\")+-- HTTP\/1.1 200 OK+-- server: Snap\/test+-- date: Thu, 07 Aug 2014 14:52:24 GMT+--+-- OK+-- ghci> T.runHandler r ('dir' \"baz\" $ 'writeBS' \"OK\")+-- HTTP\/1.1 404 Not Found+-- ...+-- @+dir :: MonadSnap m+ => ByteString -- ^ path component to match+ -> m a -- ^ handler to run+ -> m a+dir = pathWith f+ where+ f dr pinfo = dr == x+ where+ (x,_) = S.break (=='/') pinfo+{-# INLINE dir #-}+++------------------------------------------------------------------------------+-- | Runs a 'Snap' monad action only for requests where 'rqPathInfo' is+-- exactly equal to the given string. If the path matches, locally sets+-- 'rqContextPath' to the old value of 'rqPathInfo', sets 'rqPathInfo'=\"\",+-- and runs the given handler.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> import qualified "Snap.Test" as T+-- ghci> T.runHandler (T.get \"\/foo\" M.empty) ('path' \"foo\" $ 'writeBS' \"bar\")+-- HTTP\/1.1 200 OK+-- server: Snap\/test+-- date: Thu, 07 Aug 2014 14:15:42 GMT+--+-- bar+-- ghci> T.runHandler (T.get \"\/foo\" M.empty) ('path' \"bar\" $ 'writeBS' \"baz\")+-- HTTP\/1.1 404 Not Found+-- ...+-- @+path :: MonadSnap m+ => ByteString -- ^ path to match against+ -> m a -- ^ handler to run+ -> m a+path = pathWith (==)+{-# INLINE path #-}+++------------------------------------------------------------------------------+-- | Runs a 'Snap' monad action only when the first path component is+-- successfully parsed as the argument to the supplied handler function.+--+-- Note that the path segment is url-decoded prior to being passed to 'fromBS';+-- this is new as of snap-core 0.10.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> import qualified "Snap.Test" as T+-- ghci> let r = T.get \"\/11\/foo\/bar\" M.empty+-- ghci> let f = (\\i -> if i == 11 then 'writeBS' \"11\" else 'writeBS' \"???\")+-- ghci> T.runHandler r ('pathArg' f)+-- HTTP\/1.1 200 OK+-- server: Snap\/test+-- date: Thu, 07 Aug 2014 14:27:10 GMT+--+-- 11+-- ghci> let r\' = T.get \"\/foo\/11\/bar\" M.empty+-- ghci> T.runHandler r\' ('pathArg' f)+-- HTTP\/1.1 404 Not Found+-- ...+-- @+pathArg :: (R.Readable a, MonadSnap m)+ => (a -> m b)+ -> m b+pathArg f = do+ req <- getRequest+ let (p,_) = S.break (=='/') (rqPathInfo req)+ p' <- maybe mzero return $ urlDecode p+ a <- R.fromBS p'+ localRequest (updateContextPath $ S.length p) (f a)+++------------------------------------------------------------------------------+-- | Runs a 'Snap' monad action only when 'rqPathInfo' is empty.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> import qualified "Snap.Test" as T+-- ghci> let r = T.get \"\/\" M.empty+-- ghci> T.runHandler r ('ifTop' $ 'writeBS' "OK")+-- HTTP\/1.1 200 OK+-- server: Snap\/test+-- date: Thu, 07 Aug 2014 14:56:39 GMT+--+-- OK+-- ghci> let r\' = T.get \"\/foo\" M.empty+-- ghci> T.runHandler r\' ('ifTop' $ 'writeBS' \"OK\")+-- HTTP\/1.1 404 Not Found+-- ...+-- @+ifTop :: MonadSnap m => m a -> m a+ifTop = path ""+{-# INLINE ifTop #-}+++------------------------------------------------------------------------------+-- | Local Snap version of 'get'.+sget :: Snap SnapState+sget = Snap $ \sk _ st -> sk st st+{-# INLINE sget #-}+++------------------------------------------------------------------------------+-- | Local Snap monad version of 'modify'.+smodify :: (SnapState -> SnapState) -> Snap ()+smodify f = Snap $ \sk _ st -> sk () (f st)+{-# INLINE smodify #-}+++------------------------------------------------------------------------------+-- | Grabs the 'Request' object out of the 'Snap' monad.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> import qualified "Snap.Test" as T+-- ghci> let r = T.get \"\/foo\/bar\" M.empty+-- ghci> T.runHandler r ('writeBS' . 'rqURI' =\<\< 'getRequest')+-- HTTP\/1.1 200 OK+-- server: Snap\/test+-- date: Sat, 02 Aug 2014 07:51:54 GMT+--+-- \/foo\/bar+-- @+getRequest :: MonadSnap m => m Request+getRequest = liftSnap $ liftM _snapRequest sget+{-# INLINE getRequest #-}+++------------------------------------------------------------------------------+-- | Grabs something out of the 'Request' object, using the given projection+-- function. See 'gets'.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> import qualified "Snap.Test" as T+-- ghci> let r = T.get \"\/foo\/bar\" M.empty+-- ghci> T.runHandler r ('writeBS' =\<\< 'getsRequest' 'rqURI')+-- HTTP\/1.1 200 OK+-- server: Snap\/test+-- date: Sat, 02 Aug 2014 07:51:54 GMT+--+-- \/foo\/bar+-- @+getsRequest :: MonadSnap m => (Request -> a) -> m a+getsRequest f = liftSnap $ liftM (f . _snapRequest) sget+{-# INLINE getsRequest #-}+++------------------------------------------------------------------------------+-- | Grabs the 'Response' object out of the 'Snap' monad.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> import qualified "Snap.Test" as T+-- ghci> let r = T.get \"\/foo\/bar\" M.empty+-- ghci> T.runHandler r ('writeBS' . 'rspStatusReason' =\<\< 'getResponse')+-- HTTP\/1.1 200 OK+-- server: Snap\/test+-- date: Sat, 02 Aug 2014 15:06:00 GMT+--+-- OK+-- @+getResponse :: MonadSnap m => m Response+getResponse = liftSnap $ liftM _snapResponse sget+{-# INLINE getResponse #-}+++------------------------------------------------------------------------------+-- | Grabs something out of the 'Response' object, using the given projection+-- function. See 'gets'.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> import qualified "Snap.Test" as T+-- ghci> let r = T.get \"\/foo\/bar\" M.empty+-- ghci> T.runHandler r ('writeBS' =\<\< 'getsResponse' 'rspStatusReason')+-- HTTP\/1.1 200 OK+-- server: Snap\/test+-- date: Wed, 06 Aug 2014 13:35:45 GMT+--+-- OK+-- @+getsResponse :: MonadSnap m => (Response -> a) -> m a+getsResponse f = liftSnap $ liftM (f . _snapResponse) sget+{-# INLINE getsResponse #-}+++------------------------------------------------------------------------------+-- | Puts a new 'Response' object into the 'Snap' monad.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> import qualified "Snap.Test" as T+-- ghci> let rsp = 'setResponseCode' 404 'emptyResponse'+-- ghci> let req = T.get \"\/foo\/bar\" M.empty+-- ghci> T.runHandler req ('putResponse' rsp)+-- HTTP\/1.1 404 Not Found+-- server: Snap\/test+-- date: Wed, 06 Aug 2014 13:59:58 GMT+--+--+-- @+putResponse :: MonadSnap m => Response -> m ()+putResponse r = liftSnap $ smodify $ \ss -> ss { _snapResponse = r }+{-# INLINE putResponse #-}+++------------------------------------------------------------------------------+-- | Puts a new 'Request' object into the 'Snap' monad.+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> import qualified "Snap.Test" as T+-- ghci> :{+-- ghci| let hndlr = do rq \<- T.buildRequest (T.get \"\/bar\/foo\" M.empty)+-- ghci| 'putRequest' rq+-- ghci| uri\' \<- 'getsRequest' 'rqURI'+-- ghci| 'writeBS' uri\'+-- ghci| :}+-- ghci> T.runHandler (T.get \"\/foo\/bar\" M.empty) hndlr+-- HTTP\/1.1 200 OK+-- server: Snap\/test+-- date: Wed, 06 Aug 2014 15:13:46 GMT+--+-- \/bar\/foo+-- @+putRequest :: MonadSnap m => Request -> m ()+putRequest r = liftSnap $ smodify $ \ss -> ss { _snapRequest = r }+{-# INLINE putRequest #-}+++------------------------------------------------------------------------------+-- | Modifies the 'Request' object stored in a 'Snap' monad.+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> import qualified "Snap.Test" as T+-- ghci> let r = T.get \"\/foo\/bar\" M.empty+-- ghci> r\' \<- T.buildRequest $ T.get \"\/bar\/foo\" M.empty+-- ghci> T.runHandler r ('modifyRequest' (const r\') >> 'getsRequest' 'rqURI' >>= 'writeBS')+-- HTTP\/1.1 200 OK+-- server: Snap\/test+-- date: Wed, 06 Aug 2014 15:24:25 GMT+--+-- \/bar\/foo+-- @+modifyRequest :: MonadSnap m => (Request -> Request) -> m ()+modifyRequest f = liftSnap $+ smodify $ \ss -> ss { _snapRequest = f $ _snapRequest ss }+{-# INLINE modifyRequest #-}+++------------------------------------------------------------------------------+-- | Modifes the 'Response' object stored in a 'Snap' monad.+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> import qualified "Snap.Test" as T+-- ghci> let r = T.get \"\/foo\/bar\" M.empty+-- ghci> T.runHandler r ('modifyResponse' $ 'setResponseCode' 404)+-- HTTP\/1.1 404 Not Found+-- server: Snap\/test+-- date: Wed, 06 Aug 2014 15:27:11 GMT+--+--+-- @+modifyResponse :: MonadSnap m => (Response -> Response) -> m ()+modifyResponse f = liftSnap $+ smodify $ \ss -> ss { _snapResponse = f $ _snapResponse ss }+{-# INLINE modifyResponse #-}+++------------------------------------------------------------------------------+-- | Performs a redirect by setting the @Location@ header to the given target+-- URL/path and the status code to 302 in the 'Response' object stored in a+-- 'Snap' monad. Note that the target URL is not validated in any way.+-- Consider using 'redirect'' instead, which allows you to choose the correct+-- status code.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> import qualified "Snap.Test" as T+-- ghci> let r = T.get \"\/foo\/bar\" M.empty+-- ghci> T.runHandler r ('redirect' \"http:\/\/snapframework.com\")+-- HTTP\/1.1 302 Found+-- content-length: 0+-- location: http:\/\/snapframework.com+-- server: Snap\/test+-- date: Thu, 07 Aug 2014 08:52:11 GMT+-- Content-Length: 0+--+--+-- @+redirect :: MonadSnap m => ByteString -> m a+redirect target = redirect' target 302+{-# INLINE redirect #-}+++------------------------------------------------------------------------------+-- | Performs a redirect by setting the @Location@ header to the given target+-- URL/path and the status code (should be one of 301, 302, 303 or 307) in the+-- 'Response' object stored in a 'Snap' monad. Note that the target URL is not+-- validated in any way.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> import qualified "Snap.Test" as T+-- ghci> let r = T.get \"\/foo\/bar\" M.empty+-- ghci> T.runHandler r ('redirect'' \"http:\/\/snapframework.com\" 301)+-- HTTP\/1.1 307 Temporary Redirect+-- content-length: 0+-- location: http:\/\/snapframework.com+-- server: Snap\/test+-- date: Thu, 07 Aug 2014 08:55:51 GMT+-- Content-Length: 0+--+--+-- @+redirect' :: MonadSnap m => ByteString -> Int -> m a+redirect' target status = do+ r <- getResponse++ finishWith+ $ setResponseCode status+ $ setContentLength 0+ $ modifyResponseBody (const $ return . id)+ $ setHeader "Location" target r++{-# INLINE redirect' #-}+++------------------------------------------------------------------------------+-- | Log an error message in the 'Snap' monad.+--+-- Example:+--+-- @+-- ghci> import qualified "Data.ByteString.Char8" as B8+-- ghci> 'runSnap' ('logError' \"fatal error!\") ('error' . B8.unpack) undefined undefined+-- *** Exception: fatal error!+-- @+logError :: MonadSnap m => ByteString -> m ()+logError s = liftSnap $ Snap $ \sk _ st -> do+ _snapLogError st s+ sk () st+{-# INLINE logError #-}+++------------------------------------------------------------------------------+-- | Run the given stream procedure, adding its output to the 'Response' stored+-- in the 'Snap' monad state.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> import qualified "Snap.Test" as T+-- ghci> import qualified "Data.ByteString.Builder" as B+-- ghci> import qualified "System.IO.Streams" as Streams+-- ghci> let r = T.get \"\/foo\/bar\" M.empty+-- ghci> :{+-- ghci| let f str = do {+-- ghci| Streams.write (Just $ B.byteString \"Hello, streams world\") str;+-- ghci| return str }+-- ghci| :}+-- ghci> T.runHandler r ('addToOutput' f)+-- HTTP\/1.1 200 OK+-- server: Snap\/test+-- date: Wed, 06 Aug 2014 17:55:47 GMT+--+-- Hello, streams world+-- @+addToOutput :: MonadSnap m+ => (OutputStream Builder -> IO (OutputStream Builder))+ -- ^ output to add+ -> m ()+addToOutput enum = modifyResponse $ modifyResponseBody (c enum)+ where+ c a b = \out -> b out >>= a++------------------------------------------------------------------------------+-- | Adds the given 'Builder' to the body of the 'Response' stored in the+-- | 'Snap' monad state.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> import qualified "Snap.Test" as T+-- ghci> import qualified "Data.ByteString.Builder" as B+-- ghci> let r = T.get \"\/foo\/bar\" M.empty+-- ghci> T.runHandler r ('writeBuilder' $ B.byteString \"Hello, world\")+-- HTTP\/1.1 200 OK+-- server: Snap\/test+-- date: Wed, 06 Aug 2014 17:33:33 GMT+--+-- Hello, world+-- @+writeBuilder :: MonadSnap m => Builder -> m ()+writeBuilder b = addToOutput f+ where+ f str = Streams.write (Just b) str >> return str+{-# INLINE writeBuilder #-}+++------------------------------------------------------------------------------+-- | Adds the given strict 'ByteString' to the body of the 'Response' stored+-- in the 'Snap' monad state.+--+-- Warning: This function is intentionally non-strict. If any pure+-- exceptions are raised by the expression creating the 'ByteString',+-- the exception won't actually be raised within the Snap handler.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> import qualified "Snap.Test" as T+-- ghci> let r = T.get \"\/foo\/bar\" M.empty+-- ghci> T.runHandler r ('writeBS' \"Hello, bytestring world\")+-- HTTP\/1.1 200 OK+-- server: Snap\/test+-- date: Wed, 06 Aug 2014 17:34:27 GMT+--+-- Hello, bytestring world+-- @+writeBS :: MonadSnap m => ByteString -> m ()+writeBS = writeBuilder . byteString+{-# INLINE writeBS #-}+++------------------------------------------------------------------------------+-- | Adds the given lazy 'L.ByteString' to the body of the 'Response' stored+-- in the 'Snap' monad state.+--+-- Warning: This function is intentionally non-strict. If any pure+-- exceptions are raised by the expression creating the 'ByteString',+-- the exception won't actually be raised within the Snap handler.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> import qualified "Snap.Test" as T+-- ghci> let r = T.get \"\/foo\/bar\" M.empty+-- ghci> T.runHandler r ('writeLBS' \"Hello, lazy bytestring world\")+-- HTTP\/1.1 200 OK+-- server: Snap\/test+-- date: Wed, 06 Aug 2014 17:35:15 GMT+--+-- Hello, lazy bytestring world+-- @+writeLBS :: MonadSnap m => L.ByteString -> m ()+writeLBS = writeBuilder . lazyByteString+{-# INLINE writeLBS #-}+++------------------------------------------------------------------------------+-- | Adds the given strict 'T.Text' to the body of the 'Response' stored in+-- the 'Snap' monad state.+--+-- Warning: This function is intentionally non-strict. If any pure+-- exceptions are raised by the expression creating the 'ByteString',+-- the exception won't actually be raised within the Snap handler.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> import qualified "Snap.Test" as T+-- ghci> let r = T.get \"\/foo\/bar\" M.empty+-- ghci> T.runHandler r ('writeText' \"Hello, text world\")+-- HTTP\/1.1 200 OK+-- server: Snap\/test+-- date: Wed, 06 Aug 2014 17:36:38 GMT+--+-- Hello, text world+-- @+writeText :: MonadSnap m => T.Text -> m ()+writeText = writeBS . T.encodeUtf8+ -- it's inefficient, but we don't have bytestring builder text functions for+ -- 0.9-era bytestring+{-# INLINE writeText #-}+++------------------------------------------------------------------------------+-- | Adds the given lazy 'LT.Text' to the body of the 'Response' stored in the+-- 'Snap' monad state.+--+-- Warning: This function is intentionally non-strict. If any pure+-- exceptions are raised by the expression creating the 'ByteString',+-- the exception won't actually be raised within the Snap handler.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> import qualified "Snap.Test" as T+-- ghci> let r = T.get \"\/foo\/bar\" M.empty+-- ghci> T.runHandler r ('writeLazyText' \"Hello, lazy text world\")+-- HTTP\/1.1 200 OK+-- server: Snap\/test+-- date: Wed, 06 Aug 2014 17:37:41 GMT+--+-- Hello, lazy text world+-- @+writeLazyText :: MonadSnap m => LT.Text -> m ()+writeLazyText = writeLBS . LT.encodeUtf8+{-# INLINE writeLazyText #-}+++------------------------------------------------------------------------------+-- | Sets the output to be the contents of the specified file.+--+-- Calling 'sendFile' will overwrite any output queued to be sent in the+-- 'Response'. If the response body is not modified after the call to+-- 'sendFile', Snap will use the efficient @sendfile()@ system call on+-- platforms that support it.+--+-- If the response body is modified (using 'modifyResponseBody'), the file+-- will be read using @mmap()@.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> import qualified "Snap.Test" as T+-- ghci> 'writeFile' \"\/tmp\/snap-file\" \"Hello, sendFile world\"+-- ghci> let r = T.get \"\/foo\/bar\" M.empty+-- ghci> T.runHandler r ('sendFile' \"\/tmp\/snap-file\")+-- HTTP\/1.1 200 OK+-- content-length: 21+-- server: Snap\/test+-- date: Wed, 06 Aug 2014 17:45:10 GMT+-- Content-Length: 21+--+-- Hello, sendFile world+-- @+sendFile :: (MonadSnap m) => FilePath -> m ()+sendFile f = modifyResponse $ \r -> r { rspBody = SendFile f Nothing }+++------------------------------------------------------------------------------+-- | Sets the output to be the contents of the specified file, within the+-- given (start,end) range.+--+-- Calling 'sendFilePartial' will overwrite any output queued to be sent in+-- the 'Response'. If the response body is not modified after the call to+-- 'sendFilePartial', Snap will use the efficient @sendfile()@ system call on+-- platforms that support it.+--+-- If the response body is modified (using 'modifyResponseBody'), the file+-- will be read using @mmap()@.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> import qualified "Snap.Test" as T+-- ghci> 'writeFile' \"\/tmp\/snap-file\" \"Hello, sendFilePartial world\"+-- ghci> let r = T.get \"\/foo\/bar\" M.empty+-- ghci> T.runHandler r ('sendFilePartial' \"\/tmp\/snap-file\" (7, 28))+-- HTTP\/1.1 200 OK+-- content-length: 21+-- server: Snap\/test+-- date: Wed, 06 Aug 2014 17:47:20 GMT+-- Content-Length: 21+--+-- sendFilePartial world+-- @+sendFilePartial :: (MonadSnap m) => FilePath -> (Word64, Word64) -> m ()+sendFilePartial f rng = modifyResponse $ \r ->+ r { rspBody = SendFile f (Just rng) }+++------------------------------------------------------------------------------+-- | Runs a 'Snap' action with a locally-modified 'Request' state+-- object. The 'Request' object in the Snap monad state after the call+-- to localRequest will be unchanged.+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> import qualified "Snap.Test" as T+-- ghci> let r = T.get \"\/foo\/bar\" M.empty+-- ghci> r\' \<- T.buildRequest $ T.get \"\/bar\/foo\" M.empty+-- ghci> let printRqURI = 'getsRequest' 'rqURI' >>= 'writeBS' >> 'writeBS' \"\\n\"+-- ghci> T.runHandler r (printRqURI >> 'localRequest' (const r\') printRqURI)+-- HTTP\/1.1 200 OK+-- server: Snap\/test+-- date: Wed, 06 Aug 2014 15:34:12 GMT+--+-- \/foo\/bar+-- \/bar\/foo+--+-- @+localRequest :: MonadSnap m => (Request -> Request) -> m a -> m a+localRequest f m = do+ req <- getRequest++ runAct req <|> (putRequest req >> pass)++ where+ runAct req = do+ modifyRequest f+ result <- m+ putRequest req+ return result+{-# INLINE localRequest #-}+++------------------------------------------------------------------------------+-- | Fetches the 'Request' from state and hands it to the given action.+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> import qualified "Snap.Test" as T+-- ghci> import "Control.Monad.IO.Class"+-- ghci> let r = T.get \"\/foo\/bar\" M.empty+-- ghci> let h = 'withRequest' (\\rq -> 'liftIO' (T.requestToString rq) >>= 'writeBS')+-- ghci> T.runHandler r h+-- HTTP\/1.1 200 OK+-- server: Snap\/test+-- date: Wed, 06 Aug 2014 15:44:24 GMT+--+-- GET \/foo\/bar HTTP\/1.1+-- host: localhost+--+--+-- @+withRequest :: MonadSnap m => (Request -> m a) -> m a+withRequest = (getRequest >>=)+{-# INLINE withRequest #-}+++------------------------------------------------------------------------------+-- | Fetches the 'Response' from state and hands it to the given action.+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> import qualified "Snap.Test" as T+-- ghci> let r = T.get \"\/foo\/bar\" M.empty+-- ghci> T.runHandler r ('withResponse' $ 'writeBS' . 'rspStatusReason')+-- HTTP\/1.1 200 OK+-- server: Snap\/test+-- date: Wed, 06 Aug 2014 15:48:45 GMT+--+-- OK+-- @+withResponse :: MonadSnap m => (Response -> m a) -> m a+withResponse = (getResponse >>=)+{-# INLINE withResponse #-}+++------------------------------------------------------------------------------+-- | Modifies the 'Request' in the state to set the 'rqRemoteAddr'+-- field to the value in the X-Forwarded-For header. If the header is+-- not present, this action has no effect.+--+-- This action should be used only when working behind a reverse http+-- proxy that sets the X-Forwarded-For header. This is the only way to+-- ensure the value in the X-Forwarded-For header can be trusted.+--+-- This is provided as a filter so actions that require the remote+-- address can get it in a uniform manner. It has specifically limited+-- functionality to ensure that its transformation can be trusted,+-- when used correctly.+ipHeaderFilter :: MonadSnap m => m ()+ipHeaderFilter = ipHeaderFilter' "x-forwarded-for"+++------------------------------------------------------------------------------+-- | Modifies the 'Request' in the state to set the 'rqRemoteAddr'+-- field to the value from the header specified. If the header+-- specified is not present, this action has no effect.+--+-- This action should be used only when working behind a reverse http+-- proxy that sets the header being looked at. This is the only way to+-- ensure the value in the header can be trusted.+--+-- This is provided as a filter so actions that require the remote+-- address can get it in a uniform manner. It has specifically limited+-- functionality to ensure that its transformation can be trusted,+-- when used correctly.+ipHeaderFilter' :: MonadSnap m => CI ByteString -> m ()+ipHeaderFilter' header = do+ headerContents <- getHeader header <$> getRequest++ let whitespace = [ ' ', '\t', '\r', '\n' ]+ ipChrs = '.' : "0123456789"+ trim f s = f (`elem` s)++ clean = trim S.takeWhile ipChrs . trim S.dropWhile whitespace+ setIP ip = modifyRequest $ \rq -> rq { rqClientAddr = clean ip }+ maybe (return $! ()) setIP headerContents+++------------------------------------------------------------------------------+-- | This function brackets a Snap action in resource acquisition and+-- release. This is provided because MonadCatchIO's 'bracket' function+-- doesn't work properly in the case of a short-circuit return from+-- the action being bracketed.+--+-- In order to prevent confusion regarding the effects of the+-- aquisition and release actions on the Snap state, this function+-- doesn't accept Snap actions for the acquire or release actions.+--+-- This function will run the release action in all cases where the+-- acquire action succeeded. This includes the following behaviors+-- from the bracketed Snap action.+--+-- 1. Normal completion+--+-- 2. Short-circuit completion, either from calling 'fail' or 'finishWith'+--+-- 3. An exception being thrown.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> import qualified "Snap.Test" as T+-- ghci> let br = 'bracketSnap' (putStrLn \"before\") (const $ putStrLn \"after\")+-- ghci> T.runHandler (T.get \"/\" M.empty) (br $ const $ writeBS \"OK\")+-- before+-- after+-- HTTP\/1.1 200 OK+-- server: Snap\/test+-- date: Thu, 07 Aug 2014 18:41:50 GMT+--+-- OK+-- @+bracketSnap :: IO a -> (a -> IO b) -> (a -> Snap c) -> Snap c+bracketSnap before after thing = mask $ \restore ->+ stateTToSnap $ do+ a <- liftIO before+ let after' = liftIO $ after a+ r <- snapToStateT (restore $ thing a) `onException` after'+ _ <- after'+ return r+++------------------------------------------------------------------------------+-- | This exception is thrown if the handler you supply to 'runSnap' fails.+data NoHandlerException = NoHandlerException String+ deriving (Eq, Typeable)+++------------------------------------------------------------------------------+instance Show NoHandlerException where+ show (NoHandlerException e) = "No handler for request: failure was " ++ e+++------------------------------------------------------------------------------+instance Exception NoHandlerException+++------------------------------------------------------------------------------+-- | Terminate the HTTP session with the given exception.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> import qualified "Snap.Test" as T+-- ghci> import qualified "Control.Exception" as E+-- ghci> let r = T.get \"\/foo\/bar\" M.empty+-- ghci> T.runHandler r (terminateConnection $ E.AssertionFailed \"Assertion failed!\")+-- *** Exception: \<terminated: Assertion failed!>+-- @+terminateConnection :: (Exception e, MonadSnap m) => e -> m a+terminateConnection e =+ liftSnap $ Snap $ \_ fk -> fk $ EscapeSnap $ TerminateConnection+ $ SomeException e+++------------------------------------------------------------------------------+-- | Terminate the HTTP session and hand control to some external handler,+-- escaping all further HTTP traffic.+--+-- The external handler takes three arguments: a function to modify the thread's+-- timeout, and a read and a write ends to the socket.+escapeHttp :: MonadSnap m =>+ EscapeHttpHandler+ -> m ()+escapeHttp h = liftSnap $ Snap $ \_ fk st -> fk (EscapeSnap $ EscapeHttp h) st+++------------------------------------------------------------------------------+-- | Runs a 'Snap' monad action.+--+-- This function is mostly intended for library writers; instead of invoking+-- 'runSnap' directly, use 'Snap.Http.Server.httpServe' or+-- 'Snap.Test.runHandler' (for testing).+runSnap :: Snap a -- ^ Action to run.+ -> (ByteString -> IO ()) -- ^ Error logging action.+ -> ((Int -> Int) -> IO ()) -- ^ Timeout action.+ -> Request -- ^ HTTP request.+ -> IO (Request, Response)+runSnap (Snap m) logerr timeoutAction req =+ m ok diediedie ss+ where+ ok _ st = return (_snapRequest st, _snapResponse st)++ diediedie z !st = do+ resp <- case z of+ PassOnProcessing -> return fourohfour+ (EarlyTermination x) -> return x+ (EscapeSnap e) -> throwIO e+ return (_snapRequest st, resp)++ --------------------------------------------------------------------------+ fourohfour = do+ clearContentLength $+ setResponseStatus 404 "Not Found" $+ setResponseBody enum404 $+ emptyResponse++ --------------------------------------------------------------------------+ enum404 out = do+ is <- Streams.fromList html+ Streams.connect is out+ return out++ --------------------------------------------------------------------------+ html = map byteString [ "<!DOCTYPE html>\n"+ , "<html>\n"+ , "<head>\n"+ , "<title>Not found</title>\n"+ , "</head>\n"+ , "<body>\n"+ , "<code>No handler accepted \""+ , rqURI req+ , "\"</code>\n</body></html>"+ ]++ --------------------------------------------------------------------------+ dresp = emptyResponse++ --------------------------------------------------------------------------+ ss = SnapState req dresp logerr timeoutAction+{-# INLINE runSnap #-}++++--------------------------------------------------------------------------+-- | Post-process a finalized HTTP response:+--+-- * fixup content-length header+-- * properly handle 204/304 responses+-- * if request was HEAD, remove response body+--+-- Note that we do NOT deal with transfer-encoding: chunked or "connection:+-- close" here.+--+{-# INLINE fixupResponse #-}+fixupResponse :: Request -> Response -> IO Response+fixupResponse req rsp = {-# SCC "fixupResponse" #-} do+ rsp' <- case rspBody rsp of+ (Stream _) -> return rsp+ (SendFile f Nothing) -> setFileSize f rsp+ (SendFile _ (Just (s,e))) -> return $! setContentLength (e-s) rsp+ let !cl = if noBody then Nothing else rspContentLength rsp'+ let rsp'' = if noBody+ then rsp' { rspBody = Stream $ return . id+ , rspContentLength = Nothing+ }+ else rsp'+ return $! updateHeaders (H.fromList . addCL cl . fixup . H.toList) rsp''++ where+ --------------------------------------------------------------------------+ addCL Nothing xs = xs+ addCL (Just cl) xs = ("content-length", word64ToByteString cl):xs++ --------------------------------------------------------------------------+ setFileSize :: FilePath -> Response -> IO Response+ setFileSize fp r = {-# SCC "setFileSize" #-} do+ fs <- liftM fromIntegral $ getFileSize fp+ return $! r { rspContentLength = Just fs }++ ------------------------------------------------------------------------------+ getFileSize :: FilePath -> IO FileOffset+ getFileSize fp = liftM fileSize $ getFileStatus fp++ code = rspStatus rsp+ noBody = code == 204 || code == 304 || rqMethod req == HEAD++ ------------------------------------------------------------------------------+ fixup [] = []+ fixup (("date",_):xs) = fixup xs+ fixup (("content-length",_):xs) = fixup xs+ fixup (x@("transfer-encoding",_):xs) = if noBody+ then fixup xs+ else x : fixup xs+ fixup (x:xs) = x : fixup xs+++------------------------------------------------------------------------------+-- This number code stolen and massaged from Bryan's blog post:+-- http://www.serpentine.com/blog/2013/03/20/whats-good-for-c-is-good-for-haskell/++{-# INLINE countDigits #-}+countDigits :: Word64 -> Int+countDigits v0 = go 1 v0+ where go !k v+ | v < 10 = k+ | v < 100 = k + 1+ | v < 1000 = k + 2+ | v < 10000 = k + 3+ | otherwise = go (k+4) (v `quot` 10000)+++------------------------------------------------------------------------------+{-# INLINE word64ToByteString #-}+word64ToByteString :: Word64 -> ByteString+word64ToByteString d =+#if !MIN_VERSION_bytestring(0,10,6)+ S.inlinePerformIO $+#else+ S.accursedUnutterablePerformIO $+#endif+ if d < 10+ then S.create 1 $ \p -> poke p (i2w d)+ else let !n = countDigits d+ in S.create n $ posDecimal n d+++{-# INLINE posDecimal #-}+posDecimal :: Int -> Word64 -> Ptr Word8 -> IO ()+posDecimal !n0 !v0 !op0 = go n0 (plusPtr op0 (n0-1)) v0+ where go !n !op !v+ | n == 1 = poke op $! i2w v+ | otherwise = do+ let (!v', !d) = divMod v 10+ poke op $! i2w d+ go (n-1) (plusPtr op (-1)) v'+++{-# INLINE i2w #-}+i2w :: Word64 -> Word8+i2w v = 48 + fromIntegral v+++------------------------------------------------------------------------------+-- | Evaluates a 'Snap' monad action.+--+-- Unlike 'runSnap', 'evalSnap' evaluates to the value, not the 'Response'.+-- Like 'runSnap', 'evalSnap' is intended for library writers.+-- Note that there is no meaningful way of evaluating a 'Snap' monad action+-- that contains 'pass' without alternative (i.e. failure), 'finishWith'+-- (i.e. early termination), or 'escapeHttp' (i.e. escaping Snap).+-- In all of those three cases 'evalSnap' throws an IO exception.+evalSnap :: Snap a -- ^ Action to run.+ -> (ByteString -> IO ()) -- ^ Error logging action.+ -> ((Int -> Int) -> IO ()) -- ^ Timeout action.+ -> Request -- ^ HTTP request.+ -> IO a+evalSnap (Snap m) logerr timeoutAction req =+ m (\v _ -> return v) diediedie ss+ where+ diediedie z _ = case z of+ PassOnProcessing -> throwIO $ NoHandlerException "pass"+ (EarlyTermination _) -> throwIO $ ErrorCall "no value"+ (EscapeSnap e) -> throwIO e++ dresp = emptyResponse+ ss = SnapState req dresp logerr timeoutAction+{-# INLINE evalSnap #-}+++------------------------------------------------------------------------------+getParamFrom :: MonadSnap m =>+ (ByteString -> Request -> Maybe [ByteString])+ -> ByteString+ -> m (Maybe ByteString)+getParamFrom f k = do+ rq <- getRequest+ return $! liftM (S.intercalate " ") $ f k rq+{-# INLINE getParamFrom #-}+++------------------------------------------------------------------------------+-- | See 'rqParam'. Looks up a value for the given named parameter in the+-- 'Request'. If more than one value was entered for the given parameter name,+-- 'getParam' gloms the values together with @'S.intercalate' \" \"@.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> import qualified "Snap.Test" as T+-- ghci> import qualified "Data.ByteString.Char8" as B8+-- ghci> let r = T.get \"\/foo\/bar\" $ M.fromList [(\"foo\", [\"bar\"])]+-- ghci> T.runHandler r ('getParam' \"foo\" >>= 'writeBS' . B8.pack . show)+-- HTTP\/1.1 200 OK+-- server: Snap\/test+-- date: Mon, 11 Aug 2014 12:57:20 GMT+--+-- Just \"bar\"+-- @+getParam :: MonadSnap m+ => ByteString -- ^ parameter name to look up+ -> m (Maybe ByteString)+getParam = getParamFrom rqParam+{-# INLINE getParam #-}+++------------------------------------------------------------------------------+-- | See 'rqPostParam'. Looks up a value for the given named parameter in the+-- POST form parameters mapping in 'Request'. If more than one value was+-- entered for the given parameter name, 'getPostParam' gloms the values+-- together with: @'S.intercalate' \" \"@.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> import qualified "Snap.Test" as T+-- ghci> import qualified "Data.ByteString.Char8" as B8+-- ghci> let r = T.postUrlEncoded \"\/foo\/bar\" $ M.fromList [(\"foo\", [\"bar\"])]+-- ghci> T.runHandler r ('getPostParam' \"foo\" >>= 'writeBS' . B8.pack . show)+-- HTTP\/1.1 200 OK+-- server: Snap\/test+-- date: Mon, 11 Aug 2014 13:01:04 GMT+--+-- Just \"bar\"+-- @+getPostParam :: MonadSnap m+ => ByteString -- ^ parameter name to look up+ -> m (Maybe ByteString)+getPostParam = getParamFrom rqPostParam+{-# INLINE getPostParam #-}+++------------------------------------------------------------------------------+-- | See 'rqQueryParam'. Looks up a value for the given named parameter in the+-- query string parameters mapping in 'Request'. If more than one value was+-- entered for the given parameter name, 'getQueryParam' gloms the values+-- together with @'S.intercalate' \" \"@.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> import qualified "Snap.Test" as T+-- ghci> import qualified "Data.ByteString.Char8" as B8+-- ghci> let r = T.postUrlEncoded \"\/foo\/bar\" M.empty >> T.setQueryStringRaw \"foo=bar&foo=baz\"+-- ghci> T.runHandler r ('getQueryParam' \"foo\" >>= 'writeBS' . B8.pack . show)+-- HTTP\/1.1 200 OK+-- server: Snap\/test+-- date: Mon, 11 Aug 2014 13:06:50 GMT+--+-- Just \"bar baz\"+-- @+getQueryParam :: MonadSnap m+ => ByteString -- ^ parameter name to look up+ -> m (Maybe ByteString)+getQueryParam = getParamFrom rqQueryParam+{-# INLINE getQueryParam #-}+++------------------------------------------------------------------------------+-- | See 'rqParams'. Convenience function to return 'Params' from the+-- 'Request' inside of a 'MonadSnap' instance.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> import qualified "Snap.Test" as T+-- ghci> import qualified "Data.ByteString.Char8" as B8+-- ghci> let r = T.get \"\/foo\/bar\" $ M.fromList [(\"foo\", [\"bar\"])]+-- ghci> T.runHandler r ('getParams' >>= 'writeBS' . B8.pack . show)+-- HTTP\/1.1 200 OK+-- server: Snap\/test+-- date: Mon, 11 Aug 2014 13:02:54 GMT+--+-- fromList [(\"foo\",[\"bar\"])]+-- @+getParams :: MonadSnap m => m Params+getParams = getRequest >>= return . rqParams+++------------------------------------------------------------------------------+-- | See 'rqParams'. Convenience function to return 'Params' from the+-- 'Request' inside of a 'MonadSnap' instance.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> import qualified "Snap.Test" as T+-- ghci> import qualified "Data.ByteString.Char8" as B8+-- ghci> let r = T.postUrlEncoded \"\/foo\/bar\" $ M.fromList [(\"foo\", [\"bar\"])]+-- ghci> T.runHandler r ('getPostParams' >>= 'writeBS' . B8.pack . show)+-- HTTP/1.1 200 OK+-- server: Snap/test+-- date: Mon, 11 Aug 2014 13:04:34 GMT+--+-- fromList [("foo",["bar"])]+-- @+getPostParams :: MonadSnap m => m Params+getPostParams = getRequest >>= return . rqPostParams+++------------------------------------------------------------------------------+-- | See 'rqParams'. Convenience function to return 'Params' from the+-- 'Request' inside of a 'MonadSnap' instance.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> import qualified "Snap.Test" as T+-- ghci> import qualified "Data.ByteString.Char8" as B8+-- ghci> let r = T.postUrlEncoded \"\/foo\/bar\" M.empty >> T.setQueryStringRaw \"foo=bar&foo=baz\"+-- ghci> T.runHandler r ('getQueryParams' >>= 'writeBS' . B8.pack . show)+-- HTTP\/1.1 200 OK+-- server: Snap\/test+-- date: Mon, 11 Aug 2014 13:10:17 GMT+--+-- fromList [(\"foo\",[\"bar\",\"baz\"])]+-- @+getQueryParams :: MonadSnap m => m Params+getQueryParams = getRequest >>= return . rqQueryParams+++------------------------------------------------------------------------------+-- | Gets the HTTP 'Cookie' with the specified name.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> import qualified "Snap.Test" as T+-- ghci> import qualified "Data.ByteString.Char8" as B8+-- ghci> let cookie = 'Cookie' \"name\" \"value\" Nothing Nothing Nothing False False+-- ghci> let r = T.get \"\/foo\/bar\" M.empty >> T.addCookies [cookie]+-- ghci> T.runHandler r ('getCookie' \"name\" >>= 'writeBS' . B8.pack . show)+-- HTTP/1.1 200 OK+-- server: Snap/test+-- date: Thu, 07 Aug 2014 12:16:58 GMT+--+-- Just (Cookie {cookieName = "name", cookieValue = "value", ...})+-- @+getCookie :: MonadSnap m+ => ByteString+ -> m (Maybe Cookie)+getCookie name = withRequest $+ return . listToMaybe . filter (\c -> cookieName c == name) . rqCookies+++------------------------------------------------------------------------------+-- | Gets the HTTP 'Cookie' with the specified name and decodes it. If the+-- decoding fails, the handler calls pass.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> import qualified "Snap.Test" as T+-- ghci> let cookie = 'Cookie' \"name\" \"value\" Nothing Nothing Nothing False False+-- ghci> let r = T.get \"\/foo\/bar\" M.empty >> T.addCookies [cookie]+-- ghci> T.runHandler r ('readCookie' \"name\" >>= 'writeBS')+-- HTTP/1.1 200 OK+-- server: Snap/test+-- date: Thu, 07 Aug 2014 12:20:09 GMT+--+-- value+-- @+readCookie :: (MonadSnap m, R.Readable a)+ => ByteString+ -> m a+readCookie name = maybe pass (R.fromBS . cookieValue) =<< getCookie name+++------------------------------------------------------------------------------+-- | Expire given 'Cookie' in client's browser.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> import qualified "Snap.Test" as T+-- ghci> let r = T.get \"\/foo\/bar\" M.empty+-- ghci> let cookie = Cookie "name" "" Nothing (Just "/subsite") Nothing True False+-- ghci> T.runHandler r ('expireCookie' cookie)+-- HTTP/1.1 200 OK+-- set-cookie: name=; path=/subsite; expires=Sat, 24 Dec 1994 06:28:16 GMT; Secure+-- server: Snap/test+--+-- date: Thu, 07 Aug 2014 12:21:27 GMT+-- ghci> let cookie = Cookie "name" "value" Nothing Nothing Nothing False False+-- ghci> let r2 = T.get \"\/foo\/bar\" M.empty >> T.addCookies [cookie]+-- ghci> T.runHandler r ('getCookie' "name" >>= maybe (return ()) 'expireCookie')+-- HTTP/1.1 200 OK+-- set-cookie: name=; expires=Sat, 24 Dec 1994 06:28:16 GMT+-- server: Snap/test+--+--+-- @+expireCookie :: (MonadSnap m) => Cookie -> m ()+expireCookie cookie = do+ let old = UTCTime (ModifiedJulianDay 0) 0+ modifyResponse $ addResponseCookie+ $ cookie { cookieValue = ""+ , cookieExpires = (Just old) }++------------------------------------------------------------------------------+-- | Causes the handler thread to be killed @n@ seconds from now.+setTimeout :: MonadSnap m => Int -> m ()+setTimeout = modifyTimeout . const+++------------------------------------------------------------------------------+-- | Causes the handler thread to be killed at least @n@ seconds from now.+extendTimeout :: MonadSnap m => Int -> m ()+extendTimeout = modifyTimeout . max+++------------------------------------------------------------------------------+-- | Modifies the amount of time remaining before the request times out.+modifyTimeout :: MonadSnap m => (Int -> Int) -> m ()+modifyTimeout f = do+ m <- getTimeoutModifier+ liftIO $ m f+++------------------------------------------------------------------------------+-- | Returns an 'IO' action which you can use to modify the timeout value.+getTimeoutModifier :: MonadSnap m => m ((Int -> Int) -> IO ())+getTimeoutModifier = liftSnap $ liftM _snapModifyTimeout sget
src/Snap/Internal/Debug.hs view
@@ -1,77 +1,100 @@--- | An internal Snap module for (optionally) printing debugging--- messages. Normally 'debug' does nothing, but if you set @DEBUG=1@ in the--- environment you'll get debugging messages. We use 'unsafePerformIO' to make--- sure that the call to 'getEnv' is only made once.+-- | An internal Snap module for (optionally) printing debugging messages. To+-- enable debug output, compile the library with the @debug@ flag (off by+-- default) and set the environment variable @DEBUG@ to @1@. We use+-- 'unsafePerformIO' to make sure that the call to 'getEnv' is only made once. -- -- /N.B./ this is an internal interface, please don't write external code that -- depends on it. {-# LANGUAGE BangPatterns #-} {-# LANGUAGE CPP #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# OPTIONS_GHC -fno-cse #-} -module Snap.Internal.Debug where+module Snap.Internal.Debug (debug, debugErrno) where -------------------------------------------------------------------------------import Control.Concurrent-import Control.DeepSeq-import Control.Exception-import Control.Monad.Trans-import Data.Char-import Data.List-import Data.Maybe-import Foreign.C.Error-import System.Environment-import System.IO-import System.IO.Unsafe-import Text.Printf+import Control.Monad.IO.Class (MonadIO (..))++#ifndef NODEBUG+import Control.Concurrent (MVar, myThreadId, newMVar, withMVar)+import Control.Exception (SomeException, try)+import Data.Char (toLower)+import Data.List (stripPrefix)+import Data.Maybe (fromMaybe)+import Foreign.C.Error (errnoToIOError, getErrno)+import System.Environment (getEnv)+import System.IO (hFlush, hPutStrLn, stderr)+import System.IO.Unsafe (unsafePerformIO)+import Text.Printf (printf)+#endif ------------------------------------------------------------------------------ -debug, debugErrno :: forall m . (MonadIO m => String -> m ())+-- | Print out the provided debug message prefixed by the thread ID.+--+-- Example:+--+-- @+-- ghci> debug "Some debug message"+-- [ 225] Some debug message+-- @+debug :: MonadIO m => String -> m () +-- | Print out the error message corresponding to the 'Foreign.C.Error.Errno'+-- value returned by 'Foreign.C.Error.getErrno' together with any additional+-- information provided by the user (usually the location where the error+-- occurred).+--+-- Example:+--+-- @+-- ghci> debugErrno "path/to/Source.hs:34"+-- [ 323] path/to/Source.hs:34: failed (Success)+-- @+debugErrno :: MonadIO m => String -> m ()++ #ifndef NODEBUG {-# NOINLINE debug #-}-debug = let !x = unsafePerformIO $! do- !e <- try $ getEnv "DEBUG"- - !f <- either (\(_::SomeException) -> return debugIgnore)- (\y -> if y == "1" || y == "on"- then return debugOn- else if y == "testsuite"- then return debugSeq- else return debugIgnore)- (fmap (map toLower) e)- return $! f+debug = let !x = unsafePerformIO $ do+ !e <- try $ getEnv "DEBUG"++ !f <- either (\(_::SomeException) -> return debugIgnore)+ (\y0 -> let y = map toLower y0+ in if y == "1" || y == "on"+ then return debugOn+ else if y == "testsuite"+ then return debugSeq+ else return debugIgnore)+ e+ return $! f in x {-# NOINLINE debugErrno #-} debugErrno = let !x = unsafePerformIO $ do- e <- try $ getEnv "DEBUG"- - !f <- either (\(_::SomeException) -> return debugErrnoIgnore)- (\y -> if y == "1" || y == "on"- then return debugErrnoOn- else if y == "testsuite"- then return debugErrnoSeq- else return debugErrnoIgnore)- (fmap (map toLower) e)- return $! f+ e <- try $ getEnv "DEBUG"+ !f <- either (\(_::SomeException) -> return debugErrnoIgnore)+ (\y0 -> let y = map toLower y0+ in if y == "1" || y == "on"+ then return debugErrnoOn+ else if y == "testsuite"+ then return debugSeq+ else return debugErrnoIgnore)+ e+ return $! f in x ------------------------------------------------------------------------------ debugSeq :: (MonadIO m) => String -> m ()-debugSeq !s = let !s' = rnf s in return $! s' `deepseq` ()+debugSeq !s = length s `seq` return $! () {-# NOINLINE debugSeq #-}--debugErrnoSeq :: (MonadIO m) => String -> m ()-debugErrnoSeq !s = let !s' = rnf s in return $! s' `deepseq` ()-{-# NOINLINE debugErrnoSeq #-} ------------------------------------------------------------------------------ _debugMVar :: MVar ()
src/Snap/Internal/Http/Types.hs view
@@ -1,771 +1,1413 @@--- | An internal Snap module containing HTTP types.------ /N.B./ this is an internal interface, please don't write user code that--- depends on it. Most of these declarations (except for the--- unsafe/encapsulation-breaking ones) are re-exported from "Snap.Types".--{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE CPP #-}-{-# LANGUAGE EmptyDataDecls #-}-{-# LANGUAGE ForeignFunctionInterface #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE TypeSynonymInstances #-}--module Snap.Internal.Http.Types where----------------------------------------------------------------------------------import Control.Applicative hiding (empty)-import Control.Monad (liftM, when)-import qualified Data.Attoparsec as Atto-import Data.Attoparsec hiding (many, Result(..))-import Data.Bits-import Data.ByteString (ByteString)-import qualified Data.ByteString.Char8 as B-import Data.ByteString.Internal (c2w,w2c)-import qualified Data.ByteString.Nums.Careless.Hex as Cvt-import qualified Data.ByteString as S-import qualified Data.ByteString.Unsafe as S-import Data.Char-import Data.DList (DList)-import qualified Data.DList as DL-import Data.Int-import qualified Data.IntMap as IM-import Data.IORef-import Data.List hiding (take)-import Data.Map (Map)-import qualified Data.Map as Map-import Data.Maybe-import Data.Monoid-import Data.Serialize.Builder-import Data.Time.Clock-import Data.Time.Format-import Data.Word-import Foreign hiding (new)-import Foreign.C.Types-import Prelude hiding (take)-import System.Locale (defaultTimeLocale)---#ifdef PORTABLE-import Data.Time.LocalTime-import Data.Time.Clock.POSIX-#else-import Foreign.C.String-#endif---------------------------------------------------------------------------------import Data.CIByteString-import qualified Snap.Iteratee as I---#ifndef PORTABLE----------------------------------------------------------------------------------- foreign imports from cbits--foreign import ccall unsafe "set_c_locale"- set_c_locale :: IO ()--foreign import ccall unsafe "c_parse_http_time"- c_parse_http_time :: CString -> IO CTime--foreign import ccall unsafe "c_format_http_time"- c_format_http_time :: CTime -> CString -> IO ()--foreign import ccall unsafe "c_format_log_time"- c_format_log_time :: CTime -> CString -> IO ()--#endif---------------------------------------------------------------------------------type Enumerator a = I.Enumerator IO a----------------------------------------------------------------------------------- | A type alias for a case-insensitive key-value mapping.-type Headers = Map CIByteString [ByteString]------------------------------------------------------------------------------------ | A typeclass for datatypes which contain HTTP headers.-class HasHeaders a where-- -- | Modify the datatype's headers.- updateHeaders :: (Headers -> Headers) -> a -> a-- -- | Retrieve the headers from a datatype that has headers.- headers :: a -> Headers------------------------------------------------------------------------------------ | Adds a header key-value-pair to the 'HasHeaders' datatype. If a header with--- the same name already exists, the new value is appended to the headers list.-addHeader :: (HasHeaders a) => CIByteString -> ByteString -> a -> a-addHeader k v = updateHeaders $ Map.insertWith' (++) k [v]------------------------------------------------------------------------------------ | Sets a header key-value-pair in a 'HasHeaders' datatype. If a header with--- the same name already exists, it is overwritten with the new value.-setHeader :: (HasHeaders a) => CIByteString -> ByteString -> a -> a-setHeader k v = updateHeaders $ Map.insert k [v]------------------------------------------------------------------------------------ | Gets all of the values for a given header.-getHeaders :: (HasHeaders a) => CIByteString -> a -> Maybe [ByteString]-getHeaders k a = Map.lookup k $ headers a------------------------------------------------------------------------------------ | Gets a header value out of a 'HasHeaders' datatype. If many headers came--- in with the same name, they will be catenated together.-getHeader :: (HasHeaders a) => CIByteString -> a -> Maybe ByteString-getHeader k a = liftM (S.intercalate " ") (Map.lookup k $ headers a)------------------------------------------------------------------------------------ | Clears a header value from a 'HasHeaders' datatype.-deleteHeader :: (HasHeaders a) => CIByteString -> a -> a-deleteHeader k = updateHeaders $ Map.delete k------------------------------------------------------------------------------------ | Enumerates the HTTP method values (see--- <http://tools.ietf.org/html/rfc2068.html#section-5.1.1>).-data Method = GET | HEAD | POST | PUT | DELETE | TRACE | OPTIONS | CONNECT- deriving(Show,Read,Ord,Eq)----------------------------------------------------------------------------------type HttpVersion = (Int,Int)------------------------------------------------------------------------------------ | A datatype representing an HTTP cookie.-data Cookie = Cookie {- -- | The name of the cookie.- cookieName :: !ByteString-- -- | The cookie's string value.- , cookieValue :: !ByteString-- -- | The cookie's expiration value, if it has one.- , cookieExpires :: !(Maybe UTCTime)-- -- | The cookie's \"domain\" value, if it has one.- , cookieDomain :: !(Maybe ByteString)-- -- | The cookie path.- , cookiePath :: !(Maybe ByteString)-} deriving (Eq, Show)------------------------------------------------------------------------------------ | A type alias for the HTTP parameters mapping. Each parameter--- key maps to a list of ByteString values; if a parameter is specified--- multiple times (e.g.: \"@GET /foo?param=bar1¶m=bar2@\"), looking up--- \"@param@\" in the mapping will give you @[\"bar1\", \"bar2\"]@.-type Params = Map ByteString [ByteString]------------------------------------------------------------------------------------ request type----------------------------------------------------------------------------------- | An existential wrapper for the 'Enumerator' type-data SomeEnumerator = SomeEnumerator (forall a . Enumerator a)------------------------------------------------------------------------------------ | Contains all of the information about an incoming HTTP request.-data Request = Request- { -- | The server name of the request, as it came in from the request's- -- @Host:@ header.- rqServerName :: !ByteString-- -- | Returns the port number the HTTP server is listening on.- , rqServerPort :: !Int-- -- | The remote IP address.- , rqRemoteAddr :: !ByteString-- -- | The remote TCP port number.- , rqRemotePort :: !Int-- -- | The local IP address for this request.- , rqLocalAddr :: !ByteString-- -- | Returns the port number the HTTP server is listening on.- , rqLocalPort :: !Int-- -- | Returns the HTTP server's idea of its local hostname.- , rqLocalHostname :: !ByteString-- -- | Returns @True@ if this is an @HTTPS@ session (currently always- -- @False@).- , rqIsSecure :: !Bool- , rqHeaders :: Headers- , rqBody :: IORef SomeEnumerator-- -- | Returns the @Content-Length@ of the HTTP request body.- , rqContentLength :: !(Maybe Int)-- -- | Returns the HTTP request method.- , rqMethod :: !Method-- -- | Returns the HTTP version used by the client.- , rqVersion :: !HttpVersion-- -- | Returns a list of the cookies that came in from the HTTP request- -- headers.- , rqCookies :: [Cookie]--- -- | We'll be doing web components (or \"snaplets\") for version 0.2. The- -- \"snaplet path\" refers to the place on the URL where your containing- -- snaplet is hung. The value of 'rqSnapletPath' is either @\"\"@ (at the- -- top-level context) or is a path beginning with a slash, but not ending- -- with one.- --- -- An identity is that:- --- -- > rqURI r == 'S.concat' [ rqSnapletPath r- -- > , rqContextPath r- -- > , rqPathInfo r ]- --- -- note that until we introduce snaplets in v0.2, 'rqSnapletPath' will be- -- \"\"- , rqSnapletPath :: !ByteString-- -- | Handlers can (/will be; --ed/) be hung on a @URI@ \"entry point\";- -- this is called the \"context path\". If a handler is hung on the- -- context path @\"\/foo\/\"@, and you request @\"\/foo\/bar\"@, the value- -- of 'rqPathInfo' will be @\"bar\"@.- , rqPathInfo :: !ByteString-- -- | The \"context path\" of the request; catenating 'rqContextPath', and- -- 'rqPathInfo' should get you back to the original 'rqURI'. The- -- 'rqContextPath' always begins and ends with a slash (@\"\/\"@)- -- character, and represents the path (relative to your- -- component\/snaplet) you took to get to your handler.- , rqContextPath :: !ByteString-- -- | Returns the @URI@ requested by the client.- , rqURI :: !ByteString-- -- | Returns the HTTP query string for this 'Request'.- , rqQueryString :: !ByteString-- -- | Returns the 'Params' mapping for this 'Request'. \"Parameters\" are- -- automatically decoded from the query string and @POST@ body and- -- entered into this mapping.- , rqParams :: Params- }----------------------------------------------------------------------------------instance Show Request where- show r = concat [ "Request <\n"- , body- , ">" ]- where- body = concat $ map ((" "++) . (++ "\n")) [- sname- , remote- , local- , beginheaders- , hdrs- , endheaders- , contentlength- , method- , version- , cookies- , pathinfo- , contextpath- , snapletpath- , uri- , params- ]-- sname = concat [ "server-name: ", toStr $ rqServerName r ]- remote = concat [ "remote: "- , toStr $ rqRemoteAddr r- , ":"- , show (rqRemotePort r)- ]- local = concat [ "local: "- , toStr $ rqLocalAddr r- , ":"- , show $ rqServerPort r- ]- beginheaders = "Headers:\n ========================================"- endheaders = " ========================================"- hdrs' (a,b) = (B.unpack $ unCI a) ++ ": " ++ (show (map B.unpack b))- hdrs = " " ++ (concat $ intersperse "\n " $- map hdrs' (Map.toAscList $ rqHeaders r))- contentlength = concat [ "content-length: "- , show $ rqContentLength r- ]- method = concat [ "method: "- , show $ rqMethod r- ]- version = concat [ "version: "- , show $ rqVersion r- ]- cookies' = " " ++ (concat $ intersperse "\n " $- map show $ rqCookies r)- cookies = concat [ "cookies:\n"- , " ========================================\n"- , cookies'- , "\n ========================================"- ]- pathinfo = concat [ "pathinfo: ", toStr $ rqPathInfo r ]- contextpath = concat [ "contextpath: ", toStr $ rqContextPath r ]- snapletpath = concat [ "snapletpath: ", toStr $ rqSnapletPath r ]- uri = concat [ "URI: ", toStr $ rqURI r ]- params' = " " ++- (concat $ intersperse "\n " $- map (\ (a,b) -> B.unpack a ++ ": " ++ show b) $- Map.toAscList $ rqParams r)- params = concat [ "params:\n"- , " ========================================\n"- , params'- , "\n ========================================"- ]----------------------------------------------------------------------------------instance HasHeaders Request where- headers = rqHeaders- updateHeaders f r = r { rqHeaders = f (rqHeaders r) }----------------------------------------------------------------------------------instance HasHeaders Headers where- headers = id- updateHeaders = id----------------------------------------------------------------------------------- response type---------------------------------------------------------------------------------data ResponseBody = Enum (forall a . Enumerator a)- -- ^ output body is enumerator-- | SendFile FilePath (Maybe (Int64,Int64))- -- ^ output body is sendfile(), optional second argument- -- is a byte range to send----------------------------------------------------------------------------------rspBodyMap :: (forall a . Enumerator a -> Enumerator a)- -> ResponseBody- -> ResponseBody-rspBodyMap f b = Enum $ f $ rspBodyToEnum b----------------------------------------------------------------------------------rspBodyToEnum :: ResponseBody -> Enumerator a-rspBodyToEnum (Enum e) = e-rspBodyToEnum (SendFile fp Nothing) = I.enumFile fp-rspBodyToEnum (SendFile fp (Just s)) = I.enumFilePartial fp s------------------------------------------------------------------------------------ | Represents an HTTP response.-data Response = Response- { rspHeaders :: Headers- , rspHttpVersion :: !HttpVersion-- -- | We will need to inspect the content length no matter what, and- -- looking up \"content-length\" in the headers and parsing the number- -- out of the text will be too expensive.- , rspContentLength :: !(Maybe Int64)- , rspBody :: ResponseBody-- -- | Returns the HTTP status code.- , rspStatus :: !Int-- -- | Returns the HTTP status explanation string.- , rspStatusReason :: !ByteString-- -- | If true, we are transforming the request body with- -- 'transformRequestBody'- , rspTransformingRqBody :: !Bool- }----------------------------------------------------------------------------------instance Show Response where- show r = concat [ "Response <\n"- , body- , ">" ]- where- body = concat $ map ((" "++) . (++ "\n")) [- hdrs- , version- , status- , reason- ]-- hdrs = concat [ "headers:\n"- , " ==============================\n "- , show $ rspHeaders r- , "\n ==============================" ]-- version = concat [ "version: ", show $ rspHttpVersion r ]- status = concat [ "status: ", show $ rspStatus r ]- reason = concat [ "reason: ", toStr $ rspStatusReason r ]----------------------------------------------------------------------------------instance HasHeaders Response where- headers = rspHeaders- updateHeaders f r = r { rspHeaders = f (rspHeaders r) }------------------------------------------------------------------------------------ | Looks up the value(s) for the given named parameter. Parameters initially--- come from the request's query string and any decoded POST body (if the--- request's @Content-Type@ is @application\/x-www-form-urlencoded@). Parameter--- values can be modified within handlers using "rqModifyParams".-rqParam :: ByteString -- ^ parameter name to look up- -> Request -- ^ HTTP request- -> Maybe [ByteString]-rqParam k rq = Map.lookup k $ rqParams rq-{-# INLINE rqParam #-}------------------------------------------------------------------------------------ | Modifies the parameters mapping (which is a @Map ByteString ByteString@) in--- a 'Request' using the given function.-rqModifyParams :: (Params -> Params) -> Request -> Request-rqModifyParams f r = r { rqParams = p }- where- p = f $ rqParams r-{-# INLINE rqModifyParams #-}------------------------------------------------------------------------------------ | Writes a key-value pair to the parameters mapping within the given request.-rqSetParam :: ByteString -- ^ parameter name- -> [ByteString] -- ^ parameter values- -> Request -- ^ request- -> Request-rqSetParam k v = rqModifyParams $ Map.insert k v-{-# INLINE rqSetParam #-}----------------------------------------------------------------------------------- responses----------------------------------------------------------------------------------- | An empty 'Response'.-emptyResponse :: Response-emptyResponse = Response Map.empty (1,1) Nothing (Enum return) 200- "OK" False------------------------------------------------------------------------------------ | Sets an HTTP response body to the given 'Enumerator' value.-setResponseBody :: (forall a . Enumerator a) -- ^ new response body- -- enumerator- -> Response -- ^ response to modify- -> Response-setResponseBody e r = r { rspBody = Enum e }-{-# INLINE setResponseBody #-}------------------------------------------------------------------------------------ | Sets the HTTP response status. Note: normally you would use--- 'setResponseCode' unless you needed a custom response explanation.----setResponseStatus :: Int -- ^ HTTP response integer code- -> ByteString -- ^ HTTP response explanation- -> Response -- ^ Response to be modified- -> Response-setResponseStatus s reason r = r { rspStatus=s, rspStatusReason=reason }-{-# INLINE setResponseStatus #-}------------------------------------------------------------------------------------ | Sets the HTTP response code.-setResponseCode :: Int -- ^ HTTP response integer code- -> Response -- ^ Response to be modified- -> Response-setResponseCode s r = setResponseStatus s reason r- where- reason = fromMaybe "Unknown" (IM.lookup s statusReasonMap)-{-# INLINE setResponseCode #-}------------------------------------------------------------------------------------ | Modifies a response body.-modifyResponseBody :: (forall a . Enumerator a -> Enumerator a)- -> Response- -> Response-modifyResponseBody f r = r { rspBody = rspBodyMap f (rspBody r) }-{-# INLINE modifyResponseBody #-}------------------------------------------------------------------------------------ | Sets the @Content-Type@ in the 'Response' headers.-setContentType :: ByteString -> Response -> Response-setContentType = setHeader "Content-Type"-{-# INLINE setContentType #-}------------------------------------------------------------------------------------ | Adds an HTTP 'Cookie' to the 'Response' headers.-addCookie :: Cookie -- ^ cookie value- -> Response -- ^ response to modify- -> Response-addCookie (Cookie k v mbExpTime mbDomain mbPath) = updateHeaders f- where- f = Map.insertWith' (++) "Set-Cookie" [cookie]- cookie = S.concat [k, "=", v, path, exptime, domain]- path = maybe "" (S.append "; path=") mbPath- domain = maybe "" (S.append "; domain=") mbDomain- exptime = maybe "" (S.append "; expires=" . fmt) mbExpTime- fmt = fromStr . formatTime defaultTimeLocale "%a, %d-%b-%Y %H:%M:%S GMT"------------------------------------------------------------------------------------ | A note here: if you want to set the @Content-Length@ for the response,--- Snap forces you to do it with this function rather than by setting it in the--- headers; the @Content-Length@ in the headers will be ignored.------ The reason for this is that Snap needs to look up the value of--- @Content-Length@ for each request, and looking the string value up in the--- headers and parsing the number out of the text will be too expensive.------ If you don't set a content length in your response, HTTP keep-alive will be--- disabled for HTTP\/1.0 clients, forcing a @Connection: close@. For HTTP\/1.1--- clients, Snap will switch to the chunked transfer encoding if--- @Content-Length@ is not specified.-setContentLength :: Int64 -> Response -> Response-setContentLength l r = r { rspContentLength = Just l }-{-# INLINE setContentLength #-}------------------------------------------------------------------------------------ | Removes any @Content-Length@ set in the 'Response'.-clearContentLength :: Response -> Response-clearContentLength r = r { rspContentLength = Nothing }-{-# INLINE clearContentLength #-}------------------------------------------------------------------------------------ HTTP dates---- | Converts a 'CTime' into an HTTP timestamp.-formatHttpTime :: CTime -> IO ByteString---- | Converts a 'CTime' into common log entry format.-formatLogTime :: CTime -> IO ByteString---- | Converts an HTTP timestamp into a 'CTime'.-parseHttpTime :: ByteString -> IO CTime--#ifdef PORTABLE--formatHttpTime = return . format . toUTCTime- where- format :: UTCTime -> ByteString- format = fromStr . formatTime defaultTimeLocale "%a, %d %b %Y %X GMT"-- toUTCTime :: CTime -> UTCTime- toUTCTime = posixSecondsToUTCTime . realToFrac--formatLogTime ctime = do- t <- utcToLocalZonedTime $ toUTCTime ctime- return $ format t-- where- format :: ZonedTime -> ByteString- format = fromStr . formatTime defaultTimeLocale "%d/%b/%Y:%H:%M:%S %z"-- toUTCTime :: CTime -> UTCTime- toUTCTime = posixSecondsToUTCTime . realToFrac---parseHttpTime = return . toCTime . parse . toStr- where- parse :: String -> Maybe UTCTime- parse = parseTime defaultTimeLocale "%a, %d %b %Y %H:%M:%S GMT"-- toCTime :: Maybe UTCTime -> CTime- toCTime (Just t) = fromInteger $ truncate $ utcTimeToPOSIXSeconds t- toCTime Nothing = fromInteger 0--#else--formatLogTime t = do- ptr <- mallocBytes 40- c_format_log_time t ptr- S.unsafePackMallocCString ptr--formatHttpTime t = do- ptr <- mallocBytes 40- c_format_http_time t ptr- S.unsafePackMallocCString ptr--parseHttpTime s = S.unsafeUseAsCString s $ \ptr ->- c_parse_http_time ptr--#endif------------------------------------------------------------------------------------ URL ENCODING---------------------------------------------------------------------------------parseToCompletion :: Parser a -> ByteString -> Maybe a-parseToCompletion p s = toResult $ finish r- where- r = parse p s-- toResult (Atto.Done _ c) = Just c- toResult _ = Nothing----------------------------------------------------------------------------------pUrlEscaped :: Parser ByteString-pUrlEscaped = do- sq <- nextChunk DL.empty- return $ S.concat $ DL.toList sq-- where- nextChunk :: DList ByteString -> Parser (DList ByteString)- nextChunk s = (endOfInput *> pure s) <|> do- c <- anyWord8- case w2c c of- '+' -> plusSpace s- '%' -> percentEncoded s- _ -> unEncoded c s-- percentEncoded :: DList ByteString -> Parser (DList ByteString)- percentEncoded l = do- hx <- take 2- when (S.length hx /= 2 ||- (not $ S.all (isHexDigit . w2c) hx)) $- fail "bad hex in url"- - let code = (Cvt.hex hx) :: Word8- nextChunk $ DL.snoc l (S.singleton code)-- unEncoded :: Word8 -> DList ByteString -> Parser (DList ByteString)- unEncoded c l' = do- let l = DL.snoc l' (S.singleton c)- bs <- takeTill (flip elem (map c2w "%+"))- if S.null bs- then nextChunk l- else nextChunk $ DL.snoc l bs-- plusSpace :: DList ByteString -> Parser (DList ByteString)- plusSpace l = nextChunk (DL.snoc l (S.singleton $ c2w ' '))------------------------------------------------------------------------------------ | Decodes an URL-escaped string (see--- <http://tools.ietf.org/html/rfc2396.html#section-2.4>)-urlDecode :: ByteString -> Maybe ByteString-urlDecode = parseToCompletion pUrlEscaped------------------------------------------------------------------------------------ "...Only alphanumerics [0-9a-zA-Z], the special characters "$-_.+!*'(),"--- [not including the quotes - ed], and reserved characters used for their--- reserved purposes may be used unencoded within a URL."---- | URL-escapes a string (see--- <http://tools.ietf.org/html/rfc2396.html#section-2.4>)-urlEncode :: ByteString -> ByteString-urlEncode = toByteString . S.foldl' f empty- where- f b c =- if c == c2w ' '- then b `mappend` singleton (c2w '+')- else if isKosher c- then b `mappend` singleton c- else b `mappend` hexd c-- isKosher w = any ($ c) [ isAlphaNum- , flip elem ['$', '-', '.', '!', '*'- , '\'', '(', ')', ',' ]]- where- c = w2c w----------------------------------------------------------------------------------hexd :: Word8 -> Builder-hexd c = singleton (c2w '%') `mappend` singleton hi `mappend` singleton low- where- d = c2w . intToDigit- low = d $ fromEnum $ c .&. 0xf- hi = d $ fromEnum $ (c .&. 0xf0) `shift` (-4)----------------------------------------------------------------------------------finish :: Atto.Result a -> Atto.Result a-finish (Atto.Partial f) = flip feed "" $ f ""-finish x = x------------------------------------------------------------------------------------ local definitions-fromStr :: String -> ByteString-fromStr = S.pack . map c2w-{-# INLINE fromStr #-}----------------------------------------------------------------------------------- private helper functions-toStr :: ByteString -> String-toStr = map w2c . S.unpack----------------------------------------------------------------------------------statusReasonMap :: IM.IntMap ByteString-statusReasonMap = IM.fromList [- (100, "Continue"),- (101, "Switching Protocols"),- (200, "OK"),- (201, "Created"),- (202, "Accepted"),- (203, "Non-Authoritative Information"),- (204, "No Content"),- (205, "Reset Content"),- (206, "Partial Content"),- (300, "Multiple Choices"),- (301, "Moved Permanently"),- (302, "Found"),- (303, "See Other"),- (304, "Not Modified"),- (305, "Use Proxy"),- (307, "Temporary Redirect"),- (400, "Bad Request"),- (401, "Unauthorized"),- (402, "Payment Required"),- (403, "Forbidden"),- (404, "Not Found"),- (405, "Method Not Allowed"),- (406, "Not Acceptable"),- (407, "Proxy Authentication Required"),- (408, "Request Time-out"),- (409, "Conflict"),- (410, "Gone"),- (411, "Length Required"),- (412, "Precondition Failed"),- (413, "Request Entity Too Large"),- (414, "Request-URI Too Large"),- (415, "Unsupported Media Type"),- (416, "Requested range not satisfiable"),- (417, "Expectation Failed"),- (500, "Internal Server Error"),- (501, "Not Implemented"),- (502, "Bad Gateway"),- (503, "Service Unavailable"),- (504, "Gateway Time-out"),- (505, "HTTP Version not supported")- ]+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE EmptyDataDecls #-}+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE ForeignFunctionInterface #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE Rank2Types #-}+{-# LANGUAGE TypeSynonymInstances #-}++------------------------------------------------------------------------------+-- | An internal Snap module containing HTTP types.+--+-- /N.B./ this is an internal interface, please don't write user code that+-- depends on it. Most of these declarations (except for the+-- unsafe/encapsulation-breaking ones) are re-exported from "Snap.Core".+--+module Snap.Internal.Http.Types where++------------------------------------------------------------------------------+import Control.Monad (unless)+import Data.ByteString (ByteString)+import Data.ByteString.Builder (Builder, byteString, toLazyByteString)+import qualified Data.ByteString.Char8 as S+import qualified Data.ByteString.Lazy.Char8 as L+import Data.CaseInsensitive (CI)+import qualified Data.CaseInsensitive as CI+import qualified Data.IntMap as IM+import Data.List hiding (take)+import Data.Map (Map)+import qualified Data.Map as Map+import Data.Maybe (Maybe (..), fromMaybe, maybe)+import Data.Monoid (mconcat)+import Data.Time.Clock (UTCTime)+import Data.Time.Clock.POSIX (utcTimeToPOSIXSeconds)+import Data.Word (Word64)+import Foreign.C.Types (CTime (..))+import Prelude (Bool (..), Eq (..), FilePath, IO, Int, Integral (..), Monad (..), Num ((-)), Ord (..), Ordering (..), Read (..), Show (..), String, fmap, fromInteger, fromIntegral, id, not, otherwise, truncate, ($), (.))+#ifdef PORTABLE+import Prelude (realToFrac, ($!))+#endif+import System.IO (IOMode (ReadMode), SeekMode (AbsoluteSeek), hSeek, withBinaryFile)+import System.IO.Streams (InputStream, OutputStream)+import qualified System.IO.Streams as Streams+import System.IO.Unsafe (unsafePerformIO)++------------------------------------------------------------------------------+#ifdef PORTABLE+import Data.Time.Clock.POSIX+import Data.Time.Clock.POSIX+import Data.Time.Format as Time+import Data.Time.Locale.Compat (defaultTimeLocale)+import Data.Time.LocalTime+#else+import qualified Data.ByteString.Unsafe as S+import Data.Time.Format ()+import Foreign.C.String (CString)+import Foreign.Marshal.Alloc (mallocBytes)+#endif++------------------------------------------------------------------------------+import Snap.Types.Headers (Headers)+import qualified Snap.Types.Headers as H+++#ifndef PORTABLE++------------------------------------------------------------------------------+-- foreign imports from cbits+foreign import ccall unsafe "set_c_locale"+ set_c_locale :: IO ()++foreign import ccall unsafe "c_parse_http_time"+ c_parse_http_time :: CString -> IO CTime++foreign import ccall unsafe "c_format_http_time"+ c_format_http_time :: CTime -> CString -> IO ()++foreign import ccall unsafe "c_format_log_time"+ c_format_log_time :: CTime -> CString -> IO ()++#endif+++------------------------------------------------------------------------------+-- | A typeclass for datatypes which contain HTTP headers.+class HasHeaders a where+ -- | Modify the datatype's headers.+ updateHeaders :: (Headers -> Headers) -> a -> a++ -- | Retrieve the headers from a datatype that has headers.+ headers :: a -> Headers+++------------------------------------------------------------------------------+-- | Adds a header key-value-pair to the 'HasHeaders' datatype. If a header+-- with the same name already exists, the new value is appended to the headers+-- list.+--+-- Example:+--+-- @+-- ghci> import qualified "Snap.Types.Headers" as H+-- ghci> 'addHeader' \"Host\" "localhost" H.'empty'+-- H {unH = [("host","localhost")]}+-- ghci> 'addHeader' \"Host\" "127.0.0.1" it+-- H {unH = [("host","localhost,127.0.0.1")]}+-- @+addHeader :: (HasHeaders a) => CI ByteString -> ByteString -> a -> a+addHeader k v = updateHeaders $ H.insert k v+++------------------------------------------------------------------------------+-- | Sets a header key-value-pair in a 'HasHeaders' datatype. If a header with+-- the same name already exists, it is overwritten with the new value.+--+-- Example:+--+-- @+-- ghci> import qualified "Snap.Types.Headers" as H+-- ghci> 'setHeader' \"Host\" "localhost" H.'empty'+-- H {unH = [(\"host\",\"localhost\")]}+-- ghci> setHeader \"Host\" "127.0.0.1" it+-- H {unH = [("host","127.0.0.1")]}+-- @+setHeader :: (HasHeaders a) => CI ByteString -> ByteString -> a -> a+setHeader k v = updateHeaders $ H.set k v+++------------------------------------------------------------------------------+-- | Gets a header value out of a 'HasHeaders' datatype.+--+-- Example:+--+-- @+-- ghci> import qualified "Snap.Types.Headers" as H+-- ghci> 'getHeader' \"Host\" $ 'setHeader' \"Host\" "localhost" H.'empty'+-- Just "localhost"+-- @+getHeader :: (HasHeaders a) => CI ByteString -> a -> Maybe ByteString+getHeader k a = H.lookup k $ headers a+++------------------------------------------------------------------------------+-- | Lists all the headers out of a 'HasHeaders' datatype. If many+-- headers came in with the same name, they will be catenated together.+--+-- Example:+--+-- @+-- ghci> import qualified "Snap.Types.Headers" as H+-- ghci> 'listHeaders' $ 'setHeader' \"Host\" "localhost" H.'empty'+-- [("host","localhost")]+-- @+listHeaders :: (HasHeaders a) => a -> [(CI ByteString, ByteString)]+listHeaders = H.toList . headers+++------------------------------------------------------------------------------+-- | Clears a header value from a 'HasHeaders' datatype.+--+-- Example:+--+-- @+-- ghci> import qualified "Snap.Types.Headers" as H+-- ghci> 'deleteHeader' \"Host\" $ 'setHeader' \"Host\" "localhost" H.'empty'+-- H {unH = []}+-- @+deleteHeader :: (HasHeaders a) => CI ByteString -> a -> a+deleteHeader k = updateHeaders $ H.delete k+++------------------------------------------------------------------------------+-- | Enumerates the HTTP method values (see+-- <http://tools.ietf.org/html/rfc2068.html#section-5.1.1>).+data Method = GET | HEAD | POST | PUT | DELETE | TRACE | OPTIONS | CONNECT |+ PATCH | Method ByteString+ deriving(Show, Read)++instance Eq Method where+ a == b =+ normalizeMethod a `eq` normalizeMethod b+ where+ GET `eq` GET = True+ HEAD `eq` HEAD = True+ POST `eq` POST = True+ PUT `eq` PUT = True+ DELETE `eq` DELETE = True+ TRACE `eq` TRACE = True+ OPTIONS `eq` OPTIONS = True+ CONNECT `eq` CONNECT = True+ PATCH `eq` PATCH = True+ Method x1 `eq` Method y1 = x1 == y1+ _ `eq` _ = False++instance Ord Method where+ compare a b =+ check (normalizeMethod a) (normalizeMethod b)+ where+ check GET GET = EQ+ check HEAD HEAD = EQ+ check POST POST = EQ+ check PUT PUT = EQ+ check DELETE DELETE = EQ+ check TRACE TRACE = EQ+ check OPTIONS OPTIONS = EQ+ check CONNECT CONNECT = EQ+ check PATCH PATCH = EQ+ check (Method x1) (Method y1) = compare x1 y1+ check x y = compare (tag x) (tag y)++ tag :: Method -> Int+ tag (GET{}) = 0+ tag (HEAD{}) = 1+ tag (POST{}) = 2+ tag (PUT{}) = 3+ tag (DELETE{}) = 4+ tag (TRACE{}) = 5+ tag (OPTIONS{}) = 6+ tag (CONNECT{}) = 7+ tag (PATCH{}) = 8+ tag (Method{}) = 9++-- | Equate the special case constructors with their corresponding+-- @Method name@ variant.+{-# INLINE normalizeMethod #-}+normalizeMethod :: Method -> Method+normalizeMethod m@(Method name) = case name of+ "GET" -> GET+ "HEAD" -> HEAD+ "POST" -> POST+ "PUT" -> PUT+ "DELETE" -> DELETE+ "TRACE" -> TRACE+ "OPTIONS" -> OPTIONS+ "CONNECT" -> CONNECT+ "PATCH" -> PATCH+ _ -> m+normalizeMethod m = m+++------------------------------------------------------------------------------+-- | Represents a (major, minor) version of the HTTP protocol.+type HttpVersion = (Int,Int)+++------------------------------------------------------------------------------+-- | A datatype representing an HTTP cookie.+data Cookie = Cookie {+ -- | The name of the cookie.+ cookieName :: !ByteString++ -- | The cookie's string value.+ , cookieValue :: !ByteString++ -- | The cookie's expiration value, if it has one.+ , cookieExpires :: !(Maybe UTCTime)++ -- | The cookie's \"domain\" value, if it has one.+ , cookieDomain :: !(Maybe ByteString)++ -- | The cookie path.+ , cookiePath :: !(Maybe ByteString)++ -- | Tag as secure cookie?+ , cookieSecure :: !Bool++ -- | HTTP only?+ , cookieHttpOnly :: !Bool+} deriving (Eq, Show)+++------------------------------------------------------------------------------+-- | A type alias for the HTTP parameters mapping. Each parameter+-- key maps to a list of 'ByteString' values; if a parameter is specified+-- multiple times (e.g.: \"@GET /foo?param=bar1¶m=bar2@\"), looking up+-- \"@param@\" in the mapping will give you @[\"bar1\", \"bar2\"]@.+type Params = Map ByteString [ByteString]+++------------------------------------------------------------------------------+-- request type+------------------------------------------------------------------------------++------------------------------------------------------------------------------+-- | Contains all of the information about an incoming HTTP request.+data Request = Request+ { -- | The server name of the request, as it came in from the request's+ -- @Host:@ header.+ --+ -- Example:+ --+ -- @+ -- ghci> :set -XOverloadedStrings+ -- ghci> import qualified "Snap.Test" as T+ -- ghci> import qualified "Data.Map" as M+ -- ghci> :{+ -- ghci| rq <- T.buildRequest $ do+ -- ghci| T.get "\/foo\/bar" M.empty+ -- ghci| T.setHeader "host" "example.com"+ -- ghci| :}+ -- ghci> rqHostName rq+ -- "example.com"+ -- @+ rqHostName :: ByteString++ -- | The remote IP address.+ --+ -- Example:+ --+ -- @+ -- ghci> :set -XOverloadedStrings+ -- ghci> import qualified "Snap.Test" as T+ -- ghci> import qualified "Data.Map" as M+ -- ghci> rqClientAddr \`fmap\` T.buildRequest (T.get "\/foo\/bar" M.empty)+ -- "127.0.0.1"+ -- @+ , rqClientAddr :: ByteString++ -- | The remote TCP port number.+ --+ -- Example:+ --+ -- @+ -- ghci> :set -XOverloadedStrings+ -- ghci> import qualified "Snap.Test" as T+ -- ghci> import qualified "Data.Map" as M+ -- ghci> rqClientPort \`fmap\` T.buildRequest (T.get "\/foo\/bar" M.empty)+ -- "60000"+ -- @+ , rqClientPort :: {-# UNPACK #-} !Int++ -- | The local IP address for this request.+ --+ -- Example:+ --+ -- @+ -- ghci> :set -XOverloadedStrings+ -- ghci> import qualified "Snap.Test" as T+ -- ghci> import qualified "Data.Map" as M+ -- ghci> rqServerAddr \`fmap\` T.buildRequest (T.get "\/foo\/bar" M.empty)+ -- "127.0.0.1"+ -- @+ , rqServerAddr :: ByteString++ -- | Returns the port number the HTTP server is listening on. This may be+ -- useless from the perspective of external requests, e.g. if the server+ -- is running behind a proxy.+ --+ -- Example:+ --+ -- @+ -- ghci> :set -XOverloadedStrings+ -- ghci> import qualified "Snap.Test" as T+ -- ghci> import qualified "Data.Map" as M+ -- ghci> rqServerPort \`fmap\` T.buildRequest (T.get "\/foo\/bar" M.empty)+ -- 8080+ -- @+ , rqServerPort :: {-# UNPACK #-} !Int++ -- | Returns the HTTP server's idea of its local hostname, including+ -- port. This is as configured with the @Config@ object at startup.+ --+ -- Example:+ --+ -- @+ -- ghci> :set -XOverloadedStrings+ -- ghci> import qualified "Snap.Test" as T+ -- ghci> import qualified "Data.Map" as M+ -- ghci> rqLocalHostname \`fmap\` T.buildRequest (T.get "\/foo\/bar" M.empty)+ -- "localhost"+ -- @+ , rqLocalHostname :: ByteString++ -- | Returns @True@ if this is an HTTPS session.+ --+ -- Example:+ --+ -- @+ -- ghci> :set -XOverloadedStrings+ -- ghci> import qualified "Snap.Test" as T+ -- ghci> import qualified "Data.Map" as M+ -- ghci> rqIsSecure \`fmap\` T.buildRequest (T.get "\/foo\/bar" M.empty)+ -- False+ -- @+ , rqIsSecure :: !Bool++ -- | Contains all HTTP 'Headers' associated with this request.+ --+ -- Example:+ --+ -- @+ -- ghci> :set -XOverloadedStrings+ -- ghci> import qualified "Snap.Test" as T+ -- ghci> import qualified "Data.Map" as M+ -- ghci> rqHeaders \`fmap\` T.buildRequest (T.get "\/foo\/bar" M.empty)+ -- H {unH = [("host","localhost")]}+ -- @+ , rqHeaders :: Headers++ -- | Actual body of the request.+ , rqBody :: InputStream ByteString++ -- | Returns the @Content-Length@ of the HTTP request body.+ --+ -- Example:+ --+ -- @+ -- ghci> :set -XOverloadedStrings+ -- ghci> import qualified "Snap.Test" as T+ -- ghci> import qualified "Data.Map" as M+ -- ghci> rqContentLength \`fmap\` T.buildRequest (T.get "\/foo\/bar" M.empty)+ -- Nothing+ -- @+ , rqContentLength :: !(Maybe Word64)++ -- | Returns the HTTP request method.+ --+ -- Example:+ --+ -- @+ -- ghci> :set -XOverloadedStrings+ -- ghci> import qualified "Snap.Test" as T+ -- ghci> import qualified "Data.Map" as M+ -- ghci> rqMethod \`fmap\` T.buildRequest (T.get "\/foo\/bar" M.empty)+ -- GET+ -- @+ , rqMethod :: !Method++ -- | Returns the HTTP version used by the client.+ --+ -- Example:+ --+ -- @+ -- ghci> :set -XOverloadedStrings+ -- ghci> import qualified "Snap.Test" as T+ -- ghci> import qualified "Data.Map" as M+ -- ghci> rqVersion \`fmap\` T.buildRequest (T.get "\/foo\/bar" M.empty)+ -- (1,1)+ -- @+ , rqVersion :: {-# UNPACK #-} !HttpVersion++ -- | Returns a list of the cookies that came in from the HTTP request+ -- headers.+ --+ -- Example:+ --+ -- @+ -- ghci> :set -XOverloadedStrings+ -- ghci> import qualified "Snap.Test" as T+ -- ghci> import qualified "Data.Map" as M+ -- ghci> rqCookies \`fmap\` T.buildRequest (T.get "\/foo\/bar" M.empty)+ -- []+ -- @+ , rqCookies :: [Cookie]++ -- | Handlers can be hung on a @URI@ \"entry point\"; this is called the+ -- \"context path\". If a handler is hung on the context path+ -- @\"\/foo\/\"@, and you request @\"\/foo\/bar\"@, the value of+ -- 'rqPathInfo' will be @\"bar\"@.+ --+ -- The following identity holds:+ --+ -- > rqURI r == S.concat [ rqContextPath r+ -- > , rqPathInfo r+ -- > , let q = rqQueryString r+ -- > in if S.null q+ -- > then ""+ -- > else S.append "?" q+ -- > ]+ --+ -- Example:+ --+ -- @+ -- ghci> :set -XOverloadedStrings+ -- ghci> import qualified "Snap.Test" as T+ -- ghci> import qualified "Data.Map" as M+ -- ghci> rqPathInfo \`fmap\` T.buildRequest (T.get "\/foo\/bar" M.empty)+ -- "foo/bar"+ -- @+ , rqPathInfo :: ByteString++ -- | The \"context path\" of the request; catenating 'rqContextPath',+ -- and 'rqPathInfo' should get you back to the original 'rqURI'+ -- (ignoring query strings). The 'rqContextPath' always begins and ends+ -- with a slash (@\"\/\"@) character, and represents the path (relative+ -- to your component\/snaplet) you took to get to your handler.+ --+ -- Example:+ --+ -- @+ -- ghci> :set -XOverloadedStrings+ -- ghci> import qualified "Snap.Test" as T+ -- ghci> import qualified "Data.Map" as M+ -- ghci> rqContextPath \`fmap\` T.buildRequest (T.get "\/foo\/bar" M.empty)+ -- "/"+ -- @+ , rqContextPath :: ByteString++ -- | Returns the @URI@ requested by the client.+ --+ -- Example:+ --+ -- @+ -- ghci> :set -XOverloadedStrings+ -- ghci> import qualified "Snap.Test" as T+ -- ghci> import qualified "Data.Map" as M+ -- ghci> rqURI \`fmap\` T.buildRequest (T.get "\/foo\/bar" M.empty)+ -- "foo/bar"+ -- @+ , rqURI :: ByteString++ -- | Returns the HTTP query string for this 'Request'.+ --+ -- Example:+ --+ -- @+ -- ghci> :set -XOverloadedStrings+ -- ghci> import qualified "Snap.Test" as T+ -- ghci> import qualified "Data.Map" as M+ -- ghci> rq <- T.buildRequest (T.get "\/foo\/bar" (M.fromList [("name", ["value"])]))+ -- ghci> rqQueryString rq+ -- "name=value"+ -- @+ , rqQueryString :: ByteString++ -- | Returns the parameters mapping for this 'Request'. \"Parameters\"+ -- are automatically decoded from the URI's query string and @POST@ body+ -- and entered into this mapping. The 'rqParams' value is thus a union of+ -- 'rqQueryParams' and 'rqPostParams'.+ --+ -- Example:+ --+ -- @+ -- ghci> :set -XOverloadedStrings+ -- ghci> import qualified "Snap.Test" as T+ -- ghci> import qualified "Data.Map" as M+ -- ghci> :{+ -- ghci| rq <- T.buildRequest $ do+ -- ghci| T.postUrlEncoded "\/foo\/bar" $ M.fromList [("baz", ["qux"])]+ -- ghci| T.setQueryStringRaw "baz=quux"+ -- ghci| :}+ -- ghci> rqParams rq+ -- fromList [("baz",["qux","quux"])]+ -- @+ , rqParams :: Params++ -- | The parameter mapping decoded from the URI's query string.+ --+ -- Example:+ --+ -- @+ -- ghci> :set -XOverloadedStrings+ -- ghci> import qualified "Snap.Test" as T+ -- ghci> import qualified "Data.Map" as M+ -- ghci> :{+ -- ghci| rq <- T.buildRequest $ do+ -- ghci| T.postUrlEncoded "\/foo\/bar" $ M.fromList [("baz", ["qux"])]+ -- ghci| T.setQueryStringRaw "baz=quux"+ -- ghci| :}+ -- ghci> rqQueryParams rq+ -- fromList [("baz",["quux"])]+ -- @+ , rqQueryParams :: Params++ -- | The parameter mapping decoded from the POST body. Note that Snap+ -- only auto-decodes POST request bodies when the request's+ -- @Content-Type@ is @application\/x-www-form-urlencoded@.+ -- For @multipart\/form-data@ use 'Snap.Util.FileUploads.handleFileUploads'+ -- to decode the POST request and fill this mapping.+ --+ -- Example:+ --+ -- @+ -- ghci> :set -XOverloadedStrings+ -- ghci> import qualified "Snap.Test" as T+ -- ghci> import qualified "Data.Map" as M+ -- ghci> :{+ -- ghci| rq <- T.buildRequest $ do+ -- ghci| T.postUrlEncoded "\/foo\/bar" $ M.fromList [("baz", ["qux"])]+ -- ghci| T.setQueryStringRaw "baz=quux"+ -- ghci| :}+ -- ghci> rqPostParams rq+ -- fromList [("baz",["qux"])]+ -- @+ , rqPostParams :: Params+ }+++------------------------------------------------------------------------------+instance Show Request where+ show r = concat [ method, " ", uri, " HTTP/", version, "\n"+ , hdrs, "\n\n"+ , "sn=\"", sname, "\" c=", clntAddr, " s=", srvAddr+ , " ctx=", contextpath, " clen=", contentlength, secure+ , params, cookies+ ]+ where+ method = show $ rqMethod r+ uri = S.unpack $ rqURI r+ version = let (mj, mn) = rqVersion r in show mj ++ "." ++ show mn+ hdrs = intercalate "\n" $ map showHdr (H.toList $ rqHeaders r)+ showHdr (a,b) = (S.unpack $ CI.original a) ++ ": " ++ S.unpack b+ sname = S.unpack $ rqLocalHostname r+ clntAddr = concat [S.unpack $ rqClientAddr r, ":", show $ rqClientPort r]+ srvAddr = concat [S.unpack $ rqServerAddr r, ":", show $ rqServerPort r]+ contextpath = S.unpack $ rqContextPath r+ contentlength = maybe "n/a" show (rqContentLength r)+ secure = if rqIsSecure r then " secure" else ""++ params = showFlds "\nparams: " ", " $+ map (\ (a,b) -> S.unpack a ++ ": " ++ show b)+ (Map.toAscList $ rqParams r)+ cookies = showFlds "\ncookies: " "\n " $+ map show (rqCookies r)++ showFlds header delim lst+ = if not . null $ lst then header ++ (intercalate delim lst)+ else "" :: String++------------------------------------------------------------------------------+instance HasHeaders Request where+ headers = rqHeaders+ updateHeaders f r = r { rqHeaders = f (rqHeaders r) }+++------------------------------------------------------------------------------+instance HasHeaders Headers where+ headers = id+ updateHeaders = id++------------------------------------------------------------------------------+-- response type+------------------------------------------------------------------------------++type StreamProc = OutputStream Builder -> IO (OutputStream Builder)+data ResponseBody = Stream (StreamProc)+ -- ^ output body is a function that writes to a 'Builder'+ -- stream++ | SendFile FilePath (Maybe (Word64, Word64))+ -- ^ output body is sendfile(), optional second argument+ -- is a byte range to send+++------------------------------------------------------------------------------+rspBodyMap :: (StreamProc -> StreamProc) -> ResponseBody -> ResponseBody+rspBodyMap f b = Stream $ f $ rspBodyToEnum b+++------------------------------------------------------------------------------+rspBodyToEnum :: ResponseBody -> StreamProc+rspBodyToEnum (Stream e) = e++rspBodyToEnum (SendFile fp Nothing) = \out ->+ Streams.withFileAsInput fp $ \is -> do+ is' <- Streams.mapM (return . byteString) is+ Streams.connect is' out+ return out++rspBodyToEnum (SendFile fp (Just (start, end))) = \out ->+ withBinaryFile fp ReadMode $ \handle -> do+ unless (start == 0) $ hSeek handle AbsoluteSeek $ toInteger start+ is <- Streams.handleToInputStream handle+ is' <- Streams.takeBytes (fromIntegral $ end - start) is >>=+ Streams.mapM (return . byteString)+ Streams.connect is' out+ return out+++------------------------------------------------------------------------------+-- | Represents an HTTP response.+data Response = Response+ { rspHeaders :: Headers+ , rspCookies :: Map ByteString Cookie++ -- | We will need to inspect the content length no matter what, and+ -- looking up \"content-length\" in the headers and parsing the number+ -- out of the text will be too expensive.+ , rspContentLength :: !(Maybe Word64)+ , rspBody :: ResponseBody++ -- | Returns the HTTP status code.+ --+ -- Example:+ --+ -- @+ -- ghci> rspStatus 'emptyResponse'+ -- 200+ -- @+ , rspStatus :: !Int++ -- | Returns the HTTP status explanation string.+ --+ -- Example:+ --+ -- @+ -- ghci> rspStatusReason 'emptyResponse'+ -- "OK"+ -- @+ , rspStatusReason :: !ByteString++ -- | If true, we are transforming the request body with+ -- 'transformRequestBody'+ , rspTransformingRqBody :: !Bool+ }+++------------------------------------------------------------------------------+instance Show Response where+ show r = concat [ statusline+ , hdrs+ , contentLength+ , "\r\n"+ , body+ ]+ where+ statusline = concat [ "HTTP/1.1 "+ , show $ rspStatus r+ , " "+ , S.unpack $ rspStatusReason r+ , "\r\n" ]++ hdrs = concatMap showHdr $ H.toList $ renderCookies r+ $ rspHeaders $ clearContentLength r++ contentLength = maybe "" (\l -> concat ["Content-Length: ", show l, "\r\n"] ) (rspContentLength r)++ showHdr (k,v) = concat [ S.unpack (CI.original k), ": ", S.unpack v, "\r\n" ]++ -- io-streams are impure, so we're forced to use 'unsafePerformIO'.+ body = unsafePerformIO $ do+ (os, grab) <- Streams.listOutputStream+ let f = rspBodyToEnum $ rspBody r+ _ <- f os+ fmap (L.unpack . toLazyByteString . mconcat) grab++++------------------------------------------------------------------------------+instance HasHeaders Response where+ headers = rspHeaders+ updateHeaders f r = r { rspHeaders = f (rspHeaders r) }+++------------------------------------------------------------------------------+-- | Looks up the value(s) for the given named parameter. Parameters initially+-- come from the request's query string and any decoded POST body (if the+-- request's @Content-Type@ is @application\/x-www-form-urlencoded@).+-- Parameter values can be modified within handlers using "rqModifyParams".+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Snap.Test" as T+-- ghci> import qualified "Data.Map" as M+-- ghci> :{+-- ghci| rq <- T.buildRequest $ do+-- ghci| T.postUrlEncoded "\/foo\/bar" $ M.fromList [("baz", ["qux"])]+-- ghci| T.setQueryStringRaw "baz=quux"+-- ghci| :}+-- ghci> 'rqParam' "baz" rq+-- Just ["qux","quux"]+-- @+rqParam :: ByteString -- ^ parameter name to look up+ -> Request -- ^ HTTP request+ -> Maybe [ByteString]+rqParam k rq = Map.lookup k $ rqParams rq+{-# INLINE rqParam #-}+++------------------------------------------------------------------------------+-- | Looks up the value(s) for the given named parameter in the POST parameters+-- mapping.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Snap.Test" as T+-- ghci> import qualified "Data.Map" as M+-- ghci> :{+-- ghci| rq <- T.buildRequest $ do+-- ghci| T.postUrlEncoded "\/foo\/bar" $ M.fromList [("baz", ["qux"])]+-- ghci| T.setQueryStringRaw "baz=quux"+-- ghci| :}+-- ghci> 'rqPostParam' "baz" rq+-- Just ["qux"]+-- @+rqPostParam :: ByteString -- ^ parameter name to look up+ -> Request -- ^ HTTP request+ -> Maybe [ByteString]+rqPostParam k rq = Map.lookup k $ rqPostParams rq+{-# INLINE rqPostParam #-}+++------------------------------------------------------------------------------+-- | Looks up the value(s) for the given named parameter in the query+-- parameters mapping.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Snap.Test" as T+-- ghci> import qualified "Data.Map" as M+-- ghci> :{+-- ghci| rq <- T.buildRequest $ do+-- ghci| T.postUrlEncoded "\/foo\/bar" $ M.fromList [("baz", ["qux"])]+-- ghci| T.setQueryStringRaw "baz=quux"+-- ghci| :}+-- ghci> 'rqQueryParam' "baz" rq+-- Just ["quux"]+-- @+rqQueryParam :: ByteString -- ^ parameter name to look up+ -> Request -- ^ HTTP request+ -> Maybe [ByteString]+rqQueryParam k rq = Map.lookup k $ rqQueryParams rq+{-# INLINE rqQueryParam #-}+++------------------------------------------------------------------------------+-- | Modifies the parameters mapping (which is a @Map ByteString ByteString@)+-- in a 'Request' using the given function.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Snap.Test" as T+-- ghci> import qualified "Data.Map" as M+-- ghci> :{+-- ghci| rq <- T.buildRequest $ do+-- ghci| T.postUrlEncoded "\/foo\/bar" $ M.fromList [("baz", ["qux"])]+-- ghci| T.setQueryStringRaw "baz=quux"+-- ghci| :}+-- ghci> 'rqParams' rq+-- fromList [("baz",["qux","quux"])]+-- ghci> 'rqParams' $ 'rqModifyParams' (M.delete "baz") rq+-- fromList []+-- @+rqModifyParams :: (Params -> Params) -> Request -> Request+rqModifyParams f r = r { rqParams = p }+ where+ p = f $ rqParams r+{-# INLINE rqModifyParams #-}+++------------------------------------------------------------------------------+-- | Writes a key-value pair to the parameters mapping within the given+-- request.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Snap.Test" as T+-- ghci> import qualified "Data.Map" as M+-- ghci> :{+-- ghci| rq <- T.buildRequest $ do+-- ghci| T.postUrlEncoded "\/foo\/bar" $ M.fromList [("baz", ["qux"])]+-- ghci| T.setQueryStringRaw "baz=quux"+-- ghci| :}+-- ghci> 'rqParams' rq+-- fromList [("baz",["qux","quux"])]+-- ghci> 'rqParams' $ 'rqSetParam' "baz" ["corge"] rq+-- fromList [("baz", ["corge"])]+-- @+rqSetParam :: ByteString -- ^ parameter name+ -> [ByteString] -- ^ parameter values+ -> Request -- ^ request+ -> Request+rqSetParam k v = rqModifyParams $ Map.insert k v+{-# INLINE rqSetParam #-}+++ ---------------+ -- responses --+ ---------------++------------------------------------------------------------------------------+-- | An empty 'Response'.+--+-- Example:+--+-- @+-- ghci> 'emptyResponse'+-- HTTP\/1.1 200 OK+--+--+-- @+emptyResponse :: Response+emptyResponse = Response H.empty Map.empty Nothing+ (Stream (return . id))+ 200 "OK" False+++------------------------------------------------------------------------------+-- | Sets an HTTP response body to the given stream procedure.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "System.IO.Streams" as Streams+-- ghci> import qualified "Data.ByteString.Builder" as Builder+-- ghci> :{+-- ghci| let r = 'setResponseBody'+-- ghci| (\out -> do+-- ghci| Streams.write (Just $ Builder.'byteString' \"Hello, world!\") out+-- ghci| return out)+-- ghci| 'emptyResponse'+-- ghci| :}+-- ghci> r+-- HTTP\/1.1 200 OK+--+-- Hello, world!+-- @+setResponseBody :: (OutputStream Builder -> IO (OutputStream Builder))+ -- ^ new response body+ -> Response -- ^ response to modify+ -> Response+setResponseBody e r = r { rspBody = Stream e }+{-# INLINE setResponseBody #-}+++------------------------------------------------------------------------------+-- | Sets the HTTP response status. Note: normally you would use+-- 'setResponseCode' unless you needed a custom response explanation.+--+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> setResponseStatus 500 \"Internal Server Error\" 'emptyResponse'+-- HTTP\/1.1 500 Internal Server Error+--+--+-- @+setResponseStatus :: Int -- ^ HTTP response integer code+ -> ByteString -- ^ HTTP response explanation+ -> Response -- ^ Response to be modified+ -> Response+setResponseStatus s reason r = r { rspStatus=s, rspStatusReason=reason }+{-# INLINE setResponseStatus #-}+++------------------------------------------------------------------------------+-- | Sets the HTTP response code.+--+-- Example:+--+-- @+-- ghci> setResponseCode 404 'emptyResponse'+-- HTTP\/1.1 404 Not Found+--+--+-- @+setResponseCode :: Int -- ^ HTTP response integer code+ -> Response -- ^ Response to be modified+ -> Response+setResponseCode s r = setResponseStatus s reason r+ where+ reason = fromMaybe "Unknown" (IM.lookup s statusReasonMap)+{-# INLINE setResponseCode #-}+++------------------------------------------------------------------------------+-- | Modifies a response body.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "System.IO.Streams" as Streams+-- ghci> import qualified "Data.ByteString.Builder" as Builder+-- ghci> :{+-- ghci| let r = 'setResponseBody'+-- ghci| (\out -> do+-- ghci| Streams.write (Just $ Builder.'byteString' \"Hello, world!\") out+-- ghci| return out)+-- ghci| 'emptyResponse'+-- ghci| :}+-- ghci> r+-- HTTP\/1.1 200 OK+--+-- Hello, world!+-- ghci> :{+-- ghci| let r' = 'modifyResponseBody'+-- ghci| (\f out -> do+-- ghci| out' <- f out+-- ghci| Streams.write (Just $ Builder.'byteString' \"\\nBye, world!\") out'+-- ghci| return out') r+-- ghci| :}+-- ghci> r'+-- HTTP\/1.1 200 OK+--+-- Hello, world!+-- Bye, world!+-- @+modifyResponseBody :: ((OutputStream Builder -> IO (OutputStream Builder)) ->+ (OutputStream Builder -> IO (OutputStream Builder)))+ -> Response+ -> Response+modifyResponseBody f r = r { rspBody = rspBodyMap f (rspBody r) }+{-# INLINE modifyResponseBody #-}+++------------------------------------------------------------------------------+-- | Sets the @Content-Type@ in the 'Response' headers.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> setContentType \"text\/html\" 'emptyResponse'+-- HTTP\/1.1 200 OK+-- content-type: text\/html+--+--+-- @+setContentType :: ByteString -> Response -> Response+setContentType = setHeader "Content-Type"+{-# INLINE setContentType #-}+++------------------------------------------------------------------------------+-- | Convert 'Cookie' into 'ByteString' for output.+--+-- TODO: Remove duplication. This function is copied from+-- snap-server/Snap.Internal.Http.Server.Session.+cookieToBS :: Cookie -> ByteString+cookieToBS (Cookie k v mbExpTime mbDomain mbPath isSec isHOnly) = cookie+ where+ cookie = S.concat [k, "=", v, path, exptime, domain, secure, hOnly]+ path = maybe "" (S.append "; path=") mbPath+ domain = maybe "" (S.append "; domain=") mbDomain+ exptime = maybe "" (S.append "; expires=" . fmt) mbExpTime+ secure = if isSec then "; Secure" else ""+ hOnly = if isHOnly then "; HttpOnly" else ""++ -- TODO: 'formatHttpTime' uses "DD MMM YYYY" instead of "DD-MMM-YYYY",+ -- unlike the code in 'Snap.Internal.Http.Server.Session'. Is this form+ -- allowed?+ fmt = unsafePerformIO . formatHttpTime . toCTime++ toCTime :: UTCTime -> CTime+ toCTime = fromInteger . truncate . utcTimeToPOSIXSeconds++------------------------------------------------------------------------------+-- | Render cookies from a given 'Response' to 'Headers'.+--+-- TODO: Remove duplication. This function is copied from+-- snap-server/Snap.Internal.Http.Server.Session.+renderCookies :: Response -> Headers -> Headers+renderCookies r hdrs+ | null cookies = hdrs+ | otherwise = foldl' (\m v -> H.unsafeInsert "set-cookie" v m) hdrs cookies++ where+ cookies = fmap cookieToBS . Map.elems $ rspCookies r++------------------------------------------------------------------------------+-- | Adds an HTTP 'Cookie' to 'Response' headers.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> let cookie = 'Cookie' \"name\" \"value\" Nothing Nothing Nothing False False+-- ghci> 'getResponseCookie' \"name\" $ 'addResponseCookie' cookie 'emptyResponse'+-- Just (Cookie {cookieName = \"name\", cookieValue = \"value\", ...})+-- @+addResponseCookie :: Cookie -- ^ cookie value+ -> Response -- ^ response to modify+ -> Response+addResponseCookie ck@(Cookie k _ _ _ _ _ _) r = r { rspCookies = cks' }+ where+ cks'= Map.insert k ck $ rspCookies r+{-# INLINE addResponseCookie #-}+++------------------------------------------------------------------------------+-- | Gets an HTTP 'Cookie' with the given name from 'Response' headers.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> 'getResponseCookie' \"cookie-name\" 'emptyResponse'+-- Nothing+-- @+getResponseCookie :: ByteString -- ^ cookie name+ -> Response -- ^ response to query+ -> Maybe Cookie+getResponseCookie cn r = Map.lookup cn $ rspCookies r+{-# INLINE getResponseCookie #-}+++-- | Returns a list of 'Cookie's present in 'Response'+--+-- Example:+--+-- @+-- ghci> 'getResponseCookies' 'emptyResponse'+-- []+-- @+getResponseCookies :: Response -- ^ response to query+ -> [Cookie]+getResponseCookies = Map.elems . rspCookies+{-# INLINE getResponseCookies #-}+++------------------------------------------------------------------------------+-- | Deletes an HTTP 'Cookie' from the 'Response' headers. Please note+-- this does not necessarily erase the cookie from the client browser.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> let cookie = 'Cookie' \"name\" \"value\" Nothing Nothing Nothing False False+-- ghci> let rsp = 'addResponseCookie' cookie 'emptyResponse'+-- ghci> 'getResponseCookie' \"name\" rsp+-- Just (Cookie {cookieName = \"name\", cookieValue = \"value\", ...})+-- ghci> 'getResponseCookie' \"name\" $ 'deleteResponseCookie' \"name\" rsp+-- Nothing+-- @+deleteResponseCookie :: ByteString -- ^ cookie name+ -> Response -- ^ response to modify+ -> Response+deleteResponseCookie cn r = r { rspCookies = cks' }+ where+ cks'= Map.delete cn $ rspCookies r+{-# INLINE deleteResponseCookie #-}+++------------------------------------------------------------------------------+-- | Modifies an HTTP 'Cookie' with given name in 'Response' headers.+-- Nothing will happen if a matching 'Cookie' can not be found in 'Response'.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import "Data.Monoid"+-- ghci> let cookie = 'Cookie' \"name\" \"value\" Nothing Nothing Nothing False False+-- ghci> let rsp = 'addResponseCookie' cookie 'emptyResponse'+-- ghci> 'getResponseCookie' \"name\" rsp+-- Just (Cookie {cookieName = \"name\", cookieValue = \"value\", ...})+-- ghci> let f ck@('Cookie' { cookieName = name }) = ck { cookieName = name <> \"\'\"}+-- ghci> let rsp' = 'modifyResponseCookie' \"name\" f rsp+-- ghci> 'getResponseCookie' \"name\'\" rsp\'+-- Just (Cookie {cookieName = \"name\'\", ...})+-- ghci> 'getResponseCookie' \"name\" rsp\'+-- Just (Cookie {cookieName = \"name\", ...})+-- @+modifyResponseCookie :: ByteString -- ^ cookie name+ -> (Cookie -> Cookie) -- ^ modifier function+ -> Response -- ^ response to modify+ -> Response+modifyResponseCookie cn f r = maybe r modify $ getResponseCookie cn r+ where+ modify ck = addResponseCookie (f ck) r+{-# INLINE modifyResponseCookie #-}+++------------------------------------------------------------------------------+-- | A note here: if you want to set the @Content-Length@ for the response,+-- Snap forces you to do it with this function rather than by setting it in+-- the headers; the @Content-Length@ in the headers will be ignored.+--+-- The reason for this is that Snap needs to look up the value of+-- @Content-Length@ for each request, and looking the string value up in the+-- headers and parsing the number out of the text will be too expensive.+--+-- If you don't set a content length in your response, HTTP keep-alive will be+-- disabled for HTTP\/1.0 clients, forcing a @Connection: close@. For+-- HTTP\/1.1 clients, Snap will switch to the chunked transfer encoding if+-- @Content-Length@ is not specified.+--+-- Example:+--+-- @+-- ghci> setContentLength 400 'emptyResponse'+-- HTTP\/1.1 200 OK+-- Content-Length: 400+--+--+-- @+setContentLength :: Word64 -> Response -> Response+setContentLength !l r = r { rspContentLength = Just l }+{-# INLINE setContentLength #-}+++------------------------------------------------------------------------------+-- | Removes any @Content-Length@ set in the 'Response'.+--+-- Example:+--+-- @+-- ghci> clearContentLength $ 'setContentLength' 400 'emptyResponse'+-- HTTP\/1.1 200 OK+--+--+-- @+clearContentLength :: Response -> Response+clearContentLength r = r { rspContentLength = Nothing }+{-# INLINE clearContentLength #-}+++ ----------------+ -- HTTP dates --+ ----------------++------------------------------------------------------------------------------+-- | Convert a 'CTime' into an HTTP timestamp.+--+-- Example:+--+-- @+-- ghci> 'formatHttpTime' . 'fromIntegral' $ 10+-- \"Thu, 01 Jan 1970 00:00:10 GMT\"+-- @+formatHttpTime :: CTime -> IO ByteString+++------------------------------------------------------------------------------+-- | Convert a 'CTime' into common log entry format.+formatLogTime :: CTime -> IO ByteString+++------------------------------------------------------------------------------+-- | Converts an HTTP timestamp into a 'CTime'.+--+-- If the given time string is unparseable, this function will return 0.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> 'parseHttpTime' \"Thu, 01 Jan 1970 00:00:10 GMT\"+-- 10+-- @+parseHttpTime :: ByteString -> IO CTime++#ifdef PORTABLE++------------------------------------------------------------------------------+-- local definitions+fromStr :: String -> ByteString+fromStr = S.pack -- only because we know there's no unicode+{-# INLINE fromStr #-}+++------------------------------------------------------------------------------+formatHttpTime = return . format . toUTCTime+ where+ format :: UTCTime -> ByteString+ format = fromStr . formatTime defaultTimeLocale "%a, %d %b %Y %X GMT"++ toUTCTime :: CTime -> UTCTime+ toUTCTime = posixSecondsToUTCTime . realToFrac+++------------------------------------------------------------------------------+formatLogTime ctime = do+ t <- utcToLocalZonedTime $ toUTCTime ctime+ return $! format t++ where+ format :: ZonedTime -> ByteString+ format = fromStr . formatTime defaultTimeLocale "%d/%b/%Y:%H:%M:%S %z"++ toUTCTime :: CTime -> UTCTime+ toUTCTime = posixSecondsToUTCTime . realToFrac+++------------------------------------------------------------------------------+parseHttpTime = return . toCTime . prs . S.unpack+ where+ parseTime =+#if MIN_VERSION_time(1,10,0)+ parseTimeM True+#else+ Time.parseTime+#endif++ prs :: String -> Maybe UTCTime+ prs = parseTime defaultTimeLocale "%a, %d %b %Y %H:%M:%S GMT"++ toCTime :: Maybe UTCTime -> CTime+ toCTime (Just t) = fromInteger $ truncate $ utcTimeToPOSIXSeconds t+ toCTime Nothing = fromInteger 0++#else++------------------------------------------------------------------------------+formatLogTime t = do+ ptr <- mallocBytes 40+ c_format_log_time t ptr+ S.unsafePackMallocCString ptr+++------------------------------------------------------------------------------+formatHttpTime t = do+ ptr <- mallocBytes 40+ c_format_http_time t ptr+ S.unsafePackMallocCString ptr+++------------------------------------------------------------------------------+parseHttpTime s = S.unsafeUseAsCString s $ \ptr ->+ c_parse_http_time ptr++#endif+++------------------------------------------------------------------------------+-- | Adapted from:+--+-- <https://www.iana.org/assignments/http-status-codes/http-status-codes.txt>+statusReasonMap :: IM.IntMap ByteString+statusReasonMap = IM.fromList [+ (100, "Continue"),+ (101, "Switching Protocols"),+ (102, "Processing"),+ (103, "Early Hints"),+ -- 104-199 Unassigned+ (200, "OK"),+ (201, "Created"),+ (202, "Accepted"),+ (203, "Non-Authoritative Information"),+ (204, "No Content"),+ (205, "Reset Content"),+ (206, "Partial Content"),+ (207, "Multi-Status"),+ (208, "Already Reported"),+ -- 209-225 Unassigned+ (226, "IM Used"),+ -- 227-299 Unassigned,+ (300, "Multiple Choices"),+ (301, "Moved Permanently"),+ (302, "Found"),+ (303, "See Other"),+ (304, "Not Modified"),+ (305, "Use Proxy"),+ (306, "(Unused)"),+ (307, "Temporary Redirect"),+ (308, "Permanent Redirect"),+ -- 309-399 Unassigned+ (400, "Bad Request"),+ (401, "Unauthorized"),+ (402, "Payment Required"),+ (403, "Forbidden"),+ (404, "Not Found"),+ (405, "Method Not Allowed"),+ (406, "Not Acceptable"),+ (407, "Proxy Authentication Required"),+ (408, "Request Timeout"),+ (409, "Conflict"),+ (410, "Gone"),+ (411, "Length Required"),+ (412, "Precondition Failed"),+ (413, "Payload Too Large"),+ (414, "URI Too Long"),+ (415, "Unsupported Media Type"),+ (416, "Range Not Satisfiable"),+ (417, "Expectation Failed"),+ -- 418-420 Unassigned+ (421, "Misdirected Request"),+ (422, "Unprocessable Entity"),+ (423, "Locked"),+ (424, "Failed Dependency"),+ (425, "Too Early"),+ (426, "Upgrade Required"),+ -- 427 Unassigned+ (428, "Precondition Required"),+ (429, "Too Many Requests"),+ -- 430 Unassigned+ (431, "Request Header Fields Too Large"),+ -- 432-450 Unassigned+ (451, "Unavailable For Legal Reasons"),+ -- 452-499 Unassigned+ (500, "Internal Server Error"),+ (501, "Not Implemented"),+ (502, "Bad Gateway"),+ (503, "Service Unavailable"),+ (504, "Gateway Timeout"),+ (505, "HTTP Version Not Supported"),+ (506, "Variant Also Negotiates"),+ (507, "Insufficient Storage"),+ (508, "Loop Detected"),+ -- 509 Unassigned+ (510, "Not Extended"),+ (511, "Network Authentication Required")+ -- 512-599 Unassigned+ ]++------------------------------------------------------------------------------+-- Deprecated functions++-- | See 'rqClientAddr'.+rqRemoteAddr :: Request -> ByteString+rqRemoteAddr = rqClientAddr+{-# DEPRECATED rqRemoteAddr "(snap-core >= 1.0.0.0) please use 'rqClientAddr', this will be removed in 1.1.*" #-}++-- | See 'rqClientPort'.+rqRemotePort :: Request -> Int+rqRemotePort = rqClientPort+{-# DEPRECATED rqRemotePort "(snap-core >= 1.0.0.0) please use 'rqClientPort', this will be removed in 1.1.*" #-}
+ src/Snap/Internal/Instances.hs view
@@ -0,0 +1,83 @@+{-# OPTIONS_GHC -fno-warn-orphans -fno-warn-warnings-deprecations #-}+{-# LANGUAGE CPP #-}++-- Note re: "-fno-warn-warnings-deprecations" above: transformers has+-- deprecated Control.Monad.Trans.Error (which we like) but we are going to+-- provide an ErrorT instance for compatibility until the deprecated modules+-- are removed.++module Snap.Internal.Instances () where+------------------------------------------------------------------------------+import Control.Monad.Trans.Class (MonadTrans (lift))+#if !MIN_VERSION_transformers(0,6,0)+import Control.Monad.Trans.Error (Error, ErrorT)+#endif+#if MIN_VERSION_transformers(0,4,0)+import Control.Monad.Trans.Except (ExceptT)+#endif+#if !MIN_VERSION_transformers(0,6,0)+import Control.Monad.Trans.List (ListT)+#endif+import Control.Monad.Trans.Reader (ReaderT)+import qualified Control.Monad.Trans.RWS.Lazy as LRWS (RWST)+import Control.Monad.Trans.RWS.Strict (RWST)+import qualified Control.Monad.Trans.State.Lazy as LState (StateT)+import Control.Monad.Trans.State.Strict (StateT)+import qualified Control.Monad.Trans.Writer.Lazy as LWriter (WriterT)+import Control.Monad.Trans.Writer.Strict (WriterT)+import Snap.Internal.Core (MonadSnap (..))+#if !MIN_VERSION_base(4,8,0)+import Data.Monoid (Monoid)+#endif+------------------------------------------------------------------------------+++------------------------------------------------------------------------------+#if !MIN_VERSION_transformers(0,6,0)+instance (MonadSnap m, Error e) => MonadSnap (ErrorT e m) where+ liftSnap = lift . liftSnap++------------------------------------------------------------------------------+instance MonadSnap m => MonadSnap (ListT m) where+ liftSnap = lift . liftSnap+#endif++#if MIN_VERSION_transformers(0,4,0)+instance (MonadSnap m, Monoid e) => MonadSnap (ExceptT e m) where+ liftSnap = lift . liftSnap+#endif+++------------------------------------------------------------------------------+instance (MonadSnap m, Monoid w) => MonadSnap (RWST r w s m) where+ liftSnap = lift . liftSnap+++------------------------------------------------------------------------------+instance (MonadSnap m, Monoid w) => MonadSnap (LRWS.RWST r w s m) where+ liftSnap = lift . liftSnap+++------------------------------------------------------------------------------+instance MonadSnap m => MonadSnap (ReaderT r m) where+ liftSnap = lift . liftSnap+++------------------------------------------------------------------------------+instance MonadSnap m => MonadSnap (StateT s m) where+ liftSnap = lift . liftSnap+++------------------------------------------------------------------------------+instance MonadSnap m => MonadSnap (LState.StateT s m) where+ liftSnap = lift . liftSnap+++------------------------------------------------------------------------------+instance (MonadSnap m, Monoid w) => MonadSnap (WriterT w m) where+ liftSnap = lift . liftSnap+++------------------------------------------------------------------------------+instance (MonadSnap m, Monoid w) => MonadSnap (LWriter.WriterT w m) where+ liftSnap = lift . liftSnap
− src/Snap/Internal/Iteratee/Debug.hs
@@ -1,77 +0,0 @@--- | An internal Snap module for debugging iteratees.------ /N.B./ this is an internal interface, please don't write user code that--- depends on it.--{-# OPTIONS_GHC -fno-warn-orphans #-}-{-# LANGUAGE CPP #-}-{-# LANGUAGE FlexibleInstances #-}---module Snap.Internal.Iteratee.Debug- ( debugIteratee- , iterateeDebugWrapper- ) where---------------------------------------------------------------------------------import Data.Iteratee.WrappedByteString-import Data.Word (Word8)-import System.IO--------------------------------------------------------------------------------#ifndef NODEBUG-import Snap.Internal.Debug-#endif-import Snap.Iteratee-----------------------------------------------------------------------------------------------------------------------------------------------------------------instance Show (WrappedByteString Word8) where- show (WrapBS s) = show s----------------------------------------------------------------------------------debugIteratee :: Iteratee IO ()-debugIteratee = IterateeG f- where- f c@(EOF _) = do- putStrLn $ "got EOF: " ++ show c- hFlush stdout- return (Done () c)-- f c@(Chunk _) = do- putStrLn $ "got chunk: " ++ show c- hFlush stdout- return $ Cont debugIteratee Nothing---#ifndef NODEBUG--iterateeDebugWrapper :: String -> Iteratee IO a -> Iteratee IO a-iterateeDebugWrapper name iter = IterateeG f- where- f c@(EOF Nothing) = do- debug $ name ++ ": got EOF: " ++ show c- runIter iter c-- f c@(EOF (Just _)) = do- debug $ name ++ ": got EOF **error**: " ++ show c- runIter iter c-- f c@(Chunk _) = do- debug $ name ++ ": got chunk: " ++ show c- wrapResult $ runIter iter c-- wrapResult m = do- iv <- m- let i = liftI iv-- return $ Cont (iterateeDebugWrapper name i) Nothing--#else--iterateeDebugWrapper :: String -> Iteratee IO a -> Iteratee IO a-iterateeDebugWrapper _ = id-{-# INLINE iterateeDebugWrapper #-}--#endif
src/Snap/Internal/Parsing.hs view
@@ -1,26 +1,607 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE MagicHash #-} {-# LANGUAGE OverloadedStrings #-} +------------------------------------------------------------------------------ module Snap.Internal.Parsing where+------------------------------------------------------------------------------+import Control.Applicative (Alternative ((<|>)), Applicative (pure, (*>), (<*)), liftA2, (<$>))+import Control.Arrow (first, second)+import Control.Monad (Monad (return), MonadPlus (mzero), liftM, when)+import Data.Attoparsec.ByteString.Char8 (IResult (Done, Fail, Partial), Parser, Result, anyChar, char, choice, decimal, endOfInput, feed, inClass, isDigit, isSpace, letter_ascii, many', match, option, parse, satisfy, skipSpace, skipWhile, string, take, takeTill, takeWhile, sepBy')+import qualified Data.Attoparsec.ByteString.Char8 as AP+import Data.Bits (Bits (unsafeShiftL, (.&.), (.|.)))+import Data.ByteString.Builder (Builder, byteString, char8, toLazyByteString, word8)+import Data.ByteString.Char8 (ByteString)+import qualified Data.ByteString.Char8 as S+import Data.ByteString.Internal (c2w, w2c)+import qualified Data.ByteString.Lazy.Char8 as L+import Data.CaseInsensitive (CI)+import qualified Data.CaseInsensitive as CI (mk)+import Data.Char (Char, intToDigit, isAlpha, isAlphaNum, isAscii, isControl, isHexDigit, ord)+import Data.Int (Int64)+import Data.List (concat, intercalate, intersperse)+import Data.Map (Map)+import qualified Data.Map as Map (empty, insertWith, toList)+import Data.Maybe (Maybe (..), maybe)+import Data.Monoid (Monoid (mconcat, mempty), (<>))+import Data.Word (Word8)+import GHC.Exts ( Int (I#)+ , word2Int#+#if MIN_VERSION_base(4,16,0)+ , uncheckedShiftRLWord8#+ , word8ToWord#+#else+ , uncheckedShiftRL#+#endif+ )+import GHC.Word (Word8 (..))+import Prelude (Bool (..), Either (..), Enum (fromEnum, toEnum), Eq (..), Num (..), Ord (..), String, and, any, concatMap, elem, error, filter, flip, foldr, fst, id, map, not, otherwise, show, snd, ($), ($!), (&&), (++), (.), (||))+import Snap.Internal.Http.Types (Cookie (Cookie))+------------------------------------------------------------------------------ -import Control.Monad-import Data.Attoparsec.Char8 hiding (Done)-import qualified Data.Attoparsec.Char8 as Atto-import Data.ByteString.Char8 (ByteString)-import Data.ByteString.Nums.Careless.Int (int)-import Data.Int ------------------------------------------------------------------------------+{-# INLINE fullyParse #-} fullyParse :: ByteString -> Parser a -> Either String a-fullyParse s p =+fullyParse = fullyParse' parse feed++{-# INLINE (<?>) #-}+(<?>) :: Parser a -> String -> Parser a+(<?>) a !b = (AP.<?>) a b+infix 0 <?>++------------------------------------------------------------------------------+{-# INLINE fullyParse' #-}+fullyParse' :: (Parser a -> ByteString -> Result a)+ -> (Result a -> ByteString -> Result a)+ -> ByteString+ -> Parser a+ -> Either String a+fullyParse' parseFunc feedFunc s p = case r' of- (Fail _ _ e) -> Left e- (Partial _) -> Left "parse failed"- (Atto.Done _ x) -> Right x+ (Fail _ context e) -> Left $ concat [ "Parsing "+ , intercalate "/" context+ , ": "+ , e+ , "."+ ]+ (Partial _) -> Left "parse failed" -- expected to be impossible+ (Done _ x) -> Right x where- r = parse p s- r' = feed r ""+ r = parseFunc p s+ r' = feedFunc r "" +------------------------------------------------------------------------------+-- Parsers for different tokens in an HTTP request. ------------------------------------------------------------------------------ parseNum :: Parser Int64-parseNum = liftM int $ Atto.takeWhile1 Atto.isDigit+parseNum = decimal+++------------------------------------------------------------------------------+untilEOL :: Parser ByteString+untilEOL = takeWhile notend <?> "untilEOL"+ where+ notend c = not $ c == '\r' || c == '\n'+++------------------------------------------------------------------------------+crlf :: Parser ByteString+crlf = string "\r\n" <?> "crlf"+++------------------------------------------------------------------------------+toTableList :: (Char -> Bool) -> [Char]+toTableList f = l+ where+ g c = c /= '-' && f c+ !l1 = filter g $ map w2c [0..255]+ !l0 = if f '-' then ['-'] else []+ !l = l0 ++ l1+{-# INLINE toTableList #-}+++------------------------------------------------------------------------------+toTable :: (Char -> Bool) -> (Char -> Bool)+toTable = inClass . toTableList+{-# INLINE toTable #-}+++------------------------------------------------------------------------------+skipFieldChars :: Parser ()+skipFieldChars = skipWhile isFieldChar+++------------------------------------------------------------------------------+isFieldChar :: Char -> Bool+isFieldChar = toTable f+ where+ f c = (isDigit c) || (isAlpha c) || c == '-' || c == '_'+++------------------------------------------------------------------------------+-- | Parser for request headers.+pHeaders :: Parser [(ByteString, ByteString)]+pHeaders = many' header <?> "headers"+ where+ --------------------------------------------------------------------------+ slurp p = fst <$> match p++ --------------------------------------------------------------------------+ header = {-# SCC "pHeaders/header" #-}+ liftA2 (,)+ fieldName+ (char ':' *> skipSpace *> contents)++ --------------------------------------------------------------------------+ fieldName = {-# SCC "pHeaders/fieldName" #-}+ slurp (letter_ascii *> skipFieldChars)++ --------------------------------------------------------------------------+ contents = {-# SCC "pHeaders/contents" #-}+ liftA2 S.append+ (untilEOL <* crlf)+ (continuation <|> pure S.empty)++ --------------------------------------------------------------------------+ isLeadingWS w = {-# SCC "pHeaders/isLeadingWS" #-}+ w == ' ' || w == '\t'++ --------------------------------------------------------------------------+ leadingWhiteSpace = {-# SCC "pHeaders/leadingWhiteSpace" #-}+ skipWhile1 isLeadingWS++ --------------------------------------------------------------------------+ continuation = {-# SCC "pHeaders/continuation" #-}+ liftA2 S.cons+ (leadingWhiteSpace *> pure ' ')+ contents++ --------------------------------------------------------------------------+ skipWhile1 f = satisfy f *> skipWhile f+++------------------------------------------------------------------------------+-- unhelpfully, the spec mentions "old-style" cookies that don't have quotes+-- around the value. wonderful.+pWord :: Parser ByteString+pWord = pWord' isRFCText+++------------------------------------------------------------------------------+pWord' :: (Char -> Bool) -> Parser ByteString+pWord' charPred = pQuotedString' charPred <|> (takeWhile (/= ';'))+++------------------------------------------------------------------------------+pQuotedString :: Parser ByteString+pQuotedString = pQuotedString' isRFCText+++------------------------------------------------------------------------------+pQuotedString' :: (Char -> Bool) -> Parser ByteString+pQuotedString' charPred = q *> quotedText <* q+ where+ quotedText = (S.concat . L.toChunks . toLazyByteString) <$> f mempty++ f soFar = do+ t <- takeWhile qdtext+ let soFar' = soFar <> byteString t+ -- RFC says that backslash only escapes for <">+ choice [ string "\\\"" *> f (soFar' <> char8 '"')+ , pure soFar' ]++ q = char '"'+ qdtext = matchAll [ charPred, (/= '"'), (/= '\\') ]+++------------------------------------------------------------------------------+{-# INLINE isRFCText #-}+isRFCText :: Char -> Bool+isRFCText = not . isControl+++------------------------------------------------------------------------------+{-# INLINE matchAll #-}+matchAll :: [ Char -> Bool ] -> Char -> Bool+matchAll x c = and $ map ($ c) x+++------------------------------------------------------------------------------+pAvPairs :: Parser [(ByteString, ByteString)]+pAvPairs = do+ a <- pAvPair+ b <- many' (skipSpace *> char ';' *> skipSpace *> pAvPair)+ return $! a:b+++------------------------------------------------------------------------------+{-# INLINE pAvPair #-}+pAvPair :: Parser (ByteString, ByteString)+pAvPair = do+ key <- pToken <* skipSpace+ val <- liftM trim (option "" $ char '=' *> skipSpace *> pWord)+ return $! (key, val)+++------------------------------------------------------------------------------+pParameter :: Parser (ByteString, ByteString)+pParameter = pParameter' isRFCText+++------------------------------------------------------------------------------+pParameter' :: (Char -> Bool) -> Parser (ByteString, ByteString)+pParameter' valueCharPred = parser <?> "pParameter'"+ where+ parser = do+ key <- pToken <* skipSpace+ val <- liftM trim (char '=' *> skipSpace *> pWord' valueCharPred)+ return $! (trim key, val)+++------------------------------------------------------------------------------+{-# INLINE trim #-}+trim :: ByteString -> ByteString+trim = snd . S.span isSpace . fst . S.spanEnd isSpace+++------------------------------------------------------------------------------+pValueWithParameters :: Parser (ByteString, [(CI ByteString, ByteString)])+pValueWithParameters = pValueWithParameters' isRFCText+++------------------------------------------------------------------------------+pValueWithParameters' :: (Char -> Bool) -> Parser (ByteString, [(CI ByteString, ByteString)])+pValueWithParameters' valueCharPred = parser <?> "pValueWithParameters'"+ where+ parser = do+ value <- liftM trim (skipSpace *> takeWhile (/= ';'))+ params <- many' pParam+ endOfInput+ return (value, map (first CI.mk) params)+ pParam = skipSpace *> char ';' *> skipSpace *> pParameter' valueCharPred+++------------------------------------------------------------------------------+pContentTypeWithParameters :: Parser ( ByteString+ , [(CI ByteString, ByteString)] )+pContentTypeWithParameters = parser <?> "pContentTypeWithParameters"+ where+ parser = do+ value <- liftM trim (skipSpace *> takeWhile (not . isSep))+ params <- many' (skipSpace *> satisfy isSep *> skipSpace *> pParameter)+ endOfInput+ return $! (value, map (first CI.mk) params)++ isSep c = c == ';' || c == ','+++------------------------------------------------------------------------------+{-# INLINE pToken #-}+pToken :: Parser ByteString+pToken = takeWhile isToken+++------------------------------------------------------------------------------+{-# INLINE isToken #-}+isToken :: Char -> Bool+isToken = toTable f+ where+ f = matchAll [ isAscii+ , not . isControl+ , not . isSpace+ , not . flip elem [ '(', ')', '<', '>', '@', ',', ';'+ , ':', '\\', '\"', '/', '[', ']'+ , '?', '=', '{', '}' ]+ ]+++------------------------------------------------------------------------------+{-# INLINE pTokens #-}+-- | Used for "#field-name", and field-name = token, so "#token":+-- comma-separated tokens/field-names, like a header field list.+pTokens :: Parser [ByteString]+pTokens = (skipSpace *> pToken <* skipSpace) `sepBy'` char ','+++ ------------------+ -- Url encoding --+ ------------------++------------------------------------------------------------------------------+{-# INLINE parseToCompletion #-}+parseToCompletion :: Parser a -> ByteString -> Maybe a+parseToCompletion p s = toResult $ finish r+ where+ r = parse p s++ toResult (Done _ c) = Just c+ toResult _ = Nothing+++------------------------------------------------------------------------------+type DList a = [a] -> [a]++pUrlEscaped :: Parser ByteString+pUrlEscaped = do+ sq <- nextChunk id+ return $! S.concat $ sq []++ where+ --------------------------------------------------------------------------+ nextChunk :: DList ByteString -> Parser (DList ByteString)+ nextChunk !s = (endOfInput *> pure s) <|> do+ c <- anyChar+ case c of+ '+' -> plusSpace s+ '%' -> percentEncoded s+ _ -> unEncoded c s++ --------------------------------------------------------------------------+ percentEncoded :: DList ByteString -> Parser (DList ByteString)+ percentEncoded !l = do+ hx <- take 2+ when (S.length hx /= 2 || (not $ S.all isHexDigit hx)) $+ mzero++ let code = w2c ((unsafeFromHex hx) :: Word8)+ nextChunk $ l . ((S.singleton code) :)++ --------------------------------------------------------------------------+ unEncoded :: Char -> DList ByteString -> Parser (DList ByteString)+ unEncoded !c !l' = do+ let l = l' . ((S.singleton c) :)+ bs <- takeTill (flip elem ['%', '+'])+ if S.null bs+ then nextChunk l+ else nextChunk $ l . (bs :)++ --------------------------------------------------------------------------+ plusSpace :: DList ByteString -> Parser (DList ByteString)+ plusSpace l = nextChunk (l . ((S.singleton ' ') :))+++------------------------------------------------------------------------------+-- "...Only alphanumerics [0-9a-zA-Z], the special characters "$-_.+!*'(),"+-- [not including the quotes - ed], and reserved characters used for their+-- reserved purposes may be used unencoded within a URL."+++++------------------------------------------------------------------------------+-- | Decode an URL-escaped string (see+-- <http://tools.ietf.org/html/rfc2396.html#section-2.4>)+--+-- Example:+--+-- @+-- ghci> 'urlDecode' "1+attoparsec+%7e%3d+3+*+10%5e-2+meters"+-- Just "1 attoparsec ~= 3 * 10^-2 meters"+-- @+urlDecode :: ByteString -> Maybe ByteString+urlDecode = parseToCompletion pUrlEscaped+{-# INLINE urlDecode #-}+++------------------------------------------------------------------------------+-- | URL-escape a string (see+-- <http://tools.ietf.org/html/rfc2396.html#section-2.4>)+--+-- Example:+--+-- @+-- ghci> 'urlEncode' "1 attoparsec ~= 3 * 10^-2 meters"+-- "1+attoparsec+%7e%3d+3+*+10%5e-2+meters"+-- @+urlEncode :: ByteString -> ByteString+urlEncode = S.concat . L.toChunks . toLazyByteString . urlEncodeBuilder+{-# INLINE urlEncode #-}+++------------------------------------------------------------------------------+-- | URL-escape a string (see+-- <http://tools.ietf.org/html/rfc2396.html#section-2.4>) into a 'Builder'.+--+-- Example:+--+-- @+-- ghci> import "Data.ByteString.Builder"+-- ghci> 'toLazyByteString' . 'urlEncodeBuilder' $ "1 attoparsec ~= 3 * 10^-2 meters"+-- "1+attoparsec+%7e%3d+3+*+10%5e-2+meters"+-- @+urlEncodeBuilder :: ByteString -> Builder+urlEncodeBuilder = go mempty+ where+ go !b !s = maybe b' esc (S.uncons y)+ where+ (x,y) = S.span urlEncodeClean s+ b' = b <> byteString x+ esc (c,r) = let b'' = if c == ' '+ then b' <> char8 '+'+ else b' <> hexd c+ in go b'' r+++------------------------------------------------------------------------------+urlEncodeClean :: Char -> Bool+urlEncodeClean = toTable f+ where+ f c = any ($ c) [\c' -> isAscii c' && isAlphaNum c'+ , flip elem [ '$', '_', '-', '.', '!'+ , '*' , '\'', '(', ')', ',' ]]+++------------------------------------------------------------------------------+hexd :: Char -> Builder+hexd c0 = char8 '%' <> word8 hi <> word8 low+ where+ !c = c2w c0+ toDigit = c2w . intToDigit+ !low = toDigit $ fromEnum $ c .&. 0xf+ !hi = toDigit $ (c .&. 0xf0) `shiftr` 4++ shiftr (W8# a#) (I# b#) = I# (word2Int# (uncheckedShiftRL# a# b#))+#if MIN_VERSION_base(4,16,0)+ uncheckedShiftRL# a# b# = word8ToWord# (uncheckedShiftRLWord8# a# b#)+#endif+++------------------------------------------------------------------------------+finish :: Result a -> Result a+finish (Partial f) = flip feed "" $ f ""+finish x = x+++ ---------------------------------------+ -- application/x-www-form-urlencoded --+ ---------------------------------------++------------------------------------------------------------------------------+-- | Parse a string encoded in @application/x-www-form-urlencoded@ < http://en.wikipedia.org/wiki/POST_%28HTTP%29#Use_for_submitting_web_forms format>.+--+-- Example:+--+-- @+-- ghci> 'parseUrlEncoded' "Name=John+Doe&Name=Jane+Doe&Age=23&Formula=a+%2B+b+%3D%3D+13%25%21"+-- 'Data.Map.fromList' [("Age",["23"]),("Formula",["a + b == 13%!"]),("Name",["John Doe","Jane Doe"])]+-- @+parseUrlEncoded :: ByteString -> Map ByteString [ByteString]+parseUrlEncoded s = foldr ins Map.empty decoded++ where+ --------------------------------------------------------------------------+ ins (!k,v) !m = Map.insertWith (++) k [v] m++ --------------------------------------------------------------------------+ parts :: [(ByteString,ByteString)]+ parts = map breakApart $+ S.splitWith (\c -> c == '&' || c == ';') s++ --------------------------------------------------------------------------+ breakApart = (second (S.drop 1)) . S.break (== '=')++ --------------------------------------------------------------------------+ urldecode = parseToCompletion pUrlEscaped++ --------------------------------------------------------------------------+ decodeOne (a,b) = do+ !a' <- urldecode a+ !b' <- urldecode b+ return $! (a',b')++ --------------------------------------------------------------------------+ decoded = go id parts+ where+ go !dl [] = dl []+ go !dl (x:xs) = maybe (go dl xs)+ (\p -> go (dl . (p:)) xs)+ (decodeOne x)+++------------------------------------------------------------------------------+-- | Like 'printUrlEncoded', but produces a 'Builder' instead of a+-- 'ByteString'. Useful for constructing a large string efficiently in+-- a single step.+--+-- Example:+--+-- @+-- ghci> import "Data.Map"+-- ghci> import "Data.Monoid"+-- ghci> import "Data.ByteString.Builder"+-- ghci> let bldr = 'buildUrlEncoded' ('Data.Map.fromList' [("Name", ["John Doe"]), ("Age", ["23"])])+-- ghci> 'toLazyByteString' $ 'byteString' "http://example.com/script?" <> bldr+-- "http://example.com/script?Age=23&Name=John+Doe"+-- @+buildUrlEncoded :: Map ByteString [ByteString] -> Builder+buildUrlEncoded m = mconcat builders+ where+ builders = intersperse (char8 '&') $+ concatMap encodeVS $ Map.toList m++ encodeVS (k,vs) = map (encodeOne k) vs++ encodeOne k v = mconcat [ urlEncodeBuilder k+ , char8 '='+ , urlEncodeBuilder v ]+++------------------------------------------------------------------------------+-- | Given a collection of key-value pairs with possibly duplicate+-- keys (represented as a 'Data.Map.Map'), construct a string in+-- @application/x-www-form-urlencoded@ format.+--+-- Example:+--+-- @+-- ghci> 'printUrlEncoded' ('Data.Map.fromList' [("Name", ["John Doe"]), ("Age", ["23"])])+-- "Age=23&Name=John+Doe"+-- @+printUrlEncoded :: Map ByteString [ByteString] -> ByteString+printUrlEncoded = S.concat . L.toChunks . toLazyByteString . buildUrlEncoded+++ --------------------+ -- Cookie parsing --+ --------------------++------------------------------------------------------------------------------+-- these definitions try to mirror RFC-2068 (the HTTP/1.1 spec) and RFC-2109+-- (cookie spec): please point out any errors!+------------------------------------------------------------------------------+pCookies :: Parser [Cookie]+pCookies = do+ -- grab kvps and turn to strict bytestrings+ kvps <- pAvPairs+ return $! map toCookie $ filter (not . S.isPrefixOf "$" . fst) kvps++ where+ toCookie (nm,val) = Cookie nm val Nothing Nothing Nothing False False+++------------------------------------------------------------------------------+parseCookie :: ByteString -> Maybe [Cookie]+parseCookie = parseToCompletion pCookies+++ -----------------------+ -- utility functions --+ -----------------------++------------------------------------------------------------------------------+unsafeFromHex :: (Enum a, Num a, Bits a) => ByteString -> a+unsafeFromHex = S.foldl' f 0+ where+#if MIN_VERSION_base(4,5,0)+ sl = unsafeShiftL+#else+ sl = shiftL+#endif++ f !cnt !i = sl cnt 4 .|. nybble i++ nybble c | c >= '0' && c <= '9' = toEnum $! fromEnum c - fromEnum '0'+ | c >= 'a' && c <= 'f' = toEnum $! 10 + fromEnum c - fromEnum 'a'+ | c >= 'A' && c <= 'F' = toEnum $! 10 + fromEnum c - fromEnum 'A'+ | otherwise = error $ "bad hex digit: " ++ show c+{-# INLINE unsafeFromHex #-}+++------------------------------------------------------------------------------+-- Note: only works for nonnegative naturals+unsafeFromNat :: (Enum a, Num a, Bits a) => ByteString -> a+unsafeFromNat = S.foldl' f 0+ where+ zero = ord '0'+ f !cnt !i = cnt * 10 + toEnum (digitToInt i)++ digitToInt c = if d >= 0 && d <= 9+ then d+ else error $ "bad digit: '" ++ [c] ++ "'"+ where+ !d = ord c - zero+{-# INLINE unsafeFromNat #-}
src/Snap/Internal/Routing.hs view
@@ -1,55 +1,67 @@-module Snap.Internal.Routing where--+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE Rank2Types #-}+module Snap.Internal.Routing+ ( Route(..)+ , pRoute+ , route+ , routeEarliestNC+ , routeHeight+ , routeLocal+ , splitPath+ ) where -------------------------------------------------------------------------------import Control.Applicative ((<|>))-import Data.ByteString (ByteString)+import Control.Applicative ((<|>))+import Data.ByteString (ByteString)+import qualified Data.ByteString as B (head, intercalate, length, null, pack, splitWith, tail) import Data.ByteString.Internal (c2w)-import qualified Data.ByteString as B-import Data.Monoid-import qualified Data.Map as Map+import Data.HashMap.Strict (HashMap)+import qualified Data.HashMap.Strict as H (elems, empty, fromList, lookup, unionWith)+import qualified Data.Map as Map (empty, insertWith, unionWith)+import Snap.Internal.Core (MonadSnap, getRequest, getsRequest, localRequest, modifyRequest, pass, updateContextPath)+import Snap.Internal.Http.Types (Params, Request (rqContextPath, rqParams, rqPathInfo))+import Snap.Internal.Parsing (urlDecode)+#if !MIN_VERSION_base(4,8,0)+import Data.Monoid (Monoid (..))+#endif+import Data.Semigroup (Semigroup (..)) -------------------------------------------------------------------------------import Snap.Internal.Http.Types-import Snap.Internal.Types - -------------------------------------------------------------------------------{-|--The internal data type you use to build a routing tree. Matching is-done unambiguously.--'Capture' and 'Dir' routes can have a "fallback" route:-- - For 'Capture', the fallback is routed when there is nothing to capture- - For 'Dir', the fallback is routed when we can't find a route in its map--Fallback routes are stacked: i.e. for a route like:--> Dir [("foo", Capture "bar" (Action bar) NoRoute)] baz--visiting the URI foo/ will result in the "bar" capture being empty and-triggering its fallback. It's NoRoute, so we go to the nearest parent-fallback and try that, which is the baz action.---}-data Route a = Action (Snap a) -- wraps a 'Snap' action- | Capture ByteString (Route a) (Route a) -- captures the dir in a param- | Dir (Map.Map ByteString (Route a)) (Route a) -- match on a dir- | NoRoute+-- | The internal data type you use to build a routing tree. Matching is+-- done unambiguously.+--+-- 'Capture' and 'Dir' routes can have a "fallback" route:+--+-- - For 'Capture', the fallback is routed when there is nothing to capture+-- - For 'Dir', the fallback is routed when we can't find a route in its map+--+-- Fallback routes are stacked: i.e. for a route like:+--+-- > Dir [("foo", Capture "bar" (Action bar) NoRoute)] baz+--+-- visiting the URI foo/ will result in the "bar" capture being empty and+-- triggering its fallback. It's NoRoute, so we go to the nearest parent+-- fallback and try that, which is the baz action.+data Route a m = Action ((MonadSnap m) => m a) -- wraps a 'Snap' action+ -- captures the dir in a param+ | Capture ByteString (Route a m) (Route a m)+ -- match on a dir+ | Dir (HashMap ByteString (Route a m)) (Route a m)+ | NoRoute -------------------------------------------------------------------------------instance Monoid (Route a) where- mempty = NoRoute - mappend NoRoute r = r+instance Semigroup (Route a m) where+ NoRoute <> r = r - mappend l@(Action a) r = case r of+ l@(Action a) <> r = case r of (Action a') -> Action (a <|> a')- (Capture p r' fb) -> Capture p r' (mappend fb l)- (Dir _ _) -> mappend (Dir Map.empty l) r+ (Capture p r' fb) -> Capture p r' (fb <> l)+ (Dir _ _) -> Dir H.empty l <> r NoRoute -> l -- Whenever we're unioning two Captures and their capture variables@@ -57,162 +69,250 @@ -- 1. Prefer whichever route is longer -- 2. Else, prefer whichever has the earliest non-capture -- 3. Else, prefer the right-hand side- mappend l@(Capture p r' fb) r = case r of- (Action _) -> Capture p r' (mappend fb r)+ l@(Capture p r' fb) <> r = case r of+ (Action _) -> Capture p r' (fb <> r) (Capture p' r'' fb')- | p == p' -> Capture p (mappend r' r'') (mappend fb fb')- | rh' > rh'' -> Capture p r' (mappend fb r)- | rh' < rh'' -> Capture p' r'' (mappend fb' l)- | en' < en'' -> Capture p r' (mappend fb r)- | otherwise -> Capture p' r'' (mappend fb' l)+ | p == p' -> Capture p (r' <> r'') (fb <> fb')+ | rh' > rh'' -> Capture p r' (fb <> r)+ | rh' < rh'' -> Capture p' r'' (fb' <> l)+ | en' < en'' -> Capture p r' (fb <> r)+ | otherwise -> Capture p' r'' (fb' <> l) where rh' = routeHeight r' rh'' = routeHeight r'' en' = routeEarliestNC r' 1 en'' = routeEarliestNC r'' 1- (Dir rm fb') -> Dir rm (mappend fb' l)+ (Dir rm fb') -> Dir rm (fb' <> l) NoRoute -> l - mappend l@(Dir rm fb) r = case r of- (Action _) -> Dir rm (mappend fb r)- (Capture _ _ _) -> Dir rm (mappend fb r)- (Dir rm' fb') -> Dir (Map.unionWith mappend rm rm') (mappend fb fb')+ (<>) l@(Dir rm fb) r = case r of+ (Action _) -> Dir rm (fb <> r)+ (Capture _ _ _) -> Dir rm (fb <> r)+ (Dir rm' fb') -> Dir (H.unionWith (<>) rm rm') (fb <> fb') NoRoute -> l +instance Monoid (Route a m) where+ mempty = NoRoute+ mappend = (<>) -------------------------------------------------------------------------------routeHeight :: Route a -> Int+routeHeight :: Route a m -> Int routeHeight r = case r of NoRoute -> 1 (Action _) -> 1- (Capture _ r' _) -> 1+routeHeight r'- (Dir rm _) -> 1+foldl max 1 (map routeHeight $ Map.elems rm)+ (Capture _ r' _) -> 1 + routeHeight r'+ (Dir rm _) -> 1 + foldl max 1 (map routeHeight $ H.elems rm)+{-# INLINE routeHeight #-} -routeEarliestNC :: Route a -> Int -> Int++------------------------------------------------------------------------------+routeEarliestNC :: Route a m -> Int -> Int routeEarliestNC r n = case r of NoRoute -> n (Action _) -> n (Capture _ r' _) -> routeEarliestNC r' n+1 (Dir _ _) -> n+{-# INLINE routeEarliestNC #-} ------------------------------------------------------------------------------ -- | A web handler which, given a mapping from URL entry points to web -- handlers, efficiently routes requests to the correct handler. ----- The URL entry points are given as relative paths, for example: ----- > route [ ("foo/bar/quux", fooBarQuux) ]------ If the URI of the incoming request is------ > /foo/bar/quux+-- __Usage__ ----- or+-- The URL entry points are given as relative paths, for example: ----- > /foo/bar/quux/...anything...+-- > route [ ("foo/bar/quux", fooBarQuux) ] ----- then the request will be routed to \"@fooBarQuux@\", with 'rqContextPath'--- set to \"@\/foo\/bar\/quux\/@\" and 'rqPathInfo' set to--- \"@...anything...@\".+-- If the URI of the incoming request is @\/foo\/bar\/quux@ or+-- @\/foo\/bar\/quux\/...anything...@ then the request will be routed to+-- @\"fooBarQuux\"@, with 'rqContextPath' set to @\"\/foo\/bar\/quux\/\"@ and+-- 'rqPathInfo' set to @\"...anything...\"@. ----- A path component within an URL entry point beginning with a colon (\"@:@\")+-- A path component within an URL entry point beginning with a colon (@\":\"@) -- is treated as a /variable capture/; the corresponding path component within -- the request URI will be entered into the 'rqParams' parameters mapping with -- the given name. For instance, if the routes were: -- -- > route [ ("foo/:bar/baz", fooBazHandler) ] ----- Then a request for \"@\/foo\/saskatchewan\/baz@\" would be routed to--- @fooBazHandler@ with a mapping for:------ > "bar" => "saskatchewan"------ in its parameters table.+-- Then a request for @\"\/foo\/saskatchewan\/baz\"@ would be routed to+-- @fooBazHandler@ with a mapping for @\"bar\" => \"saskatchewan\"@ in its+-- parameters table. -- -- Longer paths are matched first, and specific routes are matched before -- captures. That is, if given routes: -- -- > [ ("a", h1), ("a/b", h2), ("a/:x", h3) ] ----- a request for \"@\/a\/b@\" will go to @h2@, \"@\/a\/s@\" for any /s/ will go--- to @h3@, and \"@\/a@\" will go to @h1@.+-- a request for @\"\/a\/b\"@ will go to @h2@, @\"\/a\/s\"@ for any /s/ will go+-- to @h3@, and @\"\/a\"@ will go to @h1@. ----- The following example matches \"@\/article@\" to an article index,--- \"@\/login@\" to a login, and \"@\/article\/...@\" to an article renderer.+-- The following example matches @\"\/article\"@ to an article index,+-- @\"\/login\"@ to a login, and @\"\/article\/...\"@ to an article renderer. ----- > route [ ("article", renderIndex)--- > , ("article/:id", renderArticle)--- > , ("login", method POST doLogin) ]+-- @+-- 'route' [ (\"article\", renderIndex)+-- , (\"article\/:id\", renderArticle)+-- , (\"login\", 'Snap.Core.method' POST doLogin) ]+-- @ ---route :: [(ByteString, Snap a)] -> Snap a+-- __Note: URL decoding__+--+-- A short note about URL decoding: path matching and variable capture are done+-- on /decoded/ URLs, but the contents of 'rqContextPath' and 'rqPathInfo' will+-- contain the original encoded URL, i.e. what the user entered. For example,+-- in the following scenario:+--+-- > route [ ("a b c d/", foo ) ]+--+-- A request for \"@/a+b+c+d@\" will be sent to @foo@ with 'rqContextPath' set+-- to @\"/a+b+c+d/\"@.+--+-- This behaviour changed as of Snap 0.6.1; previous versions had unspecified+-- (and buggy!) semantics here.+--+--+-- __Example:__+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as Map+-- ghci> import qualified "Data.ByteString.Char8" as B8+-- ghci> import "Snap.Test"+-- ghci> :{+-- ghci| let handler = do r \<- 'getRequest'+-- ghci| 'Snap.Core.writeBS' $ \"rqContextPath: \" \<> 'rqContextPath' r \<> \"\\n\"+-- ghci| 'Snap.Core.writeBS' $ \"rqPathInfo: \" \<> 'rqPathInfo' r \<> \"\\n\"+-- ghci| 'Snap.Core.writeBS' $ \"rqParams: \" \<> (B8.pack . show $ 'rqParams' r)+-- ghci| :}+-- ghci> 'Snap.Test.runHandler' ('Snap.Test.get' \"\/foo\/bar\" "Map.empty") ('route' [(\"foo\", handler)])+-- HTTP\/1.1 200 OK+-- server: Snap\/test+-- date: Sat, 02 Aug 2014 05:16:59 GMT+--+-- rqContextPath: \/foo\/+-- rqPathInfo: bar+-- rqParams: fromList []+-- ghci> 'Snap.Test.runHandler' ('Snap.Test.get' \"\/foo\/bar\" "Map.empty") ('route' [(\"foo\/:bar\", handler)])+-- [...]+--+-- rqContextPath: \/foo\/bar\/+-- rqPathInfo:+-- rqParams: fromList [(\"bar\",[\"bar\"])]+-- @+route :: MonadSnap m => [(ByteString, m a)] -> m a route rts = do- p <- getRequest >>= return . rqPathInfo- route' (return ()) ([], splitPath p) Map.empty rts'+ p <- getsRequest rqPathInfo+ route' (return $! ()) [] (splitPath p) Map.empty rts' where rts' = mconcat (map pRoute rts)+{-# INLINE route #-} --------------------------------------------------------------------------------- | The 'routeLocal' function is the same as 'route'', except it doesn't change--- the request's context path. This is useful if you want to route to a+-- | The 'routeLocal' function is the same as 'route', except it doesn't+-- change the request's context path. This is useful if you want to route to a -- particular handler but you want that handler to receive the 'rqPathInfo' as -- it is.-routeLocal :: [(ByteString, Snap a)] -> Snap a+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> import qualified "Data.ByteString.Char8" as B8+-- ghci> import "Snap.Test"+-- ghci> :{+-- ghci| let handler = do r \<- 'getRequest'+-- ghci| 'Snap.Core.writeBS' $ \"rqContextPath: \" \<> 'rqContextPath' r \<> \"\\n\"+-- ghci| 'Snap.Core.writeBS' $ \"rqPathInfo: \" \<> 'rqPathInfo' r \<> \"\\n\"+-- ghci| 'Snap.Core.writeBS' $ \"rqParams: \" \<> (B8.pack . show $ 'rqParams' r)+-- ghci| :}+-- ghci> 'Snap.Test.runHandler' ('Snap.Test.get' \"\/foo\/bar\" M.empty) ('routeLocal' [(\"foo\", handler)])+-- HTTP\/1.1 200 OK+-- server: Snap\/test+-- date: Sat, 02 Aug 2014 05:17:28 GMT+--+-- rqContextPath: \/+-- rqPathInfo: foo\/bar+-- ghci> 'Snap.Test.runHandler' ('Snap.Test.get' \"\/foo\/bar\" M.empty) ('routeLocal' [(\"foo\/:bar\", handler)])+-- [...]+--+-- rqContextPath: \/+-- rqPathInfo: foo\/bar+-- rqParams: fromList [(\"bar\",[\"bar\"])]+-- @+routeLocal :: MonadSnap m => [(ByteString, m a)] -> m a routeLocal rts = do req <- getRequest let ctx = rqContextPath req let p = rqPathInfo req let md = modifyRequest $ \r -> r {rqContextPath=ctx, rqPathInfo=p} - (route' md ([], splitPath p) Map.empty rts') <|> (md >> pass)+ (route' md [] (splitPath p) Map.empty rts') <|> (md >> pass) where rts' = mconcat (map pRoute rts)+{-# INLINE routeLocal #-} + ------------------------------------------------------------------------------ splitPath :: ByteString -> [ByteString] splitPath = B.splitWith (== (c2w '/'))+{-# INLINE splitPath #-} -------------------------------------------------------------------------------pRoute :: (ByteString, Snap a) -> Route a+pRoute :: MonadSnap m => (ByteString, m a) -> Route a m pRoute (r, a) = foldr f (Action a) hier where hier = filter (not . B.null) $ B.splitWith (== (c2w '/')) r f s rt = if B.head s == c2w ':' then Capture (B.tail s) rt NoRoute- else Dir (Map.fromList [(s, rt)]) NoRoute+ else Dir (H.fromList [(s, rt)]) NoRoute+{-# INLINE pRoute #-} -------------------------------------------------------------------------------route' :: Snap ()- -> ([ByteString], [ByteString])+route' :: MonadSnap m+ => m () -- ^ action to run before we call the user handler+ -> [ByteString] -- ^ the \"context\"; the list of path segments we've+ -- already successfully matched, in reverse order+ -> [ByteString] -- ^ the list of path segments we haven't yet matched -> Params- -> Route a- -> Snap a-route' pre (ctx, _) params (Action action) =+ -> Route a m+ -> m a+route' pre !ctx _ !params (Action action) = localRequest (updateContextPath (B.length ctx') . updateParams) (pre >> action) where ctx' = B.intercalate (B.pack [c2w '/']) (reverse ctx) updateParams req = req- { rqParams = Map.unionWith (++) params (rqParams req) }+ { rqParams = Map.unionWith (flip (++)) params (rqParams req) } -route' pre (ctx, []) params (Capture _ _ fb) =- route' pre (ctx, []) params fb-route' pre (ctx, cwd:rest) params (Capture p rt fb) =- (route' pre (cwd:ctx, rest) params' rt) <|>- (route' pre (ctx, cwd:rest) params fb)+route' pre !ctx [] !params (Capture _ _ fb) =+ route' pre ctx [] params fb++route' pre !ctx paths@(cwd:rest) !params (Capture p rt fb)+ | B.null cwd = fallback+ | otherwise = m <|> fallback where- params' = Map.insertWith (++) p [cwd] params+ fallback = route' pre ctx paths params fb+ m = maybe pass+ (\cwd' -> let params' = Map.insertWith (flip (++)) p [cwd'] params+ in route' pre (cwd:ctx) rest params' rt)+ (urlDecode cwd) -route' pre (ctx, []) params (Dir _ fb) =- route' pre (ctx, []) params fb-route' pre (ctx, cwd:rest) params (Dir rtm fb) =- case Map.lookup cwd rtm of- Just rt -> (route' pre (cwd:ctx, rest) params rt) <|>- (route' pre (ctx, cwd:rest) params fb)- Nothing -> route' pre (ctx, cwd:rest) params fb+route' pre !ctx [] !params (Dir _ fb) =+ route' pre ctx [] params fb+route' pre !ctx paths@(cwd:rest) !params (Dir rtm fb) = do+ cwd' <- maybe pass return $ urlDecode cwd+ case H.lookup cwd' rtm of+ Just rt -> (route' pre (cwd:ctx) rest params rt) <|>+ (route' pre ctx paths params fb)+ Nothing -> route' pre ctx paths params fb -route' _ _ _ NoRoute = pass+route' _ _ _ _ NoRoute = pass
+ src/Snap/Internal/Test/Assertions.hs view
@@ -0,0 +1,168 @@+{-# LANGUAGE CPP, OverloadedStrings #-}+module Snap.Internal.Test.Assertions where++------------------------------------------------------------------------------+import Control.Monad (liftM)+import Data.ByteString.Builder (toLazyByteString)+import Data.ByteString.Char8 (ByteString)+import qualified Data.ByteString.Char8 as S+import qualified Data.ByteString.Lazy.Char8 as L+import Data.Maybe (fromJust)+import Snap.Internal.Http.Types (Response (rspBody, rspStatus), getHeader, rspBodyToEnum)+import qualified System.IO.Streams as Streams+import Test.HUnit (Assertion, assertBool, assertEqual)+import Text.Regex.Posix ((=~))+#if !MIN_VERSION_base(4,8,0)+import Data.Monoid (mconcat)+#endif+------------------------------------------------------------------------------++------------------------------------------------------------------------------+-- | Given a 'Response', return its body as a 'ByteString'.+--+-- Example:+--+-- @+-- ghci> 'getResponseBody' 'emptyResponse'+-- \"\"+-- @+--+getResponseBody :: Response -> IO ByteString+getResponseBody rsp = do+ (os, grab) <- Streams.listOutputStream+ enum os+ liftM toBS grab++ where+ enum os = do+ os' <- rspBodyToEnum (rspBody rsp) os+ Streams.write Nothing os'++ toBS = S.concat . L.toChunks . toLazyByteString . mconcat+++------------------------------------------------------------------------------+-- | Given a 'Response', assert that its HTTP status code is 200 (success).+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Test.HUnit" as T+-- ghci> let test = T.runTestTT . T.TestCase+-- ghci> test $ 'assertSuccess' 'Snap.Core.emptyResponse'+-- Cases: 1 Tried: 1 Errors: 0 Failures: 0+-- Counts {cases = 1, tried = 1, errors = 0, failures = 0}+-- ghci> test $ 'assertSuccess' ('Snap.Core.setResponseStatus' 500 \"Internal Server Error\" 'Snap.Core.emptyResponse')+-- ### Failure:+-- Expected success (200) but got (500)+-- expected: 200+-- but got: 500+-- Cases: 1 Tried: 1 Errors: 0 Failures: 1+-- Counts {cases = 1, tried = 1, errors = 0, failures = 1}+-- @+assertSuccess :: Response -> Assertion+assertSuccess rsp = assertEqual message 200 status+ where+ message = "Expected success (200) but got (" ++ (show status) ++ ")"+ status = rspStatus rsp+++------------------------------------------------------------------------------+-- | Given a 'Response', assert that its HTTP status code is 404 (Not Found).+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> 'assert404' $ 'Snap.Core.setResponseStatus' 404 \"Not Found\" 'Snap.Core.emptyResponse'+-- ghci> 'assert404' 'Snap.Core.emptyResponse'+-- *** Exception: HUnitFailure \"Expected Not Found (404) but got (200)\\nexpected: 404\\n but got: 200\"+-- @+assert404 :: Response -> Assertion+assert404 rsp = assertEqual message 404 status+ where+ message = "Expected Not Found (404) but got (" ++ (show status) ++ ")"+ status = rspStatus rsp+++------------------------------------------------------------------------------+-- | Given a 'Response', assert that its HTTP status code is between 300 and 399+-- (a redirect), and that the Location header of the 'Response' points to the+-- specified URI.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> let r' = 'Snap.Core.setResponseStatus' 301 \"Moved Permanently\" 'Snap.Core.emptyResponse'+-- ghci> let r = 'Snap.Core.setHeader' \"Location\" \"www.example.com\" r'+-- ghci> 'assertRedirectTo' \"www.example.com\" r+-- ghci> 'assertRedirectTo' \"www.example.com\" 'Snap.Core.emptyResponse'+-- *** Exception: HUnitFailure \"Expected redirect but got status code (200)\"+-- @+assertRedirectTo :: ByteString -- ^ The Response should redirect to this+ -- URI+ -> Response+ -> Assertion+assertRedirectTo uri rsp = do+ assertRedirect rsp+ assertEqual message uri rspUri++ where+ rspUri = fromJust $ getHeader "Location" rsp+ message = "Expected redirect to " ++ show uri+ ++ " but got redirected to "+ ++ show rspUri ++ " instead"+++------------------------------------------------------------------------------+-- | Given a 'Response', assert that its HTTP status code is between 300 and 399+-- (a redirect).+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> 'assertRedirect' $ 'Snap.Core.setResponseStatus' 301 \"Moved Permanently\" 'Snap.Core.emptyResponse'+-- ghci> 'assertRedirect' 'Snap.Core.emptyResponse'+-- *** Exception: HUnitFailure \"Expected redirect but got status code (200)\"+-- @+assertRedirect :: Response -> Assertion+assertRedirect rsp = assertBool message (300 <= status && status <= 399)+ where+ message = "Expected redirect but got status code ("+ ++ show status ++ ")"+ status = rspStatus rsp+++------------------------------------------------------------------------------+-- | Given a 'Response', assert that its body matches the given regular+-- expression.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "System.IO.Streams" as Streams+-- ghci> import qualified "Data.ByteString.Builder" as Builder+-- ghci> :{+-- ghci| let r = 'Snap.Core.setResponseBody'+-- ghci| (\out -> do+-- ghci| Streams.write (Just $ Builder.byteString \"Hello, world!\") out+-- ghci| return out)+-- ghci| 'Snap.Core.emptyResponse'+-- ghci| :}+-- ghci> 'assertBodyContains' \"^Hello\" r+-- ghci> 'assertBodyContains' \"Bye\" r+-- *** Exception: HUnitFailure "Expected body to match regexp \\\"\\\"Bye\\\"\\\", but didn\'t"+-- @+assertBodyContains :: ByteString -- ^ Regexp that will match the body content+ -> Response+ -> Assertion+assertBodyContains match rsp = do+ body <- getResponseBody rsp+ assertBool message (body =~ match)+ where+ message = "Expected body to match regexp \"" ++ show match+ ++ "\", but didn't"
+ src/Snap/Internal/Test/RequestBuilder.hs view
@@ -0,0 +1,1077 @@+{- Temporary workaround for https://ghc.haskell.org/trac/ghc/ticket/9127 -}+{-# OPTIONS_GHC -fno-warn-unused-binds #-}++{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE Rank2Types #-}++module Snap.Internal.Test.RequestBuilder+ ( RequestBuilder+ , MultipartParams+ , MultipartParam(..)+ , FileData (..)+ , RequestType (..)+ , addHeader+ , buildRequest+ , delete+ , evalHandler+ , evalHandlerM+ , get+ , head+ , postMultipart+ , postRaw+ , postUrlEncoded+ , put+ , requestToString+ , responseToString+ , runHandler+ , runHandlerM+ , setContentType+ , setHeader+ , addCookies+ , setHttpVersion+ , setQueryString+ , setQueryStringRaw+ , setRequestPath+ , setRequestType+ , setSecure+ ) where++------------------------------------------------------------------------------+import Control.Monad (liftM, replicateM, void)+import Control.Monad.State.Strict (MonadIO (..), MonadState, MonadTrans, StateT, execStateT, modify)+import qualified Control.Monad.State.Strict as State+import Data.Bits (Bits ((.&.), unsafeShiftR))+import qualified Data.ByteString as S8+import Data.ByteString.Builder (Builder, byteString, char8, stringUtf8, toLazyByteString, word8)+import Data.ByteString.Char8 (ByteString)+import qualified Data.ByteString.Char8 as S+import qualified Data.ByteString.Lazy.Char8 as L+import Data.CaseInsensitive (CI, original)+import qualified Data.Map as Map+import qualified Data.Vector as V+import Data.Word (Word8)+import Prelude hiding (head)+import Snap.Core (Cookie (Cookie), Method (DELETE, GET, HEAD, POST, PUT), MonadSnap, Params, Request (rqContentLength, rqContextPath, rqCookies, rqHeaders, rqHostName, rqIsSecure, rqMethod, rqParams, rqPathInfo, rqPostParams, rqQueryParams, rqQueryString, rqURI, rqVersion), Response, Snap, deleteHeader, formatHttpTime, getHeader, parseUrlEncoded, printUrlEncoded, runSnap)+import Snap.Internal.Core (evalSnap, fixupResponse)+import Snap.Internal.Http.Types (Request (Request, rqBody), Response (rspBody, rspContentLength), rspBodyToEnum)+import qualified Snap.Internal.Http.Types as H+import qualified Snap.Types.Headers as H+import qualified System.IO.Streams as Streams+import System.PosixCompat.Time (epochTime)+import System.Random (randomIO)+import Text.Printf (printf)+#if !MIN_VERSION_base(4,8,0)+import Control.Applicative (Applicative)+import Data.Monoid (Monoid (mappend, mconcat, mempty))+#endif+------------------------------------------------------------------------------+++------------------------------------------------------------------------------+-- | RequestBuilder is a monad transformer that allows you to conveniently+-- build a snap 'Request' for testing.+newtype RequestBuilder m a = RequestBuilder (StateT Request m a)+ deriving ( Applicative+ , Functor+ , Monad+#if MIN_VERSION_base(4,13,0)+ , MonadFail+#endif+ , MonadIO+ , MonadState Request+ , MonadTrans+ )+++------------------------------------------------------------------------------+mkDefaultRequest :: IO Request+mkDefaultRequest = do+ b <- Streams.fromList $! []+ return $ Request "localhost"+ "127.0.0.1"+ 60000+ "127.0.0.1"+ 8080+ "localhost"+ False+ H.empty+ b+ Nothing+ GET+ (1,1)+ []+ ""+ "/"+ "/"+ ""+ Map.empty+ Map.empty+ Map.empty+++------------------------------------------------------------------------------+-- | Runs a 'RequestBuilder', producing the desired 'Request'.+--+-- N.B. /please/ don't use the request you get here in a real Snap application;+-- things will probably break. Don't say you weren't warned :-)+--+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> 'buildRequest' $ 'get' \"\/foo\/bar\" M.empty+-- GET \/foo\/bar HTTP\/1.1+-- host: localhost+--+-- sn="localhost" c=127.0.0.1:60000 s=127.0.0.1:8080 ctx=\/ clen=n\/a+-- @+buildRequest :: MonadIO m => RequestBuilder m () -> m Request+buildRequest mm = do+ let (RequestBuilder m) = (mm >> fixup)+ rq0 <- liftIO mkDefaultRequest+ execStateT m rq0++ where+ fixup = do+ fixupURI+ fixupMethod+ fixupCL+ fixupParams+ fixupHost++ fixupMethod = do+ rq <- rGet+ if (rqMethod rq == GET || rqMethod rq == DELETE ||+ rqMethod rq == HEAD)+ then do+ -- drain the old request body and replace it with a new one+ !_ <- liftIO $ Streams.toList $ rqBody rq+ !b <- liftIO $ Streams.fromList $! []+ -- These requests are not permitted to have bodies+ let rq' = deleteHeader "Content-Type" $+ rq { rqBody = b }+ rPut $ rq' { rqContentLength = Nothing }+ else return $! ()++ fixupCL = do+ rq <- rGet+ maybe (rPut $ deleteHeader "Content-Length" rq)+ (\cl -> rPut $ H.setHeader "Content-Length"+ (S.pack (show cl)) rq)+ (rqContentLength rq)++ fixupParams = do+ rq <- rGet+ let !query = rqQueryString rq+ -- force the stuff from mkDefaultRequest that we just overwrite+ let !_ = rqPostParams rq+ let !_ = rqParams rq+ let !_ = rqQueryParams rq+ let !queryParams = parseUrlEncoded query+ let !mbCT = getHeader "Content-Type" rq++ (!postParams, rq') <-+ if mbCT == Just "application/x-www-form-urlencoded"+ then liftIO $ do+ !l <- Streams.toList $ rqBody rq+ -- snap-server regurgitates the parsed form body+ !b <- Streams.fromList l+ return (parseUrlEncoded (S.concat l), rq { rqBody = b })+ else return (Map.empty, rq)+ let !newParams = Map.unionWith (flip (++)) queryParams postParams++ rPut $ rq' { rqParams = newParams+ , rqPostParams = postParams+ , rqQueryParams = queryParams }++ fixupHost = do+ rq <- rGet+ case H.getHeader "Host" rq of+ Nothing -> do+ let !hn = rqHostName rq+ rPut $ H.setHeader "Host" hn rq+ Just hn ->+ rPut $ rq { rqHostName = hn }+++------------------------------------------------------------------------------+-- | A request body of type \"@multipart/form-data@\" consists of a set of+-- named form parameters, each of which can by either a list of regular form+-- values or a set of file uploads.+type MultipartParams = [(ByteString, MultipartParam)]+++------------------------------------------------------------------------------+-- | A single \"@multipart/form-data@\" form parameter: either a list of regular+-- form values or a set of file uploads.+data MultipartParam =+ FormData [ByteString]+ -- ^ a form variable consisting of the given 'ByteString' values.+ | Files [FileData]+ -- ^ a file upload consisting of the given 'FileData' values.+ deriving (Show)+++------------------------------------------------------------------------------+-- | Represents a single file upload for the 'MultipartParam'.+data FileData = FileData {+ fdFileName :: ByteString -- ^ the file's name+ , fdContentType :: ByteString -- ^ the file's content-type+ , fdContents :: ByteString -- ^ the file contents+ }+ deriving (Show)+++------------------------------------------------------------------------------+-- | The 'RequestType' datatype enumerates the different kinds of HTTP+-- requests you can generate using the testing interface. Most users will+-- prefer to use the 'get', 'postUrlEncoded', 'postMultipart', 'put', and+-- 'delete' convenience functions.+data RequestType+ = GetRequest+ | RequestWithRawBody Method ByteString+ | MultipartPostRequest MultipartParams+ | UrlEncodedPostRequest Params+ | DeleteRequest+ deriving (Show)+++------------------------------------------------------------------------------+-- | Sets the type of the 'Request' being built.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> 'buildRequest' $ 'delete' \"\/foo\/bar\" M.empty >> 'setRequestType' GetRequest+-- GET \/foo\/bar HTTP\/1.1+-- host: localhost+--+-- sn="localhost" c=127.0.0.1:60000 s=127.0.0.1:8080 ctx=\/ clen=n\/a+-- @+setRequestType :: MonadIO m => RequestType -> RequestBuilder m ()+setRequestType GetRequest = do+ rq <- rGet+ body <- liftIO $ Streams.fromList $! []++ rPut $ rq { rqMethod = GET+ , rqContentLength = Nothing+ , rqBody = body+ }++setRequestType DeleteRequest = do+ rq <- rGet+ body <- liftIO $ Streams.fromList $! []++ rPut $ rq { rqMethod = DELETE+ , rqContentLength = Nothing+ , rqBody = body+ }++setRequestType (RequestWithRawBody m b) = do+ rq <- rGet+ body <- liftIO $ Streams.fromList $! [ b ]+ rPut $ rq { rqMethod = m+ , rqContentLength = Just $ fromIntegral $ S.length b+ , rqBody = body+ }++setRequestType (MultipartPostRequest fp) = encodeMultipart fp++setRequestType (UrlEncodedPostRequest fp) = do+ rq <- liftM (H.setHeader "Content-Type"+ "application/x-www-form-urlencoded") rGet+ let b = printUrlEncoded fp+ body <- liftIO $ Streams.fromList $! [b]++ rPut $ rq { rqMethod = POST+ , rqContentLength = Just $! fromIntegral $ S.length b+ , rqBody = body+ }+++------------------------------------------------------------------------------+makeBoundary :: MonadIO m => m ByteString+makeBoundary = do+ xs <- liftIO $ replicateM 16 randomWord8+ let x = S.pack $ map (toEnum . fromEnum) xs+ return $ S.concat [ "snap-boundary-", encode x ]++ where+ randomWord8 :: IO Word8+ randomWord8 = liftM (\c -> toEnum $ c .&. 0xff) randomIO++ table = V.fromList [ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'+ , 'a', 'b', 'c', 'd', 'e', 'f' ]++ encode = toByteString . S8.foldl' f mempty++#if MIN_VERSION_base(4,5,0)+ shR = unsafeShiftR+#else+ shR = shiftR+#endif++ f m c = let low = c .&. 0xf+ hi = (c .&. 0xf0) `shR` 4+ k = \i -> word8 $! toEnum $! fromEnum $!+ V.unsafeIndex table (fromEnum i)+ in m `mappend` k hi `mappend` k low+++------------------------------------------------------------------------------+multipartHeader :: ByteString -> ByteString -> Builder+multipartHeader boundary name =+ mconcat [ byteString boundary+ , byteString "\r\ncontent-disposition: form-data"+ , byteString "; name=\""+ , byteString name+ , byteString "\"\r\n" ]+++------------------------------------------------------------------------------+-- Assume initial or preceding "--" just before this+encodeFormData :: ByteString -> ByteString -> [ByteString] -> IO Builder+encodeFormData boundary name vals =+ case vals of+ [] -> return mempty+ [v] -> return $ mconcat [ hdr+ , cr+ , byteString v+ , byteString "\r\n--" ]+ _ -> multi++ where+ hdr = multipartHeader boundary name+ cr = byteString "\r\n"++ oneVal b v = mconcat [ byteString b+ , cr+ , cr+ , byteString v+ , byteString "\r\n--" ]++ multi = do+ b <- makeBoundary+ return $ mconcat [ hdr+ , multipartMixed b+ , cr+ , byteString "--"+ , mconcat (map (oneVal b) vals)+ , byteString b+ , byteString "--\r\n--" ]+++------------------------------------------------------------------------------+multipartMixed :: ByteString -> Builder+multipartMixed b = mconcat [ byteString "Content-Type: multipart/mixed"+ , byteString "; boundary="+ , byteString b+ , byteString "\r\n" ]+++------------------------------------------------------------------------------+encodeFiles :: ByteString -> ByteString -> [FileData] -> IO Builder+encodeFiles boundary name files =+ case files of+ [] -> return mempty+ _ -> do+ b <- makeBoundary+ return $ mconcat [ hdr+ , multipartMixed b+ , cr+ , byteString "--"+ , mconcat (map (oneVal b) files)+ , byteString b+ , byteString "--\r\n--"+ ]++ where+ --------------------------------------------------------------------------+ contentDisposition fn = mconcat [+ byteString "Content-Disposition: attachment"+ , byteString "; filename=\""+ , byteString fn+ , byteString "\"\r\n"+ ]++ --------------------------------------------------------------------------+ contentType ct = mconcat [+ byteString "Content-Type: "+ , byteString ct+ , cr+ ]++ --------------------------------------------------------------------------+ oneVal b fd =+ mconcat [ byteString b+ , cr+ , contentType ct+ , contentDisposition fileName+ , byteString "Content-Transfer-Encoding: binary\r\n"+ , cr+ , byteString contents+ , byteString "\r\n--"+ ]+ where+ fileName = fdFileName fd+ ct = fdContentType fd+ contents = fdContents fd++ --------------------------------------------------------------------------+ hdr = multipartHeader boundary name+ cr = byteString "\r\n"+++------------------------------------------------------------------------------+encodeMultipart :: MonadIO m => MultipartParams -> RequestBuilder m ()+encodeMultipart kvps = do+ boundary <- liftIO $ makeBoundary+ builders <- liftIO $ mapM (handleOne boundary) kvps++ let b = toByteString $ mconcat (byteString "--" : builders)+ `mappend` finalBoundary boundary++ rq0 <- rGet++ body <- liftIO $ Streams.fromList [b]++ let rq = H.setHeader "Content-Type"+ (S.append "multipart/form-data; boundary=" boundary)+ rq0++ rPut $ rq { rqMethod = POST+ , rqContentLength = Just $ fromIntegral $ S.length b+ , rqBody = body+ }+++ where+ finalBoundary b = mconcat [byteString b, byteString "--\r\n"]++ handleOne boundary (name, mp) =+ case mp of+ (FormData vals) -> encodeFormData boundary name vals+ (Files fs) -> encodeFiles boundary name fs+++------------------------------------------------------------------------------+fixupURI :: Monad m => RequestBuilder m ()+fixupURI = do+ rq <- rGet+ upd rq $! S.concat [ rqContextPath rq+ , rqPathInfo rq+ , let q = rqQueryString rq+ in if S.null q+ then ""+ else S.append "?" q+ ]+ where+ upd rq !u = let !_ = rqURI rq+ in rPut $ rq { rqURI = u }+++------------------------------------------------------------------------------+-- | Sets the request's query string to be the raw bytestring provided,+-- without any escaping or other interpretation. Most users should instead+-- choose the 'setQueryString' function, which takes a parameter mapping.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> 'buildRequest' $ 'get' \"\/foo\/bar\" M.empty >> 'setQueryStringRaw' "param0=baz¶m1=qux"+-- GET \/foo\/bar?param0=baz¶m1=qux HTTP\/1.1+-- host: localhost+--+-- sn="localhost" c=127.0.0.1:60000 s=127.0.0.1:8080 ctx=\/ clen=n\/a+-- params: param0: ["baz"], param1: ["qux"]+-- @+setQueryStringRaw :: Monad m => ByteString -> RequestBuilder m ()+setQueryStringRaw r = do+ rq <- rGet+ rPut $ rq { rqQueryString = r }+ fixupURI+++------------------------------------------------------------------------------+-- | Escapes the given parameter mapping and sets it as the request's query+-- string.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> 'buildRequest' $ 'get' \"\/foo\/bar\" M.empty >> 'setQueryString' (M.fromList [("param0", ["baz"]), ("param1", ["qux"])])+-- GET \/foo\/bar?param0=baz¶m1=qux HTTP\/1.1+-- host: localhost+--+-- sn="localhost" c=127.0.0.1:60000 s=127.0.0.1:8080 ctx=\/ clen=n\/a+-- params: param0: ["baz"], param1: ["qux"]+-- @+setQueryString :: Monad m => Params -> RequestBuilder m ()+setQueryString p = setQueryStringRaw $ printUrlEncoded p+++------------------------------------------------------------------------------+-- | Sets the given header in the request being built, overwriting any header+-- with the same name already present.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> :{+-- ghci| 'buildRequest' $ do get \"\/foo\/bar\" M.empty+-- ghci| 'setHeader' \"Accept\" "text\/html"+-- ghci| 'setHeader' \"Accept\" "text\/plain"+-- ghci| :}+-- GET \/foo\/bar HTTP\/1.1+-- accept: text\/plain+-- host: localhost+--+-- sn="localhost" c=127.0.0.1:60000 s=127.0.0.1:8080 ctx=\/ clen=n\/a+-- @+setHeader :: (Monad m) => CI ByteString -> ByteString -> RequestBuilder m ()+setHeader k v = rModify (H.setHeader k v)+++------------------------------------------------------------------------------+-- | Adds the given header to the request being built.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> :{+-- ghci| 'buildRequest' $ do 'get' \"\/foo\/bar\" M.empty+-- ghci| 'addHeader' \"Accept\" "text\/html"+-- ghci| 'addHeader' \"Accept\" "text\/plain"+-- ghci| :}+-- GET \/foo\/bar HTTP\/1.1+-- accept: text\/html,text\/plain+-- host: localhost+--+-- sn="localhost" c=127.0.0.1:60000 s=127.0.0.1:8080 ctx=\/ clen=n\/a+-- @+addHeader :: (Monad m) => CI ByteString -> ByteString -> RequestBuilder m ()+addHeader k v = rModify (H.addHeader k v)++------------------------------------------------------------------------------+-- | Adds the given cookies to the request being built.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> import "Snap.Core"+-- ghci> let cookie = 'Snap.Core.Cookie' "name" "value" Nothing Nothing Nothing False False+-- ghci> 'buildRequest' $ 'get' \"\/foo\/bar\" M.empty >> 'addCookies' [cookie]+-- GET \/foo\/bar HTTP\/1.1+-- cookie: name=value+-- host: localhost+--+-- sn="localhost" c=127.0.0.1:60000 s=127.0.0.1:8080 ctx=\/ clen=n\/a+-- cookies: Cookie {cookieName = "name", cookieValue = "value", ...}+-- @+addCookies :: (Monad m) => [Cookie] -> RequestBuilder m ()+addCookies cookies = do+ rModify $ \rq -> rq { rqCookies = rqCookies rq ++ cookies }+ allCookies <- liftM rqCookies rGet+ let cstr = map cookieToBS allCookies+ setHeader "Cookie" $ S.intercalate "; " cstr+++------------------------------------------------------------------------------+-- | Convert 'Cookie' into 'ByteString' for output.+cookieToBS :: Cookie -> ByteString+cookieToBS (Cookie k v !_ !_ !_ !_ !_) = cookie+ where+ cookie = S.concat [k, "=", v]+++------------------------------------------------------------------------------+-- | Sets the request's @content-type@ to the given MIME type.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> 'buildRequest' $ 'put' \"\/foo\/bar\" "text\/html" "some text" >> 'setContentType' "text\/plain"+-- PUT \/foo\/bar HTTP\/1.1+-- content-type: text\/plain+-- content-length: 9+-- host: localhost+--+-- sn="localhost" c=127.0.0.1:60000 s=127.0.0.1:8080 ctx=\/ clen=9+-- @+setContentType :: Monad m => ByteString -> RequestBuilder m ()+setContentType c = rModify (H.setHeader "Content-Type" c)+++------------------------------------------------------------------------------+-- | Controls whether the test request being generated appears to be an https+-- request or not.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> 'buildRequest' $ 'delete' \"\/foo\/bar\" M.empty >> 'setSecure' True+-- DELETE \/foo\/bar HTTP\/1.1+-- host: localhost+--+-- sn="localhost" c=127.0.0.1:60000 s=127.0.0.1:8080 ctx=\/ clen=n\/a secure+-- @+setSecure :: Monad m => Bool -> RequestBuilder m ()+setSecure b = rModify $ \rq -> rq { rqIsSecure = b }+++------------------------------------------------------------------------------+-- | Sets the test request's http version+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> 'buildRequest' $ 'delete' \"\/foo\/bar\" M.empty >> 'setHttpVersion' (1,0)+-- DELETE \/foo\/bar HTTP\/1.0+-- host: localhost+--+-- sn="localhost" c=127.0.0.1:60000 s=127.0.0.1:8080 ctx=\/ clen=n\/a+-- @+setHttpVersion :: Monad m => (Int,Int) -> RequestBuilder m ()+setHttpVersion v = rModify $ \rq -> rq { rqVersion = v }+++------------------------------------------------------------------------------+-- | Sets the request's path. The path provided must begin with a \"@/@\" and+-- must /not/ contain a query string; if you want to provide a query string+-- in your test request, you must use 'setQueryString' or 'setQueryStringRaw'.+-- Note that 'rqContextPath' is never set by any 'RequestBuilder' function.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> 'buildRequest' $ 'get' \"\/foo\/bar\" M.empty >> 'setRequestPath' "\/bar\/foo"+-- GET \/bar\/foo HTTP\/1.1+-- host: localhost+--+-- sn="localhost" c=127.0.0.1:60000 s=127.0.0.1:8080 ctx=\/ clen=n\/a+-- @+setRequestPath :: Monad m => ByteString -> RequestBuilder m ()+setRequestPath p0 = do+ rModify $ \rq -> rq { rqContextPath = "/"+ , rqPathInfo = p }+ fixupURI++ where+ p = if S.isPrefixOf "/" p0 then S.drop 1 p0 else p0+++------------------------------------------------------------------------------+-- | Builds an HTTP \"GET\" request with the given query parameters.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> 'buildRequest' $ 'get' \"\/foo\/bar\" (M.fromList [("param0", ["baz", "quux"])])+-- GET \/foo\/bar?param0=baz¶m0=quux HTTP\/1.1+-- host: localhost+--+-- sn="localhost" c=127.0.0.1:60000 s=127.0.0.1:8080 ctx=\/ clen=n\/a+-- params: param0: ["baz","quux"]+-- @+get :: MonadIO m =>+ ByteString -- ^ request path+ -> Params -- ^ request's form parameters+ -> RequestBuilder m ()+get uri params = do+ setRequestType GetRequest+ setQueryString params+ setRequestPath uri++------------------------------------------------------------------------------+-- | Builds an HTTP \"HEAD\" request with the given query parameters.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> 'buildRequest' $ 'head' \"\/foo\/bar\" (M.fromList ("param0", ["baz", "quux"])])+-- HEAD \/foo\/bar?param0=baz¶m0=quux HTTP\/1.1+-- host: localhost+--+-- sn="localhost" c=127.0.0.1:60000 s=127.0.0.1:8080 ctx=\/ clen=n\/a+-- params: param0: ["baz","quux"]+-- @+-- @since 1.0.4.3 +head :: MonadIO m =>+ ByteString -- ^ request path+ -> Params -- ^ request's form parameters+ -> RequestBuilder m ()+head uri params = do+ setRequestType . RequestWithRawBody HEAD $ ""+ setQueryString params+ setRequestPath uri++------------------------------------------------------------------------------+-- | Builds an HTTP \"DELETE\" request with the given query parameters.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> 'buildRequest' $ 'delete' \"\/foo\/bar\" M.empty+-- DELETE \/foo\/bar HTTP\/1.1+-- host: localhost+--+-- sn="localhost" c=127.0.0.1:60000 s=127.0.0.1:8080 ctx=\/ clen=n\/a+-- @+delete :: MonadIO m =>+ ByteString -- ^ request path+ -> Params -- ^ request's form parameters+ -> RequestBuilder m ()+delete uri params = do+ setRequestType DeleteRequest+ setQueryString params+ setRequestPath uri+++------------------------------------------------------------------------------+-- | Builds an HTTP \"POST\" request with the given form parameters, using the+-- \"application/x-www-form-urlencoded\" MIME type.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> 'buildRequest' $ 'postUrlEncoded' \"\/foo\/bar\" (M.fromList [("param0", ["baz", "quux"])])+-- POST \/foo\/bar HTTP\/1.1+-- content-type: application\/x-www-form-urlencoded+-- content-length: 22+-- host: localhost+--+-- sn="localhost" c=127.0.0.1:60000 s=127.0.0.1:8080 ctx=\/ clen=22+-- params: param0: ["baz","quux"]+-- @+postUrlEncoded :: MonadIO m =>+ ByteString -- ^ request path+ -> Params -- ^ request's form parameters+ -> RequestBuilder m ()+postUrlEncoded uri params = do+ setRequestType $ UrlEncodedPostRequest params+ setRequestPath uri+++------------------------------------------------------------------------------+-- | Builds an HTTP \"POST\" request with the given form parameters, using the+-- \"form-data/multipart\" MIME type.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> 'buildRequest' $ 'postMultipart' \"\/foo\/bar\" [("param0", FormData ["baz", "quux"])]+-- POST \/foo\/bar HTTP\/1.1+-- content-type: multipart\/form-data; boundary=snap-boundary-572334111ec0c05ad4812481e8585dfa+-- content-length: 406+-- host: localhost+--+-- sn="localhost" c=127.0.0.1:60000 s=127.0.0.1:8080 ctx=\/ clen=406+-- @+postMultipart :: MonadIO m =>+ ByteString -- ^ request path+ -> MultipartParams -- ^ multipart form parameters+ -> RequestBuilder m ()+postMultipart uri params = do+ setRequestType $ MultipartPostRequest params+ setRequestPath uri+++------------------------------------------------------------------------------+-- | Builds an HTTP \"PUT\" request.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> 'buildRequest' $ 'put' \"\/foo\/bar\" "text\/plain" "some text"+-- PUT \/foo\/bar HTTP\/1.1+-- content-type: text/plain+-- content-length: 9+-- host: localhost+--+-- sn="localhost" c=127.0.0.1:60000 s=127.0.0.1:8080 ctx=\/ clen=9+-- @+put :: MonadIO m =>+ ByteString -- ^ request path+ -> ByteString -- ^ request body MIME content-type+ -> ByteString -- ^ request body contents+ -> RequestBuilder m ()+put uri contentType putData = do+ setRequestType $ RequestWithRawBody PUT putData+ setHeader "Content-Type" contentType+ setRequestPath uri+++------------------------------------------------------------------------------+-- | Builds a \"raw\" HTTP \"POST\" request, with the given MIME type and body+-- contents.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> 'buildRequest' $ 'postRaw' \"\/foo\/bar\" "text/plain" "some text"+-- POST \/foo\/bar HTTP\/1.1+-- content-type: text\/plain+-- content-length: 9+-- host: localhost+--+-- sn="localhost" c=127.0.0.1:60000 s=127.0.0.1:8080 ctx=\/ clen=9+-- @+postRaw :: MonadIO m =>+ ByteString -- ^ request path+ -> ByteString -- ^ request body MIME content-type+ -> ByteString -- ^ request body contents+ -> RequestBuilder m ()+postRaw uri contentType postData = do+ setRequestType $ RequestWithRawBody POST postData+ setContentType contentType+ setRequestPath uri+++------------------------------------------------------------------------------+-- | Given a web handler in the 'Snap' monad, and a 'RequestBuilder' defining+-- a test request, runs the handler, producing an HTTP 'Response'.+--+-- This function will produce almost exactly the same output as running the+-- handler in a real server, except that chunked transfer encoding is not+-- applied, and the \"Transfer-Encoding\" header is not set (this makes it+-- easier to test response output).+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> import "Snap.Core"+-- ghci> 'runHandler' ('get' "foo/bar" M.empty) ('Snap.Core.writeBS' "Hello, world!")+-- HTTP\/1.1 200 OK+-- server: Snap/test+-- date: Thu, 17 Jul 2014 21:03:23 GMT+--+-- Hello, world!+-- @+runHandler :: MonadIO m =>+ RequestBuilder m () -- ^ a request builder+ -> Snap a -- ^ a web handler+ -> m Response+runHandler = runHandlerM rs+ where+ rs rq s = liftIO $ do+ (_,rsp) <- runSnap s (\x -> return $! (x `seq` ()))+ (\f -> let !_ = f 0 in return $! ())+ rq+ fixupResponse rq rsp+++------------------------------------------------------------------------------+-- | Given a web handler in some arbitrary 'MonadSnap' monad, a function+-- specifying how to evaluate it within the context of the test monad, and a+-- 'RequestBuilder' defining a test request, runs the handler, producing an+-- HTTP 'Response'.+runHandlerM :: (MonadIO m, MonadSnap n) =>+ (forall a . Request -> n a -> m Response)+ -- ^ a function defining how the 'MonadSnap' monad should be run+ -> RequestBuilder m ()+ -- ^ a request builder+ -> n b+ -- ^ a web handler+ -> m Response+runHandlerM rSnap rBuilder snap = do+ rq <- buildRequest rBuilder+ rsp <- rSnap rq snap++ -- simulate server logic+ t1 <- liftIO (epochTime >>= formatHttpTime)+ return $ H.setHeader "Date" t1+ $ H.setHeader "Server" "Snap/test"+ $ if rspContentLength rsp == Nothing &&+ rqVersion rq < (1,1)+ then H.setHeader "Connection" "close" rsp+ else rsp+++------------------------------------------------------------------------------+-- | Given a web handler in the 'Snap' monad, and a 'RequestBuilder' defining a+-- test request, runs the handler and returns the monadic value it produces.+--+-- Throws an exception if the 'Snap' handler early-terminates with+-- 'Snap.Core.finishWith' or 'Control.Monad.mzero'.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import "Control.Monad"+-- ghci> import qualified "Data.Map" as M+-- ghci> import "Snap.Core"+-- ghci> 'evalHandler' ('get' "foo/bar" M.empty) ('Snap.Core.writeBS' "Hello, world!" >> return 42)+-- 42+-- ghci> 'evalHandler' ('get' "foo/bar" M.empty) 'Control.Monad.mzero'+-- *** Exception: No handler for request: failure was pass+-- @+evalHandler :: MonadIO m =>+ RequestBuilder m ()+ -> Snap a+ -> m a+evalHandler = evalHandlerM rs+ where+ rs rq s = liftIO $ evalSnap s (const $ return $! ())+ (const $ return $! ())+ rq+++------------------------------------------------------------------------------+-- | Given a web handler in some arbitrary 'MonadSnap' monad, a function+-- specifying how to evaluate it within the context of the test monad, and a+-- 'RequestBuilder' defining a test request, runs the handler, returning the+-- monadic value it produces.+--+-- Throws an exception if the 'Snap' handler early-terminates with+-- 'Snap.Core.finishWith' or 'Control.Monad.mzero'.+evalHandlerM :: (MonadIO m, MonadSnap n) =>+ (forall a . Request -> n a -> m a) -- ^ a function defining+ -- how the 'MonadSnap'+ -- monad should be run+ -> RequestBuilder m () -- ^ a request builder+ -> n b -- ^ a web handler+ -> m b+evalHandlerM rSnap rBuilder snap = do+ rq <- buildRequest rBuilder+ rSnap rq snap+++------------------------------------------------------------------------------+-- | Converts the given 'Response' to a bytestring.+--+-- Example:+--+-- @+-- ghci> import "Snap.Core"+-- ghci> 'responseToString' 'Snap.Core.emptyResponse'+-- \"HTTP\/1.1 200 OK\\r\\n\\r\\n\"+-- @+responseToString :: Response -> IO ByteString+responseToString resp = do+ let act = rspBodyToEnum $ rspBody resp++ (listOut, grab) <- Streams.listOutputStream+ void $ act listOut+ builder <- liftM mconcat grab++ return $! toByteString $ fromShow resp `mappend` builder+++------------------------------------------------------------------------------+-- | Converts the given 'Request' to a bytestring.+--+-- Since: 1.0.0.0+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> r <- 'buildRequest' $ get \"\/foo\/bar\" M.empty+-- ghci> 'requestToString' r+-- \"GET \/foo\/bar HTTP\/1.1\\r\\nhost: localhost\\r\\n\\r\\n\"+-- @+requestToString :: Request -> IO ByteString+requestToString req0 = do+ (req, is) <- maybeChunk+ body <- liftM S.concat $ Streams.toList is+ return $! toByteString $ mconcat [ statusLine+ , mconcat . map oneHeader . H.toList+ $ rqHeaders req+ , crlf+ , byteString body+ ]+ where+ maybeChunk = do+ if getHeader "Transfer-Encoding" req0 == Just "chunked"+ then do+ let req = deleteHeader "Content-Length" $+ req0 { rqContentLength = Nothing }+ is' <- Streams.map chunk $ rqBody req+ out <- eof >>= Streams.appendInputStream is'+ return (req, out)+ else return (req0, rqBody req0)+ where+ chunk s = S.concat [ S.pack $ printf "%x\r\n" (S.length s)+ , s+ , "\r\n"+ ]+ eof = Streams.fromList ["0\r\n\r\n"]++ (v1,v2) = rqVersion req0+ crlf = char8 '\r' `mappend` char8 '\n'+ statusLine = mconcat [ fromShow $ rqMethod req0+ , char8 ' '+ , byteString $ rqURI req0+ , byteString " HTTP/"+ , fromShow v1+ , char8 '.'+ , fromShow v2+ , crlf+ ]++ oneHeader (k,v) = mconcat [ byteString $ original k+ , byteString ": "+ , byteString v+ , crlf+ ]+++------------------------------------------------------------------------------+rGet :: Monad m => RequestBuilder m Request+rGet = RequestBuilder State.get++rPut :: Monad m => Request -> RequestBuilder m ()+rPut s = RequestBuilder $ State.put s++rModify :: Monad m => (Request -> Request) -> RequestBuilder m ()+rModify f = RequestBuilder $ modify f+++------------------------------------------------------------------------------+toByteString :: Builder -> ByteString+toByteString = S.concat . L.toChunks . toLazyByteString+++------------------------------------------------------------------------------+fromShow :: Show a => a -> Builder+fromShow = stringUtf8 . show
− src/Snap/Internal/Types.hs
@@ -1,661 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE EmptyDataDecls #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RankNTypes #-}--module Snap.Internal.Types where---------------------------------------------------------------------------------import Control.Applicative-import Control.Exception (throwIO, ErrorCall(..))-import Control.Monad.CatchIO-import Control.Monad.State.Strict-import Data.ByteString.Char8 (ByteString)-import qualified Data.ByteString.Char8 as S-import qualified Data.ByteString.Lazy.Char8 as L-import qualified Data.CIByteString as CIB-import Data.Int-import Data.IORef-import qualified Data.Iteratee as Iter-import Data.Maybe-import qualified Data.Text as T-import qualified Data.Text.Encoding as T-import qualified Data.Text.Lazy as LT-import qualified Data.Text.Lazy.Encoding as LT--import Data.Typeable---------------------------------------------------------------------------------import Snap.Iteratee hiding (Enumerator)-import Snap.Internal.Http.Types-import Snap.Internal.Iteratee.Debug------------------------------------------------------------------------------------ The Snap Monad---------------------------------------------------------------------------------{-|--'Snap' is the 'Monad' that user web handlers run in. 'Snap' gives you:--1. stateful access to fetch or modify an HTTP 'Request'--2. stateful access to fetch or modify an HTTP 'Response'--3. failure \/ 'Alternative' \/ 'MonadPlus' semantics: a 'Snap' handler can- choose not to handle a given request, using 'empty' or its synonym 'pass',- and you can try alternative handlers with the '<|>' operator:-- > a :: Snap String- > a = pass- >- > b :: Snap String- > b = return "foo"- >- > c :: Snap String- > c = a <|> b -- try running a, if it fails then try b--4. convenience functions ('writeBS', 'writeLBS', 'writeText', 'writeLazyText',- 'addToOutput') for writing output to the 'Response':-- > a :: (forall a . Enumerator a) -> Snap ()- > a someEnumerator = do- > writeBS "I'm a strict bytestring"- > writeLBS "I'm a lazy bytestring"- > addToOutput someEnumerator--5. early termination: if you call 'finishWith':-- > a :: Snap ()- > a = do- > modifyResponse $ setResponseStatus 500 "Internal Server Error"- > writeBS "500 error"- > r <- getResponse- > finishWith r-- then any subsequent processing will be skipped and supplied 'Response' value- will be returned from 'runSnap' as-is.--6. access to the 'IO' monad through a 'MonadIO' instance:-- > a :: Snap ()- > a = liftIO fireTheMissiles--}----------------------------------------------------------------------------------newtype Snap a = Snap {- unSnap :: StateT SnapState (Iteratee IO) (Maybe (Either Response a))-}----------------------------------------------------------------------------------data SnapState = SnapState- { _snapRequest :: Request- , _snapResponse :: Response- , _snapLogError :: ByteString -> IO () }----------------------------------------------------------------------------------instance Monad Snap where- (Snap m) >>= f =- Snap $ do- eth <- m- maybe (return Nothing)- (either (return . Just . Left)- (unSnap . f))- eth-- return = Snap . return . Just . Right- fail = const $ Snap $ return Nothing----------------------------------------------------------------------------------instance MonadIO Snap where- liftIO m = Snap $ liftM (Just . Right) $ liftIO m----------------------------------------------------------------------------------instance MonadCatchIO Snap where- catch (Snap m) handler = Snap $ do- x <- try m- case x of- (Left e) -> let (Snap z) = handler e in z- (Right y) -> return y-- block (Snap m) = Snap $ block m- unblock (Snap m) = Snap $ unblock m----------------------------------------------------------------------------------instance MonadPlus Snap where- mzero = Snap $ return Nothing-- a `mplus` b =- Snap $ do- mb <- unSnap a- if isJust mb then return mb else unSnap b----------------------------------------------------------------------------------instance Functor Snap where- fmap = liftM----------------------------------------------------------------------------------instance Applicative Snap where- pure = return- (<*>) = ap----------------------------------------------------------------------------------instance Alternative Snap where- empty = mzero- (<|>) = mplus----------------------------------------------------------------------------------- | The Typeable instance is here so Snap can be dynamically executed with--- Hint.-snapTyCon :: TyCon-snapTyCon = mkTyCon "Snap.Types.Snap"-{-# NOINLINE snapTyCon #-}--instance Typeable1 Snap where- typeOf1 _ = mkTyConApp snapTyCon []----------------------------------------------------------------------------------liftIter :: Iteratee IO a -> Snap a-liftIter i = Snap (lift i >>= return . Just . Right)------------------------------------------------------------------------------------ | Sends the request body through an iteratee (data consumer) and--- returns the result.-runRequestBody :: Iteratee IO a -> Snap a-runRequestBody iter = do- req <- getRequest- senum <- liftIO $ readIORef $ rqBody req- let (SomeEnumerator enum) = senum-- -- make sure the iteratee consumes all of the output- let iter' = iter >>= (\a -> Iter.skipToEof >> return a)-- -- run the iteratee- result <- liftIter $ Iter.joinIM $ enum iter'-- -- stuff a new dummy enumerator into the request, so you can only try to- -- read the request body from the socket once- liftIO $ writeIORef (rqBody req)- (SomeEnumerator $ return . Iter.joinI . Iter.take 0 )-- return result------------------------------------------------------------------------------------ | Returns the request body as a bytestring.-getRequestBody :: Snap L.ByteString-getRequestBody = liftM fromWrap $ runRequestBody stream2stream-{-# INLINE getRequestBody #-}------------------------------------------------------------------------------------ | Normally Snap is careful to ensure that the request body is fully consumed--- after your web handler runs, but before the 'Response' enumerator is--- streamed out the socket. If you want to transform the request body into some--- output in O(1) space, you should use this function.------ Note that upon calling this function, response processing finishes early as--- if you called 'finishWith'. Make sure you set any content types, headers,--- cookies, etc. before you call this function.----transformRequestBody :: (forall a . Enumerator a)- -- ^ the output 'Iteratee' is passed to this- -- 'Enumerator', and then the resulting 'Iteratee' is- -- fed the request body stream. Your 'Enumerator' is- -- responsible for transforming the input.- -> Snap ()-transformRequestBody trans = do- req <- getRequest- let ioref = rqBody req- senum <- liftIO $ readIORef ioref- let (SomeEnumerator enum) = senum- liftIO $ writeIORef ioref- (SomeEnumerator $ return . Iter.joinI . Iter.take 0)-- origRsp <- getResponse- let rsp = setResponseBody- (\writeEnd -> do- i <- trans writeEnd- enum $ iterateeDebugWrapper "transformRequestBody" i)- $ origRsp { rspTransformingRqBody = True }- finishWith rsp------------------------------------------------------------------------------------ | Short-circuits a 'Snap' monad action early, storing the given--- 'Response' value in its state.-finishWith :: Response -> Snap a-finishWith = Snap . return . Just . Left-{-# INLINE finishWith #-}------------------------------------------------------------------------------------ | Fails out of a 'Snap' monad action. This is used to indicate--- that you choose not to handle the given request within the given--- handler.-pass :: Snap a-pass = empty------------------------------------------------------------------------------------ | Runs a 'Snap' monad action only if the request's HTTP method matches--- the given method.-method :: Method -> Snap a -> Snap a-method m action = do- req <- getRequest- unless (rqMethod req == m) pass- action-{-# INLINE method #-}------------------------------------------------------------------------------------ Appends n bytes of the path info to the context path with a--- trailing slash.-updateContextPath :: Int -> Request -> Request-updateContextPath n req | n > 0 = req { rqContextPath = ctx- , rqPathInfo = pinfo }- | otherwise = req- where- ctx' = S.take n (rqPathInfo req)- ctx = S.concat [rqContextPath req, ctx', "/"]- pinfo = S.drop (n+1) (rqPathInfo req)------------------------------------------------------------------------------------ Runs a 'Snap' monad action only if the 'rqPathInfo' matches the given--- predicate.-pathWith :: (ByteString -> ByteString -> Bool)- -> ByteString- -> Snap a- -> Snap a-pathWith c p action = do- req <- getRequest- unless (c p (rqPathInfo req)) pass- localRequest (updateContextPath $ S.length p) action------------------------------------------------------------------------------------ | Runs a 'Snap' monad action only when the 'rqPathInfo' of the request--- starts with the given path. For example,------ > dir "foo" handler------ Will fail if 'rqPathInfo' is not \"@\/foo@\" or \"@\/foo\/...@\", and will--- add @\"foo\/\"@ to the handler's local 'rqContextPath'.-dir :: ByteString -- ^ path component to match- -> Snap a -- ^ handler to run- -> Snap a-dir = pathWith f- where- f dr pinfo = dr == x- where- (x,_) = S.break (=='/') pinfo-{-# INLINE dir #-}------------------------------------------------------------------------------------ | Runs a 'Snap' monad action only for requests where 'rqPathInfo' is exactly--- equal to the given string. If the path matches, locally sets 'rqContextPath'--- to the old value of 'rqPathInfo', sets 'rqPathInfo'=\"\", and runs the given--- handler.-path :: ByteString -- ^ path to match against- -> Snap a -- ^ handler to run- -> Snap a-path = pathWith (==)-{-# INLINE path #-}------------------------------------------------------------------------------------ | Runs a 'Snap' monad action only when 'rqPathInfo' is empty.-ifTop :: Snap a -> Snap a-ifTop = path ""-{-# INLINE ifTop #-}------------------------------------------------------------------------------------ | Local Snap version of 'get'.-sget :: Snap SnapState-sget = Snap $ liftM (Just . Right) get-{-# INLINE sget #-}------------------------------------------------------------------------------------ | Local Snap monad version of 'modify'.-smodify :: (SnapState -> SnapState) -> Snap ()-smodify f = Snap $ modify f >> return (Just $ Right ())-{-# INLINE smodify #-}------------------------------------------------------------------------------------ | Grabs the 'Request' object out of the 'Snap' monad.-getRequest :: Snap Request-getRequest = liftM _snapRequest sget-{-# INLINE getRequest #-}------------------------------------------------------------------------------------ | Grabs the 'Response' object out of the 'Snap' monad.-getResponse :: Snap Response-getResponse = liftM _snapResponse sget-{-# INLINE getResponse #-}------------------------------------------------------------------------------------ | Puts a new 'Response' object into the 'Snap' monad.-putResponse :: Response -> Snap ()-putResponse r = smodify $ \ss -> ss { _snapResponse = r }-{-# INLINE putResponse #-}------------------------------------------------------------------------------------ | Puts a new 'Request' object into the 'Snap' monad.-putRequest :: Request -> Snap ()-putRequest r = smodify $ \ss -> ss { _snapRequest = r }-{-# INLINE putRequest #-}------------------------------------------------------------------------------------ | Modifies the 'Request' object stored in a 'Snap' monad.-modifyRequest :: (Request -> Request) -> Snap ()-modifyRequest f = smodify $ \ss -> ss { _snapRequest = f $ _snapRequest ss }-{-# INLINE modifyRequest #-}------------------------------------------------------------------------------------ | Modifes the 'Response' object stored in a 'Snap' monad.-modifyResponse :: (Response -> Response) -> Snap ()-modifyResponse f = smodify $ \ss -> ss { _snapResponse = f $ _snapResponse ss }-{-# INLINE modifyResponse #-}------------------------------------------------------------------------------------ | Performs a redirect by setting the @Location@ header to the given target--- URL/path and the status code to 302 in the 'Response' object stored in a--- 'Snap' monad. Note that the target URL is not validated in any way. Consider--- using 'redirect\'' instead, which allows you to choose the correct status--- code.-redirect :: ByteString -> Snap ()-redirect target = redirect' target 302-{-# INLINE redirect #-}------------------------------------------------------------------------------------ | Performs a redirect by setting the @Location@ header to the given target--- URL/path and the status code (should be one of 301, 302, 303 or 307) in the--- 'Response' object stored in a 'Snap' monad. Note that the target URL is not--- validated in any way.-redirect' :: ByteString -> Int -> Snap ()-redirect' target status = do- r <- getResponse-- finishWith- $ setResponseCode status- $ setContentLength 0- $ modifyResponseBody (const $ enumBS "")- $ setHeader "Location" target r--{-# INLINE redirect' #-}------------------------------------------------------------------------------------ | Log an error message in the 'Snap' monad-logError :: ByteString -> Snap ()-logError s = Snap $ gets _snapLogError >>= (\l -> liftIO $ l s)- >> return (Just $ Right ())-{-# INLINE logError #-}------------------------------------------------------------------------------------ | Adds the output from the given enumerator to the 'Response'--- stored in the 'Snap' monad state.-addToOutput :: (forall a . Enumerator a) -- ^ output to add- -> Snap ()-addToOutput enum = modifyResponse $ modifyResponseBody (>. enum)------------------------------------------------------------------------------------ | Adds the given strict 'ByteString' to the body of the 'Response' stored in--- the 'Snap' monad state.------ Warning: This function is intentionally non-strict. If any pure--- exceptions are raised by the expression creating the 'ByteString',--- the exception won't actually be raised within the Snap handler.-writeBS :: ByteString -> Snap ()-writeBS s = addToOutput $ enumBS s------------------------------------------------------------------------------------ | Adds the given lazy 'L.ByteString' to the body of the 'Response' stored in--- the 'Snap' monad state.------ Warning: This function is intentionally non-strict. If any pure--- exceptions are raised by the expression creating the 'ByteString',--- the exception won't actually be raised within the Snap handler.-writeLBS :: L.ByteString -> Snap ()-writeLBS s = addToOutput $ enumLBS s------------------------------------------------------------------------------------ | Adds the given strict 'T.Text' to the body of the 'Response' stored in the--- 'Snap' monad state.------ Warning: This function is intentionally non-strict. If any pure--- exceptions are raised by the expression creating the 'ByteString',--- the exception won't actually be raised within the Snap handler.-writeText :: T.Text -> Snap ()-writeText s = writeBS $ T.encodeUtf8 s------------------------------------------------------------------------------------ | Adds the given lazy 'LT.Text' to the body of the 'Response' stored in the--- 'Snap' monad state.------ Warning: This function is intentionally non-strict. If any pure--- exceptions are raised by the expression creating the 'ByteString',--- the exception won't actually be raised within the Snap handler.-writeLazyText :: LT.Text -> Snap ()-writeLazyText s = writeLBS $ LT.encodeUtf8 s------------------------------------------------------------------------------------ | Sets the output to be the contents of the specified file.------ Calling 'sendFile' will overwrite any output queued to be sent in the--- 'Response'. If the response body is not modified after the call to--- 'sendFile', Snap will use the efficient @sendfile()@ system call on--- platforms that support it.------ If the response body is modified (using 'modifyResponseBody'), the file will--- be read using @mmap()@.-sendFile :: FilePath -> Snap ()-sendFile f = modifyResponse $ \r -> r { rspBody = SendFile f Nothing }------------------------------------------------------------------------------------ | Sets the output to be the contents of the specified file, within the given--- (start,end) range.------ Calling 'sendFilePartial' will overwrite any output queued to be sent in the--- 'Response'. If the response body is not modified after the call to--- 'sendFilePartial', Snap will use the efficient @sendfile()@ system call on--- platforms that support it.------ If the response body is modified (using 'modifyResponseBody'), the file will--- be read using @mmap()@.-sendFilePartial :: FilePath -> (Int64,Int64) -> Snap ()-sendFilePartial f rng = modifyResponse $ \r ->- r { rspBody = SendFile f (Just rng) }------------------------------------------------------------------------------------ | Runs a 'Snap' action with a locally-modified 'Request' state--- object. The 'Request' object in the Snap monad state after the call--- to localRequest will be unchanged.-localRequest :: (Request -> Request) -> Snap a -> Snap a-localRequest f m = do- req <- getRequest-- runAct req <|> (putRequest req >> pass)-- where- runAct req = do- modifyRequest f- result <- m- putRequest req- return result-{-# INLINE localRequest #-}------------------------------------------------------------------------------------ | Fetches the 'Request' from state and hands it to the given action.-withRequest :: (Request -> Snap a) -> Snap a-withRequest = (getRequest >>=)-{-# INLINE withRequest #-}------------------------------------------------------------------------------------ | Fetches the 'Response' from state and hands it to the given action.-withResponse :: (Response -> Snap a) -> Snap a-withResponse = (getResponse >>=)-{-# INLINE withResponse #-}------------------------------------------------------------------------------------ | Modifies the 'Request' in the state to set the 'rqRemoteAddr'--- field to the value in the X-Forwarded-For header. If the header is--- not present, this action has no effect.------ This action should be used only when working behind a reverse http--- proxy that sets the X-Forwarded-For header. This is the only way to--- ensure the value in the X-Forwarded-For header can be trusted.------ This is provided as a filter so actions that require the remote--- address can get it in a uniform manner. It has specifically limited--- functionality to ensure that its transformation can be trusted,--- when used correctly.-ipHeaderFilter :: Snap ()-ipHeaderFilter = ipHeaderFilter' "x-forwarded-for"------------------------------------------------------------------------------------ | Modifies the 'Request' in the state to set the 'rqRemoteAddr'--- field to the value from the header specified. If the header--- specified is not present, this action has no effect.------ This action should be used only when working behind a reverse http--- proxy that sets the header being looked at. This is the only way to--- ensure the value in the header can be trusted.------ This is provided as a filter so actions that require the remote--- address can get it in a uniform manner. It has specifically limited--- functionality to ensure that its transformation can be trusted,--- when used correctly.-ipHeaderFilter' :: CIB.CIByteString -> Snap ()-ipHeaderFilter' header = do- headerContents <- getHeader header <$> getRequest-- let whitespace = " \t\r\n"- ipChrs = ".0123456789"- trim f s = f (`elem` s)-- clean = trim S.takeWhile ipChrs . trim S.dropWhile whitespace- setIP ip = modifyRequest $ \rq -> rq { rqRemoteAddr = clean ip }- maybe (return ()) setIP headerContents------------------------------------------------------------------------------------ | This exception is thrown if the handler you supply to 'runSnap' fails.-data NoHandlerException = NoHandlerException- deriving (Eq, Typeable)----------------------------------------------------------------------------------instance Show NoHandlerException where- show NoHandlerException = "No handler for request"----------------------------------------------------------------------------------instance Exception NoHandlerException------------------------------------------------------------------------------------ | Runs a 'Snap' monad action in the 'Iteratee IO' monad.-runSnap :: Snap a- -> (ByteString -> IO ())- -> Request- -> Iteratee IO (Request,Response)-runSnap (Snap m) logerr req = do- (r, ss') <- runStateT m ss-- e <- maybe (return $ Left fourohfour)- return- r-- -- is this a case of early termination?- let resp = case e of- Left x -> x- Right _ -> _snapResponse ss'-- return (_snapRequest ss', resp)-- where- fourohfour = setContentLength 3 $- setResponseStatus 404 "Not Found" $- modifyResponseBody (>. enumBS "404") $- emptyResponse-- dresp = emptyResponse { rspHttpVersion = rqVersion req }-- ss = SnapState req dresp logerr-{-# INLINE runSnap #-}----------------------------------------------------------------------------------evalSnap :: Snap a- -> (ByteString -> IO ())- -> Request- -> Iteratee IO a-evalSnap (Snap m) logerr req = do- (r, _) <- runStateT m ss-- e <- maybe (liftIO $ throwIO NoHandlerException)- return- r-- -- is this a case of early termination?- case e of- Left _ -> liftIO $ throwIO $ ErrorCall "no value"- Right x -> return x- where- dresp = emptyResponse { rspHttpVersion = rqVersion req }- ss = SnapState req dresp logerr-{-# INLINE evalSnap #-}------------------------------------------------------------------------------------- | See 'rqParam'. Looks up a value for the given named parameter in the--- 'Request'. If more than one value was entered for the given parameter name,--- 'getParam' gloms the values together with:------ @ 'S.intercalate' \" \"@----getParam :: ByteString -- ^ parameter name to look up- -> Snap (Maybe ByteString)-getParam k = do- rq <- getRequest- return $ liftM (S.intercalate " ") $ rqParam k rq--
+ src/Snap/Internal/Util/FileServe.hs view
@@ -0,0 +1,809 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}++-- | Contains web handlers to serve files from a directory.+module Snap.Internal.Util.FileServe+ ( -- * Helper functions+ getSafePath+ -- * Configuration for directory serving+ , MimeMap+ , HandlerMap+ , DirectoryConfig(..)+ , simpleDirectoryConfig+ , defaultDirectoryConfig+ , fancyDirectoryConfig+ , defaultIndexGenerator+ , defaultMimeTypes+ , fileType+ -- * File servers+ , serveDirectory+ , serveDirectoryWith+ , serveFile+ , serveFileAs+ -- * Internal functions+ , decodeFilePath+ , checkRangeReq+ ) where++------------------------------------------------------------------------------+import Control.Applicative (Alternative ((<|>)), Applicative ((*>), (<*)), (<$>))+import Control.Exception.Lifted (SomeException, catch, evaluate)+import Control.Monad (Monad ((>>), (>>=), return), filterM, forM_, liftM, unless, void, when, (=<<))+import Control.Monad.IO.Class (MonadIO (..))+import Data.Attoparsec.ByteString.Char8 (Parser, char, endOfInput, option, string)+import Data.ByteString.Builder (Builder, byteString, char8, stringUtf8, toLazyByteString)+import Data.ByteString.Char8 (ByteString)+import qualified Data.ByteString.Char8 as S (append, concat, intercalate, isSuffixOf, null, pack, takeWhile)+import qualified Data.ByteString.Lazy.Char8 as L+import Data.HashMap.Strict (HashMap)+import qualified Data.HashMap.Strict as Map (empty, fromList, lookup)+import Data.List (drop, dropWhile, elem, filter, foldl', null, sort, (++))+import Data.Maybe (fromMaybe, isNothing)+import Data.Monoid (Monoid (mappend, mconcat))+import qualified Data.Text as T (Text, pack, unpack)+import qualified Data.Text.Encoding as T (decodeUtf8, encodeUtf8)+import Data.Word (Word64)+import Prelude (Bool (..), Eq (..), FilePath, IO, Maybe (Just, Nothing), Num (..), Ord (..), Show (show), String, const, either, flip, fromIntegral, id, maybe, not, ($), ($!), (.), (||))+import qualified Prelude+import Snap.Core (MonadSnap (..), Request (rqPathInfo, rqQueryString, rqURI), deleteHeader, emptyResponse, finishWith, formatHttpTime, getHeader, getRequest, modifyResponse, parseHttpTime, pass, redirect, sendFile, sendFilePartial, setContentLength, setContentType, setHeader, setResponseBody, setResponseCode, urlDecode, writeBS)+import Snap.Internal.Debug (debug)+import Snap.Internal.Parsing (fullyParse, parseNum)+import System.Directory (doesDirectoryExist, doesFileExist, getDirectoryContents)+import System.FilePath (isRelative, joinPath, splitDirectories, takeExtensions, takeFileName, (</>))+import System.PosixCompat.Files (fileSize, getFileStatus, modificationTime)+++------------------------------------------------------------------------------+-- | Gets a path from the 'Request' using 'rqPathInfo' and makes sure it is+-- safe to use for opening files. A path is safe if it is a relative path+-- and has no ".." elements to escape the intended directory structure.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> import qualified "Snap.Test" as T+-- ghci> import qualified "Data.ByteString.Char8" as B8+-- ghci> T.runHandler (T.get \"\/foo\/bar\" M.empty) ('getSafePath' >>= 'writeBS' . B8.pack)+-- HTTP\/1.1 200 OK+-- server: Snap\/test+-- date: Fri, 08 Aug 2014 16:13:20 GMT+--+-- foo\/bar+-- ghci> T.runHandler (T.get \"\/foo\/..\/bar\" M.empty) ('getSafePath' >>= 'writeBS' . B8.pack)+-- HTTP\/1.1 404 Not Found+-- ...+-- @+getSafePath :: MonadSnap m => m FilePath+getSafePath = do+ req <- getRequest+ let mp = urlDecode $ rqPathInfo req++ p <- maybe pass (return . T.unpack . T.decodeUtf8) mp++ -- relative paths only!+ when (not $ isRelative p) pass++ -- check that we don't have any sneaky .. paths+ let dirs = splitDirectories p+ when (elem ".." dirs) pass++ return $! joinPath dirs+++------------------------------------------------------------------------------+-- | A type alias for dynamic handlers+type HandlerMap m = HashMap FilePath (FilePath -> m ())+++------------------------------------------------------------------------------+-- | A type alias for MIME type+type MimeMap = HashMap FilePath ByteString+++------------------------------------------------------------------------------+-- | The default set of mime type mappings we use when serving files. Its+-- value:+--+-- > Map.fromList [+-- > ( ".asc" , "text/plain" ),+-- > ( ".asf" , "video/x-ms-asf" ),+-- > ( ".asx" , "video/x-ms-asf" ),+-- > ( ".au" , "audio/basic" ),+-- > ( ".avi" , "video/x-msvideo" ),+-- > ( ".bmp" , "image/bmp" ),+-- > ( ".bz2" , "application/x-bzip" ),+-- > ( ".c" , "text/plain" ),+-- > ( ".class" , "application/octet-stream" ),+-- > ( ".conf" , "text/plain" ),+-- > ( ".cpp" , "text/plain" ),+-- > ( ".css" , "text/css" ),+-- > ( ".csv" , "text/csv" ),+-- > ( ".cxx" , "text/plain" ),+-- > ( ".doc" , "application/msword" ),+-- > ( ".docx" , S.append "application/vnd.openxmlformats-officedocument"+-- > ".wordprocessingml.document" ),+-- > ( ".dotx" , S.append "application/vnd.openxmlformats-officedocument"+-- > ".wordprocessingml.template" ),+-- > ( ".dtd" , "application/xml-dtd" ),+-- > ( ".dvi" , "application/x-dvi" ),+-- > ( ".exe" , "application/octet-stream" ),+-- > ( ".flv" , "video/x-flv" ),+-- > ( ".gif" , "image/gif" ),+-- > ( ".gz" , "application/x-gzip" ),+-- > ( ".hs" , "text/plain" ),+-- > ( ".htm" , "text/html" ),+-- > ( ".html" , "text/html" ),+-- > ( ".ico" , "image/x-icon" ),+-- > ( ".jar" , "application/x-java-archive" ),+-- > ( ".jpeg" , "image/jpeg" ),+-- > ( ".jpg" , "image/jpeg" ),+-- > ( ".js" , "text/javascript" ),+-- > ( ".json" , "application/json" ),+-- > ( ".log" , "text/plain" ),+-- > ( ".m3u" , "audio/x-mpegurl" ),+-- > ( ".m3u8" , "application/x-mpegURL" ),+-- > ( ".mka" , "audio/x-matroska" ),+-- > ( ".mk3d" , "video/x-matroska" ),+-- > ( ".mkv" , "video/x-matroska" ),+-- > ( ".mov" , "video/quicktime" ),+-- > ( ".mp3" , "audio/mpeg" ),+-- > ( ".mp4" , "video/mp4" ),+-- > ( ".mpeg" , "video/mpeg" ),+-- > ( ".mpg" , "video/mpeg" ),+-- > ( ".ogg" , "application/ogg" ),+-- > ( ".pac" , "application/x-ns-proxy-autoconfig" ),+-- > ( ".pdf" , "application/pdf" ),+-- > ( ".png" , "image/png" ),+-- > ( ".potx" , S.append "application/vnd.openxmlformats-officedocument"+-- > ".presentationml.template" ),+-- > ( ".ppsx" , S.append "application/vnd.openxmlformats-officedocument"+-- > ".presentationml.slideshow" ),+-- > ( ".ppt" , "application/vnd.ms-powerpoint" ),+-- > ( ".pptx" , S.append "application/vnd.openxmlformats-officedocument"+-- > ".presentationml.presentation" ),+-- > ( ".ps" , "application/postscript" ),+-- > ( ".qt" , "video/quicktime" ),+-- > ( ".rtf" , "text/rtf" ),+-- > ( ".sig" , "application/pgp-signature" ),+-- > ( ".sldx" , S.append "application/vnd.openxmlformats-officedocument"+-- > ".presentationml.slide" ),+-- > ( ".spl" , "application/futuresplash" ),+-- > ( ".svg" , "image/svg+xml" ),+-- > ( ".swf" , "application/x-shockwave-flash" ),+-- > ( ".tar" , "application/x-tar" ),+-- > ( ".tar.bz2" , "application/x-bzip-compressed-tar" ),+-- > ( ".tar.gz" , "application/x-tgz" ),+-- > ( ".tbz" , "application/x-bzip-compressed-tar" ),+-- > ( ".text" , "text/plain" ),+-- > ( ".tgz" , "application/x-tgz" ),+-- > ( ".tif" , "image/tiff" ),+-- > ( ".tiff" , "image/tiff" ),+-- > ( ".torrent" , "application/x-bittorrent" ),+-- > ( ".ts" , "video/mp2t" ),+-- > ( ".txt" , "text/plain" ),+-- > ( ".wav" , "audio/x-wav" ),+-- > ( ".wax" , "audio/x-ms-wax" ),+-- > ( ".webm" , "video/webm" ),+-- > ( ".wma" , "audio/x-ms-wma" ),+-- > ( ".wmv" , "video/x-ms-wmv" ),+-- > ( ".xbm" , "image/x-xbitmap" ),+-- > ( ".xlam" , "application/vnd.ms-excel.addin.macroEnabled.12" ),+-- > ( ".xls" , "application/vnd.ms-excel" ),+-- > ( ".xlsb" , "application/vnd.ms-excel.sheet.binary.macroEnabled.12" ),+-- > ( ".xlsx" , S.append "application/vnd.openxmlformats-officedocument."+-- > "spreadsheetml.sheet" ),+-- > ( ".xltx" , S.append "application/vnd.openxmlformats-officedocument."+-- > "spreadsheetml.template" ),+-- > ( ".xml" , "text/xml" ),+-- > ( ".xpm" , "image/x-xpixmap" ),+-- > ( ".xwd" , "image/x-xwindowdump" ),+-- > ( ".zip" , "application/zip" ) ]++defaultMimeTypes :: MimeMap+defaultMimeTypes =+ Map.fromList [+ ( ".asc" , "text/plain" ),+ ( ".asf" , "video/x-ms-asf" ),+ ( ".asx" , "video/x-ms-asf" ),+ ( ".au" , "audio/basic" ),+ ( ".avi" , "video/x-msvideo" ),+ ( ".bmp" , "image/bmp" ),+ ( ".bz2" , "application/x-bzip" ),+ ( ".c" , "text/plain" ),+ ( ".class" , "application/octet-stream" ),+ ( ".conf" , "text/plain" ),+ ( ".cpp" , "text/plain" ),+ ( ".css" , "text/css" ),+ ( ".csv" , "text/csv" ),+ ( ".cxx" , "text/plain" ),+ ( ".doc" , "application/msword" ),+ ( ".docx" , S.append "application/vnd.openxmlformats-officedocument"+ ".wordprocessingml.document" ),+ ( ".dotx" , S.append "application/vnd.openxmlformats-officedocument"+ ".wordprocessingml.template" ),+ ( ".dtd" , "application/xml-dtd" ),+ ( ".dvi" , "application/x-dvi" ),+ ( ".exe" , "application/octet-stream" ),+ ( ".flv" , "video/x-flv" ),+ ( ".gif" , "image/gif" ),+ ( ".gz" , "application/x-gzip" ),+ ( ".hs" , "text/plain" ),+ ( ".htm" , "text/html" ),+ ( ".html" , "text/html" ),+ ( ".ico" , "image/x-icon" ),+ ( ".jar" , "application/x-java-archive" ),+ ( ".jpeg" , "image/jpeg" ),+ ( ".jpg" , "image/jpeg" ),+ ( ".js" , "text/javascript" ),+ ( ".json" , "application/json" ),+ ( ".log" , "text/plain" ),+ ( ".m3u" , "audio/x-mpegurl" ),+ ( ".m3u8" , "application/x-mpegURL" ),+ ( ".mka" , "audio/x-matroska" ),+ ( ".mk3d" , "video/x-matroska" ),+ ( ".mkv" , "video/x-matroska" ),+ ( ".mov" , "video/quicktime" ),+ ( ".mp3" , "audio/mpeg" ),+ ( ".mp4" , "video/mp4" ),+ ( ".mpeg" , "video/mpeg" ),+ ( ".mpg" , "video/mpeg" ),+ ( ".ogg" , "application/ogg" ),+ ( ".pac" , "application/x-ns-proxy-autoconfig" ),+ ( ".pdf" , "application/pdf" ),+ ( ".png" , "image/png" ),+ ( ".potx" , S.append "application/vnd.openxmlformats-officedocument"+ ".presentationml.template" ),+ ( ".ppsx" , S.append "application/vnd.openxmlformats-officedocument"+ ".presentationml.slideshow" ),+ ( ".ppt" , "application/vnd.ms-powerpoint" ),+ ( ".pptx" , S.append "application/vnd.openxmlformats-officedocument"+ ".presentationml.presentation" ),+ ( ".ps" , "application/postscript" ),+ ( ".qt" , "video/quicktime" ),+ ( ".rtf" , "text/rtf" ),+ ( ".sig" , "application/pgp-signature" ),+ ( ".sldx" , S.append "application/vnd.openxmlformats-officedocument"+ ".presentationml.slide" ),+ ( ".spl" , "application/futuresplash" ),+ ( ".svg" , "image/svg+xml" ),+ ( ".swf" , "application/x-shockwave-flash" ),+ ( ".tar" , "application/x-tar" ),+ ( ".tar.bz2" , "application/x-bzip-compressed-tar" ),+ ( ".tar.gz" , "application/x-tgz" ),+ ( ".tbz" , "application/x-bzip-compressed-tar" ),+ ( ".text" , "text/plain" ),+ ( ".tgz" , "application/x-tgz" ),+ ( ".tiff" , "image/tiff" ),+ ( ".tif" , "image/tiff" ),+ ( ".torrent" , "application/x-bittorrent" ),+ ( ".ts" , "video/mp2t" ),+ ( ".ttf" , "font/ttf" ),+ ( ".txt" , "text/plain" ),+ ( ".wav" , "audio/x-wav" ),+ ( ".wax" , "audio/x-ms-wax" ),+ ( ".webm" , "video/webm" ),+ ( ".wma" , "audio/x-ms-wma" ),+ ( ".wmv" , "video/x-ms-wmv" ),+ ( ".xbm" , "image/x-xbitmap" ),+ ( ".xlam" , "application/vnd.ms-excel.addin.macroEnabled.12" ),+ ( ".xls" , "application/vnd.ms-excel" ),+ ( ".xlsb" , "application/vnd.ms-excel.sheet.binary.macroEnabled.12" ),+ ( ".xlsx" , S.append "application/vnd.openxmlformats-officedocument."+ "spreadsheetml.sheet" ),+ ( ".xltx" , S.append "application/vnd.openxmlformats-officedocument."+ "spreadsheetml.template" ),+ ( ".xml" , "text/xml" ),+ ( ".xpm" , "image/x-xpixmap" ),+ ( ".xwd" , "image/x-xwindowdump" ),+ ( ".zip" , "application/zip" ) ]+++------------------------------------------------------------------------------+-- | A collection of options for serving static files out of a directory.+data DirectoryConfig m = DirectoryConfig {+ -- | Files to look for when a directory is requested (e.g., index.html)+ indexFiles :: [FilePath],++ -- | Handler to generate a directory listing if there is no index.+ indexGenerator :: FilePath -> m (),++ -- | Map of extensions to pass to dynamic file handlers. This could be+ -- used, for example, to implement CGI dispatch, pretty printing of source+ -- code, etc.+ dynamicHandlers :: HandlerMap m,++ -- | MIME type map to look up content types.+ mimeTypes :: MimeMap,++ -- | Handler that is called before a file is served. It will only be+ -- called when a file is actually found, not for generated index pages.+ preServeHook :: FilePath -> m ()+ }+++------------------------------------------------------------------------------+-- | Style information for the default directory index generator.+snapIndexStyles :: ByteString+snapIndexStyles =+ S.intercalate "\n"+ [ "body { margin: 0px 0px 0px 0px; font-family: sans-serif }"+ , "div.header {"+ , "padding: 40px 40px 0px 40px; height:35px;"+ , "background:rgb(25,50,87);"+ , "background-image:-webkit-gradient("+ , "linear,left bottom,left top,"+ , "color-stop(0.00, rgb(31,62,108)),"+ , "color-stop(1.00, rgb(19,38,66)));"+ , "background-image:-moz-linear-gradient("+ , "center bottom,rgb(31,62,108) 0%,rgb(19,38,66) 100%);"+ , "text-shadow:-1px 3px 1px rgb(16,33,57);"+ , "font-size:16pt; letter-spacing: 2pt; color:white;"+ , "border-bottom:10px solid rgb(46,93,156) }"+ , "div.content {"+ , "background:rgb(255,255,255);"+ , "background-image:-webkit-gradient("+ , "linear,left bottom, left top,"+ , "color-stop(0.50, rgb(255,255,255)),"+ , "color-stop(1.00, rgb(224,234,247)));"+ , "background-image:-moz-linear-gradient("+ , "center bottom, white 50%, rgb(224,234,247) 100%);"+ , "padding: 40px 40px 40px 40px }"+ , "div.footer {"+ , "padding: 16px 0px 10px 10px; height:31px;"+ , "border-top: 1px solid rgb(194,209,225);"+ , "color: rgb(160,172,186); font-size:10pt;"+ , "background: rgb(245,249,255) }"+ , "table { max-width:100%; margin: 0 auto;" `S.append`+ " border-collapse: collapse; }"+ , "tr:hover { background:rgb(256,256,224) }"+ , "td { border:0; font-family:monospace; padding: 2px 0; }"+ , "td.filename, td.type { padding-right: 2em; }"+ , "th { border:0; background:rgb(28,56,97);"+ , "text-shadow:-1px 3px 1px rgb(16,33,57); color: white}"+ ]+++------------------------------------------------------------------------------+-- | An automatic index generator, which is fairly small and does not rely on+-- any external files (which may not be there depending on external request+-- routing).+--+-- A 'MimeMap' is passed in to display the types of files in the directory+-- listing based on their extension. Preferably, this is the same as the map+-- in the 'DirectoryConfig'+--+-- The styles parameter allows you to apply styles to the directory listing.+-- The listing itself consists of a table, containing a header row using+-- th elements, and one row per file using td elements, so styles for those+-- pieces may be attached to the appropriate tags.+defaultIndexGenerator :: MonadSnap m+ => MimeMap -- ^ MIME type mapping for reporting types+ -> ByteString -- ^ Style info to insert in header+ -> FilePath -- ^ Directory to generate index for+ -> m ()+defaultIndexGenerator mm styles d = do+ modifyResponse $ setContentType "text/html; charset=utf-8"+ rq <- getRequest++ let uri = uriWithoutQueryString rq+ let pInfo = rqPathInfo rq++ writeBS "<!DOCTYPE html>\n<html>\n<head>"+ writeBS "<title>Directory Listing: "+ writeBS uri+ writeBS "</title>"+ writeBS "<style type='text/css'>"+ writeBS styles+ writeBS "</style></head><body>"+ writeBS "<div class=\"header\">Directory Listing: "+ writeBS uri+ writeBS "</div><div class=\"content\">"+ writeBS "<table><tr><th>File Name</th><th>Type</th><th>Last Modified"+ writeBS "</th></tr>"++ when (pInfo /= "") $+ writeBS "<tr><td><a href='../'>..</a></td><td colspan=2>DIR</td></tr>"++ entries <- liftIO $ getDirectoryContents d+ dirs <- liftIO $ filterM (doesDirectoryExist . (d </>)) entries+ files <- liftIO $ filterM (doesFileExist . (d </>)) entries++ forM_ (sort $ filter (not . (`elem` ["..", "."])) dirs) $ \f0 -> do+ f <- liftIO $ liftM (\s -> T.encodeUtf8 s `mappend` "/")+ $ decodeFilePath f0+ writeBS "<tr><td class='filename'><a href='"+ writeBS f+ writeBS "'>"+ writeBS f+ writeBS "</a></td><td class='type' colspan=2>DIR</td></tr>"++ forM_ (sort files) $ \f0 -> do+ f <- liftIO $ liftM T.encodeUtf8 $ decodeFilePath f0+ stat <- liftIO $ getFileStatus (d </> f0)+ tm <- liftIO $ formatHttpTime (modificationTime stat)+ writeBS "<tr><td class='filename'><a href='"+ writeBS f+ writeBS "'>"+ writeBS f+ writeBS "</a></td><td class='type'>"+ writeBS (fileType mm f0)+ writeBS "</td><td>"+ writeBS tm+ writeBS "</tr>"++ writeBS "</table></div><div class=\"footer\">Powered by "+ writeBS "<b><a href=\"http://snapframework.com/\">Snap</a></b></div>"+ writeBS "</body>"+++------------------------------------------------------------------------------+decodeFilePath :: FilePath -> IO T.Text+decodeFilePath fp = do+ evaluate (T.decodeUtf8 bs) `catch`+ (\(_::SomeException) -> return (T.pack fp))+ where+ bs = S.pack fp++------------------------------------------------------------------------------+-- | A very simple configuration for directory serving. This configuration+-- uses built-in MIME types from 'defaultMimeTypes', and has no index files,+-- index generator, dynamic file handlers, or 'preServeHook'.+simpleDirectoryConfig :: MonadSnap m => DirectoryConfig m+simpleDirectoryConfig = DirectoryConfig {+ indexFiles = [],+ indexGenerator = const pass,+ dynamicHandlers = Map.empty,+ mimeTypes = defaultMimeTypes,+ preServeHook = const $ return $! ()+ }+++------------------------------------------------------------------------------+-- | A reasonable default configuration for directory serving. This+-- configuration uses built-in MIME types from 'defaultMimeTypes', serves+-- common index files @index.html@ and @index.htm@, but does not autogenerate+-- directory indexes, nor have any dynamic file handlers. The 'preServeHook'+-- will not do anything.+defaultDirectoryConfig :: MonadSnap m => DirectoryConfig m+defaultDirectoryConfig = DirectoryConfig {+ indexFiles = ["index.html", "index.htm"],+ indexGenerator = const pass,+ dynamicHandlers = Map.empty,+ mimeTypes = defaultMimeTypes,+ preServeHook = const $ return $! ()+ }+++------------------------------------------------------------------------------+-- | A more elaborate configuration for file serving. This configuration+-- uses built-in MIME types from 'defaultMimeTypes', serves common index files+-- @index.html@ and @index.htm@, and autogenerates directory indexes with a+-- Snap-like feel. It still has no dynamic file handlers, nor 'preServeHook',+-- which should be added as needed.+--+-- Files recognized as indexes include @index.html@, @index.htm@,+-- @default.html@, @default.htm@, @home.html@+--+-- Example of how the autogenerated directory index looks like:+--+-- <<data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAf8AAAFWCAIAAADogAPPAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB94IChYlE8/LMqEAACAASURBVHja7J13fBTFF8DfzOzutVwagST0KqH33nsHKwIWRAHpUhQUUSwoKCKoFCmiAlIEKUGaEHrvAqEJBEgigZByuX67O/P74y6Vu5DwQwJkvp/9fAh7uzNv3rx5++bt7C4q8kxb4HA4HE4hQwBAXAscDodT+Lw/d/4cDofDY38Oh8PhcO/P4XA4nKfU+yPu/DkcDofH/hwOh8MpDGCuAg6HwymUsT9P/XA4HA6P/TkcDodTOGJ/nvfncDicQuj9Ec/8cDgcTuGDZ344HA6nUMb+PPPD4XA4PPbncDgcTiGJ/Xnen8PhcHjsz+FwOJxCEfvzNT8cDofDY38Oh8PhFI7Yn6/54XA4HO79ORwOh1NIvD93/hwOh8Njfw6Hw+EUBvhdXw6HwymUsT9f8cnhcDg89udwOBxO4Yj9ed6fw+FwCqX355kfDofDKXzwzA+Hw+EUztifZ344HA6nEHp/nvjhcDgcHvtzOBwOp1DA8/4cDodTOGN/nvrhcDicwuj9eeaHw+FwCh8888PhcDiFMvbn7/nhcDgcHvtzOBwOp3DE/jzvz+FwOIXS+3Pnz+FwODz253A4HE5hgOf9ORwOh8f+HA6Hwykk3p8v+ORwOJxCCAZABbHh4I6T44/9lnM7svjU6o/nDm9RK4B4O0vb+JMF8cd++bWJvoDEzpRfX7bhoGdLaQtYjNw3ocwrX8Uf++38J1X09znyoSjWq04eny7Lw4ZDXvn5t/hV3csJGTtJUJ2eC1fNjzv2W/yxJfvee0b3+LciH63jW6HeCirzk16pevf8ZbPivhAJkl9gkbJlK/d6o3Kv55t9MuT7Rf84mJezMqQvMDQRL29b2j1s57QV6+Mf40s7yvIHysOR/5difejkcemyPIVCgRHPVoXri8/EK+miGqp+PuPlrkaw3vjn1B1X/NkU+YnNlHppHaeQZ34evTEzYKCqKmUAAKd/fm7gEZPHQyBAWBNWc+jkMRMa1fhkTv+rLy/YmZru/xmlzHps2ug6Pwh2s70gxyADQR9YJIeDfcxgwIAqzK1koLnJ+VAU61Unj0+X5akJzFi9cT2c/OveWy7kaRQpUqaaESBuTY/eqy4oBOMn9q2I97aOw71/ATgwyqjiVGUFAIBaFbtJAQDGAAEgot48NPOdVGHZ9HEVm0/pt7XN3JtOj/Uy6rI57KZbZj0RtYAKaLUSA0YVVaWe/zy2Lo1SKjsVRb2vnA9BsT508rh0Wd7aQKlUvWOEJmX/nzFOABEAGFMZQwIAJF+/YzEzQcdEjJDwJPr+zNZd87SOwymQAckYU5nKMpMDCCGMARBQRZVtSuqZBTP2mQDKdGtVRZP1PE3jL39POL3i1ya6LHu1LaYtjD/6+aA6jb/4eW780SU3/vr43QgpW4VScKtXB65eM/fG0SXxRxedXDr6o66ljT6ajnRhnd4YuHrNnJijS+KP/nRyyagJ7cL1mc5T02rGr1cXtQwA0Lf94NLRJYeHl9bkLCG82+DhGzfMj3NXt2zM5J7lA71U50tyodKAr+KPLjk1oaIu+/FNJ8+NP7pgQTNDHq45lFGVupXM2P06xKti3QZirP9i/+Ur59w4uiT+6JJ/Nn2+9L12DYNIjqyPT534LFnbdMr8+KNfDIqo0Ofd93b99XP80SXxu2esm9y1eQjxHqcEPTNg4gcHdi2JP7okZvOnc9+oXqnV2JijS3b2K5bVH5OwdtuOLok/+mm/ojg/FglMKvFcI13a4T2XbO76wket+S12bZ/yAFDz3XPRWxOi3q6ry6p4pC1Re/ik97ZvWhB7dEn80SU3dnyz7tOebcPuvTzkr7H3s0DIr4Vka52dOz1OuhkVwFveslaJdaIhSHJHjAyoKquylSrOpFNRp5QWrYtGNChK/o6nAICQABqJEAQAgHGWMtx/Fh/8zbDiLPbg0YTQsMRT/yoZByB9uRGzJk6oJUHi5ci1MYk4pH6bekM+qdul9qxnvzqVSHP4uogP5r8/tAKiCRcj195M1ZVo26n+qKm1m3w/ue9vcQ4ABq5Lvy+cHN/p41cqkGs7vlh/K+FcippFGhJce8qCMa+VAppwaeP62FRNaNO2dQZPqvNs04XPfrz/pnyvIu6VnF3f+tfpof1rd2hXb87VA9b0o/2rvtnWD1L2LD5lg/t2W7bfEfJ9vG/FAiBtvZEfb3glVP33/MY/Ym8r2nJ1G3V8qX/bJsV6vrrypD0j9PeuE5xLyZ4GhA+dPTnUeX7J78sXJktV23Ue0K3Pqhp+XV9bfcaR3UxDGnz764jnQ8By5dSq0ylC6Wo9h41vl+CQ0huH7m13bo32EhxryzVuE+g8vOWmlRCEEKOmnT8utJav8+7g+gGJ+6cvuWoy34xVSUaZUtnOK5f2bSApMceOr9iV5tIG12hcr2GXF5fWL/JSn18PWdiDNfa+FggAAGr+LOSe1nHHx3FnfgoIjwUKWNCmB2kMEQlhrDCgttuXkqB1aEClIgLEuwAAEAN2T/6CUUpdlAKAWNy69YU+Px8yI0QEBCT9UKnmoBETakmmXfO6fLDrugsBQnhmxWE/fDLx2be/PPTe27vNWfy/VPOtt4dWQKlRczpN3BMrI0BIWth5+co3mg59peu26WsTFabIcXs3/5xWaewrFcRr+5csv2zNmgvGxg7jh7xWCu5s+a7bJwfiZQQICcE1Jy6YOKTtWz+cvPTC6kTlPpIjRlXXvwfnH+43r3G9N+oaDu6zMgBgrEij9u11EL/mr1P2h5px8qpYt4z6CkNeCIXkXX36zN9voYAQEv7o8/2Mbxu1HVYncuBBm7shPnXiq2RGKXUxCgBCqGVTz5eXHrNSAITWH45fNnNSxTZvVokcfcqZxSP6tRnz5vMhcHXZ5z1nnUtmCCHxi7aDNn/d0s+Ta8pMNcnxO15se0ASwGJW864lxnDZNvXD1AufRjsZ0gBTmZLy96bIs6WEAYPrByScWr7ywB0koozkFfJrO+qFBpJ67Jv3XlzuMVCkK/POkq8nVGj8asTKQ8cdD9TY+1ngXcoYA6rky0JytI77fk5G5qeg1htlXAQylwwiTLCgw6IOUZfVBQCCQfBIyBijLofqSWV4TmSUqi67e+e1NRsO305VnBamyCy9ZKavMLhnCLCLn37+52WTSbabZLtJSY3+6dudd0DXqV+tEJwpANOUfa1rMMA/X07dcjXN5HKkKg6T/cqWSdMj5/505BZDjDKqOqni9NxNpS6quJgn040AAAXWGNRKB87jE77YGmP2lOC8fWT6p1tvA67Xp3l5Ed1Hcgaq4pRtcVG/HkkDbfveNdwSUhTYsXd1EWKXrotxMciPhu/Vc7bNq2IBEDBgKhMQgH947eIqs6cpdpNqjVnz/ujGnUYMOWhPb4hPnfgqmVGqOu2KygDg4vLIE8lmxWFWHCbFcuPg8RQAQ9lQLcrSChRQ9a02ekjbPeH7/betqarDpDpS47cu+GCPKz2vhQAQA8RUWbbdTUqIvXvX5KR5NUUGwHBw+3ahcG7vsTSMADHKVNmhyg5GKQAAc6myg1HVo0mGGFOurlw48auZY1dctNnTFLtJtqcqqVf2HEsG0IUZhQdr7H0tEAABY/mykHtah/lKR76lr/gsiMwPZJ2o5xAAEyyIKtX5awHAZXYxzwGMUeqiGSlshACAIaCqTBkAqDfOJbgQQggxjJHbpzNKQirU8ANQpMav9S9L3eVQYID8ggkALhtRVnvwjt0To+HAUjUDAP49efyujDBBwChVgCZHr1p8SeOHiAAEIVVAOH3ujAgiAiCcIaEY/kwlDHB574kUipC7BBVUm/nCgWO2zt2LR1TUo8tpkJvkCANCTHUmH1mzPqn56/U7tCt2fEWCSsIbvVkT4Mz6tXEqwxK6b1SLUOYl4F4lZ49771UsAFBVUVJP/7AmocMrER+uXD3u30u79h+L2ns46nDMTeaPRI1Hhlx04qNkAEapTCkDUOKvJsmYYMBUlVXZZkl1AACRMEKIpUunKV6lKgF2ad9Zq/v+EKiqk7luHd8RA60qAzDImQZ023ae15gyRkKq9yoP0V9HJyIREEYIIyIikq1RCGP3jWvGqOpKOhe18RxViV/RyrXLVygTWqZUyapVI5q3CAYALGbKn4/G5sUCEcq3heRsHQ/9OQWe+fGZEEIAGBmKVykCACkX78r3H7wAAE4z1Qk6LULYnT8BAEoVIvn5AYBQvveA8l7O1Pnp06fyVFWxqNUDgM3iFP1FUQTKVMVBXTaquihVMBEAESxqsSoDdueyNVhId4LugSrq9ADUYnZK/qIgAgOqOFWXhTpTE20AetFPQlkyFV4lx5hImEjUdumXtbdfH1RxQJuQ1b/dqdCjfQQoe3879i8T0CNYaMQAmMxcqQe+GNLpYv+RfVp3rla5S+/KXXq/CpB2av3K8d8dvOC+auamk/uiOkEraDUIkKo4mMPunkDgzNvUDFQFNH4GAKfZpWr8RSIApapsU1SL+a6V5kglElHQBAAAImLedcQoK1qvWXWI++qIWUESAgBCCNIySgFjAACkwZIOaPodWqYyVSHBNUZ8PHJsx9J6z17XrQvRl+PUkmWJj9vs92lsXiwQgXuGnA8L8dI6Difd+6MC8PDZKkU50tAI4SIN2tfDAPEnjyRTAHLPkSjLY0TuvxAT9ESkWdwXY6oqW1PtAHBtaYu+W66qBAAYU1WXhbqsWNAIuiAsaDwZY6ZQS5odAAxFDFoDljEQhjBRMcGiRgvI6c5kI0AIe0YXEgCR9AALqKIo1lQrgNa/mFFrsDIMAIAFYFQm+mIGANVmcrL7So6wiAUNVc2X1kaeHzSoWs/6Jdef6dcjFKz7Fx52ANbnrctyVbKPmzDZz8IMGFLSTq//ZdDmVYJfWI261Vs2q/dizzp1nh28Aic3//KyBSAXnfguGWVE6QjrMFHdlWGiZLkoIrdWVVWV01KsAMGBIQYp1gGYEYYBY1XxCzLg7MaAMEKikE27ebjOMaZv2K0S+nd91C23VAgBAiQgTNIbhRESs0wwCNJVGLXo64kRKPHwlinr/z75T9w/1++k2RwNPlvSqmxg9iW2eWts3izQM6nJu4V4ax33ehzPCHdnBR7xltUt5PwVENKVe/udhlqAC3/suSYjzykoy0nZjvcUiBDJWg4GBqA648+ddwCUq98owKY6U1VnKnWZmeIUwpq+N+GNMb0qBBPmKZxRV+KlaDNAePW6wQJCgBBCRBSkoNrjZl87+tOytkaPh0OZ0XuWGhkCxXHj7ysUoELjuoEexWJCEJH0FZvW1wHEX7zuuL/kiGAsaBAizthdC05SKN+4Z9MW3YtB0rYNR6wCQnnushx5IF994VWxGBAh2tKtRkwYM+WFIsSW4kq6cnLX5u+nTu08cFMcQEi9qqWkLBV504mvLoOssuH05mCEEEY5C2EIFMf1ExdUgIr1qvphhAAjRARChKCarUvnNAaE3Gkb93qfvG0U+ZV9vrZwZ9/R6yrJdqIvBRJseKbt6xEIEre8MWbR4i1HTl2+aXaYKdJUqhgIAAjj/DY2HxaYPwvx3Tq+FfoNF+gt33vvRmJtWNXh0z4YUw7gVuSHa267skkI93tzQPbSGCDz2R83pgCqMvmjdhVFlaoKVWXQlHz1i4nvvd77tSrYpeLMhLTt4uI/kwAqvjusbpiAABBCWCrZbHzPIFBiNkbbWcadNNWlAGiMOhFluUcKiCYdm7fTDto6n4+sH+4ugWESUHXc5z1DAc78ceCqcn/JEWCEJSxoQEnctuS4HUqNGtMmDO78/vsNGxYB/f9K9npkDsUijEWiLfHsWz0GvTu4exiiiktxmF02sy4kOABAvXM7Wc1+7/Renfgo2Ydsmf2AMva7tZp8Yu5fFvBv/ln/ygGeu/RicOO+X3TUeSkZS/6B/kUC9RLO2y1fynTPNGuqtezZccuBhDwpkDJVVRgAiKKe2lXFwVSZMVKq05BJdQAARImgfDY2XxaYdwvJtXV843d9C5Zar67+2epeSwFE9C8SUiZYAgC4e+C9QYuOWP2w+H9MVBECZjs4/fM5Nb4Z3mLkge2d/9x9JQEF1mjWuFEogpjNo+ZftyICAOlu3XFs1tR5DaYP7TTsYNXo9YfjzfribTpWLyfad09bvO5O5gpCJSk2EaBIg2Gbfrp2Nmr5qN/iHO65PU3d+tlXS5/55LWuww7WurDxcGyKFNqkba0qBkjZPWvo77dUZsjLzBsTggWNqjjuHvwjMrXhy0VFuLp5eQwCTB5ADQFtB/1RwZEzFe2Mnf3hr5uTaC43U5SEQ5OXdtjwWuMfty3us+30+STZv1T1nu0qGFnMd7OO5XhU4l6dPITnitxaZWlR0777o8GHL7w24VCj05vPJEP4M92blZQcAFrwrMRJhxRtvmr9q9Xh+vieX6xIpHlI+pOIjjX87X9vuKLk6SFYxih12S7+teRCrw+rtF+xKmDpjmtJOKBi3Ua9amsv/J1atFagsaiegJnmp6H5tcA8Wki+W8cpXHn/Aljzk2X5BwmpGhGS+ZNsjb14fvfWjfOX7rmi+AlaETDytjooa/7I6wHAEEaYACYs7cwXA9450b/v291qd3mhEgGw3b72588bZiw+clnWYuzJECNMEMbUdPrzN8acfrPfkG61XnqpGoAz7vTuKXN/XXDCzkQ9YPdsHMmxW979vu68IfVLVakcmFRMvyLeQTHCAiCsJO5/v9+4QwP7DO5S87kXqgC1/3vx8PfLfpuz8YpZG0xElD0W9rUmhyBRwrJA06KX7jC9/GLA6ZX7bjIJEZx3v5mZfNGFVo+49wgSrs2RjMtULANgqqy6zEdnfdgttt/Ylxo27dG1NYBquX1i87KFP67dckuPRG2mj/amE7v3Lsve3oy/s2VJMg7AgAVASL2zZ0xfNXp03wFtar/8DMiJVzd889FyvyFrh5Rw2mSaZTXRPRrI3ZMzKob1aBEon9r3t13w3OPNKjDKvoYKPHeOqP3S92+OcU0cMqh9gwEDG4Aj5ezRQ2P6/rrO2mtvZL/yLSJCl92JU/PR2HxYYJ4tJNfWcTiASjZ56xFXyaiiONIUuwkwEXUBSJDSF5kwpsqqy6IqTixoRF0gEXUZL4ehilO2pzDZRXRGQeOPMPHstCZT1SXoAgSNEWUPfKjiku2pqmwlREO0fpho3DsVpwWYQiSDoPFHREhfceFS7Kmq04ZFjaDxQ0QCxlTZrjrTAGFBFyRIBkCIMao6zbItBSEi6AKIqAfPYkdGFZdiS1UUGyFaQWtERARgVHGqDgulMtEYRa0/ImJmc3xL7l6wpDrSZMVv+B9LPit/cniXaRssBpTn2J9RWbGbFIcZE4lojUgQs19/CSIEIQyAvCqWAaMuu2JPZVQlGqP7LT2MKorTospWIuhEffoNc/cKSG868Vqyp+22FKo4s7adqi7FbqKyjWj8BE1Aer8wqrgUpyMsVHImW0xMj4nAKFVlq2KXm361YuNz4saRo4edUNOXXamguAAABAljct87nIyqisOsylYiGXL2AqOK0yLbUhAWREMRknGpY1RxWmVbMmNM1AUSSQcIMaqoLqtqtzCggi5Q1AZk2lWeG5tHC8y7heTWOg6nANf8MMaY4lTsJkAEkHvVDQNGARgWNILGHwu6XFePIN87M+6xiYLGDxilipPZ0txjjKkKAwVjDRa0CGU6CIxEIvkxSpnqVOxpgAUASlUFASGiHgsaSF8NghABRFTVhRxmYEAkAxABACEiYY0fAcoUWbabEBYAGKMyMEpEPZH0gMUcS0FyuYeBkIAEXbFmr4+qDCmRa6JMEhJIfjrLrWSqyg7KVJRNk4AFiWiNWNAi70/euV+4J2BBUp02xZmmyg6MEaOUUhkTCYt6lKUtuejERxvvvR/gs38RkYih1PClXw4udumDLu/+FMfcbk0q3WFMZ39wnd5yxeVeuMwAmCIrDhMACLoAhvXo/t6fUtUFgBCWIOfVAvlaCoUEEQsaKjtUl5mpTvfT0oyqgDEwxihljKH8NzaPFph3C8m1dRxOwbznB2MiEknHFJkhhBj1rHpDgIiEBImIOixoMcr29mmMCSEaChgT0b2ow10SFjUIE4IFjHHOmT4hBOkQworLxlQnoyogQAQT4kdEAxa12aIhz8FIddmo4mJUAUBYkIigJZIeESldUcQdmlGXjQEwRhFKX8+HMJL0GGFVziwBYZEIGizpiaDJ+pLL+0guhr/99eCXi/tXLGlE8tkPFly2EiPKz8ydIYyJiEUdqApDCBjN5tYYQ8yzAMSrYgEQIRLSGBEiquJgVKXuF/eIWizqiKRH2XrHu058lAwIu5esYELEjLZjRLCgAcZQjoMBI5q08o+YgcMqT93wY6dtp84nK4awim07VC9F7Hun/bQ1NXP5jOcpP/dZCOXhmTjARAAiEEHE2V9BygBjLBBRhzDBOMu7cRAioAGdv4oJVWVKFYwwwgKRtABMVZyYEPeqo3w3Nq8WmGcL8d06DgcK5mkvjImoQ0QESu+JVgnGBGFy79uAESZEayRURVjM+BUjQdAYgdHskWb2s0QtJiKliscDIoyw+0Xt3qoQdYhILP1ghAnCImCc6UcQICwJWn8m6gC5F2tmPo6AMCGSDglZSkAYYREwyeHf7yO5arlp969U0miPPTZ78jfLbolIk79pO0KESAZ39sBrF+D056G8KtajKEErYJFQA2MUgCFAgAWEBYRwjijZq058lYyRIGj9gNKsEwjAhEh64r4kZ01fMMpU+8Vfp3S89eyYPk1a9ezeGgAU8z/Htn288NdfT8pUG5ARTmMiifogAMB5e9oLEwFpjO5p4j3XBYRFrYgxIIyJkMNOBFGPiQiq6n6nBcIEYQEYI6LsbveDNTZPFphnC8mldRwOAKBSTQdzLTxeMEqpzBRFdVlV2YpFnagLwoJUOGfuVHEq9lRKFSLpsaBFCLsX3SiONISJYAgmoqHQvbSSWwjn4cT+3GAeN39HFcVpVp1WRlVMRCLqkSAU3tezYAwEU0WmdhMmdoTdL0+SAWMsGbAgFcKUBrcQzsPK/HCjedwiu/S7CIJAJAORdFnvThe6ySl2v7qHUNnBmMpUdzpEwqKeSHqEC2XAyy2E85C8P+dxC3YFQeMPogEQBiIU8oGNEAJBEpA/SHrGKDAGyJNnR4gUzoCXWwiHx/5P6+AmmC/NznoBAISIBETiquAWwnmY3p9/5o3D4XAKYxTBVcDhcDiFMfbnmR8Oh8MplN6fO38Oh8PhsT+Hw+FwCgM878/hcDiFMvYPLFuHa4HD4XB47M/hcDgc7v05HA6Hw70/h8PhcLj353A4HA73/hwOh8Ph3p/D4XA43PtzOBwOh3t/DofD4XDvz+FwOJxHD7p+x861wOFwODz253A4HA73/hwOh8N5GhH4+505HA6Hx/4cDofDKRyxP/+4C4fD4fDYn8PhcDiFI/bnoT+Hw+Hw2J/D4XA43PtzOBwO5ylFQDz1w+FwODz253A4HA73/hwOh8N5OhGAL/jncDicQuj9ue/ncDicQhn7c/fP4XA4hQ+e9+dwOJxCGfvz0J/D4XB47M/hcDgc7v05HA6H85TCn/XlcDgcHvtzOBwOh3t/DofD4XDvz+FwOJynB77ik8PhcAql9+fP+nI4HE6h9P7c/XM4HE7hg+f9ORwOp1DG/jzy53A4HB77czgcDod7fw6Hw+E8pfA3PXA4HA6P/TkcDofDvT+Hw+Fwnlb4mh8Oh8PhsT+Hw+FwCknszx/15XA4HB77czgcDqdwxP489OdwOBwe+3M4HA6ncMT+BfWOzzqd3uba53A4HDents1/1N6fP+vL4XA4Bc6jd8U888PhcDiFEe79ORwOh3v/xxltxa+2LI49lHPb82a4qIv4Yfvi2ENTR5cTIOvf+Sn24Mjy+vQZWEiXD2MPLb7ydXU/biDe0Xee66UvYg8tjo3sW1PD9VNASKUnrV8ce2jRsqa6h+EbtJXbdXyh9D3jSF9l3k5Pd0f1K+b52VBtXpRn546+xYQHEfvbz6tKOcavplSzGT9/d+PQ4th9079rW292voZ2jpI59/YwAiiQ7YFhaSnxt5PSt4SYZIWBkpbidJgtVvXBiy3Vr/9b5QVuEHnsBMu/8fG3k+JvJye63Hust24nxd9Oun7D5KBcP08+pMiLU7/eMaV70yCSy1GVWlYMwgAA2jK1G+gfSsVZx7Km7lt9e0cY8d3LW/ecO/6v1fR/D/PHmUfvhJ+MZ30ZU5kiMwYAzq3vj33riB0AAGGECSAENPaD5wZMJCIAMK2Lec7JQ7GUphcLAKXGvtd83YhdsbLCFJbXIgohlFJq2jN5XAMGAGLEkG92DgqFEz+2GXzMjAkiAkKEK6kgxghlikwZADD2f5suw7ryFf0ALDlKyjpkKAB+pm51/cGdaTi8bpVQ94hBAIw90NAGxhRqPucZyxgDFQOL6QHg9LzvBkWmqsDQcwMmEhEhkhevlb3kJ2Es87u+3jtSVRSHRXXbnCNZtiXJtiTZnkJlO1WcCi7z/e7lcUe+Gl2aKnarewC4fbcQFDHii0//PrA49tCCY4sGDqzhR7KZh6w43McDAAi1e3/SygiyXZYVd63uPvGv1uGHn2deO7Q49tDimC2fzn+9cjAGEtpmy6HFsev6D3p75K7ti2MPLTgy+6VOVeuOnznt0qHFsVFfLOhbzojhfjI8gc6fqYrLIltTZGuSy3JXllUAAGqWbcmKLL007+fYQ4t3vxEuuvNqVfoeO7Q4dutbTYrVWn5gcezOMQNf7r9h46LYQz+dXvxmv4oat8E/ZSoqoDEiK04L9Rn7eDdjAKlch34r18yJPbQ49tCis6vGTmodIqEiAxd/NrI4APj1nj8v5tuafumlUuqSHVYKAHB7zz8q6Cp0KCsyqqnbvDhA0t5oB3iuC55+bfnaoPXr58UeWhy7Y9rKd1tUNXg8KBEeJwAAIABJREFUnFCk+rhvvrxwaHHcrmk/9a8QhACAgSJnjuVKpd9bO2dBHQCA2h/Nid31diN9ac9P5UguNuO9ZOAT0ic388OoKssAAKDpOvOnUzt/O7Xzt1M7luz7pkUYRlnWSqmqIjNI9/7aMmPmjJ/QthS7eHrj/jhNtaaTfxwzqEx6eochxlSquNymvGFbAoC207svNjdQqmbG/iigxtSZfZ+N8Is9cmT9vuu2wFJdhw4ZVyU9jRjW6uOObMfyLdvvCsXrdVn004je7PT8lWfv6sO7jHp7aHnhPjI8qVNUBAgzYIwqarq/QQghmhK16qwToEKXemVEABArdawfBpC4c/ffNgAA0NWYPKpqyv6oVSfSilRp/tXctzoH46dSRQUzJ1MUj9u/J9T1ZcZSxe5LPmvfLOjWulVbFm6+QktXf3vqqMFlnJcPnI5jAACJ585sPJWqQIb7V6nsnl5bzkTFAAQ0b1BE0JfqEgGQdGJPvJzp/bFfmwkf/TasSb1Q5eqFG7FCsWYvDNj8Q7eqGgCxxLBZY0c3C/Mz34g6nFRpwJsvh7qlZJljWTGf2X7wlBUAwHb+6IbN5++oWZZE+rIZXyU/CbF/QWR+njjXExBSMiD9P+ZASRAwFtOD+SyXFQb+dXsMKgdw8ZdeQ6L+VaUyfT7ZNbLcwBdK/fJtjCPnfMsRvXheYq1pA8Nafj1o8/PRmdMDkG+v/va3C9rLv665lAqBz86fN7duQJVwwhLcIyJx9ruzpl6mZazVO4wpBUk7Bo3/+birWEr97z6rWLRecdG/WJ5keJLiBUxA44clHVNl1eHEbpVjvaALQowmHdmy3Vqre+mmXYtvnRUf2rN1EEDSunXXLHIEAwBwbHv/o4E7TVTcEv3zt59F1B/ZOvBIwtOmoscwK+TdjIuLWnu50gBw5+q+qL+2nU1ZtKl2BXv8mZiEhBlzG7RbMKakeeesme+eZYDxPSkK9e7p4zFQsVyLiqUOl66vAeeBg5ftTTKOEUq3mdw9EODmN69PnnneQYLrfb383T5Vek5qufet+NavVwSQo8f2nbYi3iaUf3HvH33LAQAiWEgfy65bG77+Fqo3/ak+XFg+f/gWq6qtnFG4r6G9YmtT7yVzvPHEeX/nlmFvDzziZAghjBEWmEIpdrkv7QhlvcKjsKoldAAQ8cb+vW9k7A2tHOqHYhws62UCAICmnp7x2e7n57Yu/fqwtxe6MnNDyf9EbTOjrs0mTHmuQd3qVYsiABAEqjjtDAAg4cT1uy4bTUu0AQDEn7uWkiIzescCAEgUxfBnvMkQUSyHDE/U5RdjggFEijAVqMcVIBETibnM8p2jP+1I694r7Pk2IQt31+seBhAXtfJiqoxtlAFA3PYTcQ6bjLDj8NG7EBFSulpo6eCnTkWPX7KOejVjEdIObt6YUq1X2Q7fLugAYIs5eXzd6rgjJ2xM9WPuWwiKgzEReUkRI0fciSOp/cpVbNyrc5EiACd3XDE3zLze+FeqVgEALv65/GS8iyLsOrw0KqXPy0E1m4QG7C0ZCgBXdu++kaio4LpyYF9y33LB4JlYuOuV7UxJn1dSF6OK6vkJgPoa2kWLnPJRMser93/invWlzhSXzQEIsKgTJD/GaGa2J6tPpx7bUU7O7zvjko2pgAiRDOBKNTG4J5BBDNS7e+dP3NP4x1alBw3KqExFxZr/smZ0RyOLP7F3028LdnYaOqIKYqrsqVSVZQpMVRR3+luxuxhQxaGoHqv1LoMzyfTU+TWGABhQxXRi+a6EXr0qdG7UVtu4JMDlP3b+46RUdF9QBT+JAJOpywEYAQBTC5GKCqhjfJux7HD9u31w12ub+3R/vl2DltWLlavbcmzdpo00bz+/Omtu2EeiwnZja7TSp1nV0b0BIH7H32lqw4z8kKy4VPc4dCduVadNVRkAMCqr7vtq7gQRVZnqGT3ZxnJWx4QIozQ948QYk73YjMZALbdTSrfwWvKTEVbxu773t2XmvofDMm7tej9Otsb9fcMJIJQs6bx25ejJa84mvce/3rJFMa/nIERE6krY+OWiw0rW3WKpts92NIJ8eHq7ATMmzt910YHSbZp5AmHJSMT05dVIQzR+iKTPXlWLdxmKsqfPsyFEEJEAYduFP1fGAZTp/uWroQBXf9uRLOOMm7hle3evbNT5Y79ynZoXAYCYUzFXCo2KCq5vfJgxpUUb9Rg/tHu9pMiB/QaWqzfgjT/TAIRaTUtrMWYMAIgo6gD7uAfPLH9HxQCACAApZ/fG08zIX3GlXDh1HQCqdO9Xt4So8SdF6rzSIRhAPr0nJvFqdBwAlG1UNzQISwZN8bpNi95rTTosSB6HSHSYZK5O9Dq033u1RYtisun+JXOyZX6erOAfYW2QqNcDQghhhEXGVG/unDEmp+xfsji24dBS3dauCNtwSd+2U5UgiD/9y0rF21UQCzrATkdM5Lgfuh4YUz69NmpJTHFCWU2tLkN7QVyFTh/XAQDQGCSUeaKEiAulBylYkBBS02esPmT4aYkC4lPn/REmIhE0iuvmqt9vjB5bJpgAnN285TbOmjSuOnzavlYXE4Ij6hUHMO3+dvu/SZbCoqJHBWn98ZQD1sxRYf17ycBd3szYqFFJpVdf7xwKTWvU2XnCEty8jT+A4+CuW07muG0FAMMLX3xW6fjqVz47k8ruDVVZwvFjN6FSaQDXuaOXXaRyxuCjsuufdVO29VjUqey7S2Y/G31HqlCutBbkM8smRyXaHZFzTjw/tV7DeQvGNNxnqda9bZV7zYlgQJ55B8ICYAwU5T60T803Wy/fv+THeQzx2P++tq0lkp6IOixoEMK5TRKs0Z+99uGMnddNpeq90KkKiTk2c+Tn0y/KPsIjHSEaxpwXFn81J9bjvKniTNwxa8jSc7elGu98Nnx4lZgZ8y9TgPJNyvln9BUC9yoYz5DwrDdNl8KrDJeezvVnCAtIkBCoMX9u+BsAgB5edTIBhCx3429+P33b9WKV6xVHiWc2vfPazG3JrFCp6BERGFK6RNGMrUpJKcW7GVeAEz93G/X71su4wXMvjHitTcWUM8umTBq5PUlVkzbNiTxrAQgJL6bTa5E3V0UE540jh9MAAC7svmHOMRjVpPXjh70+78DJ22LFauVKq7f2Lfu6w8A1F50Ayq3FI8ZP2RHnKN9wcJ9qcuR3X0bnc/5/j818O2Ly1+dd/2/JhW1OmGqRC6Ti6u0H5/1gKtudlrtAVUEfKGiMKGMqykBVbC5LEmOqqC+CEXLZUhhTRV0AMFAcaQBI0BoREajiZJQJkg6LeuSORRmosk22JVFKJUMwEXSqbJNtqapiRwgLGqOg9aeqU7GnAUKCxggIKY40xqioMQLCssOEAImGECJKisOiONIQESV9MAC4bMlUdQkaPwBQnVYvMgiajOzQkwtVXYojTXGYsaiVdMFY1ABjVHG4bCn6WkOOrng21HX87S5fb7LoVdku44prjnzRmlz9oNuHP8e7FKcZqEokP0BPs4oeaXe47G7DI5IfkfSeMYLcSxfsqsOcw4wFyQ8LEqUyUIYIYaqqyjamKoLWiAWN4jBT1SVojILWDxMJ3J6dMVW2uSxJDJio82eqqjjSGJWxqBe0flR2qopT1PgRjZGqTtmWApQSrT+RtFR2qU4zZVTQGhHCiiMNGAg6fyxoqWyX7akIkKgPAkRkewpjqqQPRkSQbalMlQWtv6AxUuryDHMfQ5uIWsZU7yUbgohoQORxX+FybseCR575eRrf8IwQAUKwoKGyQ3FZESDGVEREhpCvWysIYSxqiaRnVGGe57wAIQKAgDGquhAgxigwRhnFKA9zJoQxFpmg3CsDQ0/pe7URNtbrN/3V8tUbVwsFSNq0ZneaACRrW5nqSFMcMqMKFrRE1AJCTC1MKvqPYVRRXRaqONKzJgQTEdyPxGQ3Y3eChikyU2WkCoCAAcNERERCmCBMQGGqbEMIg5Zggr0MMQEjhBnC2PP8LcocS0TCok6VbarToso2YBQYI6JWEHUMEBacVHYoTguWHYyqjKp5d82+hjbCCCHJs/+BSi58iZ8nZcUnwkTUMEoxEbK5BcQQYCJoGKOYEMSQ+2+EBUwkpGUqJlRxAQAmIpb0mEiZyS7EECJY0CJGESIMIYxEIukZU5mqIEFCWECYCBqDqjiBMcCIiDpgFCOCMCGC1p3qBsCIECxosPvxdAAiaBDCmIhY0CCM7pXh6XgXAkIYEZGIOiSI6StDGGX+dVtUKwG2K1G/jZ15yYqM6J7TEMJY1BONDolaBEjw1k38dRH5T+JiLEjAKPO4egYACChCCBEJSSiHGbvHCABQBIyqwIAQDRa1RNQijImkZ8CAUkAoSyYTASZY1AIwhAWEEJZ0iEpY0iEiYqoCACICQhghImj8ECJUcTKmIkywoCGiHhENAhA0RgUwoy738QTpESKABYQzxy9ChAgaijAiBDAginMf2ohoEMKCBu4tGWEBeCzhPfNjLaDMT7t8ZH7cHpkBw1jIFmW4XzxCXcDAnShgVPb8jTEwylQVmMoAEGAgBGGCIMe5MjCG0l8e4q4IKAVCMBIAAXVHEMAAsDs+QoARQsxj6yIgDExhqgoIYSICAFVlYAxhATABuI8MT3CYyZh7noQAYywAQoxRSmXVYVGcZowF94wbABSXTbbeBcYEXSARNIDc72hydyVj7KlV0aPtDspU2W2WWcNJt/tjjOY0Y4QREhhQoApjDBhDCHuMFgFQlVIFUQaEYCxA+mT33uHGGMNEACCMKUCpuwTkvv5QlTLF/ZwtwgLGJD2DRKmqAFUZQu4jARAQghAwVfEU7vmbIUIACcBYZr25DG2fJZPHfzJ5LuqRZ36eiEGGEEEC8ZVfQVibdZKQ5TefZ2U5V5N7RZhg8JaAzpaVRiLCmf/FQtYy7yPDkxz7I0REyFiZQylVnKrTosg2AIRELcISZAkcGQJEBCRqs49DhNBTq6JHPRUTNL6TCsS7GQMG7C0BgAXibX8uww2BBCRHxwrEa3YBYSx4f+UyEoQsf5NspWWp1+fQ9l0yT/x4qdFUQLE/5ynDZrWfjr6wLWrfv7duVyhXunXzxjWrVtYbdKpKz124vHbjNlVVn+3WoWb1CEnkt3M5nMfgemOyce/PeQgoipqcmppw+67L5QoKDAwtGmww6BFCjDFTmjn+39uMseLFiwX6+2PMvyjH4TwG3j+Ne38Oh8MpfPAojMPhcLj3f5SkKd3CbZvS8vnTf1Hdwy754hz7M3qzv+G/qe4BhYzsphf99aK/vn1OqdIiu4W3f9ja/g/KzEd1jotzejyjF/0N7R+jLuBwHjfvX1Bfd/G8wcPr5i9svqXv7p99Z5rSLdy2Oe1Bq/Na5n/REKv6wyzaZ7efyfpfVfdAze+52SabE/5oEeDtp1s7HlzUtMhu4e1z9sv/WaZnY+ZdA2u3+eG6nN8uOPHDrKt9dt81WXc83C6gSft+eLNpRLDor9eUa9R7yvrLdvYfdFaWS3U2xbpurBvbopJR9DeW6jB2Tawr10Lkq7Pri/71Z8XID0MkZr2yfvKrjYoH6UX/gJItXv1qz23Fu0i+hHdvyo2fOxm8/5R720G+9dcXrzcpVVQv+ofVG/rLWatXzTPbjW3Th7QpF6IX/QNLpcvJ7BeXjm1fPlQv+uv9q3UYseKSDfJee+bO9K1o711mX7qSE/6a8kqD8CC96F+i/tvzT5hTI7sZxJZzY1QABMqNuQ39c5SWi7r+4w0DQgWzed6N858dX1ANUeGmFTWKwBg99qI+tmW6NyVm2WcHmk7uX1bKbxek3LQWbxQR+JC7QL3xa78+K4JHr76YlpySsHNmD9cfPxy1/AcND+i12a6YE9a2CMyqWOXagj6jD7eafzE14eLPTQ8N77/wquy7EJpyeFMMxjGRR1LYQxDJcnjmr3caf7Q5+rbt7s2dH5Rc3b/P/BjFm0jehXdv9Nbadz6/WMZf69tgvJ8Oafvf6/zmX+UmbDp313rr+Kwax34+4VXzlkPfLIqt8/7Gc4m2uzd2Tiq//s0+P8ZYz8/qN/Z06wXHbtuSbp+Z3er0hH4zLrjyXLt7p2dL2TawdOlXxzby9yo/mA990H3IgTpT911PMd/Y9WX16JWnbQgATi/+/ZqMQL76x+JTgFqtTFDMnorS/7ZHdQt41A6hQPP+Kts+3hquSwtrYP/D/WI1k9JVl2bUpRl11k2m9MPcO8Ns+1LVPmGZPzE7XTXGGhGYZjRaXpiuplAAADlWHt3EbNSllWziWJt7maHWqV/aagSmGYMtw9dSxZeQJqVrMcuYIdZwXVp4Q8f6OM9uOVYe1dBs1Jlbvqckqtnk3JOq9g3LXt3DUJb58rqPOxU36rQVW41YHeMEADBFdi1Wc8yQhuE6IbzhsPVxss+d3psW2VUnGHWCUdcuq6jMfnnVmFYRgYLRWP6F6QdTqO/adYIx7Pl9qXv6hKUX4qNMOXb16CYhRp1QssnwtbHpcoY2mvrlCzUCBWNwjeFr47J3AUvbP3U+DBvfPAD5aJEcu2ZUwyCjLqjle9vSu8At0nN7Uvf0Dcspw/+L7fyf0RVHv9+7Zqhe1AaXb/b658tmt2aRXcPTazGl/32fpj0Q8vXIpQmdp73frqSfoWSH8dO63Fqy/qbvvjWfWvtP5fdGV7684VRaNtmy/638u35MkyCjLrj1sHcahfpWl3/7uet/HNmjTgl/SRNYueu74565vOlSWv5EoklbPnz/bI8f3q+pzWfb6a0/v1hX4dtVnzxXK8yg8SvVcvjcma38vcs5e80PQzrXCPeTNIHPdHrnnQr/bL1sunk8sdqwoe3L+EuSf7k2Q0dWuXM01vlAg/BW5Fcbig0fVd/gXc6ETVNWl/7ql3EdKgVqdcGVO4364ZuWfgCGMm3g9xWXnM6Lv62TupT143l/ALDQnUbNyTvG5W3V98YoSQwgQNhs9zcn6FsEZjkscydZmeBvthu6BQAAO/GJbfx5Yf4ZY2Ks/jWnctEKQNnm0Y49zXSX7hh/aaqMHS3fpT7KBACLutYkbY43Hp+ON05yRTtykzPKT3PyjvG3Nsq4cUoyA2Bs6xjH/ja6f+74fWxULlpykfMhYdo2uO9CadSef5Nurnrx0sQBi264PYrlfJTflJN3Yn9rs3vcuC3JzPfOewnomSXSycB64pOe4893mn8mNTF202vOHRetPmrPPN0dv0R1C/BRJk3cPHrknmbLL9258UvTnWNHb7zrfoOn5cRa06DN8SnHp5fcOGlOti6Qry37dH/Tyf3Lij5axO5uHTNqf5vV/9w597FxR3oXeBPpYaEt36zEuekfL9h9Odl1348P5NK0B8Nx8/idEi2e8bgd/TOtiicevemzWNvFjaeDur34XLegMxsv2HwdxZK3jxu+q8mKi3eiJxv/Om/OqzNOPrbpenDtcJYfkVjq/k9G7Wv5/eSWQfl+rMn2T9Q/RapcGd+kmGDUGat3m7j1lpIZwYR7v2gp5pidc2dfq9ihYnCNF+rHL1q466ZZls3X9yz4Mb5+72r6+5zuDeeln6ZFN5/Up5yPB1Zsl3dcLt6tSdGcXlUo2aWvceMvh4/8uin0je4lHptHGws072/AQ4cLxY2o1VCpxCn5gjXnN9+9fgje818bXbIehszUtC6NdIH4uUmapkZANjXyNB48XChhRG1GSKVOKxdtPssEAxk2UihtQJV7iZVT6S1nnuQsfkK+YANkVTecwoOGCuFG1GaEVN2QveRcbmk88BbQdeWJzR91q+SvL1b/lcEVYnZcsbllqzF0eNvixvBWQ4cUP7H2gu+deVbsmSXrxSEz321d2k8XWOW5SR83NfqsPc9lno88XWrw8LYljCXajBhS6vT6ix456w4b2aG0wVi51+uVU09l6QJm3j91Pgyb0DwAg48WWaM3nCoxaGjrcGOJNiOGPYoukCLe2fD7q/Lykc2LFQko1eaNr/ckyDnqyvjbd9Pydz8psxXUnubSBOowsh2Z2GP8SQjSOE0O6uNc59XtB1CzDpUrd2iJDmy77gTvctrOrTtWfPDI9iWN4S2HDs2hQ183Y2xnf3hj4N4WX46uIeYmUo4usB2f+s6GmlOndQnBeeqdbMeo5sTEsxuvt1t43pKUcGxK+XWDB65MUH33tSmyq04IKlbp2TmBH/48rKKu1Ms/fBY2v2O1YrrgYhV7zCs66fu+pYQ8W2/GTtOeGYuVV99vXwT5EFu1JNr1xYwkQwajrt2mNAAQSnR5PWTd8HHbKr3VIRR7M5gC2Qo09hdQySAAACEQ+dmZOV/fYJPZDQuqGp5jYgaJdvCUGXS/MgkUMwIAIBGJGW/1vK+cNpamAKhwxw4lAz0VBTyKd+XJ8Rvf7fyMn59O8CvR90Ci2fPNWyGwZJDg/tfPlpCm+N6Z13qSb1hCqoZLeao9z1mrRLvRI1JQKT/7bU+/EGMxIwEAJBpEluUbfPK1ZZ/ub5YR+HttkWq+YzeWDPSU+Ui6AMTw9mMW7Tl3x3Xn4rqhRVa9/tLsq75066tpDx6n6fwlZ6qdgr7R1I1f14MUlyZA52P4Kgn7tqfU7h6h10f0rJWy5UCCDymVtASbsVS6YvOgQ5py8PNeHadpPtoyu0coybtIzujZw5eU+GTWc+EPFPdirTGowdhPXqsVotUERPSaOL7i2bXRNs9sb8utqO4BOSe1W+xySsLFP95O/nLI4uuOmF8GTIh/c/PZBFtywqW1b8VPGrAwRvZ9ug9lxf4+ZVel8YOr+c5bEUMRrT3RonpKzjIDJuFdXisfc7vB4JZFHqNF9rggV6IoLM4EgEAxMase+Yv3WQ+E3N/rdP9XQmX82PmE7McIUFQHnjJTmUWHjILvMiEvy49yyukpU0RheohPr8ik5HEx0/+x2Y58Nmpt+Wkn4s1y2s1fG/mnr3hQUuNMCiBQTPEWXTFPe73uRO7XGFGF5rpiRgou43f3fIIrT7Xf2y9eyxSMRXVmj0ipcRZdqFHIpQtY2oGpP8Kw8S0CUGYX3NMi0T9Mb4lPL/NRdEHmhvUl6vee+O4z17b/4yAilu0KAwSq9bZFzb915XElk650/dD4/Vc8E2Tblb3xxRqW1nlfoJN6bN2ZOxueDxP8wrpvvPP3hmMpDHmTU/QP06fdTPGhw3smFDfWvN3muVVV5h5ZMay6Ad9PpKzCO6/uPpu4Z3jFQJ3g57lR1O7PtDy3XVuidilNlp9YHlSKxMCKHUeNKhe98WLyhU0Xyg0Z3qFcoCQFVugwclSFi5suOfK5HtHx97xZNzt/+EJxwfdZ+kptysVtPZ5Mc5QDAELxV6Ossb+0Mz4s23gYW4Fmfqx0/jzltoUd+NEVX0+sps9VJwTCJHYomnl8lx6/3hPmj3Puj2MuE9001XnYDEhPetakC+cqty1s/zxXbE2hij7/zt+kdAmzbjL5kLOuWFUPSI971aWLflTuWNi+ea5z1v/e81CXXSaGoACt62bUrBnHLBmynZ0/b/dtS8KBH+fH132+qt7nTgSApPAqATd2nLxLczF1fc3Xezrnj5u5P87qMl3eNPXzw2aftSMARPzDpNhD0Sm5llmlZ83YhXP33Lbc2j9vXmzNXu5+8d4F8rVln+xv9kn/cmLWLrinRfqqverGL/px3x3LrX3z5j6KLkiLGtXn3V+izsSlOl3JFzfP/PZ8eLNy/iVqBV5c/uc/VlvcnjmzT1vva12RXcLaZbWuvLogsWzPV8O2vv/17nirLT5qxvtbir/2bBnR64nmsxsuVpt+2eRQrA7HuenVL23426rxJqe++vMNby38fnu8KXb3nHnnbLmYn+nEjJ4tR115JXL/98+X1uRBpGzCB/Tc6lCs7u322haBrVbdjuoRkOe2SxV6dbf9MGnZ2SSHnHoxcur0KzWer673ps+0nWNenbji4LU0p+JIPLvh6+nnSzQv71+qbkj0nDnbr6fJijlm59zvo0Pql9bk1h332A9N2vbFKr8hY5v441y6jIT3nNAxeuygOXuvW2Vn0qW/78j/V9j5VGd+/HCbJGfNEHPvKGHGt0IwAjApnbVphlDbvlT15dA0g9b6Z8YNGQMZOhAtbWf217l3ogaf6adVVAZUMQeHWxeCEGEAwKjbd9rme+wVQsz99pAZ34lFca5l5lnO1nedNUPMvXeQb2YKIRgAoc7faptG2cuHWCYlC1UewU18Y5OJH9Xa0qtkkdDW88QetTJq9KvS+u6EmiEle+9o9s3Mbp6kqtedAKCrNXJSo509wvy17f40AZgiO2sFQ+jz+1L3vBwqGNw7wdDgs8hpFf8cUCUgOLzDQmgTYfBdOwAY6g0dGLa0XVF/ne8ycbFu333XfE/vCiGl+u1pNeO7HkV92l2WpT7ZuuCeFqGQzt/Oahr1XPmQiEnJ7R5JFzQe+ZJu24QOVcIMwcWbTTzZ/LuVYyv7Vx/6ea/YMVVDw9os1L9Qx5h7Ecx8Zuu/lbpU1ud+h99rv4gVh6yY2WDngEoBxSq9vrfR7F/fruj95qP90qYTxg6tSgjumLNVZ+OJjZeYNzlRcMcZs9sc6ls5tNYUR5fqRuzzfqx5z6QPo5JS9n3auqhRKxi0gkHb6Jd/sReRvAuf99UNXk+XIkYvG6+b36u0X1BY/YlXnl34Ux/vOSRj45F9dH+OalwiQBtSpv2UK13mrxlb2Vh93Irp1XcOqldMF1i0+tCoGtNWjK+qyXvtAPKVZV8dqTdhQMX7vD8UB3X4IfLLMpsH1gg1Gso9t6Hmh580MTy+T3shq0MBTm7mqHSu7BxxydA94DGULbJz5e9GXMqeuPS680lBvjqnfbeznxyZ1yaL93+iW5S9dXM7dvt70uEf2wU+bm9WV+MXtWu5+7O/l7X042O+sFBg3/YyaJ+kZ/D3hT6gtFaHPzey/NxXrTB8z8WnuHXDdj1mrTPv/+zbWy+NaGP77aebVUbdZ1LCecpu6wdDAAAgAElEQVS8f0F98cbmfELcoknp/IxzxOXHMvZHni+2Zv9ciredTzRPX4seH/yqdzZM6Vqib9ozfb5d+1wY/8pOYQLZnDzzw+FwOIUO/oZnDofDKYwIfDLN4XA4hdH7F8THhDkcDodTwPDMD4fD4RTO2J+H/hwOh1MIY//H5eNTfMvLZorsVKztnyauCr5xc+LbE/2mh3xhkjsWs240Fe6LdUDPv+7s7PHonjxgpp1vVWv5/TWZx0kAAOrdvbP6NynvT7SSWKL+i5+uu2TN5V2npsiOxdo+iMWaIjtKRCsRrZTl9Myd9/zk8/Q8HJxXQ7BeXvdxn/phBoloDcUb9522y/3eUNf1NaObl9ERra5E69Frbrh8CJ+Bcn1Re9195Lm3TAA5fuPETuWDJKLVl2g1YvlVR/7kzOvpXoX31XbvyLe2ff5y3VCDRLRh9d6cd9yUEtlRQ5rMvqYAACgxsxtoc/bOQ+qjB4j9+ZDm+Lbka0vTX7jGAeX6z31e/i149LrLZnPa7b2zerpWf3/U/B9UFNDzL5fqSFzXMsf3iAJbrU5UHS73lmsQkO3I+x2cF8yHZvx8u8nkzZfv2k1xuz4stfqV3j9ek+Wr8/qMOtjyp39Md//5pfmBoa/+eEX2JTwAgHprzahPLpYNyO3bXt7KVONXvTlgfbmpB2/bUm78+aZ5Sv/vLrjyLqcjz6d7Fd57271ffNIOTOg8eH+dafvj0iz/7vmy5rkVp20AAKcW/X7VBeC6snrRSUDpvZO4rmVmTz3KqO4xyPyAna58x1LBaNLqzc9+paZQQADmy/KkDmlayVSmhf33GPf8VO4ombRFbXtTlZeKmrSS1edc1SR3LGIeNdhSVDIVrW9fF5e+s6jliym2KkaTNsA89A/qfrutGi+PapimldKaD7HXC7E+7vPfLFFJpqimyI56EuwX1GjIFyMbh9Z8/5gdAIFqvrxuUodwraQp02L47zFO5Gnv+lENA7VSUPMho+qFeAphKfu/7lkhQCLBtd9ced2V4zseafu+nAfDP2gRgE2RHYvUHDW4YVGJFK0/dF2c7Km9aKMvpjxfxUi0ATWG/hGn+q7oadhs5yOjK437oHftML2kDa7Y/PUvl89p6+9N2+7OKvrc3tQ9LxXN3mUP+oLnfL0P0uuRXvsa1Lt/vduwqERC6r29Jlb2WaZ/+wWR80f3qFvSX9IGVe7x7riIy39eMl3fsPRWl68/6FDSz1iyw/tfd/l3yfqbsk8Z2N0t7084++zs92tpc/kYvbcyTdHro58Z/9FLVYM0+qJ1XvtijHb9uuuuPMuZlOfTvSrQa5le34dKEzZ9vrr0179O6FgpWKsrUrnTmDkzWvsBGMq0hd+XX3K6Li5bK3Up5/f4vOOzwCpnxz62jjsvLo72N/1r6O+SL9gA0pS3eruk0X6JJv8/XqLj+7uuqwCB4nY5wHlX3zJQWHM3wCkbegT6LtZCdxi1Z5P9V7VR3hkjJ7m1a1bXmKTtt/3//hZvmOg65wQAtm2cfWdT/bVk4+f+SrT5sfc9gT23y6rz7rqWOdoulHj5ixcTF8+x9HkJ1v56zgGQtu2t3guk0XsTTbF/vHRpfP9F11UASN42btjOpiuvJZ//3H+7p72Qtm/8qz8Vm3ridvyfL0Z/8M6GOyzrVw2uLf1kf/NP+5eT3IqN3mGccjY5blWbPe+M2ZKu2ONrTIO33079+9sSGybOPuf0UdHTsenKtyhxdtpHC3b9k+zKGLVetZ3ZWa3W3FWd8s7cLDbvXty8p1840UjEWKpe70833nDlx/376mtLdJT/lLPJcb+33Tdm9Ka7efJGNOn4n9eCaheHm8fulGxZ2eDeb6jcumTikZtOX9eu1H2TR+5p9cOnrYJy83lO72V6vkGeXrIj7mScu/n/a+88w6K41gB8Zivbl94WFbCiYsHeQNQYTWJPtScxNpT0m2ZN1GiK3ZhritGYGwsq2AsgikaNHezS3KUKbO+7M/cHi1JmllkRWfR7H548cThtzpz99syZ4byapCF+sfs0TtvJdT07quvcPch+a7xz9E7wK318a86pWSEvjxcl/n727OaDAe+8ImO6zf7+jXYfbcA370Wz1nBjmmMenowx8z36ihCSsHZeFSx4mSHmY90ncFrlWO8aXCxWwJw9hxUowqLjOMEXrQ4BkIAZF88KEWBtRnLaqu0FZoQM9oTzjBnvs4JF2IDZnI6CJrscwW0e8+KQUHH4i8MG+GuzyiwISYbvvHpowcutxHy/7hOmt8o5dteAkCEj4XzwjPcHB4sCB8ye6Thfw7VtJ/zi541p5RXQ+91ZLTL2VDHAEupTSzeg2Z/1r9xrUxA5e05soCgwOm5G8MWEyo6NiosfEiIQtRk5ua36coGZoqJnA07bD/btnGT7a2YvX5FA1m/SitRCK3lvN8wjn2Mmu9ZoNxnVdw580Sbt7UHxqSqqpw6qtHE+TC774U/svkKKa13lssouJdzQ03gCkLF2wtsno7/9oCPHqLZwpTwGMpz9dPgnF5An16w24uTZdP8unZ0YuWL5MB/nWwnhZGV6dBjd8e73y/beUVlMpVe2LVxxoVxrwmm30+sxstdVJunilV1XYuRXON2QOmlIRc+rEULMoJcm+e6eHX+o5bQX/N3nLcvGW/mx4nk6RvuAmscLEk1DwtVctpobYDhVgsyEi8WysBApwhBiSxgiA6GzVVgXsAAxwhBicBAbRzhCmI0oMmDNKlNKWU12SoqxhTwPFoMt5Hkw7GY7QhiyFiR+NCRcwGUzuQFvnCrRmgmE2bRFBlEzKQtDiC2ROc7Xps5XXPywFZfLZvKavf1PfrHW/kixsnVBer9Fk8Me+kNY0pDK7CJDUWXHCgPETAwhBkfAxq04oqjoWfnhBA3++Le02+VW1d09cd7bx49dl2Ul6+0GvVtnCYM6jV34+wKfAxsv6yiSSaMTSu0W68OflBECimtd9bIaS3R257XjytNfvxy7jLvg8MaR/iwGT8Ixq404xu/13cHvuiOlmSvhka4mmK+vnblFtnj1uCBmXdo0sjLZQa//+uuI2x939+VJWk061uyVjn6+IqbDG3P8QcoIidN2Ml3NTuPcye0uAh8PY6nO7ii5dE+01PErdsCwyWHZxT2nR3sx4J0fhNiM5kL8elH1g3rb/DnWsOWiEoPEpOD3qr4NKIYQXqdO1kbI1QghZFPjWh4mpJposLAAPpGnRAghmwpXPUs73enPzp+zO2z5pRKDzaTY0ktMIIQQSxTA1+QpbQghm0rhOF+WRNY8eku+zREjLMmVz50cE//P+1fZZN+mkqttCCGbOl/L86PuWLKKnrn3pPnB3d/44uM22UfvlJL1tksj9rHAGCwG4UrZVNe6ymXV8QNEznZ8N+fteK//iO3tNp7fNauDAEPIo1l3P8XJu477Bf3dNIVvz+akk2JTVuq1B6kzwvlsJsdndJoqbawPxSsuFGWyg19ZdjRbY7VblFe+a32ztNXQVnza7UQuZadbJgm8VoNC5Qf+LSe5r2AFTUo1FP0xSOxOf1/VeNGfz5gyAv30gTlNTpjV+L6l5jNahHBksCKBJ+ZhwY+uNJ/XVUnPxPy5eHpmdf262jrEt/proHr7T+tthTri5AaHhZGidua4HvjGVTaFGk9ea8kwPEOhCbcaKi2MR1f+4OhDfsdxPQo2rjqmUMuT127IqLzrHz9Q8e3Xe+6qzdrclNUzPzqhdbzqs2VBer9F1V/10V/7af2JQl3RyQ0/53cdS92xZBU9G2iSZ7320a/Hr8pVZnP5rf0//ng9oF8oh6y3HSNW7M+Vp2cqq4/YpCG+j/VunyYlfvKCnRfkWotFnXtibfzXBbFTO9NfWKO61lUva9TY9lSXFVf/+/0rveOyJuxPXz+2mcNwxW4xalLgoU+XJyv0ekXyd58eCp48qjmbYtnq4Y1I6Z5oaXRCKcUrLqRlGlM/mrnutEJvMRRf2/3VWx9kjZo3IpBB1p+k7dTQzk7/3ElhBo747IXMD95dk5artZof3Lpa7N6vSjfiU1+sxxLBilbWSW00Ij/dz4jZVoiQmDlvPvPAKxqxj34Dh9VZWCW9kBk3DdsyUMPj6Ks9panx2ETIGFhmivDUjD3GWrma7cukMGkysKEreYPOGMJ8dIvNrI4ihLn5ooMmacijeROTw658VIVq/Vfce978TgdeCRb7RG/gvOzoQ4bX0JXrB515I8wncrF5WEcRA8MQwsT9V2x7u3RBbx++d/dPM7pO6CpECCPU6Us3oNmfD5BU6xNhxMCy/0R4Bo891mfl6peoO5a0omfiR9wr/nXeoU+GtPTli/z7fHqp79qdH7b1IettR49FxU0L3DLQh8d5+FyR0Fw7lN9qeFuB69daFDWlX+G6cW28BTzfzu/sly05sGGYN5P+Sz+k1xohYfvY8v9EeAaPO95v1aph3lTWWl3aF58nlypPLhjg48FmcthMDrvnbwWMlrP+t6Zn6uRwsWf4xBO912+b2ZJNNVDpvr3EJilTFDWp679xHcVCSfOXlt4d9EfKd9FVBieqq52aznSzkzae/NxJ18gYXkPXHVrWfP87Hbz5/JBRiZHzFvYWOX15q1Gf+mJWG/7szM7U1sHh5rlZwhGuvDlrV1gG9LMtyeTHPB9OO7ti04B+J5ZkbiM/X2vW2oHDri7+d1NsVbdi4uDw1XOzUlzsWKcVPYdYs9YNGnZ5/vlfBrud2RF4Dmk0syObpWqgktO8H6fkIVKL1SZ9li+19tSC7wtfnxNr+PPX++3mtqW6x2eHz0m/8zQqeg5hh8edvAPdADzv0b9BQq1Lc3+cOPudftyXNnVrzpq9vKmtn/XZmKDjMP43LwS8oW795pq9YwIYTb8iAADqwbO18gMAAADQnPuDKRsAAOA5BG7LAQAAIPoDAAAAzwdgdQcAAIC5PwAAAPC8RP9G/hNWa6y3LklDa0foG2t0MqaKyaKZ/rHb0GAVNWY/J8YyGUwmg8mMrXlSmsRY79gn3aUNUKYL1ZlurHlJxmQwWbHPzhWEH/h54js8N3IDJOzUcuFIOnvs6e0rf8AnnpHY7PTSP/ZW6g1cUSP188hUHMeVe2KkVc704a/KUx7/TNWJg7xik9RPtMyHepnkd9r0XZ1tdfUKXlj5Q9bEM0qbPeWJXUF14iAGg1nzp9aJPxnzhf5OwvxXu/hzGAwmN7Db68tSimyPfmvL/e9AD+dV46Unf5zYqxmfwWSw/LuMW5Bw20A0TJMsuTvn9g1mM5jsoP5zd+ZZqnZUlRYShpyDy6f2bcZjMJgeQTXPqMZP7TIRsimSPhscKmQwmJygfrO2ZZloXiZHG+qV3aXGI2vh4cXjOvlxGAymT9cp6y9olImDmIzua7LtCGHIlrOmG5NkIDXYWHpGvL42IlfP6NUWYzwzFQHOsWZvnn+q/+IpYa56JW3lufqgXm0lT/IKSkam4Dju+AaN2aOs+EfKyIaw8WnPfPdbcZ+Fh3JUJmPRifkhO98Y+1OWY78we8GOufNvOpUj2nJ/efW1rV7v78vWmQ0lZ9a8Ytm55qy2IZpkvbf+9VmnozdnazTZm/unz3xzwz1rRUdVzjMqs5/+9md51Jf7szUmY+GJReF7x4/dkEW+AxpZmfb8v6ZM3R227N8yoz7/wDvab8avpFAzIoQeXZ3KC1TP7C40ntCk/2fwO6e7rjhdYtA/SF8amfm/S3qEELq0afs9C0KWe9s3XUJYZRVPYSw5B28slOYYVI5QOUKaKt2N2/W2rbM1Mm45YqmGL7GU2qqmrJJeaY4RqWe+rRahclEn3Y48pxWRpTTnmt/rpERIGTVFFyGqLLN2Re6CVXVz139i/BBiBfSaue2e0TF6RO1nvh0lQkjUafqOPDPlQZLIhePKPTGOURBT/RLc2jq7n4yLEKvZ8CWnSm3UtVcbSjF7lJRlmnP/ntFVihCSdp3xd25lOyXdFiwcFcZFiBfxzvb7lmrna1cem9qy98q7ZsozMuduf6+TCCFR1JQZEaIatZO04QmN21p9KCX5MNvKTn4zvAUXIX77KVuzTPWp0F60ta/XC0kVxZYkvhnY7L1dv0VLqU9Nte9F775bi+w1DpM0yclQodOkktvfdpJNTFZXHFQfnyDrtPy2mayXamQv/rOv94v7VGS/M5OVWXpohF/PTXKb45OQt7F/t69vmGhcHUcp9ctOv/G2/C2DgoZuK7DVKFPYfHD7Tl9cMRovf9Gly/AwEWn0bwQab4IrYafgUlwpqDZHQMT5r3TvX2dtviUxFAumWGw39FVTsvYopTgudHxJ6uxHRbybavGuWNuceGuZk83Oa6ckiP1zDWmxgny16Bux7YauRpOqV+QOqA9PGbeJ88EplU6x99XbH0/clFOxdb7u+lHRNzfV+btiT8yJP+joBNKDVPPZGtM0pD//1cvvXx+6+ZbGUHxoiuX4DT1F7Y+yV4zglJESijLxB0lxcSn9/ydXy//XLzkuLulBxR+Yay/sVE9LKVVfXynb8/m6DBP1xL/2GRGl++fOSYvdla++8Y34WOUVJGvS04fQpH08/he/ZVdLCw6Py/zPnL0lj/8H9XjZ+QPZXp2DuQgRqrR5c07ErP0mxtPZq3oe4f1lGUu//Dn5drmFqKtJNIcKaZOIvH+LZdFtHNtMC9pEy0rO55nqujHT5hxbt+5eqyGteWS/NpGXiSGs6t+lmuSX5eaKz0VirFdsYtUtmrUnXvdlMBgMj8CuYxck5ZorlgTrlZ1u4w23j9wOHtHXt2ZUZYW8PF6057d/zvx+IPCdl2VMt4kqROOissRINXtVlf/UW6c1Vy/MpJFSZYkRqldlEQRBWLNNXWW6EzrqKmqn1FrHB6tXZRMEQVizjJHC6iVXrcgNKd0+wH/EYTVBqPbGCCNXZVkJgrBmr+oqm3hCR3HQcWp7Y6QxNU+txkH96WnNIxZmmuuunX6Z2tS3grs6mpS1smvw+FQtQaj2xoiifr5vIwgCL9nW12vIvkeF4KrjU1v2XnXPUlla7TPSpo4P7roq21FmpLBKdaRNemIjtnrhVf/58P91p6Y2j1qdbSUIwl7wex/ZGynax6sM111dOcgr8M3dRTaC0J79uLXfK3/l2+o8QUv+0eWT+oWJEGIH9njr2+MFFvImORkqdJqkShrsFZ1QThD6fz584aOzioRor8FJKqejoiLoiF/ceI9igJGWWZa/ZbB361m7binNxgeXf58chthDnFVEELhVm3955+f9vZpNS1Za65cdp9/4fUO8o3eVV00fszdvb4w0ZlfGpgFB7SJajku8t5tkwDQSbra4bSVydVj7QHqJWViIJ0IIsaSYyEBobK6ktBPFRixEihBCLE+GlIXcHqti74cxzXkYhmE+r58s0ZoqpmksaYgnCyHEkspEhiJHJ5AepFtPea7Op30gh1btNLFrS4wiR5M8Q0TGYm2F8YQp8hczEUIYR8DGrfaqE/95p/p/XWXFv/YZ2bXFRlGI1FGme11Bm0qhuBgfxsYwjBk09Ux+scb+OLP+8vTFQ6OXchcd3TTKn2m+vmr6H7Jv1rwaVPfUkR005NM/TmVp7PqcxLk+f78+au1dI0WTXBsq1ZvE4Ek4ZpURR/xePxz5/qHZ0cmzk1QCt6juHphdvvCdX3NI6yItkx30xubfRt76qLMnlxc64UjzEZF+vhX2XCQZmapMrX2Hh7GEQZ3HLf5joc++jReN9cuuo914psDHw/hAZ3ekV+19eAfMChw+NTy7qOf0GG83CrluFv3ZWAshcb2Q7uNZuaris0ZoeZiI6UpKFhbIJxQVB5VEExAQ6s9+NSsh/PsrSgtuL9ra+6FB0KaSqyo0ivlankMoTX4QIYQwJlY1ypJeAq8WwtLrhRZatVe+aVOHY5Ap8uNpHU1SKrQ8f2cXCxHqk0vWobgvB1T3StY4I5Y4kK9TVJbZeFeQyWZYDRYCIWTXVn6rsaQhLWK2FeOVM8nHWIAy526f1vulvyM2XdgT10GAIWS6l3z1Qcp7oWwMw6SjTqhOjJIOTKzDEcbgB3d/66tP2mYdvWOlaBLlUKHRJI/mPfwVaXcqLYx30hR+FGbHKoOFLWk57P348MykG6TeN4oy2cEjViTnGgmC0F77sc3N0tZDW9PYORxjOvyX9cxOt/G81oPD5PvPk5sdp5w0lf05GMyO1PCZb48i1sWbTsgJs9qe+I3ptJN3FfT29eushTo8bb1ZEcV5JKVTWwd6ahPVTlPymWOi8I3rbUU6PHWd6Zre7aM/bnloEDzyw/fnHhoE9dfWr0st1BWlrd+oeGjmIz2IEOIERkhzj1wodRb/+Z3eHmVaF//jCbnerL6T+M3i01rq2hFCTHEA9/6pDKXTMiNGd7r/09oThbrCE+s23O80KoLv9FWfGhN/0jPitx8Tpdi4/mSRrjB13bpGu4Lc4C6eN7ck3dUZFMlr1la84IEEkRNj5UsX7b6jNmtzkldO/zD14TBWJw70rCtq4+rzK17uPvPepENnNo6rVAlKRqbWXH1KJf9S0RyfPvbDTceu3leZzeW3kr7/ITOwX6g3RZOohgqdJrFbjJ4ceODjb48r9HrF8W8/Phg8ZXQL0he0NMmzXv9sS3q2ymwzlmQkLFuRGdw/jPR7grRMY8r709emK/QWQ/HVhC9fm3tv9IKRlWrG6v2pSY6bMH/7v3KtxaLOSV0dtzh/0NQu9npmp914ZuCoL4ZmzH1n1YlcrdX84Ka7mx1RY674V3/BpmL9C9fb/pihCWKXI4Zq6NfWMjv1ur9IPX2SWoDKBR31f+c5fUJAltKSZ54WqURI2WWyvr3I/df9DZnrRoeyEWKFDPviqyhJzF5VxdJ5xPRJnQUICTq++3eexREaah+sPOmcza81xxBCldmrvyFTeQlu/jGjTxAbIUbw0K9PltkpanfMIjX/zOvriRDCnJVpzvnftM4ShJCk87T/5ZgpV8xrrPg/DHZkZ2TJ2z4tUoiQoMvkGe1FjbTuT1jlO96O4CHECR21cGG3yp6xq86vfDVCjBCSdpmy8ZLaMYxx9YnprXqsuG2pq4qaH9NumxQ26jbUXJm/ueOLUR29MYQQkrQcNHfbbQNO2iRnQ4Vek8zZf8/q5cdAiOHfe/b2HDPFAMD1dxMXjenohSGEGN4RL322O8dEWVftMu3qyxsndhAihNj+nccuOig3U3WFXXVx47t9grkIISRoET3zt2savL7ZXWo8rr+1NS66OQ8hxPDp8saKtAdlNB4UNRKIaKLQj9Hu/xT3iUUitxhS9cJyb1XvllOPq/Bn5oyqn93qvi2nHFXi7jx+gOeGRntYhmHK+hdyQqp84ilr3Rt5IuDpPfgJjz9z9xk+u7npd+EiA25Co0X/+kZVtXVgC9P7uaK6n6fRTwkAAPDcgBEEAb0AAADwvAGb2QAAAED0BwAAACD6AwAAABD9AQAAAIj+AAAAAET/x0FtHYgpMUyJYdq6tiuhyO7pNGOdCVDD5G3YTqOxT8Cz3QAAAJp89JewUwlPQiWsvrm8eyBhpyrd8u8DKLYkbDAIdfLbrfqsfmQyetoNcLOvXgYWtSbbhhBCtuw1URijYb4I1YkDsQqqlE96kArTvT+n9/RjYhjTr+eMbVnmen+jE/rbCfPGdvZjYRjGCej62rLkIhtCCFlyd8zpE8jCMFZg3zk7cy3U7aydsp4VUUGakmb2+vc8shYcXjwm0peFYZh3l0nrL6iVtYYNOY0wqYKVH8DJSCbbcO355tJ/KxV9/73UgN/xjm0Y6jpIEf9urX7tw5sjExR6vXzXiOsfvLb6tqWeTdKeWfFrcd9Fh3PVZlNx2oKQHa+PqTA7vjbzdPQfOVptzh/902e8seGelbydZCnrWRHFkCVLSTt7fXue0KR/Oujt9KjvzjwwGkpPL+uU8ZfD7Fh12GCk+/w0xqTK3bbrUd8y/ydGiVB5QC/9X1mVaVjlfI6y87vG6VHKNh9b9ZV7t82YqhahcmFktb3bKn2N+ojKvdtIyqx777nKVqksVcSQ+p33Kzf3UlhmdlEipOz+rr6DuOH3EXq0eVb1HaNYiM8Rd3736+lRPm0+Pq8nCIKwqW8lPLQw/pVlqmzw7pldRAiJu78b10FcuRlZ+akllba/P7NrOCtqKVZqNEC1t6oXcOd9xx5hpGWS1t70dlWqUPRdNZmuOBR9e1WEJW/7Q2nl9ocb7Um6LVxUKa3cIbc+zU2cLLeWRoZOP62vtPVMD41cdstC4Ibbfz4Udi5NL7NTbslXt+Gl+M++Xi/se3BneSfZxBRNxUFN8gRZpxV3LGTttFCnrGdFNc+dLKXexdofu+ftBVsHBQ39q9BOY9iA3YVkwX3yWAvnQ7FaL018zf7RBLPDosBivLmM8+AXk+5NDkqwZBgRQgjp7MfEvFsaye5B1rlzrKUVvsY5hrRYQYFGvERsddj+qMqkvx6lsx8V8W5pJAmx1ri5DjHkoXj98b4CuUa8VGzN1DZ8z1BNQFjBby579cEv63RvvoYSNmcYEVIfnjz2v5wP09X6/MTXbn80YVOODSGi/FD8zON9t8s1t5aKjzoaTGjSPnprk9+318oKj7ya+WlcdQFhjYk/aQN014+KltzSFCTEnoibWyFcJCuTtPamCCvklQmiPb/+c+a3A4HvviJjIoQ/SJo9O6X/3wqN4u9+ybNnP5JW7lC9l1qmubFKtueztRmmp9hI0/0LJQG9Qx17EHuE9goouXDfpD//5Uvx11/847bWWHJ4qvnYDX3Va1oRg2hOP/Gy8/srzI7ni2UxjyyMMbKSc+RmRxPtlK5WVGPZirQiFe3s9cRw+/Dt4JFkZseaw+Z5X/enCL57MoWLX2GI+ViPSdxW2dY7FRYFLiN2GCtUzBw+nOWvwUsr7mMFzDlz2UEixsA5HrKLlkw9Qnp7wkXGzDnsQBFjUDw3UuC0TPpUqSj4giXTgIN3q64AABsKSURBVJDBvvMcY9YHbJmIERPn4aioUeA2jx02JFQcPnz4AH9NVqkFIclLezIPL36llZjv12PS9FbZx+4YEDJk7DwXPOuDITJRYEzcLEeDDde2pvh9MH9sa6+APu/NbnFtz3VDlRX/2ooVkp6JnDN3UJAocOCcmcEXEjINFGWS1t40w3/wy5N9d82KO9hq+lB/BkLIcH335Waz5g4KFgUPip/V7HJlHwqi4j94oZlA1Hb05Haqy/nmp9hG3KS2cEQejo82w0PMsahNuqu/7ubMWfdJbHOhh2e7cQsX9hM95mKB/tqaNyanxaz4MJJjVFu4Uh4DGc5+NPTjf5En16w2khqMcdopG6Ii22PU/ljYdSVGvkOR43haUPHVUmvYQPQnRbHXENNciWFKzEd/soRwGAQxJOBjLAYS8DGGnXDM3av6Go2E1k7payQv04WPfC0xpI0oNGDNKg82plYQ4wj4HiwGR8D3YNjNNoTILYw2TaFB1KzS4edosBMBIc0V/9peQNIySWtvouG/hqLPVWnl0/hMe0g4Fq0Jr/wu0Fg4Yg87qbDT5S+WxzQ7uuqApF9RjdcQSFOyaGevJ2B2dLnHMPTow6G3fTXLGv69WGnxtBcJeoud5qzia9TxMTGLwtfoUpl1VVRVDJlXXr0iN4HUwsgSB/I1eeXVPYiUAkJ6E39EKlwkK5O09qYa/qsr+lyTVj6d28GQKN+is7mOhRVT7rkiv27NRGTCzodTiDrdnAjVy+zomgPySVckfQwD5WMBZkfX4GARUvzIBcIR/3FksCKBJ+ZhwY/8YKpmECQJc1V8jd04Hah8jU7KpPleP5kY8tWe+IaVVrkaP77GdM3gTuGJ1MLI7/hqz4INK4/K1fLja9Y7GkwlIKT/qk9tLyBpmaS1Pxu4JK1ET+UPJjhho98QJ3y+9nSR0VCUvubz3dI3R4dKyISdjvkXDTdnPc2O9FO6WlGN/iRNyaedvb4zWTA7ukjOZl1z7OE7NnjmOm0ouxyxVMO+MERJNHtVle8F5Vb5r6rynZ/JagEqF0bqd9x34mskK9O59qvqcQoxpFVhntFZiZCyx3R9pEST+PTe+amgUqMojdmbW+W/KkoLo1WRMKOzECFxj+nxkZLYREoBIZlbkaDw0pF5AUmlhqS1N22TWuU/aUsrH9vs6EyZSY7x9h/vdvNGCGE+3aZtuWOkEnaSuTkbwuxI6musZ0VUr+KQpqSZvf49D2bHJk+pub+XJklFSwxpk5t6ynQp2iZzcjb5f3vK3qRsMKlb8VHP7OjvNTipHmO3jtqfbdzN7AiA2bFxnlY+CbNjg3KqigyyTjFkrMji7g5I7al5KwrfiI81bP0lLyK+HdUChRO3ImHOPbr9rmfnCut1g9T+bANmR8CtnmE13oqTW8ZKtXWgp+6EB3Pan8INYxgsRC2GxIl/VuhHf25Vt+GsS+K/0xpz90st6Dhc8PUg39fVbd5clzQmwNUnPurEgZ6jTni0n/bnmkiPp147AABPfAoOZkcAAIDnEJiDAQAAQPQHAAAAIPoDAAAAEP0BAAAAiP4AAAAARP9nkJqbQBA3VuuCMCXG0DZJr6ETP1FD7D3wlAWQNasz3Vg9PAjDGsq9BQAQ/Z8j9PYfvrdP/Edqx0VN0mvoxE9Uz20OSQP9k9k6sZZXku7FuvDD91kT/1HZ8SfsS7KXnvxhQk8ZD8Mwpm/nsfMSbusb4n1pcrshYbi5ZVb/ZnwMwzB+8wGzttwyUFbubmZHquz0pyn1E0NaFUmfDmrBxzCMHdhn5rZ7pjonSdXbQN8rCWbHZxAbkatn9G6LQX89PR7bK2krz9UH9W4recIXy5b7y7hXt3h9sD9HbzE++GftCMuO1WcbwlRDajfUXV8xNv5S7C/X1Waz+vovAy+/P3bFdfKw7n5mR/Ls9Kcp9RNDmvL/mjxld9i3F8pNhoKD72q/fmvlTcoOqbZ5ScXsgb5XEsyO9J2OZMZEcrMjWUp7uXXJcBUXlfPb6/7MrlbsB59oIzzLEVIOXmItt7tQO4kY8pHrsbrx8alvz0PiayR1K1IIF53uaVVtux4SBSBV7dWGUo0tsaqV6br+sMpmcxRnZMnb8V4nEUKiqCkzIkSub8jlKur9L3r3/bMYd7btnbSyGfU3O9awG97f/6JPv79KHLXjJX/18xl2QN1EzI6k2VW0N9SrpxhSfniEX89fFI5AYLv/c/9u39w003Y60q8dzI4uUNuYSGVhJHMrpn2k3+THu1YmOfKq/dM4yyMroc7+y2XWqn+lugJh33/M57S0aycVQz5yPbL2qjwJopFWfkh9jYjMrUh1kO48i0wBSFo7qReQtMzH0B/WmPiTKCRL98+JS4tNKNDcXCI+VnmxHk9VSA+P8AGyjCVf/Jx8u9xS54LPEzM7VtoNPSNf7674aWNKrtZq1eam/PSTvPvrHUm3S3JDsyNpdvq7RZkeT834qCIMYViVrVhM8ktyM0V27YnXfDAMw7gBXcbMT8o1u6KlBLOjC9Q2JlJZGMnciltTsA/mc1p7Mfq859HimvWRlZDPnP+Tx5BwTBDIWrhP8KKEdu2kYkh3Wbgn8zUiMrci1UGaGMgUgFS10y3TVf1hLb1M7TPSZyZclM2cMzBQFDwoPu5pXCxO248P7ppi3fZed28uN6jn+OXJhdRPJJ6M2bGK3VAYMn7jN4EbBoeKORxx6OB1vot/eiuEdJsuNzQ7kmanv1tUPcWQXh3HRt5dvmTPbZXFVHpl67zl/5Y/VJ/Vmg/ZCLOVwK3anMNftj0xNXpOispOu3YwO7pAbWMilYWRzK2oUNjjw5QYpmQG6c/k44+shGyspc9j1U4hhnSTVXASXyMicytSHaRbD6kCkKJ2uk9LXdQf1l7xr31Gdm2xURQidZT5dC4WO2jIp3+cytLY9TmJc33+fn3U2rtU8f8JmB2r2w1tOZsmfCKfdjhLZTar7h18L//TCf/NJq3dDc2OpNldiFKuqhmrV8QMemPzbyNvfdTZk8sLnXCk+YhIP1+Hgo3i9QSMJQzqPG7xHwt99m28aKB9mmB2dOlRak1jIpWFkcytGNKCta3YkyA8CcKTqP4qDoY9Vu2kYkg3gdTXiMjcilQHEUIIY2J1hSI2mQKQqvaKQuv0ArqmPyTzSpIoJMWBfJ1C1Si2SAY/uPtbX33SNuvoHSOTzbAaLARCyK6t/FZ7AtSyGxpuJN0ImzPnhTAJhyMJfzE+vtXN/TeNZFnd0OxImt2FNbf6VsQOHrEiOddIEIT22o9tbpa2Htqaxh7jGJPFIHCCfu1gdnQlotUyJlJZGGunFDAnxuJLF1nuqJE2x7ZyuiHV+dsXtSWOZL5GEjGkm0Dqa0RkbkWqgwghTmCENPfIhVJnMYpPpgCkqh3R8wK6pD8kfdWn9hnx24+JUmxcf7JIV5i6bt3TuFia49PHfrjp2NX7KrO5/FbS9z9kBvYLlQR38by5JemuzqBIXrP2Up3NoPN6JandkBvSzSdz1Zqj2RqrVZtzdO3qDJ9uzUiXzt3Q7Eia3YV7LtpqRtKKNCnvT1+brtBbDMVXE758be690QtGBjLIsmuS4ybM3/6vXGuxqHNSV8ctzh80tYuU9mmC2dGld35qGRMpzI5kbkW7yrryVbUYlSOpespGW4VBkJaskdrXSCaGpGX4anjIfI2kbkUK4WLF+eVsfq05hpwr68gUgOS2SBIvIEWZtPWHZF5JijOy5G2fFilESNBl8oz2Igp53hME193c8cWojt4YQghJWg6au+22ASes8h1vR/AQ4oSOWriwm6SKcfOJmR0RQt02KazajF/e7V3xrJQb3PvdXzJ0eFMxO1Jlp2m1fHw1I0Ko26b75Zc3TuwgRAix/TuPXXRQbqZ6yceuurjx3T4VfSxoET3zt2sa3AUtJZgdXYj+NONpQ0Texo/mDWCaJdxiSNXTfUjilWzSZwRmRwDMjjXX1KQ6RMOY+BD6Kenz2GW6u8Sx6eLEK/lMnB2YHQH3ofHMjiohuTGRdMmeZkr6NESZAAAATQcwOwIAADyPwL41AAAAEP0BAAAAiP4AAAAARH8AAAAAoj8AAAAA0f+JUXs/hsYqhFbJTUf3CGbHRux5TxBMAhD9n06klrBTlU/lRf4mpHsEs2M90R4bF9j7t4JH+3fhBb/19h95RNsw18uZMbFuEeCTN1A+NbMjaUonc5ea2Q3ZB5dP7hPCxTCMG+jIXs/aXXJqgtnxeQF0jw2KW5kdOX6tvQz5aps9f3N/7/6b8+02dYFe1EA7S1OoBEmkg6Sn3wAGyqdmdiRNyaeeu9TMfnrZxvvdvjqQozWbitIWh+95a8yGrPL61e6CUxPMjvR32qltTFRZYiTqhYu0YdxyxFO/s8NupZY4KpIMg8OUCJVjPuopP9nU9qplVtvDhySlK7JJ99A9gtmxUc2O9oLf+7WccUZb9FcsC7EGbivS/jMzrNO3ty2EvfzUkuEtuAjx20/5M9tM2TYXtgMiUwk+oL3ZEamBkiBrp5Oh0rhmR9KUrm/3hJds6+v94n51/Wqn79QEs6ML6zOkxkStfYeKm1omvbGKseczc4aJQuKot332niXsB7Ha7Fl2mtfhsvmivmqZVdcByFJS4ba6RzA7Nq7ZkSGUBRFFqgdn/rzi28Xn8pb0EmWBXiSTMjVpH721ye/ba2WFR17N/DRubwlO0Tb6UKkEa0sHSSE1UBJk7aQ/VKrx1MyONFJSP1axaXOOrV17r9ULrXnUZdIRQ9J3ajY1syNq5F3myDZe/vl+xXesua+Xdp+KIHTWqc3Vq7MrvlzNfWS6FC1B6KxTA5UvLDCnXLUrzU7LdJKydmOE6lVZBEEQ1mxTV5nuhI4gtNbxwepV2QRBENYsY6TQDbZ9Lt0+wH/EYXXFtCJyVZaVIAhr9qqusokndBQH6e8Jqj89rXnEwkxz3bXTL1Ob+lZwV0eTslZ2DR6fqq2YeEb9fN/mmKN5DakyHau+zzPpGWlTxwd3XZXtKDNS2NA7PJuuftE5ZvVvo/w6Lt79RRvvl39Z2zdg9BGN7tTU5lGrs60Vtwd9ZG+kaJ22jdaHImmwV3RCOUHo//nwhY/OKhKivQYnVZaAW7X5l3d+3t+r2bRkqs1CLflHl0/qFyZCiB3Y461vjxdYCNJ2OhkqTva5vrpykFfgm7uLbE7aSXkJqmSnX5GrO9o+vAUUv7jxntnlMmultOT9+VpAZcT0HfNHLsVNkmrfEO/oXeVV2xCzN29vjDRmV8amAUHtIlqOS7y32312eHa/JWsm5i9GCCGMg7Fxwo4oJI4C1nc7eaHnTO8MUHtyVS98Z6d8rkU/JXJn3SOYHRvX7Mj2a+1ZlLTupPilEdEvx3DT1u0tFMo8WTaVQnExPoyNYRgzaOqZ/GKNvd5tc2pMrCYdpLirIDFQGsna6fJQeWpmR9opSd8vkIxMJXCL6u6B2eUL3/nVcZdMWiYdMSR9pyaYHV2lbqschcTRuy934yFxttJTvo9zZ7nxDPX9Nf2U7qt7BLNjI5sdGUKZX2HyJfbgEa28Ikb2xK8kZ3NDpCyWNKRFzLaHi+x4ykhJvdtGQyXokA7W1eZHBkorWTudDZXaPDWzYz0ckFV6iC1pOez9+PDMpBuG+tVO36kJZkdXZ/oBXPxUBuEsIpFKHDW2uAnGXZdxo5Uw6Ak7F6OcdDhJ2YR0j2B2bGyzI9uvlQ+SDBzbno9EXcZEMRCSyqQsQeTEWPnSRbvvqM3anOSV0z9M1TptG53X/0lFhkYy6aCAdMCTGSi9ydrpZKjUHH5Py+zoqgOylppx1uufbUnPVpltxpKMhGUrMoP7h3Goy6QjhqTv1ASzo4u2PPyfeRpPVI6wyndpHq6kV/l/Eokjjmds0ceElCNUzg/VfnbAbqV6j4g0ZdPTPYLZsZHNjvaiLX18h+0pxQmCsN77sSMSjD2mIQjCrjq/8tUIMUJI2mXKxksVb5RRtI2e2ZFUJUglHaRroCRrp7Oh4iZmR4RQt03XEyhf5apxrXH93cRFYzp6YQghhnfES5/tzjE5q52OGNIlpyaYHZusKLGp6R7B7Ahmxyc+fgAwOzb4cj+mdM+bIdA9NiZgdgSAp0XjmR3dMFaC7hEAgOcGMDsCAAA8j8AWNQAAABD9AQAAAIj+AAAAAER/AAAAAKI/AAAAANEfAAAAgOgPAAAAQPQHAAAAIPoDAAAAEP0BAAAAiP4AAAAARH8AAAAAoj8AAAAA0R8AAACA6A8AAABA9AcAAAAg+gMAAAAQ/QEAAACI/gAAABD9AQAAAIj+AAAAAER/AAAAAKI/AAAAANEfAAAAgOgPAAAAQPQHAAAAIPoDAAAAEP0BAAAAiP4AAAAARH8AAAAAoj8AAAAA0R8AAACA6A8AAABA9AcAAAAg+gMAAAAQ/QEAAACI/gAAABD9AQAAAIj+AAAAAER/AAAAAKI/AAAA8KzAOnXtPvQCAADA8wamNxPQCwAAAM8bsPIDAAAA0R8AAACA6A8AAABA9AcAAAAg+gMAAAAQ/QEAAACI/gAAAABEfwAAAACiPwAAAADRHwAAAIDoDwAAAED0BwAAACD6AwAAABD9AQAAAIj+AAAAAER/AAAAAKI/AAAAANEfAAAAgOgPAADw/MJy9ktbafqRy4X4owNsgXdou3YdA3mN86VBmHPT028H9h7Skv+wAbgu52jag7aDu7fgYi4VZrqduWvJv2fPlOnsCJN4Rozq9dan7YP5MCQAAIDojxBCDL8uvbp7MxFChN1clnvj/MUMQWz3lnysSZ82ocnd8ubhM0qGb49WHfzw4jNZ1/84tEzNX/5jqACDUQEAAER/hJgcLp9XkcxD0DosK/d6id7eks80l+ZczMzL19oQSxAU3iYqnJuVdq64Zb+BzbiYTXn++IXioB7DIyVM3HDn1LmC8D7RMo5VKb+ckS1XWwm2UNaqXVSYlKXLPnamzMfLnFNk9enQs7e09Er1BBwMNxTe+/e6vMSI+D5BvhaCLJZbSm5dypSXGzEPvxZturfz0l5JP2ONGN7Tj4sQwvU30s6XtOoTLXt0d2AvKshSItQm+pNtUX4sZC+8+evs8/nK4jJzKE917eveR3M79XwlOOv4wVIDVxIZN3z6rGABZpX/7/ivP97MLcMR4gTEdp/yfa9WuksLB6QWduseK7hzIk1t8QqI/nz4+LFeHPgKAQDAvXFlCQe3lMkVKsT34TNxvTz93H17SNdXXho0orcM5VxJzyECAriaQrUVIbv+wQMrMpWV6XFEmJVyg6C5DweZis6fvaf3j3zppUEjewXb714+l28mEMItqhJ+u2Ev9Ormo/63VgK7Xn7mUiGndc9RL0X3C7IUGUijv7HQ4t9v6KCR/Vqw5Bn/5Fo8W/ixyhQlZgIhhOsK80yScN9qAZnVrGXPVgjdTv1qyPaNi8+eypCO/XPSos29mnlUFnn14o2QHjM29GvPUl/74djJPNx2+9y6L6/nCluPmj9weH9GUcrprTtVdoQQQrYLF2/Jot7+rk8kKkr7ZFfCNSsMLAAAmvrcHy88l7rz0Y2AsHlkZLgAGbIUKkGLoaESDwZC0pDOrQsO55RgnXxYimKVzZf3oBwLbOZVWlpmCeWUF+mEgX5cZJLLi1lBA1t68RgISWUdQ+XHc0pNnRDCBGEtvARcZCyuneBBualALWjRSyZkM5CkWet2uaXZJF9hooj2wVI2hiTBkeHyo/JSa7PgEPbF7FKLLJitVhRZvNv413gq4OE3avt473Vnk5Oyz/4uP/t7OmJIe34z+p03vR09EhA5/v2IUA8Ltufs9WO6/FI7q3vfr893UDL5fE3prTIJ45RJe99YEf1RSPdp87uGcFB7niI+7v653aVjOwVyYHABANCUoz/Dt1P3KG8WQojB5PA8WAyEELKbDVaMK+A67hwwjsCDYTXZhUH+WGaB1igptkhbBvroC/NVRm6+lh/cjocRGr0FN8mTD8oflS0wmAkmwtg8FoYQbiZJoNfqrYjDc1SEsYUeJDcrGJMrcszsGVwhB7OarQxRCxknNbfM5C/IKbD7d/KsEYsJk0mt4UbMHRk9D7MUl95Jubpz/pVzXxxq1e+tWDZCCCGxiM+qqJKFEIHjiDCWn1t1dM/O/HIr4nmzcISQvbI4qUTARAghjp9EgJCh0IzDyAIAoIlHf8Ty4IsENZIxODw2UWYw44jNQAgRFr0JZ4s4bFEzL/xKQYlGz28hFYh9WXcUhdkqrizCg4EwDo/NEAQNiQkTMyqWkYwGnMOz5iGEEIYQeQI2KihH5QZTRUWEzUgWVwm71WQjEAtDiLAYrYjL42AMj5AgXm5BYamgEPfp4Vmj/YRyb8KHnxd6DH75241tpf6+HcZ1yfn1Sl6WtqAMRwEVXykMxzeO454BL91x8Ne/in2njF7+Uag089D7b958tGwmVxQaIr1EhD67VIuQp8wDXqQFAMDNebwwhQkCg6X6nCu5ajOOW9SKK3d0Ipm/iMHylEmMOVmlHr5eHKbQX2ovzC5k+QXxGQhhPH+ZjznvSo7KhBN244Or6adT72jsVcokS6DlBDbzMeVeu6+x4HZtwb0barJZNa6+e19rQ4gwld7ONvu08OFhiMEPCONrbtwsQYEyr5rfcZh0cPcoETId3//Z8J1r4xKXv7g1IQsh3/Du4VRfh4TdZEcI2YxWQ07e8Q1ZRoTspsrGqG6snXR4x9IDP84rREjaZ4wPLPsAANDk5/7kXxqCZn172C9mXkq6bsPYwsDQzv1bChkIcTwDvbEHGl8vHoaYIj8vRqEmwFfIqIjuQb17Wi9lXNl/w0pgHC9Z2/4Rniyjskr4J0nAYUl7dTdfuHYhMQP38AqWSZgltb+LWEKJ9tbxZIvVzvAK7dRT5oEhhBg8WQvh1Wvm1l1FzNrt92n93q7hCUvOnTmdd/EOQmy+LLbriHl92ooQoSc9Y6b/G0PG/HMgcfv+xUe8O49qF3L6qvx6kcqGIYRQWOfBwfJjm8rMXgExX700sgMLBhYAAG4OpjcTz+q5WYqvHr4hjI4OlzTYQoxdfnHhgFR5xxd+3BPpxYThBAAARP9GhbCZdAZt7pXrJSE9Bobyawf/KaHfN2gDNud8DGMLAACI/k8bu+rOkVN5Nt9WA7q3kDbklBzm/gAAQPQHAAAAmgzwaiIAAABEfwAAAACiPwAAAADRHwAAAIDoDwAAAED0BwAAACD6AwAAABD9AQAAAIj+AAAAAER/AAAAoHH4P1Uc0MdGDHDoAAAAAElFTkSuQmCC>>++fancyDirectoryConfig :: MonadSnap m => DirectoryConfig m+fancyDirectoryConfig = DirectoryConfig {+ indexFiles = ["index.html", "index.htm"],+ indexGenerator = defaultIndexGenerator defaultMimeTypes snapIndexStyles,+ dynamicHandlers = Map.empty,+ mimeTypes = defaultMimeTypes,+ preServeHook = const $ return $! ()+ }+++------------------------------------------------------------------------------+-- | Serves static files from a directory using the default configuration+-- as given in 'defaultDirectoryConfig'.+serveDirectory :: MonadSnap m+ => FilePath -- ^ Directory to serve from+ -> m ()+serveDirectory = serveDirectoryWith defaultDirectoryConfig+{-# INLINE serveDirectory #-}+++------------------------------------------------------------------------------+-- | Serves static files from a directory. Configuration options are+-- passed in a 'DirectoryConfig' that captures various choices about desired+-- behavior. The relative path given in 'rqPathInfo' is searched for a+-- requested file, and the file is served with the appropriate mime type if it+-- is found. Absolute paths and \"@..@\" are prohibited to prevent files from+-- being served from outside the sandbox.+serveDirectoryWith :: MonadSnap m+ => DirectoryConfig m -- ^ Configuration options+ -> FilePath -- ^ Directory to serve from+ -> m ()+serveDirectoryWith cfg base = do+ b <- directory <|> file <|> redir+ when (not b) pass++ where+ idxs = indexFiles cfg+ generate = indexGenerator cfg+ mimes = mimeTypes cfg+ dyns = dynamicHandlers cfg+ pshook = preServeHook cfg++ -- Serves a file if it exists; passes if not+ serve f = do+ liftIO (doesFileExist f) >>= flip unless pass+ let fname = takeFileName f+ let staticServe f' = pshook f >> serveFileAs (fileType mimes fname) f'+ lookupExt staticServe dyns fname f >> return True++ -- Serves a directory via indices if available. Returns True on success,+ -- False on failure to find an index. Passes /only/ if the request was+ -- not for a directory (no trailing slash).+ directory = do+ rq <- getRequest+ let uri = uriWithoutQueryString rq+ unless ("/" `S.isSuffixOf` uri) pass+ rel <- (base </>) <$> getSafePath+ b <- liftIO $ doesDirectoryExist rel+ if b then do let serveRel f = serve (rel </> f)+ foldl' (<|>) pass (Prelude.map serveRel idxs)+ <|> (generate rel >> return True)+ <|> return False+ else return False++ -- Serves a file requested by name. Passes if the file doesn't exist.+ file = serve =<< ((base </>) <$> getSafePath)++ -- If the request is for a directory but lacks a trailing slash, redirects+ -- to the directory name with a trailing slash.+ redir = do+ rel <- (base </>) <$> getSafePath+ liftIO (doesDirectoryExist rel) >>= flip unless pass+ rq <- getRequest+ let uri = uriWithoutQueryString rq+ let qss = queryStringSuffix rq+ let u = S.concat [uri, "/", qss]+ redirect u+++------------------------------------------------------------------------------+-- | Serves a single file specified by a full or relative path. If the file+-- does not exist, throws an exception (not that it does /not/ pass to the+-- next handler). The path restrictions on 'serveDirectory' don't apply to+-- this function since the path is not being supplied by the user.+serveFile :: MonadSnap m+ => FilePath -- ^ path to file+ -> m ()+serveFile fp = serveFileAs (fileType defaultMimeTypes (takeFileName fp)) fp+{-# INLINE serveFile #-}+++------------------------------------------------------------------------------+-- | Same as 'serveFile', with control over the MIME mapping used.+serveFileAs :: MonadSnap m+ => ByteString -- ^ MIME type+ -> FilePath -- ^ path to file+ -> m ()+serveFileAs mime fp = do+ reqOrig <- getRequest++ -- If-Range header must be ignored if there is no Range: header in the+ -- request (RFC 2616 section 14.27)+ let req = if isNothing $ getHeader "range" reqOrig+ then deleteHeader "if-range" reqOrig+ else reqOrig++ -- check "If-Modified-Since" and "If-Range" headers+ let mbH = getHeader "if-modified-since" req+ mbIfModified <- liftIO $ case mbH of+ Nothing -> return Nothing+ (Just s) -> liftM Just $ parseHttpTime s++ -- If-Range header could contain an entity, but then parseHttpTime will+ -- fail and return 0 which means a 200 response will be generated anyways+ mbIfRange <- liftIO $ case getHeader "if-range" req of+ Nothing -> return Nothing+ (Just s) -> liftM Just $ parseHttpTime s++ dbg $ "mbIfModified: " ++ Prelude.show mbIfModified+ dbg $ "mbIfRange: " ++ Prelude.show mbIfRange++ -- check modification time and bug out early if the file is not modified.+ --+ -- TODO: a stat cache would be nice here, but it'd need the date thread+ -- stuff from snap-server to be folded into snap-core+ filestat <- liftIO $ getFileStatus fp+ let mt = modificationTime filestat+ maybe (return $! ()) (\lt -> when (mt <= lt) notModified) mbIfModified++ let sz = fromIntegral $ fileSize filestat+ lm <- liftIO $ formatHttpTime mt++ -- ok, at this point we know the last-modified time and the+ -- content-type. set those.+ modifyResponse $ setHeader "Last-Modified" lm+ . setHeader "Accept-Ranges" "bytes"+ . setContentType mime+++ -- now check: is this a range request? If there is an 'If-Range' header+ -- with an old modification time we skip this check and send a 200+ -- response+ let skipRangeCheck = maybe (False)+ (\lt -> mt > lt)+ mbIfRange++ -- checkRangeReq checks for a Range: header in the request and sends a+ -- partial response if it matches.+ wasRange <- if skipRangeCheck+ then return False+ else liftSnap $ checkRangeReq req fp sz++ dbg $ "was this a range request? " ++ Prelude.show wasRange++ -- if we didn't have a range request, we just do normal sendfile+ unless wasRange $ do+ modifyResponse $ setResponseCode 200+ . setContentLength sz+ liftSnap $ sendFile fp++ where+ --------------------------------------------------------------------------+ notModified = finishWith $+ setResponseCode 304 emptyResponse+++------------------------------------------------------------------------------+lookupExt :: a -> HashMap FilePath a -> FilePath -> a+lookupExt def m f =+ if null ext+ then def+ else fromMaybe (lookupExt def m (next ext)) mbe++ where+ next = dropWhile (/= '.') . drop 1+ ext = takeExtensions f+ mbe = Map.lookup ext m+++------------------------------------------------------------------------------+-- | Determine a given file's MIME type from its filename and the provided MIME+-- map.+fileType :: MimeMap -> FilePath -> ByteString+fileType = lookupExt defaultMimeType+++------------------------------------------------------------------------------+defaultMimeType :: ByteString+defaultMimeType = "application/octet-stream"+++------------------------------------------------------------------------------+data RangeReq = RangeReq !Word64 !(Maybe Word64)+ | SuffixRangeReq !Word64+++------------------------------------------------------------------------------+rangeParser :: Parser RangeReq+rangeParser = string "bytes=" *>+ (byteRangeSpec <|> suffixByteRangeSpec) <*+ endOfInput+ where+ byteRangeSpec = do+ start <- fromIntegral <$> parseNum+ void $! char '-'+ end <- option Nothing $ liftM Just parseNum++ return $! RangeReq start (fromIntegral <$> end)++ suffixByteRangeSpec =+ liftM (SuffixRangeReq . fromIntegral) $ char '-' *> parseNum+++------------------------------------------------------------------------------+checkRangeReq :: (MonadSnap m) => Request -> FilePath -> Word64 -> m Bool+checkRangeReq req fp sz = do+ -- TODO/FIXME: multiple ranges+ maybe (return False)+ (\s -> either (const $ return False)+ withRange+ (fullyParse s rangeParser))+ (getHeader "range" req)++ where+ withRange (RangeReq start mend) = do+ let end = fromMaybe (sz-1) mend+ dbg $ "withRange: start=" ++ Prelude.show start+ ++ ", end=" ++ Prelude.show end++ if start < 0 || end < start || start >= sz || end >= sz+ then send416+ else send206 start end++ withRange (SuffixRangeReq nbytes) = do+ let end = sz-1+ let start = sz - nbytes++ dbg $ "withRange: start=" ++ Prelude.show start+ ++ ", end=" ++ Prelude.show end++ if start < 0 || end < start || start >= sz || end >= sz+ then send416+ else send206 start end++ -- note: start and end INCLUSIVE here+ send206 start end = do+ dbg "inside send206"+ let !len = end-start+1+ let crng = S.concat . L.toChunks $+ toLazyByteString $+ mconcat [ byteString "bytes "+ , fromShow start+ , char8 '-'+ , fromShow end+ , char8 '/'+ , fromShow sz ]++ modifyResponse $ setResponseCode 206+ . setHeader "Content-Range" crng+ . setContentLength len++ dbg $ "send206: sending range (" ++ Prelude.show start+ ++ "," ++ Prelude.show (end+1) ++ ") to sendFilePartial"++ -- end here was inclusive, sendFilePartial is exclusive+ sendFilePartial fp (start,end+1)+ return True+++ send416 = do+ dbg "inside send416"+ -- if there's an "If-Range" header in the request, then we just send+ -- back 200+ if getHeader "If-Range" req /= Nothing+ then return False+ else do+ let crng = S.concat . L.toChunks $+ toLazyByteString $+ mconcat [ byteString "bytes */"+ , fromShow sz ]++ modifyResponse $ setResponseCode 416+ . setHeader "Content-Range" crng+ . setContentLength 0+ . deleteHeader "Content-Type"+ . deleteHeader "Content-Encoding"+ . deleteHeader "Transfer-Encoding"+ . setResponseBody (return . id)++ return True+++------------------------------------------------------------------------------+dbg :: (MonadIO m) => String -> m ()+dbg s = debug $ "FileServe:" ++ s+++------------------------------------------------------------------------------+uriWithoutQueryString :: Request -> ByteString+uriWithoutQueryString rq = S.takeWhile (/= '?') uri+ where+ uri = rqURI rq+++------------------------------------------------------------------------------+queryStringSuffix :: Request -> ByteString+queryStringSuffix rq = S.concat [ s, qs ]+ where+ qs = rqQueryString rq+ s = if S.null qs then "" else "?"+++------------------------------------------------------------------------------+fromShow :: Show a => a -> Builder+fromShow = stringUtf8 . show
+ src/Snap/Internal/Util/FileUploads.hs view
@@ -0,0 +1,1125 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}++module Snap.Internal.Util.FileUploads+ ( -- * Functions+ handleFormUploads+ , foldMultipart+ , PartFold+ , FormParam+ , FormFile (..)+ , storeAsLazyByteString+ , withTemporaryStore+ -- ** Backwards compatible API+ , handleFileUploads+ , handleMultipart+ , PartProcessor++ -- * Uploaded parts+ , PartInfo(..)+ , PartDisposition(..)+ , toPartDisposition++ -- ** Policy+ -- *** General upload policy+ , UploadPolicy(..)+ , defaultUploadPolicy+ , doProcessFormInputs+ , setProcessFormInputs+ , getMaximumFormInputSize+ , setMaximumFormInputSize+ , getMaximumNumberOfFormInputs+ , setMaximumNumberOfFormInputs+ , getMinimumUploadRate+ , setMinimumUploadRate+ , getMinimumUploadSeconds+ , setMinimumUploadSeconds+ , getUploadTimeout+ , setUploadTimeout++ -- *** File upload policy+ , FileUploadPolicy(..)+ , defaultFileUploadPolicy+ , setMaximumFileSize+ , setMaximumNumberOfFiles+ , setSkipFilesWithoutNames+ , setMaximumSkippedFileSize++ -- *** Per-file upload policy+ , PartUploadPolicy(..)+ , disallow+ , allowWithMaximumSize++ -- * Exceptions+ , FileUploadException(..)+ , fileUploadExceptionReason+ , BadPartException(..)+ , PolicyViolationException(..)+ ) where++------------------------------------------------------------------------------+import Control.Applicative (Alternative ((<|>)), Applicative (pure, (*>), (<*)))+import Control.Arrow (Arrow (first))+import Control.Exception.Lifted (Exception, SomeException (..), bracket, catch, finally, fromException, mask, throwIO, toException)+import qualified Control.Exception.Lifted as E (try)+import Control.Monad (Functor (fmap), Monad (return, (>>=)), MonadPlus (mzero), forM_, guard, liftM, sequence, unless, void, when, (>=>))+import Control.Monad.IO.Class (liftIO)+import Data.Attoparsec.ByteString.Char8 (Parser, isEndOfLine, string, takeWhile)+import qualified Data.Attoparsec.ByteString.Char8 as Atto (try)+import Data.ByteString.Char8 (ByteString)+import qualified Data.ByteString.Char8 as S+import Data.ByteString.Internal (c2w)+import qualified Data.ByteString.Lazy.Internal as LB (ByteString (Empty), chunk)+import qualified Data.CaseInsensitive as CI (mk)+import Data.Int (Int, Int64)+import qualified Data.IORef as IORef+import Data.List (find, map, (++))+import qualified Data.Map as Map (insertWith)+import Data.Maybe (Maybe (..), fromMaybe, isJust, maybe)+import Data.Text (Text)+import qualified Data.Text as T (concat, pack, unpack)+import qualified Data.Text.Encoding as TE (decodeUtf8)+import Data.Typeable (Typeable, cast)+import Prelude (Bool (..), Double, Either (..), Eq (..), FilePath, IO, Ord (..), Show (..), String, const, either, foldr, fst, id, max, not, otherwise, seq, snd, succ, ($), ($!), (.), (^), (||))+import Snap.Core (HasHeaders (headers), Headers, MonadSnap, Request (rqParams, rqPostParams), getHeader, getRequest, getTimeoutModifier, putRequest, runRequestBody)+import Snap.Internal.Parsing (crlf, fullyParse, pContentTypeWithParameters, pHeaders, pValueWithParameters')+import qualified Snap.Types.Headers as H (fromList)+import System.Directory (removeFile)+import System.FilePath ((</>))+import System.IO (BufferMode (NoBuffering), Handle, hClose, hSetBuffering, openBinaryTempFile)+import System.IO.Error (isDoesNotExistError)+import System.IO.Streams (InputStream, MatchInfo (..), TooManyBytesReadException, search)+import qualified System.IO.Streams as Streams+import System.IO.Streams.Attoparsec (parseFromStream)+import System.PosixCompat.Temp (mkstemp)+------------------------------------------------------------------------------++------------------------------------------------------------------------------+-- | Reads uploaded files into a temporary directory and calls a user handler+-- to process them.+--+-- Note: /THE REQUEST MUST BE CORRECTLY ENCODED/. If the request's+-- @Content-type@ is not \"@multipart/formdata@\", this function skips+-- processing using 'pass'.+--+-- Given a temporary directory, global and file-specific upload policies, and a+-- user handler, this function consumes a request body uploaded with+-- @Content-type: multipart/form-data@. Each file is read into the temporary+-- directory, and is then passed to the user handler. After the user handler+-- runs (but before the 'Response' body is streamed to the client), the files+-- are deleted from disk; so if you want to retain or use the uploaded files in+-- the generated response, you need to move or otherwise process them.+--+-- The argument passed to the user handler is a tuple:+--+-- > (PartInfo, Either PolicyViolationException FilePath)+--+-- The first half of this tuple is a 'PartInfo', which contains the+-- information the client browser sent about the given upload part (like+-- filename, content-type, etc). The second half of this tuple is an 'Either'+-- stipulating that either:+--+-- 1. the file was rejected on a policy basis because of the provided+-- 'PartUploadPolicy' handler+--+-- 2. the file was accepted and exists at the given path.+--+-- /Exceptions/+--+-- If the client's upload rate passes below the configured minimum (see+-- 'setMinimumUploadRate' and 'setMinimumUploadSeconds'), this function+-- terminates the connection. This setting is there to protect the server+-- against slowloris-style denial of service attacks.+--+-- If the given 'UploadPolicy' stipulates that you wish form inputs to be+-- placed in the 'rqParams' parameter map (using 'setProcessFormInputs'), and+-- a form input exceeds the maximum allowable size, this function will throw a+-- 'PolicyViolationException'.+--+-- If an uploaded part contains MIME headers longer than a fixed internal+-- threshold (currently 32KB), this function will throw a 'BadPartException'.++handleFileUploads ::+ (MonadSnap m) =>+ FilePath -- ^ temporary directory+ -> UploadPolicy -- ^ general upload policy+ -> (PartInfo -> PartUploadPolicy) -- ^ per-part upload policy+ -> (PartInfo -> Either PolicyViolationException FilePath -> IO a)+ -- ^ user handler (see function+ -- description)+ -> m [a]+handleFileUploads tmpdir uploadPolicy partPolicy partHandler =+ handleMultipart uploadPolicy go++ where+ go partInfo stream = maybe disallowed takeIt mbFs+ where+ ctText = partContentType partInfo+ fnText = fromMaybe "" $ partFileName partInfo++ ct = TE.decodeUtf8 ctText+ fn = TE.decodeUtf8 fnText++ (PartUploadPolicy mbFs) = partPolicy partInfo++ takeIt maxSize = do+ str' <- Streams.throwIfProducesMoreThan maxSize stream+ fileReader tmpdir partHandler partInfo str' `catch` tooMany maxSize++ tooMany maxSize (_ :: TooManyBytesReadException) =+ partHandler partInfo+ (Left $+ PolicyViolationException $+ T.concat [ "File \""+ , fn+ , "\" exceeded maximum allowable size "+ , T.pack $ show maxSize ])++ disallowed =+ partHandler partInfo+ (Left $+ PolicyViolationException $+ T.concat [ "Policy disallowed upload of file \""+ , fn+ , "\" with content-type \""+ , ct+ , "\"" ] )+++------------------------------------------------------------------------------+-- | Contents of form field of type @file@+data FormFile a = FormFile+ { formFileName :: !ByteString+ -- ^ Name of a field+ , formFileValue :: a+ -- ^ Result of storing file+ } deriving (Eq, Ord, Show)++data UploadState a = UploadState+ { numUploadedFiles :: !Int+ , uploadedFiles :: !([FormFile a] -> [FormFile a])+ }++-- | Processes form data and calls provided storage function on+-- file parts.+--+-- You can use this together with 'withTemporaryStore', 'storeAsLazyByteString'+-- or provide your own callback to store uploaded files.+--+-- If you need to process uploaded file mime type or file name, do it in the+-- store callback function.+--+-- See also 'foldMultipart'.+--+-- Example using with small files which can safely be stored in memory.+--+-- @+--+-- import qualified Data.ByteString.Lazy as Lazy+--+-- handleSmallFiles :: MonadSnap m => [(ByteString, ByteString, Lazy.ByteString)]+-- handleSmallFiles = handleFormUploads uploadPolicy filePolicy store+--+-- where+-- uploadPolicy = defaultUploadPolicy+-- filePolicy = setMaximumFileSize (64*1024)+-- $ setMaximumNumberOfFiles 5+-- defaultUploadPolicy+-- store partInfo stream = do+-- content <- storeAsLazyByteString partInfo stream+-- let+-- fileName = partFileName partInfo+-- fileMime = partContentType partInfo+-- in (fileName, fileMime, content)+-- @+--+handleFormUploads ::+ (MonadSnap m) =>+ UploadPolicy -- ^ general upload policy+ -> FileUploadPolicy -- ^ Upload policy for files+ -> (PartInfo -> InputStream ByteString -> IO a)+ -- ^ A file storage function+ -> m ([FormParam], [FormFile a])+handleFormUploads uploadPolicy filePolicy partHandler = do+ (params, !st) <- foldMultipart uploadPolicy go (UploadState 0 id)+ return (params, uploadedFiles st [])+ where+ go !partInfo stream !st = do+ when (numUploads >= maxFiles) throwTooManyFiles++ case partFileName partInfo of+ Nothing -> onEmptyName+ Just _ -> takeIt++ where+ numUploads = numUploadedFiles st+ files = uploadedFiles st+ maxFiles = maxNumberOfFiles filePolicy+ maxFileSize = maxFileUploadSize filePolicy+ fnText = fromMaybe "" $ partFileName partInfo++ fn = TE.decodeUtf8 fnText++ takeIt = do+ str' <- Streams.throwIfProducesMoreThan maxFileSize stream+ r <- partHandler partInfo str' `catch` tooMany maxFileSize+ let f = FormFile (partFieldName partInfo) r+ return $! UploadState (succ numUploads) (files . ([f] ++) )++ skipIt maxSize = do+ str' <- Streams.throwIfProducesMoreThan maxSize stream+ !_ <- Streams.skipToEof str' `catch` tooMany maxSize+ return $! UploadState (succ numUploads) files++ onEmptyName = if skipEmptyFileName filePolicy+ then skipIt (maxEmptyFileNameSize filePolicy)+ else takeIt+++ throwTooManyFiles = throwIO . PolicyViolationException $ T.concat+ ["number of files exceeded the maximum of "+ ,T.pack (show maxFiles) ]++ tooMany maxSize (_ :: TooManyBytesReadException) =+ throwIO . PolicyViolationException $+ T.concat [ "File \""+ , fn+ , "\" exceeded maximum allowable size "+ , T.pack $ show maxSize ]+++------------------------------------------------------------------------------+-- | A type alias for a function that will process one of the parts of a+-- @multipart/form-data@ HTTP request body with accumulator.+type PartFold a = PartInfo -> InputStream ByteString -> a -> IO a+++------------------------------------------------------------------------------+-- | Given an upload policy and a function to consume uploaded \"parts\",+-- consume a request body uploaded with @Content-type: multipart/form-data@.+--+-- If 'setProcessFormInputs' is 'True', then parts with disposition @form-data@+-- (a form parameter) will be processed and returned as first element of+-- resulting pair. Parts with other disposition will be fed to 'PartFold'+-- handler.+--+-- If 'setProcessFormInputs' is 'False', then parts with any disposition will+-- be fed to 'PartFold' handler and first element of returned pair will be+-- empty. In this case it is important that you limit number of form inputs+-- and sizes of inputs in your 'PartFold' handler to avoid common DOS attacks.+--+-- Note: /THE REQUEST MUST BE CORRECTLY ENCODED/. If the request's+-- @Content-type@ is not \"@multipart/formdata@\", this function skips+-- processing using 'pass'.+--+-- Most users will opt for the higher-level 'handleFileUploads', which writes+-- to temporary files, rather than 'handleMultipart'. This function should be+-- chosen, however, if you need to stream uploaded files directly to your own+-- processing function: e.g. to a database or a remote service via RPC.+--+-- If the client's upload rate passes below the configured minimum (see+-- 'setMinimumUploadRate' and 'setMinimumUploadSeconds'), this function+-- terminates the connection. This setting is there to protect the server+-- against slowloris-style denial of service attacks.+--+-- /Exceptions/+--+-- If the given 'UploadPolicy' stipulates that you wish form inputs to be+-- processed (using 'setProcessFormInputs'), and a form input exceeds the+-- maximum allowable size or the form exceeds maximum number of inputs, this+-- function will throw a 'PolicyViolationException'.+--+-- If an uploaded part contains MIME headers longer than a fixed internal+-- threshold (currently 32KB), this function will throw a 'BadPartException'.+--+-- /Since: 1.0.3.0/+foldMultipart ::+ (MonadSnap m) =>+ UploadPolicy -- ^ global upload policy+ -> PartFold a -- ^ part processor+ -> a -- ^ seed accumulator+ -> m ([FormParam], a)+foldMultipart uploadPolicy origPartHandler zero = do+ hdrs <- liftM headers getRequest+ let (ct, mbBoundary) = getContentType hdrs++ tickleTimeout <- liftM (. max) getTimeoutModifier+ let bumpTimeout = tickleTimeout $ uploadTimeout uploadPolicy++ let partHandler = if doProcessFormInputs uploadPolicy+ then captureVariableOrReadFile+ (getMaximumFormInputSize uploadPolicy)+ origPartHandler+ else \x y acc -> liftM File $ origPartHandler x y acc++ -- not well-formed multipart? bomb out.+ guard (ct == "multipart/form-data")++ boundary <- maybe (throwIO $ BadPartException+ "got multipart/form-data without boundary")+ return+ mbBoundary++ -- RateTooSlowException will be caught and properly dealt with by+ -- runRequestBody+ runRequestBody (proc bumpTimeout boundary partHandler)++ where+ --------------------------------------------------------------------------+ uploadRate = minimumUploadRate uploadPolicy+ uploadSecs = minimumUploadSeconds uploadPolicy+ maxFormVars = maximumNumberOfFormInputs uploadPolicy++ --------------------------------------------------------------------------+ proc bumpTimeout boundary partHandler =+ Streams.throwIfTooSlow bumpTimeout uploadRate uploadSecs >=>+ internalFoldMultipart maxFormVars boundary partHandler zero++------------------------------------------------------------------------------+-- | A type alias for a function that will process one of the parts of a+-- @multipart/form-data@ HTTP request body without usinc accumulator.+type PartProcessor a = PartInfo -> InputStream ByteString -> IO a+++------------------------------------------------------------------------------+-- | A variant of 'foldMultipart' accumulating results into a list.+-- Also puts captured 'FormParam's into rqPostParams and rqParams maps.+--+handleMultipart ::+ (MonadSnap m) =>+ UploadPolicy -- ^ global upload policy+ -> PartProcessor a -- ^ part processor+ -> m [a]+handleMultipart uploadPolicy origPartHandler = do+ (captures, files) <- foldMultipart uploadPolicy partFold id+ procCaptures captures+ return $! files []++ where+ partFold info input acc = do+ x <- origPartHandler info input+ return $ acc . ([x]++)+ --------------------------------------------------------------------------+ procCaptures [] = pure ()+ procCaptures params = do+ rq <- getRequest+ putRequest $ modifyParams (\m -> foldr ins m params) rq++ --------------------------------------------------------------------------+ ins (!k, !v) = Map.insertWith (\_ ex -> (v:ex)) k [v]+ -- prepend value if key exists, since we are folding from right++ --------------------------------------------------------------------------+ modifyParams f r = r { rqPostParams = f $ rqPostParams r+ , rqParams = f $ rqParams r+ }++------------------------------------------------------------------------------+-- | Represents the disposition type specified via the @Content-Disposition@+-- header field. See <https://www.ietf.org/rfc/rfc1806.txt RFC 1806>.+data PartDisposition =+ DispositionAttachment -- ^ @Content-Disposition: attachment@.+ | DispositionFile -- ^ @Content-Disposition: file@.+ | DispositionFormData -- ^ @Content-Disposition: form-data@.+ | DispositionOther ByteString -- ^ Any other value.+ deriving (Eq, Show)+++------------------------------------------------------------------------------+-- | 'PartInfo' contains information about a \"part\" in a request uploaded+-- with @Content-type: multipart/form-data@.+data PartInfo =+ PartInfo+ { partFieldName :: !ByteString+ -- ^ Field name associated with this part (i.e., the name specified with+ -- @\<input name=\"partFieldName\" ...@).+ , partFileName :: !(Maybe ByteString)+ -- ^ Name of the uploaded file.+ , partContentType :: !ByteString+ -- ^ Content type of this part.+ , partDisposition :: !PartDisposition+ -- ^ Disposition type of this part. See 'PartDisposition'.+ , partHeaders :: !Headers+ -- ^ Remaining headers associated with this part.+ }+ deriving (Show)+++------------------------------------------------------------------------------+toPartDisposition :: ByteString -> PartDisposition+toPartDisposition s | s == "attachment" = DispositionAttachment+ | s == "file" = DispositionFile+ | s == "form-data" = DispositionFormData+ | otherwise = DispositionOther s+++------------------------------------------------------------------------------+-- | All of the exceptions defined in this package inherit from+-- 'FileUploadException', so if you write+--+-- > foo `catch` \(e :: FileUploadException) -> ...+--+-- you can catch a 'BadPartException', a 'PolicyViolationException', etc.+data FileUploadException = forall e . (ExceptionWithReason e, Show e) =>+ WrappedFileUploadException e+ deriving (Typeable)+++------------------------------------------------------------------------------+class Exception e => ExceptionWithReason e where+ exceptionReason :: e -> Text+++------------------------------------------------------------------------------+instance Show FileUploadException where+ show (WrappedFileUploadException e) = show e+++------------------------------------------------------------------------------+instance Exception FileUploadException+++------------------------------------------------------------------------------+-- | Human-readable error message corresponding to the 'FileUploadException'.+fileUploadExceptionReason :: FileUploadException -> Text+fileUploadExceptionReason (WrappedFileUploadException e) = exceptionReason e+++------------------------------------------------------------------------------+uploadExceptionToException :: ExceptionWithReason e => e -> SomeException+uploadExceptionToException = toException . WrappedFileUploadException+++------------------------------------------------------------------------------+uploadExceptionFromException :: ExceptionWithReason e => SomeException -> Maybe e+uploadExceptionFromException x = do+ WrappedFileUploadException e <- fromException x+ cast e+++------------------------------------------------------------------------------+-- | Thrown when a part is invalid in some way (e.g. the headers are too large).+data BadPartException = BadPartException {+ -- | Human-readable error message corresponding to the 'BadPartException'.+ badPartExceptionReason :: Text+ }+ deriving (Typeable)++instance Exception BadPartException where+ toException = uploadExceptionToException+ fromException = uploadExceptionFromException++instance ExceptionWithReason BadPartException where+ exceptionReason (BadPartException e) = T.concat ["Bad part: ", e]++instance Show BadPartException where+ show = T.unpack . exceptionReason+++------------------------------------------------------------------------------+-- | Thrown when an 'UploadPolicy' or 'PartUploadPolicy' is violated.+data PolicyViolationException = PolicyViolationException {+ -- | Human-readable error message corresponding to the+ -- 'PolicyViolationException'.+ policyViolationExceptionReason :: Text+ } deriving (Typeable)++instance Exception PolicyViolationException where+ toException e@(PolicyViolationException _) =+ uploadExceptionToException e+ fromException = uploadExceptionFromException++instance ExceptionWithReason PolicyViolationException where+ exceptionReason (PolicyViolationException r) =+ T.concat ["File upload policy violation: ", r]++instance Show PolicyViolationException where+ show (PolicyViolationException s) = "File upload policy violation: "+ ++ T.unpack s+++------------------------------------------------------------------------------+-- | 'UploadPolicy' controls overall policy decisions relating to+-- @multipart/form-data@ uploads, specifically:+--+-- * whether to treat parts without filenames as form input (reading them into+-- the 'rqParams' map)+--+-- * because form input is read into memory, the maximum size of a form input+-- read in this manner, and the maximum number of form inputs+--+-- * the minimum upload rate a client must maintain before we kill the+-- connection; if very low-bitrate uploads were allowed then a Snap server+-- would be vulnerable to a trivial denial-of-service using a+-- \"slowloris\"-type attack+--+-- * the minimum number of seconds which must elapse before we start killing+-- uploads for having too low an upload rate.+--+-- * the amount of time we should wait before timing out the connection+-- whenever we receive input from the client.+data UploadPolicy = UploadPolicy {+ processFormInputs :: Bool+ , maximumFormInputSize :: Int64+ , maximumNumberOfFormInputs :: Int+ , minimumUploadRate :: Double+ , minimumUploadSeconds :: Int+ , uploadTimeout :: Int+}+++------------------------------------------------------------------------------+-- | A reasonable set of defaults for upload policy. The default policy is:+--+-- [@maximum form input size@] 128kB+--+-- [@maximum number of form inputs@] 10+--+-- [@minimum upload rate@] 1kB/s+--+-- [@seconds before rate limiting kicks in@] 10+--+-- [@inactivity timeout@] 20 seconds+--+defaultUploadPolicy :: UploadPolicy+defaultUploadPolicy = UploadPolicy True maxSize maxNum minRate minSeconds tout+ where+ maxSize = 2^(17::Int)+ maxNum = 10+ minRate = 1000+ minSeconds = 10+ tout = 20+++------------------------------------------------------------------------------+-- | Does this upload policy stipulate that we want to treat parts without+-- filenames as form input?+doProcessFormInputs :: UploadPolicy -> Bool+doProcessFormInputs = processFormInputs+++------------------------------------------------------------------------------+-- | Set the upload policy for treating parts without filenames as form input.+setProcessFormInputs :: Bool -> UploadPolicy -> UploadPolicy+setProcessFormInputs b u = u { processFormInputs = b }+++------------------------------------------------------------------------------+-- | Get the maximum size of a form input which will be read into our+-- 'rqParams' map.+getMaximumFormInputSize :: UploadPolicy -> Int64+getMaximumFormInputSize = maximumFormInputSize+++------------------------------------------------------------------------------+-- | Set the maximum size of a form input which will be read into our+-- 'rqParams' map.+setMaximumFormInputSize :: Int64 -> UploadPolicy -> UploadPolicy+setMaximumFormInputSize s u = u { maximumFormInputSize = s }+++------------------------------------------------------------------------------+-- | Get the maximum size of a form input which will be read into our+-- 'rqParams' map.+getMaximumNumberOfFormInputs :: UploadPolicy -> Int+getMaximumNumberOfFormInputs = maximumNumberOfFormInputs+++------------------------------------------------------------------------------+-- | Set the maximum size of a form input which will be read into our+-- 'rqParams' map.+setMaximumNumberOfFormInputs :: Int -> UploadPolicy -> UploadPolicy+setMaximumNumberOfFormInputs s u = u { maximumNumberOfFormInputs = s }+++------------------------------------------------------------------------------+-- | Get the minimum rate (in /bytes\/second/) a client must maintain before+-- we kill the connection.+getMinimumUploadRate :: UploadPolicy -> Double+getMinimumUploadRate = minimumUploadRate+++------------------------------------------------------------------------------+-- | Set the minimum rate (in /bytes\/second/) a client must maintain before+-- we kill the connection.+setMinimumUploadRate :: Double -> UploadPolicy -> UploadPolicy+setMinimumUploadRate s u = u { minimumUploadRate = s }+++------------------------------------------------------------------------------+-- | Get the amount of time which must elapse before we begin enforcing the+-- upload rate minimum+getMinimumUploadSeconds :: UploadPolicy -> Int+getMinimumUploadSeconds = minimumUploadSeconds+++------------------------------------------------------------------------------+-- | Set the amount of time which must elapse before we begin enforcing the+-- upload rate minimum+setMinimumUploadSeconds :: Int -> UploadPolicy -> UploadPolicy+setMinimumUploadSeconds s u = u { minimumUploadSeconds = s }+++------------------------------------------------------------------------------+-- | Get the \"upload timeout\". Whenever input is received from the client,+-- the connection timeout is set this many seconds in the future.+getUploadTimeout :: UploadPolicy -> Int+getUploadTimeout = uploadTimeout+++------------------------------------------------------------------------------+-- | Set the upload timeout.+setUploadTimeout :: Int -> UploadPolicy -> UploadPolicy+setUploadTimeout s u = u { uploadTimeout = s }+++------------------------------------------------------------------------------++-- | File upload policy, if any policy is violated then+-- 'PolicyViolationException' is thrown+data FileUploadPolicy = FileUploadPolicy+ { maxFileUploadSize :: !Int64+ , maxNumberOfFiles :: !Int+ , skipEmptyFileName :: !Bool+ , maxEmptyFileNameSize :: !Int64+ }++-- | A default 'FileUploadPolicy'+--+-- [@maximum file size@] 1MB+--+-- [@maximum number of files@] 10+--+-- [@skip files without name@] yes+--+-- [@maximum size of skipped file@] 0+--+--+defaultFileUploadPolicy :: FileUploadPolicy+defaultFileUploadPolicy = FileUploadPolicy maxFileSize maxFiles+ skipEmptyName maxEmptySize+ where+ maxFileSize = 1048576 -- 1MB+ maxFiles = 10+ skipEmptyName = True+ maxEmptySize = 0++-- | Maximum size of single uploaded file.+setMaximumFileSize :: Int64 -> FileUploadPolicy -> FileUploadPolicy+setMaximumFileSize maxSize s =+ s { maxFileUploadSize = maxSize }++-- | Maximum number of uploaded files.+setMaximumNumberOfFiles :: Int -> FileUploadPolicy -> FileUploadPolicy+setMaximumNumberOfFiles maxFiles s =+ s { maxNumberOfFiles = maxFiles }++-- | Skip files with empty file names.+--+-- If set, parts without filenames will not be fed to storage function.+--+-- HTML5 form data encoding standard states that form input fields of type+-- file, without value set, are encoded same way as if file with empty body,+-- empty file name, and type @application/octet-stream@ was set as value.+--+-- You most likely want to use this with zero bytes allowed to avoid storing+-- such fields (see 'setMaximumSkippedFileSize').+--+-- By default files without names are skipped.+--+-- /Since: 1.0.3.0/+setSkipFilesWithoutNames :: Bool -> FileUploadPolicy -> FileUploadPolicy+setSkipFilesWithoutNames shouldSkip s =+ s { skipEmptyFileName = shouldSkip }++-- | Maximum size of file without name which can be skipped.+--+-- Ignored if 'setSkipFilesWithoutNames' is @False@.+--+-- If skipped file is larger than this setting then 'FileUploadException'+-- is thrown.+--+-- By default maximum file size is 0.+--+-- /Since: 1.0.3.0/+setMaximumSkippedFileSize :: Int64 -> FileUploadPolicy -> FileUploadPolicy+setMaximumSkippedFileSize maxSize s =+ s { maxEmptyFileNameSize = maxSize }+++------------------------------------------------------------------------------+-- | Upload policy can be set on an \"general\" basis (using 'UploadPolicy'),+-- but handlers can also make policy decisions on individual files\/parts+-- uploaded. For each part uploaded, handlers can decide:+--+-- * whether to allow the file upload at all+--+-- * the maximum size of uploaded files, if allowed+data PartUploadPolicy = PartUploadPolicy (Maybe Int64)+++------------------------------------------------------------------------------+-- | Disallows the file to be uploaded.+disallow :: PartUploadPolicy+disallow = PartUploadPolicy Nothing+++------------------------------------------------------------------------------+-- | Allows the file to be uploaded, with maximum size /n/ in bytes.+allowWithMaximumSize :: Int64 -> PartUploadPolicy+allowWithMaximumSize = PartUploadPolicy . Just+++------------------------------------------------------------------------------+-- | Stores file body in memory as Lazy ByteString.+storeAsLazyByteString :: InputStream ByteString -> IO LB.ByteString+storeAsLazyByteString !str = do+ f <- Streams.fold (\f c -> f . LB.chunk c) id str+ return $! f LB.Empty+++------------------------------------------------------------------------------+-- | Store files in a temporary directory, and clean up on function exit.+--+-- Files are safe to move until function exists.+--+-- If asynchronous exception is thrown during cleanup, temporary files may+-- remain.+--+-- @+-- uploadsHandler = withTemporaryStore "/var/tmp" "upload-" $ \store -> do+-- (inputs, files) <- handleFormUploads defaultUploadpolicy+-- defaultFileUploadPolicy+-- (const store)+-- saveFiles files+--+-- @+--+withTemporaryStore ::+ MonadSnap m+ => FilePath -- ^ temporary directory+ -> String -- ^ file name pattern+ -> ((InputStream ByteString -> IO FilePath) -> m a)+ -- ^ Action taking store function+ -> m a+withTemporaryStore tempdir pat act = do+ ioref <- liftIO $ IORef.newIORef []+ let+ modifyIORef' ref f = do -- ghc 7.4 does not have modifyIORef'+ x <- IORef.readIORef ref+ let x' = f x+ x' `seq` IORef.writeIORef ref x'++ go input = do+ (fn, h) <- openBinaryTempFile tempdir pat+ modifyIORef' ioref (fn:)+ hSetBuffering h NoBuffering+ output <- Streams.handleToOutputStream h+ Streams.connect input output+ hClose h+ pure fn++ cleanup = liftIO $ do+ files <- IORef.readIORef ioref+ forM_ files $ \fn ->+ removeFile fn `catch` handleExists+ handleExists e = unless (isDoesNotExistError e) $ throwIO e++ act go `finally` cleanup+++------------------------------------------------------------------------------+-- private exports follow. FIXME: organize+------------------------------------------------------------------------------++------------------------------------------------------------------------------+captureVariableOrReadFile ::+ Int64 -- ^ maximum size of form input+ -> PartFold a -- ^ file reading code+ -> PartInfo -> InputStream ByteString+ -> a+ -> IO (Capture a)+captureVariableOrReadFile maxSize fileHandler partInfo stream acc =+ if isFile+ then liftM File $ fileHandler partInfo stream acc+ else variable `catch` handler++ where+ isFile = isJust (partFileName partInfo) ||+ partDisposition partInfo == DispositionFile++ variable = do+ !x <- liftM S.concat $+ Streams.throwIfProducesMoreThan maxSize stream >>= Streams.toList+ return $! Capture fieldName x++ fieldName = partFieldName partInfo++ handler (_ :: TooManyBytesReadException) =+ throwIO $ PolicyViolationException $+ T.concat [ "form input '"+ , TE.decodeUtf8 fieldName+ , "' exceeded maximum permissible size ("+ , T.pack $ show maxSize+ , " bytes)" ]+++------------------------------------------------------------------------------+data Capture a = Capture !ByteString !ByteString+ | File a+++------------------------------------------------------------------------------+fileReader :: FilePath+ -> (PartInfo -> Either PolicyViolationException FilePath -> IO a)+ -> PartProcessor a+fileReader tmpdir partProc partInfo input =+ withTempFile tmpdir "snap-upload-" $ \(fn, h) -> do+ hSetBuffering h NoBuffering+ output <- Streams.handleToOutputStream h+ Streams.connect input output+ hClose h+ partProc partInfo $ Right fn+++------------------------------------------------------------------------------+data MultipartState a = MultipartState+ { numFormVars :: {-# UNPACK #-} !Int+ , numFormFiles :: {-# UNPACK #-} !Int+ , capturedFields :: !([FormParam] -> [FormParam])+ , accumulator :: !a+ }++------------------------------------------------------------------------------+-- | A form parameter name-value pair+type FormParam = (ByteString, ByteString)++------------------------------------------------------------------------------+addCapture :: ByteString -> ByteString -> MultipartState a -> MultipartState a+addCapture !k !v !ms =+ let !kv = (k,v)+ f = capturedFields ms . ([kv]++)+ !ms' = ms { capturedFields = f+ , numFormVars = succ (numFormVars ms) }+ in ms'+++------------------------------------------------------------------------------+internalFoldMultipart ::+ Int -- ^ max num fields+ -> ByteString -- ^ boundary value+ -> (PartInfo -> InputStream ByteString -> a -> IO (Capture a)) -- ^ part processor+ -> a+ -> InputStream ByteString+ -> IO ([FormParam], a)+internalFoldMultipart !maxFormVars !boundary clientHandler !zeroAcc !stream = go+ where+ --------------------------------------------------------------------------+ initialState = MultipartState 0 0 id zeroAcc++ --------------------------------------------------------------------------+ go = do+ -- swallow the first boundary+ _ <- parseFromStream (parseFirstBoundary boundary) stream+ bmstream <- search (fullBoundary boundary) stream+ ms <- foldParts goPart bmstream initialState+ return $ (capturedFields ms [], accumulator ms)++ --------------------------------------------------------------------------+ pBoundary !b = Atto.try $ do+ _ <- string "--"+ string b++ --------------------------------------------------------------------------+ fullBoundary !b = S.concat ["\r\n", "--", b]+ pLine = takeWhile (not . isEndOfLine . c2w) <* eol+ parseFirstBoundary !b = pBoundary b <|> (pLine *> parseFirstBoundary b)+++ --------------------------------------------------------------------------+ takeHeaders !str = hdrs `catch` handler+ where+ hdrs = do+ str' <- Streams.throwIfProducesMoreThan mAX_HDRS_SIZE str+ liftM toHeaders $ parseFromStream pHeadersWithSeparator str'++ handler (_ :: TooManyBytesReadException) =+ throwIO $ BadPartException "headers exceeded maximum size"++ --------------------------------------------------------------------------+ goPart !str !state = do+ hdrs <- takeHeaders str++ -- are we using mixed?+ let (contentType, mboundary) = getContentType hdrs+ let (fieldName, fileName, disposition) = getFieldHeaderInfo hdrs++ if contentType == "multipart/mixed"+ then maybe (throwIO $ BadPartException $+ "got multipart/mixed without boundary")+ (processMixed fieldName str state)+ mboundary+ else do+ let info = PartInfo fieldName fileName contentType disposition hdrs+ handlePart info str state++ --------------------------------------------------------------------------+ handlePart !info !str !ms = do+ r <- clientHandler info str (accumulator ms)+ case r of+ Capture !k !v -> do+ when (maxFormVars <= numFormVars ms) throwTooMuchVars+ return $! addCapture k v ms+ File !newAcc -> return $! ms { accumulator = newAcc+ , numFormFiles = succ (numFormFiles ms)+ }++ throwTooMuchVars =+ throwIO . PolicyViolationException+ $ T.concat [ "number of form inputs exceeded maximum of "+ , T.pack $ show maxFormVars ]++ --------------------------------------------------------------------------+ processMixed !fieldName !str !state !mixedBoundary = do+ -- swallow the first boundary+ _ <- parseFromStream (parseFirstBoundary mixedBoundary) str+ bm <- search (fullBoundary mixedBoundary) str+ foldParts (mixedStream fieldName) bm state+++ --------------------------------------------------------------------------+ mixedStream !fieldName !str !acc = do+ hdrs <- takeHeaders str++ let (contentType, _) = getContentType hdrs+ let (_, fileName, disposition) = getFieldHeaderInfo hdrs++ let info = PartInfo fieldName fileName contentType disposition hdrs+ handlePart info str acc+++------------------------------------------------------------------------------+getContentType :: Headers+ -> (ByteString, Maybe ByteString)+getContentType hdrs = (contentType, boundary)+ where+ contentTypeValue = fromMaybe "text/plain" $+ getHeader "content-type" hdrs++ eCT = fullyParse contentTypeValue pContentTypeWithParameters+ (!contentType, !params) = either (const ("text/plain", [])) id eCT++ boundary = findParam "boundary" params+++------------------------------------------------------------------------------+getFieldHeaderInfo :: Headers -> (ByteString, Maybe ByteString, PartDisposition)+getFieldHeaderInfo hdrs = (fieldName, fileName, disposition)+ where+ contentDispositionValue = fromMaybe "unknown" $+ getHeader "content-disposition" hdrs++ eDisposition = fullyParse contentDispositionValue $ pValueWithParameters' (const True)++ (!dispositionType, dispositionParameters) =+ either (const ("unknown", [])) id eDisposition++ disposition = toPartDisposition dispositionType++ fieldName = fromMaybe "" $ findParam "name" dispositionParameters++ fileName = findParam "filename" dispositionParameters+++------------------------------------------------------------------------------+findParam :: (Eq a) => a -> [(a, b)] -> Maybe b+findParam p = fmap snd . find ((== p) . fst)+++------------------------------------------------------------------------------+partStream :: InputStream MatchInfo -> IO (InputStream ByteString)+partStream st = Streams.makeInputStream go++ where+ go = do+ s <- Streams.read st+ return $! s >>= f++ f (NoMatch s) = return s+ f _ = mzero+++++------------------------------------------------------------------------------+-- | Assuming we've already identified the boundary value and split the input+-- up into parts which match and parts which don't, run the given 'ByteString'+-- InputStream over each part and grab a list of the resulting values.+--+-- TODO/FIXME: fix description+foldParts :: (InputStream ByteString -> MultipartState a -> IO (MultipartState a))+ -> InputStream MatchInfo+ -> (MultipartState a)+ -> IO (MultipartState a)+foldParts partFunc stream = go+ where+ part acc pStream = do+ isLast <- parseFromStream pBoundaryEnd pStream++ if isLast+ then return Nothing+ else do+ !x <- partFunc pStream acc+ Streams.skipToEof pStream+ return $! Just x++ go !acc = do+ cap <- partStream stream >>= part acc+ maybe (return acc) go cap++ pBoundaryEnd = (eol *> pure False) <|> (string "--" *> pure True)+++------------------------------------------------------------------------------+eol :: Parser ByteString+eol = (string "\n") <|> (string "\r\n")+++------------------------------------------------------------------------------+pHeadersWithSeparator :: Parser [(ByteString,ByteString)]+pHeadersWithSeparator = pHeaders <* crlf+++------------------------------------------------------------------------------+toHeaders :: [(ByteString,ByteString)] -> Headers+toHeaders kvps = H.fromList kvps'+ where+ kvps' = map (first CI.mk) kvps+++------------------------------------------------------------------------------+mAX_HDRS_SIZE :: Int64+mAX_HDRS_SIZE = 32768+++------------------------------------------------------------------------------+withTempFile :: FilePath+ -> String+ -> ((FilePath, Handle) -> IO a)+ -> IO a+withTempFile tmpl temp handler =+ mask $ \restore -> bracket make cleanup (restore . handler)++ where+ make = mkstemp $ tmpl </> (temp ++ "XXXXXXX")+ cleanup (fp,h) = sequence $ map gobble [hClose h, removeFile fp]++ t :: IO z -> IO (Either SomeException z)+ t = E.try++ gobble = void . t
− src/Snap/Iteratee.hs
@@ -1,519 +0,0 @@-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE CPP #-}-{-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE PackageImports #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeSynonymInstances #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}---- | Snap Framework type aliases and utilities for iteratees. Note that as a--- convenience, this module also exports everything from @Data.Iteratee@ in the--- @iteratee@ library.------ /WARNING/: Note that all of these types are scheduled to change in the--- @darcs@ head version of the @iteratee@ library; John Lato et al. are working--- on a much improved iteratee formulation.--module Snap.Iteratee- ( -- * Convenience aliases around types from @Data.Iteratee@- Stream- , IterV- , Iteratee- , Enumerator-- -- * Re-export types and functions from @Data.Iteratee@- , module Data.Iteratee-- -- * Helper functions-- -- ** Enumerators- , enumBS- , enumLBS- , enumFile- , enumFilePartial- , InvalidRangeException-- -- ** Conversion to/from 'WrappedByteString'- , fromWrap- , toWrap-- -- ** Iteratee utilities- , drop'- , takeExactly- , takeNoMoreThan- , countBytes- , bufferIteratee- , mkIterateeBuffer- , unsafeBufferIterateeWithBuffer- , unsafeBufferIteratee- ) where---------------------------------------------------------------------------------import Control.Monad-import Control.Monad.CatchIO-import Control.Exception (SomeException)-import Data.ByteString (ByteString)-import qualified Data.ByteString as S-import qualified Data.ByteString.Unsafe as S-import qualified Data.ByteString.Lazy as L-import qualified Data.DList as D-import Data.Int-import Data.IORef-import Data.Iteratee-import Data.Iteratee.IO (enumHandle)-import qualified Data.Iteratee.Base.StreamChunk as SC-import Data.Iteratee.WrappedByteString-import qualified Data.ListLike as LL-import Data.Monoid (mappend)-import Data.Typeable-import Foreign-import Foreign.C.Types-import GHC.ForeignPtr-import Prelude hiding (catch,drop)-import System.IO-import "monads-fd" Control.Monad.Trans (liftIO)--#ifndef PORTABLE-import System.IO.Posix.MMap-import System.PosixCompat.Files-#endif----------------------------------------------------------------------------------type Stream = StreamG WrappedByteString Word8-type IterV m = IterGV WrappedByteString Word8 m-type Iteratee m = IterateeG WrappedByteString Word8 m-type Enumerator m a = Iteratee m a -> m (Iteratee m a)----------------------------------------------------------------------------------instance (Functor m, MonadCatchIO m) =>- MonadCatchIO (IterateeG s el m) where- --catch :: Exception e => m a -> (e -> m a) -> m a- catch m handler = IterateeG $ \str -> do- ee <- try $ runIter m str- case ee of- (Left e) -> runIter (handler e) str- (Right v) -> return v-- --block :: m a -> m a- block m = IterateeG $ \str -> block $ runIter m str- unblock m = IterateeG $ \str -> unblock $ runIter m str------------------------------------------------------------------------------------ | Wraps an 'Iteratee', counting the number of bytes consumed by it.-countBytes :: (Monad m) => Iteratee m a -> Iteratee m (a, Int64)-countBytes = go 0- where- go !n iter = IterateeG $ f n iter-- f !n !iter ch@(Chunk ws) = do- iterv <- runIter iter ch- case iterv of- Done x rest -> let !n' = n + m - len rest- in return $! Done (x, n') rest- Cont i err -> return $ Cont ((go $! n + m) i) err- where- m = fromIntegral $ S.length (unWrap ws)-- len (EOF _) = 0- len (Chunk s) = fromIntegral $ S.length (unWrap s)-- f !n !iter stream = do- iterv <- runIter iter stream- case iterv of- Done x rest -> return $ Done (x, n) rest- Cont i err -> return $ Cont (go n i) err------------------------------------------------------------------------------------ | Buffers an iteratee.------ Our enumerators produce a lot of little strings; rather than spending all--- our time doing kernel context switches for 4-byte write() calls, we buffer--- the iteratee to send 8KB at a time.------ The IORef returned can be set to True to "cancel" buffering. We added this--- so that transfer-encoding: chunked (which needs its own buffer and therefore--- doesn't need /its/ output buffered) can switch the outer buffer off.----bufferIteratee :: Iteratee IO a -> IO (Iteratee IO a, IORef Bool)-bufferIteratee iteratee = do- esc <- newIORef False- return $ (start esc iteratee, esc)-- where- blocksize = 8192-- start esc iter = IterateeG $! checkRef esc iter-- checkRef esc iter ch = do- quit <- readIORef esc- if quit- then runIter iter ch- else f (D.empty,0) iter ch-- --go :: (DList ByteString, Int) -> Iteratee m a -> Iteratee m a- go (!dl,!n) iter = IterateeG $! f (dl,n) iter-- --f :: (DList ByteString, Int) -> Iteratee m a -> Stream -> m (IterV m a)- f _ !iter ch@(EOF (Just _)) = runIter iter ch- f (!dl,_) !iter ch@(EOF Nothing) = do- iter' <- if S.null str- then return iter- else liftM liftI $ runIter iter $ Chunk big- runIter iter' ch- where- str = S.concat $ D.toList dl- big = WrapBS str-- f (!dl,!n) iter (Chunk (WrapBS s)) =- if n' >= blocksize- then do- iterv <- runIter iter (Chunk big)- case iterv of- Done x rest -> return $ Done x rest- Cont i (Just e) -> return $ Cont i (Just e)- Cont i Nothing -> return $ Cont (go (D.empty,0) i) Nothing- else return $ Cont (go (dl',n') iter) Nothing- where- m = S.length s- n' = n+m- dl' = D.snoc dl s- big = WrapBS $ S.concat $ D.toList dl'---bUFSIZ :: Int-bUFSIZ = 8192----- | Creates a buffer to be passed into 'unsafeBufferIterateeWithBuffer'.-mkIterateeBuffer :: IO (ForeignPtr CChar)-mkIterateeBuffer = mallocPlainForeignPtrBytes bUFSIZ----------------------------------------------------------------------------------- | Buffers an iteratee, \"unsafely\". Here we use a fixed binary buffer which--- we'll re-use, meaning that if you hold on to any of the bytestring data--- passed into your iteratee (instead of, let's say, shoving it right out a--- socket) it'll get changed out from underneath you, breaking referential--- transparency. Use with caution!----unsafeBufferIteratee :: Iteratee IO a -> IO (Iteratee IO a)-unsafeBufferIteratee iter = do- buf <- mkIterateeBuffer- unsafeBufferIterateeWithBuffer buf iter------------------------------------------------------------------------------------ | Buffers an iteratee, \"unsafely\". Here we use a fixed binary buffer which--- we'll re-use, meaning that if you hold on to any of the bytestring data--- passed into your iteratee (instead of, let's say, shoving it right out a--- socket) it'll get changed out from underneath you, breaking referential--- transparency. Use with caution!------ This version accepts a buffer created by 'mkIterateeBuffer'.----unsafeBufferIterateeWithBuffer :: ForeignPtr CChar- -> Iteratee IO a- -> IO (Iteratee IO a)-unsafeBufferIterateeWithBuffer buf iteratee = do- return $! start iteratee-- where- start iter = IterateeG $! f 0 iter- go bytesSoFar iter =- {-# SCC "unsafeBufferIteratee/go" #-}- IterateeG $! f bytesSoFar iter-- sendBuf n iter =- {-# SCC "unsafeBufferIteratee/sendBuf" #-}- withForeignPtr buf $ \ptr -> do- s <- S.unsafePackCStringLen (ptr, n)- runIter iter $ Chunk $ WrapBS s-- copy c@(EOF _) = c- copy (Chunk (WrapBS s)) = Chunk $ WrapBS $ S.copy s-- f _ iter ch@(EOF (Just _)) = runIter iter ch-- f !n iter ch@(EOF Nothing) =- if n == 0- then runIter iter ch- else do- iter' <- liftM liftI $ sendBuf n iter- runIter iter' ch-- f !n iter (Chunk (WrapBS s)) = do- let m = S.length s- if m+n > bUFSIZ- then overflow n iter s m- else copyAndCont n iter s m-- copyAndCont n iter s m =- {-# SCC "unsafeBufferIteratee/copyAndCont" #-} do- S.unsafeUseAsCStringLen s $ \(p,sz) ->- withForeignPtr buf $ \bufp -> do- let b' = plusPtr bufp n- copyBytes b' p sz-- return $ Cont (go (n+m) iter) Nothing--- overflow n iter s m =- {-# SCC "unsafeBufferIteratee/overflow" #-} do- let rest = bUFSIZ - n- let m2 = m - rest- let (s1,s2) = S.splitAt rest s-- S.unsafeUseAsCStringLen s1 $ \(p,_) ->- withForeignPtr buf $ \bufp -> do- let b' = plusPtr bufp n- copyBytes b' p rest-- iv <- sendBuf bUFSIZ iter- case iv of- Done x r -> return $- Done x (copy r `mappend` (Chunk $ WrapBS s2))- Cont i (Just e) -> return $ Cont i (Just e)- Cont i Nothing -> do- -- check the size of the remainder; if it's bigger than the- -- buffer size then just send it- if m2 >= bUFSIZ- then do- iv' <- runIter i (Chunk $ WrapBS s2)- case iv' of- Done x r -> return $ Done x (copy r)- Cont i' (Just e) -> return $ Cont i' (Just e)- Cont i' Nothing -> return $ Cont (go 0 i') Nothing- else copyAndCont 0 i s2 m2------------------------------------------------------------------------------------ | Enumerates a strict bytestring.-enumBS :: (Monad m) => ByteString -> Enumerator m a-enumBS bs = enumPure1Chunk $ WrapBS bs-{-# INLINE enumBS #-}------------------------------------------------------------------------------------ | Enumerates a lazy bytestring.-enumLBS :: (Monad m) => L.ByteString -> Enumerator m a-enumLBS lbs = el chunks- where- el [] i = return i- el (x:xs) i = do- i' <- liftM liftI $ runIter i (Chunk $ WrapBS x)- el xs i'-- chunks = L.toChunks lbs------------------------------------------------------------------------------------ | Converts a lazy bytestring to a wrapped bytestring.-toWrap :: L.ByteString -> WrappedByteString Word8-toWrap = WrapBS . S.concat . L.toChunks-{-# INLINE toWrap #-}------------------------------------------------------------------------------------ | Converts a wrapped bytestring to a lazy bytestring.-fromWrap :: WrappedByteString Word8 -> L.ByteString-fromWrap = L.fromChunks . (:[]) . unWrap-{-# INLINE fromWrap #-}------------------------------------------------------------------------------------ | Skip n elements of the stream, if there are that many--- This is the Int64 version of the drop function in the iteratee library-drop' :: (SC.StreamChunk s el, Monad m)- => Int64- -> IterateeG s el m ()-drop' 0 = return ()-drop' n = IterateeG step- where- step (Chunk str)- | strlen <= n = return $ Cont (drop' (n - strlen)) Nothing- where- strlen = fromIntegral $ SC.length str- step (Chunk str) = return $ Done () (Chunk (LL.drop (fromIntegral n) str))- step stream = return $ Done () stream------------------------------------------------------------------------------------ | Reads n elements from a stream and applies the given iteratee to--- the stream of the read elements. Reads exactly n elements, and if--- the stream is short propagates an error.-takeExactly :: (SC.StreamChunk s el, Monad m)- => Int64- -> EnumeratorN s el s el m a-takeExactly 0 iter = return iter-takeExactly n' iter =- if n' < 0- then takeExactly 0 iter- else IterateeG (step n')- where- step n chk@(Chunk str)- | SC.null str = return $ Cont (takeExactly n iter) Nothing- | strlen < n = liftM (flip Cont Nothing) inner- | otherwise = done (Chunk s1) (Chunk s2)- where- strlen = fromIntegral $ SC.length str- inner = liftM (check (n - strlen)) (runIter iter chk)- (s1, s2) = SC.splitAt (fromIntegral n) str- step _n (EOF (Just e)) = return $ Cont undefined (Just e)- step _n (EOF Nothing) = return $ Cont undefined (Just (Err "short write"))- check n (Done x _) = drop' n >> return (return x)- check n (Cont x Nothing) = takeExactly n x- check n (Cont _ (Just e)) = drop' n >> throwErr e- done s1 s2 = liftM (flip Done s2) (runIter iter s1 >>= checkIfDone return)------------------------------------------------------------------------------------ | Reads up to n elements from a stream and applies the given iteratee to the--- stream of the read elements. If more than n elements are read, propagates an--- error.-takeNoMoreThan :: (SC.StreamChunk s el, Monad m)- => Int64- -> EnumeratorN s el s el m a-takeNoMoreThan n' iter =- if n' < 0- then takeNoMoreThan 0 iter- else IterateeG (step n')- where- step n chk@(Chunk str)- | SC.null str = return $ Cont (takeNoMoreThan n iter) Nothing- | strlen < n = liftM (flip Cont Nothing) inner- | otherwise = done (Chunk s1) (Chunk s2)- where- strlen = fromIntegral $ SC.length str- inner = liftM (check (n - strlen)) (runIter iter chk)- (s1, s2) = SC.splitAt (fromIntegral n) str-- step _n (EOF (Just e)) = return $ Cont undefined (Just e)- step _n chk@(EOF Nothing) = do- v <- runIter iter chk-- case v of- (Done x s) -> return $ Done (return x) s- (Cont _ (Just e)) -> return $ Cont undefined (Just e)- (Cont _ Nothing) -> return $ Cont (throwErr $ Err "premature EOF") Nothing-- check _ v@(Done _ _) = return $ liftI v- check n (Cont x Nothing) = takeNoMoreThan n x- check _ (Cont _ (Just e)) = throwErr e-- done _ (EOF _) = error "impossible"- done s1 s2@(Chunk s2') = do- v <- runIter iter s1- case v of- (Done x s') -> return $ Done (return x) (s' `mappend` s2)- (Cont _ (Just e)) -> return $ Cont undefined (Just e)- (Cont i Nothing) ->- if SC.null s2'- then return $ Cont (takeNoMoreThan 0 i) Nothing- else return $ Cont undefined (Just $ Err "too many bytes")----------------------------------------------------------------------------------{-# INLINE _enumFile #-}-_enumFile :: FilePath -> Iteratee IO a -> IO (Iteratee IO a)-_enumFile fp iter = do- h <- liftIO $ openBinaryFile fp ReadMode- enumHandle h iter `finally` hClose h----------------------------------------------------------------------------------data InvalidRangeException = InvalidRangeException- deriving (Typeable)--instance Show InvalidRangeException where- show InvalidRangeException = "Invalid range"--instance Exception InvalidRangeException----------------------------------------------------------------------------------{-# INLINE _enumFilePartial #-}-_enumFilePartial :: FilePath- -> (Int64,Int64)- -> Iteratee IO a- -> IO (Iteratee IO a)-_enumFilePartial fp (start,end) iter = do- let len = end - start- withBinaryFile fp ReadMode- (\h -> do- unless (start == 0) $ hSeek h AbsoluteSeek $ toInteger start- enumHandle h $ joinI $ takeExactly len iter)---enumFile :: FilePath -> Iteratee IO a -> IO (Iteratee IO a)-enumFilePartial :: FilePath- -> (Int64,Int64)- -> Iteratee IO a- -> IO (Iteratee IO a)--#ifdef PORTABLE--enumFile = _enumFile-enumFilePartial fp rng@(start,end) iter = do- when (end < start) $ throw InvalidRangeException- _enumFilePartial fp rng iter--#else---- 40MB limit-maxMMapFileSize :: FileOffset-maxMMapFileSize = 41943040--tooBigForMMap :: FilePath -> IO Bool-tooBigForMMap fp = do- stat <- getFileStatus fp- return $ fileSize stat > maxMMapFileSize---enumFile fp iter = do- -- for small files we'll use mmap to save ourselves a copy, otherwise we'll- -- stream it- tooBig <- tooBigForMMap fp-- if tooBig- then _enumFile fp iter- else do- es <- try $- liftM WrapBS $- unsafeMMapFile fp-- case es of- (Left (e :: SomeException)) -> return $ throwErr- $ Err- $ "IO error" ++ show e-- (Right s) -> liftM liftI $ runIter iter $ Chunk s---enumFilePartial fp rng@(start,end) iter = do- when (end < start) $ throw InvalidRangeException-- let len = end - start-- tooBig <- tooBigForMMap fp-- if tooBig- then _enumFilePartial fp rng iter- else do- es <- try $ unsafeMMapFile fp-- case es of- (Left (e::SomeException)) -> return $ throwErr- $ Err- $ "IO error" ++ show e-- (Right s) -> liftM liftI $ runIter iter- $ Chunk- $ WrapBS- $ S.take (fromEnum len)- $ S.drop (fromEnum start) s--#endif
− src/Snap/Starter.hs
@@ -1,99 +0,0 @@-{-# LANGUAGE TemplateHaskell #-}-module Main where---------------------------------------------------------------------------------import Char-import Data.List-import qualified Data.Text as T-import System-import System.Directory-import System.Console.GetOpt-import System.FilePath---------------------------------------------------------------------------------import Snap.StarterTH------------------------------------------------------------------------------------ Creates a value tDir :: ([String], [(String, String)])-$(buildData "tDirDefault" "default")-$(buildData "tDirBareBones" "barebones")----------------------------------------------------------------------------------usage :: String-usage = unlines- ["Usage:"- ,""- ," snap <action>"- ,""- ," <action> can be one of:"- ," init - create a new project directory structure in the current directory"- ]----------------------------------------------------------------------------------data InitFlag = InitBareBones- | InitHelp- deriving (Show, Eq)---setup :: String -> ([FilePath], [(String, String)]) -> IO ()-setup projName tDir = do- mapM createDirectory (fst tDir)- mapM_ write (snd tDir)- where- write (f,c) =- if isSuffixOf "foo.cabal" f- then writeFile (projName++".cabal") (insertProjName $ T.pack c)- else writeFile f c- isNameChar c = isAlphaNum c || c == '-'- insertProjName c = T.unpack $ T.replace- (T.pack "projname")- (T.pack $ filter isNameChar projName) c---------------------------------------------------------------------------------initProject :: [String] -> IO ()-initProject args = do- case getOpt Permute options args of- (flags, _, [])- | InitHelp `elem` flags -> do putStrLn initUsage- exitFailure- | otherwise -> init' (InitBareBones `elem` flags)-- (_, _, errs) -> do putStrLn $ concat errs- putStrLn initUsage- exitFailure- where- initUsage = unlines- ["Usage:"- ,""- ," snap init"- ,""- ," -b --barebones Depend only on -core and -server"- ," -h --help Print this message"- ]-- options =- [ Option ['b'] ["barebones"] (NoArg InitBareBones)- "Depend only on -core and -server"- , Option ['h'] ["help"] (NoArg InitHelp)- "Print this message"- ]-- init' isBareBones = do- cur <- getCurrentDirectory- let dirs = splitDirectories cur- projName = last dirs- setup projName (if isBareBones then tDirBareBones else tDirDefault)----------------------------------------------------------------------------------main :: IO ()-main = do- args <- getArgs- case args of- ("init":args') -> initProject args'- _ -> do putStrLn usage- exitFailure-
− src/Snap/StarterTH.hs
@@ -1,55 +0,0 @@-{-# LANGUAGE TemplateHaskell #-}-module Snap.StarterTH where---------------------------------------------------------------------------------import qualified Data.Foldable as F-import Data.List-import Language.Haskell.TH-import System.Directory.Tree------------------------------------------------------------------------------------------------------------------------------------------------------------------- Convenience types-type FileData = (String, String)-type DirData = FilePath------------------------------------------------------------------------------------ Gets all the directorys in a DirTree-getDirs :: [FilePath] -> DirTree a -> [FilePath]-getDirs prefix (Dir n c) = (intercalate "/" (reverse (n:prefix))) : concatMap (getDirs (n:prefix)) c-getDirs _ (File _ _) = []-getDirs _ (Failed _ _) = []------------------------------------------------------------------------------------ Reads a directory and returns a tuple of the list of all directories--- encountered and a list of filenames and content strings.-readTree :: FilePath -> IO ([DirData], [FileData])-readTree dir = do- d <- readDirectory $ dir++"/."- let ps = zipPaths $ "" :/ (free d)- fd = F.foldr (:) [] ps- dirs = tail $ getDirs [] $ free d- return $ (dirs, fd)------------------------------------------------------------------------------------ Calls readTree and returns it's value in a quasiquote.-dirQ :: FilePath -> Q Exp-dirQ tplDir = do- d <- runIO $ readTree $ "project_template/"++tplDir- runQ [| d |]------------------------------------------------------------------------------------ Creates a declaration assigning the specified name the value returned by--- dirQ.-buildData :: String -> FilePath -> Q [Dec]-buildData dirName tplDir = do- v <- valD (varP (mkName dirName))- (normalB $ dirQ tplDir)- []- return [v]-
+ src/Snap/Test.hs view
@@ -0,0 +1,58 @@+-- | The Snap.Test module contains primitives and combinators for testing Snap+-- applications.+module Snap.Test+ ( -- * Combinators and types for testing Snap handlers.++ -- ** Types+ RequestBuilder+ , MultipartParams+ , MultipartParam(..)+ , FileData (..)+ , RequestType (..)++ -- ** Building Requests and testing handlers+ , buildRequest+ , runHandler+ , runHandlerM+ , evalHandler+ , evalHandlerM++ -- *** Convenience functions for generating common types of HTTP requests+ , get+ , head+ , postUrlEncoded+ , postMultipart+ , put+ , postRaw+ , delete++ -- *** Precise control over building Requests+ , addHeader+ , setContentType+ , setHeader+ , addCookies+ , setHttpVersion+ , setQueryString+ , setQueryStringRaw+ , setRequestPath+ , setRequestType+ , setSecure++ -- * HUnit Assertions+ , assertSuccess+ , assert404+ , assertRedirectTo+ , assertRedirect+ , assertBodyContains++ -- * Getting response bodies+ , getResponseBody++ -- * Dumping HTTP Messages+ , requestToString+ , responseToString+ )+ where++import Snap.Internal.Test.Assertions (assert404, assertBodyContains, assertRedirect, assertRedirectTo, assertSuccess, getResponseBody)+import Snap.Internal.Test.RequestBuilder (FileData (..), MultipartParam (..), MultipartParams, RequestBuilder, RequestType (..), addCookies, addHeader, buildRequest, delete, evalHandler, evalHandlerM, get, postMultipart, postRaw, postUrlEncoded, put, requestToString, responseToString, runHandler, runHandlerM, setContentType, setHeader, setHttpVersion, setQueryString, setQueryStringRaw, setRequestPath, setRequestType, setSecure)
− src/Snap/Types.hs
@@ -1,129 +0,0 @@-{-|--This module contains the core type definitions, class instances, and functions-for HTTP as well as the 'Snap' monad, which is used for web handlers.---}-module Snap.Types- (- -- * The Snap Monad- Snap- , runSnap- , NoHandlerException(..)-- -- ** Functions for control flow and early termination- , finishWith- , pass-- -- ** Routing- , method- , path- , dir- , ifTop- , route- , routeLocal-- -- ** Access to state- , getRequest- , getResponse- , putRequest- , putResponse- , modifyRequest- , modifyResponse- , localRequest- , withRequest- , withResponse-- -- ** Logging- , logError-- -- ** Grabbing/transforming request bodies- , runRequestBody- , getRequestBody- , transformRequestBody-- -- * HTTP Datatypes and Functions- -- $httpDoc- --- , Request- , Response- , Headers- , HasHeaders(..)- , Params- , Method(..)- , Cookie(..)- , HttpVersion-- -- ** Headers- , addHeader- , setHeader- , getHeader- , deleteHeader- , ipHeaderFilter- , ipHeaderFilter'-- -- ** Requests- , rqServerName- , rqServerPort- , rqRemoteAddr- , rqRemotePort- , rqLocalAddr- , rqLocalHostname- , rqIsSecure- , rqContentLength- , rqMethod- , rqVersion- , rqCookies- , rqPathInfo- , rqContextPath- , rqURI- , rqQueryString- , rqParams- , rqParam- , getParam- , rqModifyParams- , rqSetParam-- -- ** Responses- , emptyResponse- , setResponseCode- , setResponseStatus- , rspStatus- , rspStatusReason- , setContentType- , addCookie- , setContentLength- , clearContentLength- , redirect- , redirect'-- -- *** Response I/O- , setResponseBody- , modifyResponseBody- , addToOutput- , writeBS- , writeLazyText- , writeText- , writeLBS- , sendFile- , sendFilePartial-- -- * Iteratee- , Enumerator- , SomeEnumerator(..)-- -- * HTTP utilities- , formatHttpTime- , parseHttpTime- , urlEncode- , urlDecode- ) where---------------------------------------------------------------------------------import Snap.Internal.Http.Types-import Snap.Internal.Routing-import Snap.Internal.Types----------------------------------------------------------------------------------- $httpDoc--- HTTP-related datatypes: 'Request', 'Response', 'Cookie', etc.
+ src/Snap/Types/Headers.hs view
@@ -0,0 +1,349 @@+{-# LANGUAGE OverloadedStrings #-}++-- | An opaque data type for HTTP headers. Intended to be imported qualified,+-- i.e:+--+-- > import Snap.Types.Headers (Headers)+-- > import qualified Snap.Types.Headers as H+-- >+-- > foo :: Headers+-- > foo = H.empty++module Snap.Types.Headers+ ( -- * Headers type+ Headers++ -- * Headers creation+ , empty++ -- * Predicates+ , null+ , member++ -- * Lookup+ , lookup+ , lookupWithDefault++ -- * Adding/setting headers+ , insert+ , unsafeInsert+ , set++ -- * Deleting+ , delete++ -- * Traversal+ , foldl'+ , foldr+ , foldedFoldl'+ , foldedFoldr++ -- * Lists+ , toList+ , fromList++ , unsafeFromCaseFoldedList+ , unsafeToCaseFoldedList++ ) where++------------------------------------------------------------------------------+import Control.Arrow (first)+import Data.ByteString.Char8 (ByteString)+import qualified Data.ByteString.Char8 as S+import Data.CaseInsensitive (CI)+import qualified Data.CaseInsensitive as CI+import qualified Data.CaseInsensitive.Unsafe as CI+import qualified Data.List as List+import Data.Maybe (fromMaybe)+import Prelude (Bool (..), Eq (..), Maybe (..), Show (..), fst, id, map, otherwise, uncurry, ($), ($!), (.))+------------------------------------------------------------------------------++------------------------------------------------------------------------------+-- | A key-value map that represents a collection of HTTP header fields. Keys+-- are case-insensitive.+newtype Headers = H { unH :: [(ByteString, ByteString)] }+ deriving (Show)+++------------------------------------------------------------------------------+-- | An empty collection of HTTP header fields.+--+-- Example:+--+-- @+-- ghci> H.'empty'+-- H {unH = []}+-- @+empty :: Headers+empty = H []+++------------------------------------------------------------------------------+-- | Is a given collection of HTTP header fields empty?+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> H.'null' H.'empty'+-- True+-- ghci> H.'null' $ H.'fromList' [(\"Host\", \"localhost\")]+-- False+-- @+null :: Headers -> Bool+null = List.null . unH+{-# INLINE null #-}+++------------------------------------------------------------------------------+-- | Does this collection of HTTP header fields contain a given field?+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> H.'member' \"host\" $ H.'fromList' [(\"Host\", \"localhost\")]+-- True+-- ghci> H.'member' \"Accept\" $ H.'fromList' [(\"Host\", \"localhost\")]+-- False+-- @+member :: CI ByteString -> Headers -> Bool+member k0 = f . unH+ where+ k = CI.foldedCase k0+ f m = List.any ((k ==) . fst) m+{-# INLINE member #-}+++------------------------------------------------------------------------------+-- | Look up the value of a given HTTP header field.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> H.'lookup' \"host\" $ H.'fromList' [(\"Host\", \"localhost\")]+-- Just \"localhost\"+-- ghci> H.'lookup' \"Accept\" $ H.'fromList' [(\"Host\", \"localhost\")]+-- Nothing+-- @+lookup :: CI ByteString -> Headers -> Maybe ByteString+lookup k (H m) = List.lookup (CI.foldedCase k) m+{-# INLINE lookup #-}+++------------------------------------------------------------------------------+-- | Look up the value of a given HTTP header field or return the provided+-- default value when that header field is not present.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> let hdrs = H.'fromList' [(\"Host\", \"localhost\")]+-- ghci> H.'lookupWithDefault' \"host\" \"127.0.0.1\" $ hdrs+-- \"localhost\"+-- ghci> H.'lookupWithDefault' \"Accept\" \"text\/plain\" $ hdrs+-- \"text\/plain\"+-- @+lookupWithDefault :: ByteString -> CI ByteString -> Headers -> ByteString+lookupWithDefault d k m = fromMaybe d $ lookup k m+++------------------------------------------------------------------------------+-- | Insert a key-value pair into the headers map. If the key already exists in+-- the map, the values are catenated with ", ".+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> let hdrs = H.'insert' \"Accept\" \"text\/plain\" $ H.'empty'+-- ghci> hdrs+-- H {unH = [(\"accept\",\"text\/plain\")]}+-- ghci> H.'insert' \"Accept\" \"text\/html\" $ hdrs+-- H {unH = [(\"accept\",\"text\/plain,text\/html\")]}+-- @+insert :: CI ByteString -> ByteString -> Headers -> Headers+insert k0 v (H m) = H $! go id m+ where+ k = CI.foldedCase k0++ go dl [] = dl [(k, v)]+ go dl (z@(x,y):xs) | k == x = dl ((k, concatHeaderValues v y):xs)+ | otherwise = go (dl . (z:)) xs++ concatHeaderValues :: ByteString -> ByteString -> ByteString+ concatHeaderValues new old = S.concat [old, ",", new]+++------------------------------------------------------------------------------+-- | Insert a key-value pair into the headers map, without checking whether the+-- header already exists. The key /must/ be already case-folded, or none of the+-- lookups will work!+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> let hdrs = H.'unsafeInsert' \"accept\" \"text\/plain\" $ H.'empty'+-- ghci> hdrs+-- H {unH = [(\"accept\",\"text\/plain\")]}+-- ghci> let hdrs' = H.'unsafeInsert' \"accept\" \"text\/html\" $ hdrs+-- ghci> hdrs'+-- H {unH = [(\"accept\",\"text\/html\"), (\"accept\",\"text\/plain\")]}+-- ghci> H.'lookup' \"accept\" hdrs'+-- Just \"text\/html\"+-- @+unsafeInsert :: ByteString -> ByteString -> Headers -> Headers+unsafeInsert k v (H hdrs) = H ((k,v):hdrs)+++------------------------------------------------------------------------------+-- | Set the value of a HTTP header field to a given value, replacing the old+-- value.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> H.'set' \"accept\" \"text\/plain\" $ H.'empty'+-- H {unH = [(\"accept\",\"text\/plain\")]}+-- ghci> H.'set' \"accept\" \"text\/html\" $ H.'fromList' [(\"Accept\", \"text\/plain\")]+-- H {unH = [(\"accept\",\"text\/html\")]}+-- @+set :: CI ByteString -> ByteString -> Headers -> Headers+set k0 v (H m) = H $ go m+ where+ k = CI.foldedCase k0++ go [] = [(k,v)]+ go (x@(k',_):xs) | k == k' = (k,v) : List.filter ((k /=) . fst) xs+ | otherwise = x : go xs+++------------------------------------------------------------------------------+-- | Delete all key-value pairs associated with the given key from the headers+-- map.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> H.'delete' \"accept\" $ H.'fromList' [(\"Accept\", \"text\/plain\")]+-- H {unH = []}+-- @+delete :: CI ByteString -> Headers -> Headers+delete k (H m) = H $ List.filter ((k' /=) . fst) m+ where+ k' = CI.foldedCase k+++------------------------------------------------------------------------------+-- | Strict left fold over all key-value pairs in the headers map.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import "Data.Monoid"+-- ghci> let hdrs = H.'fromList' [(\"Accept\", \"text\/plain\"), (\"Accept\", \"text\/html\")]+-- ghci> let f (cntr, acc) _ val = (cntr+1, val <> \";\" <> acc)+-- ghci> H.'foldl'' f (0, \"\") hdrs+-- (2,\"text\/html;text\/plain;\")+-- @+foldl' :: (a -> CI ByteString -> ByteString -> a)+ -> a+ -> Headers+ -> a+foldl' f a (H m) = List.foldl' f' a m+ where+ f' v (x,y) = f v (CI.unsafeMk x) y+++------------------------------------------------------------------------------+-- | Same as 'foldl'', but the key parameter is of type 'ByteString' instead of+-- 'CI' 'ByteString'. The key is case-folded (lowercase).+foldedFoldl' :: (a -> ByteString -> ByteString -> a)+ -> a+ -> Headers+ -> a+foldedFoldl' f a (H m) = List.foldl' f' a m+ where+ f' v (x,y) = f v x y+++------------------------------------------------------------------------------+-- | Right fold over all key-value pairs in the headers map.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import "Data.Monoid"+-- ghci> let hdrs = H.'fromList' [(\"Accept\", \"text\/plain\"), (\"Accept\", \"text\/html\")]+-- ghci> let f _ val (cntr, acc) = (cntr+1, val <> \";\" <> acc)+-- ghci> H.'foldr' f (0, \"\") hdrs+-- (2,\"text\/plain;text\/html;\")+-- @+foldr :: (CI ByteString -> ByteString -> a -> a)+ -> a+ -> Headers+ -> a+foldr f a (H m) = List.foldr f' a m+ where+ f' (x, y) v = f (CI.unsafeMk x) y v+++------------------------------------------------------------------------------+-- | Same as 'foldr', but the key parameter is of type 'ByteString' instead of+-- 'CI' 'ByteString'. The key is case-folded (lowercase).+foldedFoldr :: (ByteString -> ByteString -> a -> a)+ -> a+ -> Headers+ -> a+foldedFoldr f a (H m) = List.foldr (uncurry f) a m+++------------------------------------------------------------------------------+-- | Convert a 'Headers' value to a list of key-value pairs.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> let l = [(\"Accept\", \"text\/plain\"), (\"Accept\", \"text\/html\")]+-- ghci> H.'toList' . H.'fromList' $ l+-- [(\"accept\",\"text\/plain\"),(\"accept\",\"text\/html\")]+-- @+toList :: Headers -> [(CI ByteString, ByteString)]+toList = map (first CI.unsafeMk) . unH+++------------------------------------------------------------------------------+-- | Build a 'Headers' value from a list of key-value pairs.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> H.'fromList' [(\"Accept\", \"text\/plain\"), (\"Accept\", \"text\/html\")]+-- H {unH = [(\"accept\",\"text\/plain\"),(\"accept\",\"text\/html\")]}+-- @+fromList :: [(CI ByteString, ByteString)] -> Headers+fromList = H . map (first CI.foldedCase)+++------------------------------------------------------------------------------+-- | Like 'fromList', but the keys are assumed to be already case-folded (in+-- lowercase).+unsafeFromCaseFoldedList :: [(ByteString, ByteString)] -> Headers+unsafeFromCaseFoldedList = H+++------------------------------------------------------------------------------+-- | Like 'toList', but does not convert the keys to 'CI' 'ByteString', so key+-- comparisons will be case-sensitive.+unsafeToCaseFoldedList :: Headers -> [(ByteString, ByteString)]+unsafeToCaseFoldedList = unH
+ src/Snap/Util/CORS.hs view
@@ -0,0 +1,281 @@+{-# LANGUAGE OverloadedStrings #-}+-- | Add <http://www.w3.org/TR/cors/ CORS> (cross-origin resource sharing)+-- headers to a Snap application. CORS headers can be added either conditionally+-- or unconditionally to the entire site, or you can apply CORS headers to a+-- single route.+--+-- To use in a snaplet, simply use 'wrapSite':+--+-- @+-- wrapSite $ applyCORS defaultOptions+-- @+module Snap.Util.CORS+ ( -- * Applying CORS to a specific response+ applyCORS++ -- * Option Specification+ , CORSOptions(..)+ , defaultOptions++ -- ** Origin lists+ , OriginList(..)+ , OriginSet, mkOriginSet, origins++ -- * Internals+ , HashableURI(..), HashableMethod (..)+ ) where++import Control.Applicative+import Control.Monad (join, when)+import Data.CaseInsensitive (CI)+import Data.Hashable (Hashable(..))+import Data.Maybe (fromMaybe)+import Data.Text.Encoding (decodeUtf8, encodeUtf8)+import Network.URI (URI (..), URIAuth (..), parseURI)++import qualified Data.Attoparsec.ByteString.Char8 as Attoparsec+import qualified Data.ByteString.Char8 as S+import qualified Data.CaseInsensitive as CI+import qualified Data.HashSet as HashSet+import qualified Data.Text as Text++import qualified Snap.Core as Snap+import Snap.Internal.Parsing (pTokens)++-- | A set of origins. RFC 6454 specifies that origins are a scheme, host and+-- port, so the 'OriginSet' wrapper around a 'HashSet.HashSet' ensures that each+-- 'URI' constists of nothing more than this.+newtype OriginSet = OriginSet { origins :: HashSet.HashSet HashableURI }++-- | Used to specify the contents of the @Access-Control-Allow-Origin@ header.+data OriginList+ = Everywhere+ -- ^ Allow any origin to access this resource. Corresponds to+ -- @Access-Control-Allow-Origin: *@+ | Nowhere+ -- ^ Do not allow cross-origin requests+ | Origins OriginSet+ -- ^ Allow cross-origin requests from these origins.++-- | Specify the options to use when building CORS headers for a response. Most+-- of these options are 'Snap.Handler' actions to allow you to conditionally+-- determine the setting of each header.+data CORSOptions m = CORSOptions+ { corsAllowOrigin :: m OriginList+ -- ^ Which origins are allowed to make cross-origin requests.++ , corsAllowCredentials :: m Bool+ -- ^ Whether or not to allow exposing the response when the omit credentials+ -- flag is unset.++ , corsExposeHeaders :: m (HashSet.HashSet (CI S.ByteString))+ -- ^ A list of headers that are exposed to clients. This allows clients to+ -- read the values of these headers, if the response includes them.++ , corsAllowedMethods :: m (HashSet.HashSet HashableMethod)+ -- ^ A list of request methods that are allowed.++ , corsAllowedHeaders :: HashSet.HashSet S.ByteString -> m (HashSet.HashSet S.ByteString)+ -- ^ An action to determine which of the request headers are allowed.+ -- This action is supplied the parsed contents of+ -- @Access-Control-Request-Headers@.+ }++-- | Liberal default options. Specifies that:+--+-- * All origins may make cross-origin requests+-- * @allow-credentials@ is true.+-- * No extra headers beyond simple headers are exposed.+-- * @GET@, @POST@, @PUT@, @DELETE@ and @HEAD@ are all allowed.+-- * All request headers are allowed.+--+-- All options are determined unconditionally.+defaultOptions :: Monad m => CORSOptions m+defaultOptions = CORSOptions+ { corsAllowOrigin = return Everywhere+ , corsAllowCredentials = return True+ , corsExposeHeaders = return HashSet.empty+ , corsAllowedMethods = return $! defaultAllowedMethods+ , corsAllowedHeaders = return+ }++defaultAllowedMethods :: HashSet.HashSet HashableMethod+defaultAllowedMethods = HashSet.fromList $ map HashableMethod+ [ Snap.GET, Snap.POST, Snap.PUT, Snap.DELETE, Snap.HEAD ]+++-- | Apply CORS headers to a specific request. This is useful if you only have+-- a single action that needs CORS headers, and you don't want to pay for+-- conditional checks on every request.+--+-- You should note that 'applyCORS' needs to be used before you add any+-- 'Snap.method' combinators. For example, the following won't do what you want:+--+-- > method POST $ applyCORS defaultOptions $ myHandler+--+-- This fails to work as CORS requires an @OPTIONS@ request in the preflighting+-- stage, but this would get filtered out. Instead, use+--+-- > applyCORS defaultOptions $ method POST $ myHandler+applyCORS :: Snap.MonadSnap m => CORSOptions m -> m () -> m ()+applyCORS options m =+ (join . fmap decodeOrigin <$> getHeader "Origin") >>= maybe m corsRequestFrom++ where+ corsRequestFrom origin = do+ originList <- corsAllowOrigin options+ if origin `inOriginList` originList+ then Snap.method Snap.OPTIONS (preflightRequestFrom origin)+ <|> handleRequestFrom origin+ else m++ preflightRequestFrom origin = do+ maybeMethod <- fmap (parseMethod . S.unpack) <$>+ getHeader "Access-Control-Request-Method"++ case maybeMethod of+ Nothing -> m++ Just method -> do+ allowedMethods <- corsAllowedMethods options++ if method `HashSet.member` allowedMethods+ then do+ maybeHeaders <-+ fromMaybe (Just HashSet.empty) . fmap splitHeaders+ <$> getHeader "Access-Control-Request-Headers"++ case maybeHeaders of+ Nothing -> m+ Just headers -> do+ allowedHeaders <- corsAllowedHeaders options headers++ if not $ HashSet.null $+ headers `HashSet.difference` allowedHeaders+ then m+ else do+ addAccessControlAllowOrigin origin+ addAccessControlAllowCredentials++ commaSepHeader+ "Access-Control-Allow-Headers"+ id (HashSet.toList allowedHeaders)++ commaSepHeader+ "Access-Control-Allow-Methods"+ (S.pack . show) (HashSet.toList allowedMethods)++ else m++ handleRequestFrom origin = do+ addAccessControlAllowOrigin origin+ addAccessControlAllowCredentials++ exposeHeaders <- corsExposeHeaders options+ when (not $ HashSet.null exposeHeaders) $+ commaSepHeader+ "Access-Control-Expose-Headers"+ CI.original (HashSet.toList exposeHeaders)++ m++ addAccessControlAllowOrigin origin =+ addHeader "Access-Control-Allow-Origin"+ (encodeUtf8 $ Text.pack $ show origin)++ addAccessControlAllowCredentials = do+ allowCredentials <- corsAllowCredentials options+ when (allowCredentials) $+ addHeader "Access-Control-Allow-Credentials" "true"++ decodeOrigin :: S.ByteString -> Maybe URI+ decodeOrigin = fmap simplifyURI . parseURI . Text.unpack . decodeUtf8++ addHeader k v = Snap.modifyResponse (Snap.addHeader k v)++ commaSepHeader k f vs =+ case vs of+ [] -> return ()+ _ -> addHeader k $ S.intercalate ", " (map f vs)++ getHeader = Snap.getsRequest . Snap.getHeader++ splitHeaders = either (const Nothing) (Just . HashSet.fromList) .+ Attoparsec.parseOnly pTokens++mkOriginSet :: [URI] -> OriginSet+mkOriginSet = OriginSet . HashSet.fromList .+ map (HashableURI . simplifyURI)++simplifyURI :: URI -> URI+simplifyURI uri = uri { uriAuthority =+ fmap simplifyURIAuth (uriAuthority uri)+ , uriPath = ""+ , uriQuery = ""+ , uriFragment = ""+ }+ where simplifyURIAuth auth = auth { uriUserInfo = "" }++--------------------------------------------------------------------------------+parseMethod :: String -> HashableMethod+parseMethod "GET" = HashableMethod Snap.GET+parseMethod "POST" = HashableMethod Snap.POST+parseMethod "HEAD" = HashableMethod Snap.HEAD+parseMethod "PUT" = HashableMethod Snap.PUT+parseMethod "DELETE" = HashableMethod Snap.DELETE+parseMethod "TRACE" = HashableMethod Snap.TRACE+parseMethod "OPTIONS" = HashableMethod Snap.OPTIONS+parseMethod "CONNECT" = HashableMethod Snap.CONNECT+parseMethod "PATCH" = HashableMethod Snap.PATCH+parseMethod s = HashableMethod $ Snap.Method (S.pack s)++--------------------------------------------------------------------------------+-- | A @newtype@ over 'URI' with a 'Hashable' instance.+newtype HashableURI = HashableURI URI+ deriving (Eq)++instance Show HashableURI where+ show (HashableURI u) = show u++instance Hashable HashableURI where+ hashWithSalt s (HashableURI (URI scheme authority path query fragment)) =+ s `hashWithSalt`+ scheme `hashWithSalt`+ fmap hashAuthority authority `hashWithSalt`+ path `hashWithSalt`+ query `hashWithSalt`+ fragment++ where+ hashAuthority (URIAuth userInfo regName port) =+ s `hashWithSalt`+ userInfo `hashWithSalt`+ regName `hashWithSalt`+ port++inOriginList :: URI -> OriginList -> Bool+_ `inOriginList` Nowhere = False+_ `inOriginList` Everywhere = True+origin `inOriginList` (Origins (OriginSet xs)) =+ HashableURI origin `HashSet.member` xs+++--------------------------------------------------------------------------------+newtype HashableMethod = HashableMethod Snap.Method+ deriving (Eq)++instance Hashable HashableMethod where+ hashWithSalt s (HashableMethod Snap.GET) = s `hashWithSalt` (0 :: Int)+ hashWithSalt s (HashableMethod Snap.HEAD) = s `hashWithSalt` (1 :: Int)+ hashWithSalt s (HashableMethod Snap.POST) = s `hashWithSalt` (2 :: Int)+ hashWithSalt s (HashableMethod Snap.PUT) = s `hashWithSalt` (3 :: Int)+ hashWithSalt s (HashableMethod Snap.DELETE) = s `hashWithSalt` (4 :: Int)+ hashWithSalt s (HashableMethod Snap.TRACE) = s `hashWithSalt` (5 :: Int)+ hashWithSalt s (HashableMethod Snap.OPTIONS) = s `hashWithSalt` (6 :: Int)+ hashWithSalt s (HashableMethod Snap.CONNECT) = s `hashWithSalt` (7 :: Int)+ hashWithSalt s (HashableMethod Snap.PATCH) = s `hashWithSalt` (8 :: Int)+ hashWithSalt s (HashableMethod (Snap.Method m)) =+ s `hashWithSalt` (9 :: Int) `hashWithSalt` m++instance Show HashableMethod where+ show (HashableMethod m) = show m
src/Snap/Util/FileServe.hs view
@@ -1,426 +1,47 @@-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE CPP #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE ScopedTypeVariables #-}- -- | Contains web handlers to serve files from a directory.-module Snap.Util.FileServe-(- getSafePath-, fileServe-, fileServe'-, fileServeSingle-, fileServeSingle'-, defaultMimeTypes-, MimeMap-) where---------------------------------------------------------------------------------import Control.Applicative-import Control.Monad-import Control.Monad.Trans-import Data.Attoparsec.Char8 hiding (Done)-import qualified Data.ByteString.Char8 as S-import qualified Data.ByteString.Lazy.Char8 as L-import Data.ByteString.Char8 (ByteString)-import Data.Int-import Data.Map (Map)-import qualified Data.Map as Map-import Data.Maybe (fromMaybe, isNothing)-import Prelude hiding (show, Show)-import qualified Prelude-import System.Directory-import System.FilePath-import System.PosixCompat.Files-import Text.Show.ByteString hiding (runPut)--------------------------------------------------------------------------------import Snap.Internal.Debug-import Snap.Internal.Parsing-import Snap.Iteratee hiding (drop)-import Snap.Types------------------------------------------------------------------------------------ | A type alias for MIME type-type MimeMap = Map FilePath ByteString------------------------------------------------------------------------------------ | The default set of mime type mappings we use when serving files. Its--- value: ----- > Map.fromList [--- > ( ".asc" , "text/plain" ),--- > ( ".asf" , "video/x-ms-asf" ),--- > ( ".asx" , "video/x-ms-asf" ),--- > ( ".avi" , "video/x-msvideo" ),--- > ( ".bz2" , "application/x-bzip" ),--- > ( ".c" , "text/plain" ),--- > ( ".class" , "application/octet-stream" ),--- > ( ".conf" , "text/plain" ),--- > ( ".cpp" , "text/plain" ),--- > ( ".css" , "text/css" ),--- > ( ".cxx" , "text/plain" ),--- > ( ".dtd" , "text/xml" ),--- > ( ".dvi" , "application/x-dvi" ),--- > ( ".gif" , "image/gif" ),--- > ( ".gz" , "application/x-gzip" ),--- > ( ".hs" , "text/plain" ),--- > ( ".htm" , "text/html" ),--- > ( ".html" , "text/html" ),--- > ( ".jar" , "application/x-java-archive" ),--- > ( ".jpeg" , "image/jpeg" ),--- > ( ".jpg" , "image/jpeg" ),--- > ( ".js" , "text/javascript" ),--- > ( ".log" , "text/plain" ),--- > ( ".m3u" , "audio/x-mpegurl" ),--- > ( ".mov" , "video/quicktime" ),--- > ( ".mp3" , "audio/mpeg" ),--- > ( ".mpeg" , "video/mpeg" ),--- > ( ".mpg" , "video/mpeg" ),--- > ( ".ogg" , "application/ogg" ),--- > ( ".pac" , "application/x-ns-proxy-autoconfig" ),--- > ( ".pdf" , "application/pdf" ),--- > ( ".png" , "image/png" ),--- > ( ".ps" , "application/postscript" ),--- > ( ".qt" , "video/quicktime" ),--- > ( ".sig" , "application/pgp-signature" ),--- > ( ".spl" , "application/futuresplash" ),--- > ( ".swf" , "application/x-shockwave-flash" ),--- > ( ".tar" , "application/x-tar" ),--- > ( ".tar.bz2" , "application/x-bzip-compressed-tar" ),--- > ( ".tar.gz" , "application/x-tgz" ),--- > ( ".tbz" , "application/x-bzip-compressed-tar" ),--- > ( ".text" , "text/plain" ),--- > ( ".tgz" , "application/x-tgz" ),--- > ( ".torrent" , "application/x-bittorrent" ),--- > ( ".txt" , "text/plain" ),--- > ( ".wav" , "audio/x-wav" ),--- > ( ".wax" , "audio/x-ms-wax" ),--- > ( ".wma" , "audio/x-ms-wma" ),--- > ( ".wmv" , "video/x-ms-wmv" ),--- > ( ".xbm" , "image/x-xbitmap" ),--- > ( ".xml" , "text/xml" ),--- > ( ".xpm" , "image/x-xpixmap" ),--- > ( ".xwd" , "image/x-xwindowdump" ),--- > ( ".zip" , "application/zip" ) ]+-- Example usage: ---defaultMimeTypes :: MimeMap-defaultMimeTypes = Map.fromList [- ( ".asc" , "text/plain" ),- ( ".asf" , "video/x-ms-asf" ),- ( ".asx" , "video/x-ms-asf" ),- ( ".avi" , "video/x-msvideo" ),- ( ".bz2" , "application/x-bzip" ),- ( ".c" , "text/plain" ),- ( ".class" , "application/octet-stream" ),- ( ".conf" , "text/plain" ),- ( ".cpp" , "text/plain" ),- ( ".css" , "text/css" ),- ( ".cxx" , "text/plain" ),- ( ".dtd" , "text/xml" ),- ( ".dvi" , "application/x-dvi" ),- ( ".gif" , "image/gif" ),- ( ".gz" , "application/x-gzip" ),- ( ".hs" , "text/plain" ),- ( ".htm" , "text/html" ),- ( ".html" , "text/html" ),- ( ".jar" , "application/x-java-archive" ),- ( ".jpeg" , "image/jpeg" ),- ( ".jpg" , "image/jpeg" ),- ( ".js" , "text/javascript" ),- ( ".log" , "text/plain" ),- ( ".m3u" , "audio/x-mpegurl" ),- ( ".mov" , "video/quicktime" ),- ( ".mp3" , "audio/mpeg" ),- ( ".mpeg" , "video/mpeg" ),- ( ".mpg" , "video/mpeg" ),- ( ".ogg" , "application/ogg" ),- ( ".pac" , "application/x-ns-proxy-autoconfig" ),- ( ".pdf" , "application/pdf" ),- ( ".png" , "image/png" ),- ( ".ps" , "application/postscript" ),- ( ".qt" , "video/quicktime" ),- ( ".sig" , "application/pgp-signature" ),- ( ".spl" , "application/futuresplash" ),- ( ".swf" , "application/x-shockwave-flash" ),- ( ".tar" , "application/x-tar" ),- ( ".tar.bz2" , "application/x-bzip-compressed-tar" ),- ( ".tar.gz" , "application/x-tgz" ),- ( ".tbz" , "application/x-bzip-compressed-tar" ),- ( ".text" , "text/plain" ),- ( ".tgz" , "application/x-tgz" ),- ( ".torrent" , "application/x-bittorrent" ),- ( ".ttf" , "application/x-font-truetype" ),- ( ".txt" , "text/plain" ),- ( ".wav" , "audio/x-wav" ),- ( ".wax" , "audio/x-ms-wax" ),- ( ".wma" , "audio/x-ms-wma" ),- ( ".wmv" , "video/x-ms-wmv" ),- ( ".xbm" , "image/x-xbitmap" ),- ( ".xml" , "text/xml" ),- ( ".xpm" , "image/x-xpixmap" ),- ( ".xwd" , "image/x-xwindowdump" ),- ( ".zip" , "application/zip" ) ]----------------------------------------------------------------------------------- | Gets a path from the 'Request' using 'rqPathInfo' and makes sure it is--- safe to use for opening files. A path is safe if it is a relative path--- and has no ".." elements to escape the intended directory structure.-getSafePath :: Snap FilePath-getSafePath = do- req <- getRequest- let p = S.unpack $ rqPathInfo req-- -- relative paths only!- when (not $ isRelative p) pass-- -- check that we don't have any sneaky .. paths- let dirs = splitDirectories p- when (elem ".." dirs) pass-- return $ joinPath dirs------------------------------------------------------------------------------------ | Serves files out of the given directory. The relative path given in--- 'rqPathInfo' is searched for the given file, and the file is served with the--- appropriate mime type if it is found. Absolute paths and \"@..@\" are prohibited--- to prevent files from being served from outside the sandbox.+-- @+-- {-# LANGUAGE OverloadedStrings #-} ----- Uses 'defaultMimeTypes' to determine the @Content-Type@ based on the file's--- extension.-fileServe :: FilePath -- ^ root directory- -> Snap ()-fileServe = fileServe' defaultMimeTypes-{-# INLINE fileServe #-}------------------------------------------------------------------------------------ | Same as 'fileServe', with control over the MIME mapping used.-fileServe' :: MimeMap -- ^ MIME type mapping- -> FilePath -- ^ root directory- -> Snap ()-fileServe' mm root = do- sp <- getSafePath- let fp = root </> sp-- -- check that the file exists- liftIO (doesFileExist fp) >>= flip unless pass-- let fn = takeFileName fp- let mime = fileType mm fn- fileServeSingle' mime fp-{-# INLINE fileServe' #-}------------------------------------------------------------------------------------ | Serves a single file specified by a full or relative path. The--- path restrictions on fileServe don't apply to this function since--- the path is not being supplied by the user.-fileServeSingle :: FilePath -- ^ path to file- -> Snap ()-fileServeSingle fp =- fileServeSingle' (fileType defaultMimeTypes (takeFileName fp)) fp-{-# INLINE fileServeSingle #-}------------------------------------------------------------------------------------ | Same as 'fileServeSingle', with control over the MIME mapping used.-fileServeSingle' :: ByteString -- ^ MIME type mapping- -> FilePath -- ^ path to file- -> Snap ()-fileServeSingle' mime fp = do- reqOrig <- getRequest-- -- If-Range header must be ignored if there is no Range: header in the- -- request (RFC 2616 section 14.27)- let req = if isNothing $ getHeader "range" reqOrig- then deleteHeader "if-range" reqOrig- else reqOrig-- -- check "If-Modified-Since" and "If-Range" headers- let mbH = getHeader "if-modified-since" req- mbIfModified <- liftIO $ case mbH of- Nothing -> return Nothing- (Just s) -> liftM Just $ parseHttpTime s-- -- If-Range header could contain an entity, but then parseHttpTime will- -- fail and return 0 which means a 200 response will be generated anyways- mbIfRange <- liftIO $ case getHeader "if-range" req of- Nothing -> return Nothing- (Just s) -> liftM Just $ parseHttpTime s-- dbg $ "mbIfModified: " ++ Prelude.show mbIfModified- dbg $ "mbIfRange: " ++ Prelude.show mbIfRange-- -- check modification time and bug out early if the file is not modified.- --- -- TODO: a stat cache would be nice here, but it'd need the date thread- -- stuff from snap-server to be folded into snap-core- filestat <- liftIO $ getFileStatus fp- let mt = modificationTime filestat- maybe (return $! ()) (\lt -> when (mt <= lt) notModified) mbIfModified-- let sz = fromIntegral $ fileSize filestat- lm <- liftIO $ formatHttpTime mt-- -- ok, at this point we know the last-modified time and the- -- content-type. set those.- modifyResponse $ setHeader "Last-Modified" lm- . setHeader "Accept-Ranges" "bytes"- . setContentType mime--- -- now check: is this a range request? If there is an 'If-Range' header- -- with an old modification time we skip this check and send a 200 response- let skipRangeCheck = maybe (False)- (\lt -> mt > lt)- mbIfRange-- -- checkRangeReq checks for a Range: header in the request and sends a- -- partial response if it matches.- wasRange <- if skipRangeCheck- then return False- else checkRangeReq req fp sz-- dbg $ "was this a range request? " ++ Prelude.show wasRange-- -- if we didn't have a range request, we just do normal sendfile- unless wasRange $ do- modifyResponse $ setResponseCode 200- . setContentLength sz- sendFile fp-- where- --------------------------------------------------------------------------- notModified = finishWith $- setResponseCode 304 emptyResponse----------------------------------------------------------------------------------fileType :: MimeMap -> FilePath -> ByteString-fileType mm f =- if null ext- then defaultMimeType- else fromMaybe (fileType mm (drop 1 ext))- mbe-- where- ext = takeExtensions f- mbe = Map.lookup ext mm----------------------------------------------------------------------------------defaultMimeType :: ByteString-defaultMimeType = "application/octet-stream"----------------------------------------------------------------------------------data RangeReq = RangeReq { _rangeFirst :: !Int64- , _rangeLast :: !(Maybe Int64)- }- | SuffixRangeReq { _suffixLength :: !Int64 }- deriving (Eq, Prelude.Show)----------------------------------------------------------------------------------rangeParser :: Parser RangeReq-rangeParser = string "bytes=" *>- (byteRangeSpec <|> suffixByteRangeSpec) <*- endOfInput- where- byteRangeSpec = do- start <- parseNum- char '-'- end <- option Nothing $ liftM Just parseNum-- return $ RangeReq start end-- suffixByteRangeSpec = liftM SuffixRangeReq $ char '-' *> parseNum----------------------------------------------------------------------------------checkRangeReq :: Request -> FilePath -> Int64 -> Snap Bool-checkRangeReq req fp sz = do- -- TODO/FIXME: multiple ranges- dbg $ "checkRangeReq, fp=" ++ fp ++ ", sz=" ++ Prelude.show sz- maybe (return False)- (\s -> either (const $ return False)- withRange- (fullyParse s rangeParser))- (getHeader "range" req)-- where- withRange rng@(RangeReq start mend) = do- dbg $ "withRange: got Range request: " ++ Prelude.show rng- let end = fromMaybe (sz-1) mend- dbg $ "withRange: start=" ++ Prelude.show start- ++ ", end=" ++ Prelude.show end-- if start < 0 || end < start || start >= sz || end >= sz- then send416- else send206 start end-- withRange rng@(SuffixRangeReq nbytes) = do- dbg $ "withRange: got Range request: " ++ Prelude.show rng- let end = sz-1- let start = sz - nbytes-- dbg $ "withRange: start=" ++ Prelude.show start- ++ ", end=" ++ Prelude.show end-- if start < 0 || end < start || start >= sz || end >= sz- then send416- else send206 start end-- -- note: start and end INCLUSIVE here- send206 start end = do- dbg "inside send206"- let len = end-start+1- let crng = S.concat $- L.toChunks $- L.concat [ "bytes "- , show start- , "-"- , show end- , "/"- , show sz ]-- modifyResponse $ setResponseCode 206- . setHeader "Content-Range" crng- . setContentLength len-- dbg $ "send206: sending range (" ++ Prelude.show start- ++ "," ++ Prelude.show (end+1) ++ ") to sendFilePartial"-- -- end here was inclusive, sendFilePartial is exclusive- sendFilePartial fp (start,end+1)- return True--- send416 = do- dbg "inside send416"- -- if there's an "If-Range" header in the request, then we just send- -- back 200- if getHeader "If-Range" req /= Nothing- then return False- else do- let crng = S.concat $- L.toChunks $- L.concat ["bytes */", show sz]- - modifyResponse $ setResponseCode 416- . setHeader "Content-Range" crng- . setContentLength 0- . deleteHeader "Content-Type"- . deleteHeader "Content-Encoding"- . deleteHeader "Transfer-Encoding"- . setResponseBody (enumBS "")- - return True--+-- module Main where+--+-- import "Snap.Core" (Snap, route)+-- import "Snap.Http.Server" (quickHttpServe)+-- import "Snap.Util.FileServe"+--+-- site :: Snap ()+-- site = 'Snap.Core.route'+-- [ (\"\/files\", 'serveDirectory' \"static\")+-- , (\"\/simple\", 'serveDirectoryWith' 'simpleDirectoryConfig' \"static\")+-- , (\"\/default\", 'serveDirectoryWith' 'defaultDirectoryConfig' \"static\")+-- , (\"\/fancy\", 'serveDirectoryWith' 'fancyDirectoryConfig' \"static\")+-- , (\"\/paper\", 'serveFile' \"static\/paper.pdf\")+-- , (\"\/thesis\", 'serveFileAs' \"application\/pdf\" \"static\/thesis.pdf\")+-- ]+--+-- main :: IO ()+-- main = 'Snap.Http.Server.quickHttpServe' site+-- @+module Snap.Util.FileServe+ ( -- * Helper functions+ getSafePath+ -- * Configuration for directory serving+ , MimeMap+ , HandlerMap+ , DirectoryConfig(..)+ , simpleDirectoryConfig+ , defaultDirectoryConfig+ , fancyDirectoryConfig+ , defaultIndexGenerator+ , defaultMimeTypes+ , fileType+ -- * File servers+ , serveDirectory+ , serveDirectoryWith+ , serveFile+ , serveFileAs+ ) where -dbg :: (MonadIO m) => String -> m ()-dbg s = debug $ "FileServe:" ++ s+import Snap.Internal.Util.FileServe (DirectoryConfig (..), HandlerMap, MimeMap, defaultDirectoryConfig, defaultIndexGenerator, defaultMimeTypes, fancyDirectoryConfig, fileType, getSafePath, serveDirectory, serveDirectoryWith, serveFile, serveFileAs, simpleDirectoryConfig)
+ src/Snap/Util/FileUploads.hs view
@@ -0,0 +1,125 @@+------------------------------------------------------------------------------+-- | This module contains primitives and helper functions for handling+-- requests with @Content-type: multipart/form-data@, i.e. HTML forms and file+-- uploads.+--+-- Typically most users will want to use 'handleFileUploads', which writes+-- uploaded files to a temporary directory before sending them on to a handler+-- specified by the user.+--+-- Users who wish to handle their file uploads differently can use the+-- lower-level interface called 'handleMultipart'. That function takes+-- uploaded files and streams them to a consumer of the user's choosing.+--+-- Using these functions requires making \"policy\" decisions which Snap can't+-- really make for users, such as \"what's the largest PDF file a user is+-- allowed to upload?\" and \"should we read form inputs into the parameters+-- mapping?\". Policy is specified on a \"global\" basis (using+-- 'UploadPolicy'), and on a per-file basis (using 'PartUploadPolicy', which+-- allows you to reject or limit the size of certain uploaded+-- @Content-type@s).+--+-- Example usage:+--+-- @+-- {-\# LANGUAGE OverloadedStrings #-}+--+-- module Main where+--+-- import qualified Data.ByteString.Char8 as B8+-- import Data.Functor ((\<$>))+-- import "Snap.Core" ('Snap.Core.Snap', 'Snap.Core.route', 'Snap.Core.writeBS')+-- import Snap.Http.Server (quickHttpServe)+-- import "Snap.Util.FileUploads"+-- import System.Posix (FileOffset, fileSize, getFileStatus)+--+-- uploadForm :: 'Snap.Core.Snap' ()+-- uploadForm = 'Snap.Core.writeBS' \"\<form enctype=\\\"multipart\/form-data\\\" action=\\"\/do-upload\\\" method=\\\"POST\\\">\\+-- \\\<input name=\\\"file\\\" type=\\\"file\\\" \/>\\+-- \\\<input type=\\\"submit\\\" value=\\\"Send File\\\" \/>\\+-- \\\<\/form>\"+--+-- getFileSize :: FilePath -> IO FileOffset+-- getFileSize path = fileSize \<$> getFileStatus path+--+-- -- Upload handler that prints out the uploaded file\'s size.+-- doUpload :: 'Snap.Core.Snap' ()+-- doUpload = do+-- l \<- 'handleFileUploads' \"\/tmp\" 'defaultUploadPolicy'+-- (const $ 'allowWithMaximumSize' ('getMaximumFormInputSize' 'defaultUploadPolicy'))+-- (\\pinfo mbfname -> do fsize \<- either (const $ return 0) getFileSize mbfname+-- return ('partFileName' pinfo, fsize))+-- 'writeBS' . B8.pack . show $ l+--+-- site :: 'Snap.Core.Snap' ()+-- site = 'Snap.Core.route'+-- [ (\"\/upload\", uploadForm)+-- , (\"\/do-upload\", doUpload)]+--+-- main :: IO ()+-- main = quickHttpServe site+-- @+module Snap.Util.FileUploads+ ( -- * Functions+ handleFormUploads+ , foldMultipart+ , PartFold+ , FormParam+ , FormFile (..)+ , storeAsLazyByteString+ , withTemporaryStore+ -- ** Backwards compatible API+ , handleFileUploads+ , handleMultipart+ , PartProcessor++ -- * Uploaded parts+ , PartInfo+ , PartDisposition(..)+ , partFieldName+ , partFileName+ , partContentType+ , partHeaders+ , partDisposition++ -- ** Policy+ -- *** General upload policy+ , UploadPolicy+ , defaultUploadPolicy+ , doProcessFormInputs+ , setProcessFormInputs+ , getMaximumFormInputSize+ , setMaximumFormInputSize+ , getMaximumNumberOfFormInputs+ , setMaximumNumberOfFormInputs+ , getMinimumUploadRate+ , setMinimumUploadRate+ , getMinimumUploadSeconds+ , setMinimumUploadSeconds+ , getUploadTimeout+ , setUploadTimeout++ -- *** File upload policy+ , FileUploadPolicy+ , defaultFileUploadPolicy+ , setMaximumFileSize+ , setMaximumNumberOfFiles+ , setSkipFilesWithoutNames+ , setMaximumSkippedFileSize++ -- *** Per-file upload policy+ , PartUploadPolicy+ , disallow+ , allowWithMaximumSize++ -- * Exceptions+ , FileUploadException+ , fileUploadExceptionReason+ , BadPartException+ , badPartExceptionReason+ , PolicyViolationException+ , policyViolationExceptionReason+ ) where+++import Snap.Internal.Util.FileUploads (BadPartException (badPartExceptionReason), FileUploadException, FileUploadPolicy, FormFile (..), FormParam, PartDisposition (..), PartFold, PartInfo (..), PartProcessor, PartUploadPolicy, PolicyViolationException (policyViolationExceptionReason), UploadPolicy, allowWithMaximumSize, defaultFileUploadPolicy, defaultUploadPolicy, disallow, doProcessFormInputs, fileUploadExceptionReason, foldMultipart, getMaximumFormInputSize, getMaximumNumberOfFormInputs, getMinimumUploadRate, getMinimumUploadSeconds, getUploadTimeout, handleFileUploads, handleFormUploads, handleMultipart, setMaximumFileSize, setMaximumFormInputSize, setMaximumNumberOfFiles, setMaximumNumberOfFormInputs, setMaximumSkippedFileSize, setMinimumUploadRate, setMinimumUploadSeconds, setProcessFormInputs, setSkipFilesWithoutNames, setUploadTimeout, storeAsLazyByteString, withTemporaryStore)
src/Snap/Util/GZip.hs view
@@ -1,45 +1,52 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE CPP #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} -module Snap.Util.GZip-( withCompression-, withCompression' ) where--import qualified Codec.Compression.GZip as GZip-import qualified Codec.Compression.Zlib as Zlib-import Control.Concurrent-import Control.Applicative hiding (many)-import Control.Exception-import Control.Monad-import Control.Monad.Trans-import Data.Attoparsec.Char8 hiding (Done)-import qualified Data.ByteString.Lazy.Char8 as L-import Data.ByteString.Char8 (ByteString)-import Data.Iteratee.WrappedByteString-import Data.Maybe-import qualified Data.Set as Set-import Data.Set (Set)-import Data.Typeable-import Prelude hiding (catch, takeWhile)+--------------------------------------------------------------------------------+-- | Helpers for running a 'Snap' web handler with compression. --------------------------------------------------------------------------------import Snap.Internal.Debug-import Snap.Internal.Parsing-import Snap.Iteratee hiding (Enumerator)-import Snap.Types+module Snap.Util.GZip+ ( withCompression+ , withCompression'+ , noCompression+ , BadAcceptEncodingException+ , compressibleMimeTypes+ ) where +import Control.Applicative (Alternative ((<|>), many), Applicative ((*>), (<*), pure), (<$>))+import Control.Exception (Exception, throwIO)+import Control.Monad (Functor (fmap), Monad ((>>), (>>=), return), MonadPlus (mplus), void, when)+import Control.Monad.IO.Class (MonadIO (liftIO))+import Data.Attoparsec.ByteString.Char8 (Parser, char, endOfInput, isAlpha_ascii, isDigit, skipSpace, string, takeWhile, takeWhile1)+import Data.ByteString.Builder (Builder)+import Data.ByteString.Char8 (ByteString)+import qualified Data.ByteString.Char8 as S (takeWhile)+import qualified Data.Char as Char (isSpace)+import Data.Maybe (Maybe (Just, Nothing), fromMaybe, isJust, maybe)+import Data.Set (Set)+import qualified Data.Set as Set (fromList, member)+import Data.Typeable (Typeable)+import Prelude (Either (..), Eq (..), IO, Show (show), id, not, ($), ($!), (&&), (++), (||))+import Snap.Core (MonadSnap, clearContentLength, finishWith, getHeader, getRequest, getResponse, modifyResponse, modifyResponseBody, setHeader)+import Snap.Internal.Debug (debug)+import Snap.Internal.Parsing (fullyParse)+import System.IO.Streams (OutputStream)+import qualified System.IO.Streams as Streams (compressBuilder, gzipBuilder) ------------------------------------------------------------------------------ -- | Runs a 'Snap' web handler with compression if available. ----- If the client has indicated support for @gzip@ or @compress@ in its+-- If the client has indicated support for @gzip@ or @deflate@ in its -- @Accept-Encoding@ header, and the @Content-Type@ in the response is one of -- the following types: -- -- * @application/x-javascript@ --+-- * @application/json@+-- -- * @text/css@ -- -- * @text/html@@@ -54,26 +61,54 @@ -- -- Then the given handler's output stream will be compressed, -- @Content-Encoding@ will be set in the output headers, and the--- @Content-Length@ will be cleared if it was set. (We can't process the stream--- in O(1) space if the length is known beforehand.)+-- @Content-Length@ will be cleared if it was set. (We can't process the+-- stream in O(1) space if the length is known beforehand.) -- -- The wrapped handler will be run to completion, and then the 'Response' -- that's contained within the 'Snap' monad state will be passed to -- 'finishWith' to prevent further processing. ---withCompression :: Snap a -- ^ the web handler to run- -> Snap ()+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> import qualified "Snap.Test" as T+-- ghci> let r = T.get \"/\" M.empty >> T.addHeader \"Accept-Encoding\" \"gzip,deflate\"+-- ghci> let h = 'Snap.Core.modifyResponse' ('Snap.Core.setContentType' \"text\/plain\") >> 'Snap.Core.writeBS' \"some text\"+-- ghci> T.runHandler r h+-- HTTP\/1.1 200 OK+-- content-type: text\/plain+-- server: Snap\/test+-- date: Fri, 08 Aug 2014 15:40:45 GMT+--+-- some text+-- ghci> T.runHandler r ('withCompression' h)+-- HTTP\/1.1 200 OK+-- content-type: text\/plain+-- vary: Accept-Encoding+-- content-encoding: gzip+-- server: Snap\/test+-- date: Fri, 08 Aug 2014 15:40:10 GMT+--+--+-- @+withCompression :: MonadSnap m+ => m a -- ^ the web handler to run+ -> m () withCompression = withCompression' compressibleMimeTypes ------------------------------------------------------------------------------ -- | The same as 'withCompression', with control over which MIME types to -- compress.-withCompression' :: Set ByteString+withCompression' :: MonadSnap m+ => Set ByteString -- ^ set of compressible MIME types- -> Snap a+ -> m a -- ^ the web handler to run- -> Snap ()+ -> m () withCompression' mimeTable action = do _ <- action resp <- getResponse@@ -81,7 +116,7 @@ -- If a content-encoding is already set, do nothing. This prevents -- "withCompression $ withCompression m" from ruining your day. when (not $ isJust $ getHeader "Content-Encoding" resp) $ do- let mbCt = getHeader "Content-Type" resp+ let mbCt = fmap chop $ getHeader "Content-Type" resp debug $ "withCompression', content-type is " ++ show mbCt @@ -93,7 +128,8 @@ getResponse >>= finishWith where- chkAcceptEncoding :: Snap ()+ chop = S.takeWhile (\c -> c /= ';' && not (Char.isSpace c))+ chkAcceptEncoding = do req <- getRequest debug $ "checking accept-encoding"@@ -103,18 +139,28 @@ types <- liftIO $ parseAcceptEncoding s - chooseType types+ chooseType Nothing types + chooseType !m [] = maybe (return $! ()) id m+ chooseType !_ ("gzip":_) = gzipCompression "gzip"+ chooseType !m ("deflate":xs) =+ chooseType (m `mplus` Just (compressCompression "deflate")) xs - chooseType [] = return $! ()- chooseType ("gzip":_) = gzipCompression "gzip"- chooseType ("compress":_) = compressCompression "compress"- chooseType ("x-gzip":_) = gzipCompression "x-gzip"- chooseType ("x-compress":_) = compressCompression "x-compress"- chooseType (_:xs) = chooseType xs+ chooseType !_ ("x-gzip":_) = gzipCompression "x-gzip"+ chooseType !m ("x-deflate":xs) =+ chooseType (m `mplus` Just (compressCompression "x-deflate")) xs+ chooseType !m (_:xs) = chooseType m xs ------------------------------------------------------------------------------+-- | Turn off compression by setting \"Content-Encoding: identity\" in the+-- response headers. 'withCompression' is a no-op when a content-encoding is+-- already set.+noCompression :: MonadSnap m => m ()+noCompression = modifyResponse $ setHeader "Content-Encoding" "identity"+++------------------------------------------------------------------------------ -- private following ------------------------------------------------------------------------------ @@ -123,6 +169,7 @@ compressibleMimeTypes :: Set ByteString compressibleMimeTypes = Set.fromList [ "application/x-font-truetype" , "application/x-javascript"+ , "application/json" , "text/css" , "text/html" , "text/javascript"@@ -133,127 +180,37 @@ -------------------------------------------------------------------------------gzipCompression :: ByteString -> Snap ()+gzipCompression :: MonadSnap m => ByteString -> m () gzipCompression ce = modifyResponse f where- f = setHeader "Content-Encoding" ce .- setHeader "Vary" "Accept-Encoding" .- clearContentLength .- modifyResponseBody gcompress+ f r = setHeader "Content-Encoding" ce $+ setHeader "Vary" "Accept-Encoding" $+ clearContentLength $+ modifyResponseBody gcompress r -------------------------------------------------------------------------------compressCompression :: ByteString -> Snap ()+compressCompression :: MonadSnap m => ByteString -> m () compressCompression ce = modifyResponse f where- f = setHeader "Content-Encoding" ce .- setHeader "Vary" "Accept-Encoding" .- clearContentLength .- modifyResponseBody ccompress----------------------------------------------------------------------------------gcompress :: forall a . Enumerator a -> Enumerator a-gcompress = compressEnumerator GZip.compress+ f r = setHeader "Content-Encoding" ce $+ setHeader "Vary" "Accept-Encoding" $+ clearContentLength $+ modifyResponseBody ccompress r -------------------------------------------------------------------------------ccompress :: forall a . Enumerator a -> Enumerator a-ccompress = compressEnumerator Zlib.compress+gcompress :: (OutputStream Builder -> IO (OutputStream Builder))+ -> OutputStream Builder+ -> IO (OutputStream Builder)+gcompress body stream = Streams.gzipBuilder 5 stream >>= body -------------------------------------------------------------------------------compressEnumerator :: forall a .- (L.ByteString -> L.ByteString)- -> Enumerator a- -> Enumerator a-compressEnumerator compFunc enum iteratee = do- writeEnd <- newChan- readEnd <- newChan- tid <- forkIO $ threadProc readEnd writeEnd-- enum (IterateeG $ f readEnd writeEnd tid iteratee)-- where- --------------------------------------------------------------------------- streamFinished :: Stream -> Bool- streamFinished (EOF _) = True- streamFinished (Chunk _) = False--- --------------------------------------------------------------------------- consumeSomeOutput :: Chan Stream- -> Iteratee IO a- -> IO (Iteratee IO a)- consumeSomeOutput writeEnd iter = do- e <- isEmptyChan writeEnd- if e- then return iter- else do- ch <- readChan writeEnd-- iter' <- liftM liftI $ runIter iter ch- consumeSomeOutput writeEnd iter'--- --------------------------------------------------------------------------- consumeRest :: Chan Stream- -> Iteratee IO a- -> IO (IterV IO a)- consumeRest writeEnd iter = do- ch <- readChan writeEnd-- iv <- runIter iter ch- if (streamFinished ch)- then return iv- else consumeRest writeEnd $ liftI iv--- --------------------------------------------------------------------------- f readEnd writeEnd tid i (EOF Nothing) = do- writeChan readEnd Nothing- x <- consumeRest writeEnd i- killThread tid- return x-- f _ _ tid _ (EOF (Just e)) = do- killThread tid- return $ Cont undefined (Just e)-- f readEnd writeEnd tid i (Chunk s') = do- let s = unWrap s'- writeChan readEnd $ Just s- i' <- consumeSomeOutput writeEnd i- return $ Cont (IterateeG $ f readEnd writeEnd tid i') Nothing--- --------------------------------------------------------------------------- threadProc :: Chan (Maybe ByteString)- -> Chan Stream- -> IO ()- threadProc readEnd writeEnd = do- stream <- getChanContents readEnd-- let bs = L.fromChunks $ streamToChunks stream- let output = L.toChunks $ compFunc bs-- runIt output `catch` \(e::SomeException) ->- writeChan writeEnd $ EOF (Just $ Err $ show e)-- where- runIt (x:xs) = do- writeChan writeEnd (toChunk x) >> runIt xs-- runIt [] = do- writeChan writeEnd $ EOF Nothing- --------------------------------------------------------------------------- streamToChunks [] = []- streamToChunks (Nothing:_) = []- streamToChunks ((Just x):xs) = x:(streamToChunks xs)--- --------------------------------------------------------------------------- toChunk = Chunk . WrapBS+ccompress :: (OutputStream Builder -> IO (OutputStream Builder))+ -> OutputStream Builder+ -> IO (OutputStream Builder)+ccompress body stream = Streams.compressBuilder 5 stream >>= body ------------------------------------------------------------------------------@@ -261,37 +218,38 @@ -- that order. acceptParser :: Parser [ByteString] acceptParser = do- xs <- option [] $ (:[]) <$> encoding+ xs <- ((:[]) <$> encoding) <|> (return $! []) ys <- many (char ',' *> encoding) endOfInput- return $ xs ++ ys+ return $! xs ++ ys where encoding = skipSpace *> c <* skipSpace c = do x <- coding- option () qvalue+ qvalue <|> (return $! ()) return x qvalue = do skipSpace- char ';'+ void $! char ';' skipSpace- char 'q'+ void $! char 'q' skipSpace- char '='+ void $! char '=' float- return ()+ return $! () - coding = string "*" <|> takeWhile isCodingChar+ coding = string "*" <|> takeWhile1 isCodingChar isCodingChar ch = isDigit ch || isAlpha_ascii ch || ch == '-' || ch == '_' float = takeWhile isDigit >>- option () (char '.' >> takeWhile isDigit >> pure ())+ (char '.' >> takeWhile isDigit >> (pure $! ())) <|> (pure $! ()) ------------------------------------------------------------------------------+-- | Thrown when the 'Accept-Encoding' request header has invalid format. data BadAcceptEncodingException = BadAcceptEncodingException deriving (Typeable) @@ -313,4 +271,3 @@ Right x -> return x where r = fullyParse s acceptParser-
+ src/Snap/Util/Proxy.hs view
@@ -0,0 +1,90 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE OverloadedStrings #-}++-- | This module provides facilities for patching incoming 'Requests' to+-- correct the value of 'rqClientAddr' if the snap server is running behind a+-- proxy.+--+-- Example usage:+--+-- @+-- m :: Snap ()+-- m = undefined -- code goes here+--+-- applicationHandler :: Snap ()+-- applicationHandler = behindProxy X_Forwarded_For m+-- @+--+module Snap.Util.Proxy+ ( ProxyType(..)+ , behindProxy+ ) where++------------------------------------------------------------------------------+import Control.Applicative (Alternative ((<|>)))+import Control.Monad (mfilter)+import qualified Data.ByteString.Char8 as S (breakEnd, dropWhile, null, readInt, spanEnd)+import Data.Char (isSpace)+import Data.Maybe (fromMaybe)+import Snap.Core (MonadSnap, Request (rqClientAddr, rqClientPort), getHeader, modifyRequest)+------------------------------------------------------------------------------+++------------------------------------------------------------------------------+-- | What kind of proxy is this? Affects which headers 'behindProxy' pulls the+-- original remote address from.+--+-- Currently only proxy servers that send @X-Forwarded-For@ or @Forwarded-For@+-- are supported.+data ProxyType = NoProxy -- ^ no proxy, leave the request alone+ | X_Forwarded_For -- ^ Use the @Forwarded-For@ or+ -- @X-Forwarded-For@ header+ deriving (Read, Show, Eq, Ord)+++------------------------------------------------------------------------------+-- | Rewrite 'rqClientAddr' if we're behind a proxy.+--+-- Example:+--+-- @+-- ghci> :set -XOverloadedStrings+-- ghci> import qualified "Data.Map" as M+-- ghci> import qualified "Snap.Test" as T+-- ghci> let r = T.get \"\/foo\" M.empty >> T.addHeader \"X-Forwarded-For\" \"1.2.3.4\"+-- ghci> let h = 'Snap.Core.getsRequest' 'rqClientAddr' >>= 'Snap.Core.writeBS')+-- ghci> T.runHandler r h+-- HTTP\/1.1 200 OK+-- server: Snap\/test+-- date: Fri, 08 Aug 2014 14:32:29 GMT+--+-- 127.0.0.1+-- ghci> T.runHandler r ('behindProxy' 'X_Forwarded_For' h)+-- HTTP\/1.1 200 OK+-- server: Snap\/test+-- date: Fri, 08 Aug 2014 14:33:02 GMT+--+-- 1.2.3.4+-- @+behindProxy :: MonadSnap m => ProxyType -> m a -> m a+behindProxy NoProxy = id+behindProxy X_Forwarded_For = ((modifyRequest xForwardedFor) >>)+{-# INLINE behindProxy #-}+++------------------------------------------------------------------------------+xForwardedFor :: Request -> Request+xForwardedFor req = req { rqClientAddr = ip+ , rqClientPort = port+ }+ where+ extract = fst . S.spanEnd isSpace . S.dropWhile isSpace . snd . S.breakEnd (== ',')++ ip = fromMaybe (rqClientAddr req) $ mfilter (not . S.null) $ fmap extract $+ getHeader "Forwarded-For" req <|>+ getHeader "X-Forwarded-For" req++ port = maybe (rqClientPort req) fst $ (S.readInt =<<) $ fmap extract $+ getHeader "Forwarded-Port" req <|>+ getHeader "X-Forwarded-Port" req+{-# INLINE xForwardedFor #-}
+ test/Snap/Core/Tests.hs view
@@ -0,0 +1,954 @@+{-# OPTIONS_GHC -fno-warn-warnings-deprecations #-}++-- Deprecations warnings turned off due to ErrorT deprecation+--+-- It's a pain that this setting is per-module, because we might end up hiding+-- deprecation warnings that we want to see. TODO: move any code that emits+-- these warnings to isolated modules.++{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE Rank2Types #-}+{-# LANGUAGE ScopedTypeVariables #-}++------------------------------------------------------------------------------+module Snap.Core.Tests ( tests ) where+------------------------------------------------------------------------------+import Control.Applicative (Alternative ((<|>)), Applicative ((<*>), pure), (<$>))+import Control.Concurrent.MVar (newEmptyMVar, putMVar, takeMVar)+import Control.DeepSeq (deepseq)+import Control.Exception.Lifted (ErrorCall (..), Exception, SomeException (..), catch, fromException, mask, throwIO, try)+import Control.Monad (Functor (fmap), Monad ((>>), (>>=), return), MonadPlus (mplus, mzero), forM_, liftM, void)+import Control.Monad.Base (MonadBase (liftBase))+import Control.Monad.IO.Class (liftIO)+#if !MIN_VERSION_transformers(0,6,0)+import Control.Monad.Trans.Error (ErrorT (runErrorT))+#endif+import Data.ByteString.Builder (byteString)+#if MIN_VERSION_transformers(0,4,0)+import Control.Monad.Trans.Except (runExceptT)+#endif+#if !MIN_VERSION_transformers(0,6,0)+import Control.Monad.Trans.List (ListT (runListT))+#endif+import Control.Monad.Trans.Reader (ReaderT (runReaderT))+import qualified Control.Monad.Trans.RWS.Lazy as LRWS (RWST (runRWST))+import Control.Monad.Trans.RWS.Strict (RWST (runRWST))+import qualified Control.Monad.Trans.State.Lazy as LState (evalStateT)+import Control.Monad.Trans.State.Strict (evalStateT)+import qualified Control.Monad.Trans.Writer.Lazy as LWriter (WriterT (runWriterT))+import Control.Monad.Trans.Writer.Strict (WriterT (runWriterT))+import Control.Parallel.Strategies (rdeepseq, using)+import Data.ByteString.Char8 (ByteString)+import qualified Data.ByteString.Char8 as S (length, pack, replicate)+import qualified Data.ByteString.Lazy.Char8 as L (ByteString, fromChunks, isPrefixOf)+import qualified Data.IntMap as IM (toList)+import Data.IORef (newIORef, readIORef, writeIORef)+import qualified Data.Map as Map (empty, fromList)+import Data.Maybe (isJust)+import Data.Monoid (mappend)+import Data.Text (Text)+import qualified Data.Text.Encoding as T (encodeUtf8)+import Data.Text.Lazy ()+import Prelude (Bool (..), Either (..), Enum (..), Eq (..), IO, Int, Maybe (Just, Nothing), Num (..), Show (..), String, const, either, fail, flip, id, map, maybe, not, seq, undefined, ($), ($!), (&&), (++), (.))+import Snap.Internal.Core (EscapeSnap (..), MonadSnap (..), NoHandlerException (NoHandlerException), Snap, addToOutput, bracketSnap, catchFinishWith, dir, escapeHttp, evalSnap, finishWith, getParam, getParams, getPostParam, getPostParams, getQueryParam, getQueryParams, getRequest, getResponse, getsResponse, ifTop, ipHeaderFilter, localRequest, logError, method, methods, modifyResponse, pass, path, pathArg, putRequest, putResponse, readRequestBody, redirect, redirect', runRequestBody, runSnap, setTimeout, terminateConnection, transformRequestBody, updateContextPath, withRequest, withResponse, writeBS, writeLBS, writeLazyText, writeText)+import Snap.Internal.Http.Types (Cookie (Cookie), Method (..), Request (rqBody, rqClientAddr, rqContextPath, rqIsSecure, rqURI), Response (rspContentLength, rspStatus, rspStatusReason, rspTransformingRqBody), addHeader, deleteHeader, emptyResponse, getHeader, rqRemoteAddr, setContentLength, setHeader, setResponseCode, setResponseStatus, statusReasonMap)+import Snap.Internal.Parsing (urlDecode, urlEncode)+import qualified Snap.Test as Test (RequestType (RequestWithRawBody), buildRequest, evalHandler, get, getResponseBody, postRaw, runHandler, setRequestType)+import Snap.Test.Common (coverEqInstance, coverOrdInstance, coverReadInstance, coverShowInstance, coverTypeableInstance, expectExceptionH, forceSameType, waitabit)+import System.IO.Streams (InputStream)+import qualified System.IO.Streams as Streams (fromList, makeInputStream, nullInput, nullOutput, read, throwIfTooSlow, toList, write)+import Test.Framework (Test)+import Test.Framework.Providers.HUnit (testCase)+import Test.Framework.Providers.QuickCheck2 (testProperty)+import Test.HUnit (Assertable (assert), assertBool, assertEqual, assertFailure)+import Test.QuickCheck (Gen, arbitrary, elements, oneof, variant)+------------------------------------------------------------------------------+++tests :: [Test]+tests = [ testFail+ , testAlternative+ , testEarlyTermination+ , testEscapeHttp+ , testCatchFinishWith+ , testRqBody+ , testRqBodyException+ , testRqBodyTermination+ , testRqBodyTooLong+ , testRqBodyTooSlow+ , testTrivials+ , testMethod+ , testMethods+ , testMethodEq+ , testMethodNotEq+ , testDir+ , testCatchIO+ , testWrites+ , testParam+ , testURLEncode1+ , testURLEncode2+ , testDir2+ , testIpHeaderFilter+ , testMZero404+ , testEvalSnap+ , testLocalRequest+ , testRedirect+ , testBracketSnap+ , testCoverInstances+ , testPathArgs+ , testStateTAndExceptions+ , test304Fixup+ , testChunkedFixup+ ]+++------------------------------------------------------------------------------+expectSpecificException :: Exception e => e -> IO a -> IO ()+expectSpecificException e0 m = do+ r <- try m++ let b = either (\se -> isJust $+ forceSameType (Just e0) (fromException se))+ (const False)+ r+ assertBool ("expected specific exception: " ++ show e0) b+++------------------------------------------------------------------------------+expect404 :: IO (Request,Response) -> IO ()+expect404 m = do+ (_,r) <- m+ assertBool "expected 404" (rspStatus r == 404)+++------------------------------------------------------------------------------+expectNo404 :: IO (Request,Response) -> IO ()+expectNo404 m = do+ (_,r) <- m+ assertBool ("expected 200, got " ++ show (rspStatus r))+ (rspStatus r /= 404)+++------------------------------------------------------------------------------+mkRequest :: ByteString -> IO Request+mkRequest uri = Test.buildRequest $ Test.get uri Map.empty+++------------------------------------------------------------------------------+mkRequestQuery :: ByteString -> ByteString -> [ByteString] -> IO Request+mkRequestQuery uri k v =+ Test.buildRequest $ Test.get uri $ Map.fromList [(k,v)]+++------------------------------------------------------------------------------+mkZomgRq :: IO Request+mkZomgRq = Test.buildRequest $ Test.get "/" Map.empty+++------------------------------------------------------------------------------+mkMethodRq :: Method -> IO Request+mkMethodRq m = Test.buildRequest $ do+ Test.get "/" Map.empty+ Test.setRequestType $ Test.RequestWithRawBody m ""+++------------------------------------------------------------------------------+mkIpHeaderRq :: IO Request+mkIpHeaderRq = do+ rq <- mkZomgRq+ return $ setHeader "X-Forwarded-For" "1.2.3.4"+ $ deleteHeader "X-Forwarded-For"+ $ setHeader "X-Forwarded-For" "1.2.3.4" rq+++------------------------------------------------------------------------------+mkRqWithBody :: IO Request+mkRqWithBody = Test.buildRequest $ Test.postRaw "/" "text/plain" "zazzle"+++------------------------------------------------------------------------------+mkRqWithEnum :: (InputStream ByteString) -> IO Request+mkRqWithEnum str = do+ rq <- Test.buildRequest $ Test.postRaw "/" "text/plain" ""++ return $! rq { rqBody = str }+++------------------------------------------------------------------------------+testCatchIO :: Test+testCatchIO = testCase "core/catchIO" $ do+ (_,rsp) <- go f+ (_,rsp2) <- go g++ assertEqual "catchIO 1" (Just "bar") $ getHeader "foo" rsp+ assertEqual "catchIO 2" Nothing $ getHeader "foo" rsp2++ where+ f :: Snap ()+ f = (mask $ \restore -> restore $ throwIO $ NoHandlerException "")+ `catch` h++ g :: Snap ()+ g = return () `catch` h++ h :: SomeException -> Snap ()+ h e = e `seq` modifyResponse $ addHeader "foo" "bar"+++------------------------------------------------------------------------------+go :: Snap a -> IO (Request,Response)+go m = do+ zomgRq <- mkZomgRq+ runSnap m dummy timeoutModifier zomgRq+ where+ dummy !x = return $! (show x `using` rdeepseq) `seq` ()+ timeoutModifier !f = return $! f 0 `seq` ()+++------------------------------------------------------------------------------+goMeth :: Method -> Snap a -> IO (Request,Response)+goMeth m s = do+ methRq <- mkMethodRq m+ runSnap s dummy timeoutModifier methRq+ where+ dummy !x = return $! (show x `using` rdeepseq) `seq` ()+ timeoutModifier !f = return $! f 0 `seq` ()+++------------------------------------------------------------------------------+goIP :: Snap a -> IO (Request,Response)+goIP m = do+ rq <- mkIpHeaderRq+ runSnap m dummy timeoutModifier rq+ where+ timeoutModifier !f = return $! f 0 `seq` ()+ dummy = const $ return ()+++------------------------------------------------------------------------------+goPath :: ByteString -> Snap a -> IO (Request,Response)+goPath s m = do+ rq <- mkRequest s+ runSnap m dummy timeoutModifier rq+ where+ timeoutModifier !f = return $! f 0 `seq` ()+ dummy = const $ return ()+++------------------------------------------------------------------------------+goPathQuery :: ByteString+ -> ByteString+ -> [ByteString]+ -> Snap a+ -> IO (Request,Response)+goPathQuery s k v m = do+ rq <- mkRequestQuery s k v+ runSnap m dummy (const (return ())) rq+ where+ dummy = const $ return ()+++------------------------------------------------------------------------------+goBody :: Snap a -> IO (Request,Response)+goBody m = do+ rq <- mkRqWithBody+ runSnap m dummy (const (return ())) rq+ where+ dummy = const $ return ()+++------------------------------------------------------------------------------+goEnum :: InputStream ByteString+ -> Snap b+ -> IO (Request,Response)+goEnum enum m = do+ rq <- mkRqWithEnum enum+ runSnap m logerr tout rq+++------------------------------------------------------------------------------+testFail :: Test+testFail = testCase "failure" $ expect404 (go pass)+++------------------------------------------------------------------------------+setFoo :: ByteString -> Snap ()+setFoo s = do+ modifyResponse (addHeader "Foo" s)+ fmap id $ pure ()+ pure id <*> (liftIO $ return ())+ !x <- liftBase $! return $! ()+ return $! x+++------------------------------------------------------------------------------+testAlternative :: Test+testAlternative = testCase "core/alternative" $ do+ (_,resp) <- go (pass <|> setFoo "Bar")+ assertEqual "foo present" (Just "Bar") $ getHeader "Foo" resp++ (_,resp2) <- go (fail "fail should not error"+ <|> fail2+ <|> setFoo "Bar"+ <|> setFoo "Baz")+ assertEqual "alternative chooses correct branch"+ (Just "Bar") $ getHeader "Foo" resp2++ where+ fail2 :: Snap ()+ fail2 = pass >>= \_ -> return ()+++------------------------------------------------------------------------------+sampleResponse :: Response+sampleResponse = addHeader "Foo" "Quux" $ emptyResponse+++------------------------------------------------------------------------------+testEarlyTermination :: Test+testEarlyTermination = testCase "core/earlyTermination" $ do+ (_,resp) <- go (finishWith sampleResponse >>= \_ -> setFoo "Bar")+ assertEqual "foo" (Just "Quux") $ getHeader "Foo" resp+++------------------------------------------------------------------------------+testStateTAndExceptions :: Test+testStateTAndExceptions = testCase "core/stateT_exceptions" $ do+ Test.evalHandler (return ()) h1 >>= assertEqual "h1" True+ Test.evalHandler (return ()) h2 >>= assertEqual "h2" True+ Test.evalHandler (return ()) h3 >>= assertEqual "h3" True+ Test.evalHandler (return ()) h4 >>= assertEqual "h4" True+ Test.evalHandler (return ()) h5 >>= assertEqual "h5" True++ where+ useState = do rq <- getRequest+ return (rqURI rq == "/")++ h1 = do let m = ((try mzero) :: Snap (Either SomeException Int))+ (m >> return False) <|> useState++ h2 = do catchFinishWith (getResponse >>= finishWith)+ useState++ h3 = do catchFinishWith (return ())+ useState++ h4 = do (void (catchFinishWith mzero)) <|> return ()+ useState++ h5 = do let m1 = (void (getResponse >>= finishWith)) `mplus` return ()+ void (catchFinishWith m1)+ useState+++------------------------------------------------------------------------------+testEscapeHttp :: Test+testEscapeHttp = testCase "core/escapeHttp" $ flip catch catchEscape $ do+ (_, _) <- go (escapeHttp escaper)+ assertFailure "HTTP escape was ignored"+ where+ escaper _ _ _ = liftIO $ assert True+ tickle _ = return ()++ catchEscape (ex :: EscapeSnap) =+ case ex of+ EscapeHttp e -> do+ input <- Streams.nullInput+ output <- Streams.nullOutput+ e tickle input output+ _ -> assertFailure "got TerminateConnection"+++------------------------------------------------------------------------------+isLeft :: Either a b -> Bool+isLeft (Left _) = True+isLeft _ = False+++------------------------------------------------------------------------------+isRight :: Either a b -> Bool+isRight (Right _) = True+isRight _ = False+++------------------------------------------------------------------------------+logerr :: ByteString -> IO ()+logerr !_ = return $! ()+++------------------------------------------------------------------------------+tout :: (Int -> Int) -> IO ()+tout !f = let !_ = f 2 in return $! ()+++------------------------------------------------------------------------------+testBracketSnap :: Test+testBracketSnap = testCase "core/bracketSnap" $ do+ rq <- mkZomgRq++ ref <- newIORef 0++ expectSpecificException (NoHandlerException "") $+ evalSnap (act ref) logerr tout rq++ y <- readIORef ref+ assertEqual "bracketSnap/after1" (1::Int) y++ expectSpecificException (ErrorCall "no value") $+ evalSnap (act ref <|> finishWith emptyResponse)+ logerr+ tout+ rq++ y' <- readIORef ref+ assertEqual "bracketSnap/after" 2 y'+++ expectSpecificException (ErrorCall "foo") $+ evalSnap (act2 ref)+ logerr+ tout+ rq++ y'' <- readIORef ref+ assertEqual "bracketSnap/after" 3 y''++ where+ act ref = bracketSnap (liftIO $ readIORef ref)+ (\z -> liftIO $ writeIORef ref $! z+1)+ (\z -> z `seq` mzero)++ act2 ref = bracketSnap (liftIO $ readIORef ref)+ (\z -> liftIO $ writeIORef ref $! z+1)+ (\z -> z `seq` liftIO $ throwIO $ ErrorCall "foo")+++------------------------------------------------------------------------------+testCatchFinishWith :: Test+testCatchFinishWith = testCase "core/catchFinishWith" $ do+ rq <- mkZomgRq+ x <- evalSnap (catchFinishWith $ finishWith emptyResponse)+ logerr tout rq+ assertBool "catchFinishWith" $ isLeft x+ let (Left resp) = x+ assertEqual "code" 200 (rspStatus resp)++ y <- evalSnap (catchFinishWith $ return $! ())+ logerr tout rq+ assertBool "catchFinishWith" $ isRight y+ let (Right val) = y+ assertEqual "val" () val++ expectExceptionH $ evalSnap (catchFinishWith pass) logerr tout rq+++------------------------------------------------------------------------------+testRqBody :: Test+testRqBody = testCase "core/requestBodies" $ do+ mvar1 <- newEmptyMVar+ mvar2 <- newEmptyMVar++ _ <- goBody $ f mvar1 mvar2++ v1 <- takeMVar mvar1+ v2 <- takeMVar mvar2++ assertEqual "rq body" "zazzle" v1+ assertEqual "rq body 2" "" v2++ (_,rsp) <- goBody (g >> putResponse emptyResponse)+ bd <- getBody rsp++ assertEqual "detached rq body" "zazzle" bd+ assertBool "transforming" (rspTransformingRqBody rsp)++ where+ f mvar1 mvar2 = do+ readRequestBody 100000 >>= liftIO . putMVar mvar1+ readRequestBody 100000 >>= liftIO . putMVar mvar2++ g = transformRequestBody (return . id)+++------------------------------------------------------------------------------+testRqBodyTooLong :: Test+testRqBodyTooLong = testCase "core/requestBodyTooLong" $ do+ expectExceptionH $ goBody $ f 2+ (_, rsp) <- goBody $ f 200000+ bd <- getBody rsp+ assertEqual "detached rq body" "zazzle" bd+ where+ f sz = readRequestBody sz >>= writeLBS+++------------------------------------------------------------------------------+testRqBodyException :: Test+testRqBodyException = testCase "core/requestBodyException" $ do+ str <- Streams.fromList listData+ (req,resp) <- goEnum str hndlr+ bd <- getBody resp+ b' <- Streams.toList $ rqBody req+ assertEqual "request body was consumed" [] b'+ assertEqual "response body was produced" "OK" bd+ where+ listData = ["the", "quick", "brown", "fox"]++ h0 = runRequestBody $ \str -> do+ !_ <- Streams.read str+ throwIO $ ErrorCall "foo"++ hndlr = h0 `catch` \(_::SomeException) -> writeBS "OK"+++------------------------------------------------------------------------------+testRqBodyTooSlow :: Test+testRqBodyTooSlow = testCase "core/requestBodyTooSlow" $ do+ str <- Streams.makeInputStream strFunc >>=+ Streams.throwIfTooSlow (return ()) 100000.0 1+ expectExceptionH (goEnum str hndlr)++ where+ strFunc = waitabit >> return (Just "1")+ hndlr = runRequestBody $ \_ -> throwIO $ ErrorCall "foo"+++------------------------------------------------------------------------------+testRqBodyTermination :: Test+testRqBodyTermination = testCase "core/requestBodyTermination" $ do+ str <- Streams.fromList ["the", "quick", "brown", "fox"]+ expectExceptionH $ goEnum str h0+ where+ h0 = (runRequestBody $ \str -> do+ !_ <- Streams.read str+ throwIO $ TerminateConnection+ $ SomeException $ ErrorCall "foo")+ `catch` tc++ tc (ex :: EscapeSnap) = case ex of+ TerminateConnection e -> terminateConnection e+ _ -> throwIO ex+++------------------------------------------------------------------------------+testTrivials :: Test+testTrivials = testCase "core/trivials" $ do+ (rq,rsp) <- go $ do+ req <- getRequest+ putRequest $ req { rqIsSecure=True }+ putResponse $ setResponseStatus 333 "333" sampleResponse+ r <- getResponse+ liftIO $ assertEqual "rsp status" 333 $ rspStatus r+ code <- getsResponse rspStatus+ liftIO $ assertEqual "rsp status 2" 333 code+ !_ <- localRequest (\x -> x {rqIsSecure=False}) $ do+ q <- getRequest+ liftIO $ assertEqual "localrq" False $ rqIsSecure q+ return ()++ !_ <- logError "foo"+ writeText "zzz"+ writeLazyText "zzz"++ let req' = updateContextPath 0 req+ let cp1 = rqContextPath req+ let cp2 = rqContextPath req'++ setTimeout 30+ !_ <- getParams+ !_ <- getPostParams+ !_ <- getQueryParams++ liftIO $ assertEqual "updateContextPath 0" cp1 cp2++ withRequest $ return . (`seq` ())+ withResponse $ return . (`seq` ())++ return ()++ b <- getBody rsp+ coverShowInstance b+ coverShowInstance $ NoHandlerException ""+ coverShowInstance GET+ coverReadInstance GET+ coverEqInstance GET+ coverEqInstance $ NoHandlerException ""+ coverOrdInstance GET++ Prelude.map (\(x,y) -> (x,show y)) (IM.toList statusReasonMap)+ `deepseq` return ()++ let cookie = Cookie "" "" Nothing Nothing Nothing False False+ coverEqInstance cookie+ coverShowInstance cookie++ assertEqual "rq secure" True $ rqIsSecure rq+ assertEqual "rsp status" 333 $ rspStatus rsp+ coverTypeableInstance (undefined :: Snap ())+ coverShowInstance (EscapeHttp undefined)++ -- number serialization+ forM_ [ 0, 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 10000000000+ , 100000000000+ ] $ \i -> do+ let s = show i+ rsp_cl <- Test.runHandler (return ()) (clHandler i)+ assertEqual ("number " ++ s)+ (Just $ S.pack s)+ (getHeader "content-length" rsp_cl)+ where+ clHandler i = do modifyResponse (setContentLength i)+ writeBS (S.replicate (fromEnum i) ' ')++------------------------------------------------------------------------------+testMethod :: Test+testMethod = testCase "core/method" $ do+ expect404 $ go (method POST $ return ())+ expectNo404 $ go (method GET $ return ())+++------------------------------------------------------------------------------+testMethods :: Test+testMethods = testCase "core/methods" $ do+ expect404 $ go (methods [POST,PUT,PATCH,Method "MOVE"] $ return ())+ expectNo404 $ go (methods [GET] $ return ())+ expectNo404 $ go (methods [POST,GET] $ return ())+ expectNo404 $ go (methods [PUT,GET] $ return ())+ expectNo404 $ go (methods [GET,PUT,DELETE] $ return ())+ expectNo404 $ go (methods [GET,PUT,DELETE,PATCH] $ return ())+ expectNo404 $ go (methods [GET,Method "COPY"] $ return ())+ expect404 $ goMeth PATCH (methods [POST,PUT,GET,Method "FOO"] $ return ())+ expect404 $ goMeth (Method "Baz")+ (methods [GET,POST,Method "Foo"] $ return ())+ expectNo404 $ goMeth (Method "Baz")+ (method (Method "Baz") $ return ())+ expectNo404 $ goMeth (Method "Foo")+ (methods [Method "Baz",PATCH,GET,Method "Foo"] $ return ())++ expectNo404 $ goMeth GET (method (Method "GET") $ return ())+ expectNo404 $ goMeth (Method "GET") (method GET $ return ())+++------------------------------------------------------------------------------+methodGen :: Int -> Gen Method+methodGen n = variant n $ oneof+ [ elements [ GET, HEAD, POST, PUT, DELETE+ , TRACE, OPTIONS, CONNECT, PATCH ]+ , Method <$> arbitrary+ ]+++------------------------------------------------------------------------------+testMethodEq :: Test+testMethodEq = testProperty "core/Method/eq" $ prop+ where+ prop n = do+ m <- methodGen n+ return $ m == m && toMeth m == m+ toMeth GET = Method "GET"+ toMeth HEAD = Method "HEAD"+ toMeth POST = Method "POST"+ toMeth PUT = Method "PUT"+ toMeth DELETE = Method "DELETE"+ toMeth TRACE = Method "TRACE"+ toMeth OPTIONS = Method "OPTIONS"+ toMeth CONNECT = Method "CONNECT"+ toMeth PATCH = Method "PATCH"+ toMeth (Method a) = Method a+++------------------------------------------------------------------------------+testMethodNotEq :: Test+testMethodNotEq = testProperty "core/Method/noteq" $ prop+ where+ prop n = do+ m <- methodGen n+ m' <- methodGen (n + 1)+ return $ (m /= m') == not (m == m')+++------------------------------------------------------------------------------+testDir :: Test+testDir = testCase "core/dir" $ do+ expect404 $ goPath "foo/bar" (dir "zzz" $ return ())+ expectNo404 $ goPath "foo/bar" (dir "foo" $ return ())+ expect404 $ goPath "fooz/bar" (dir "foo" $ return ())+ expectNo404 $ goPath "foo/bar" (path "foo/bar" $ return ())+ expect404 $ goPath "foo/bar/z" (path "foo/bar" $ return ())+ expectNo404 $ goPath "" (ifTop $ return ())+ expect404 $ goPath "a" (ifTop $ return ())+++------------------------------------------------------------------------------+testParam :: Test+testParam = testCase "core/getParam" $ do+ expect404 $ goPath "/foo" f+ expectNo404 $ goPathQuery "/foo" "param" ["foo"] f+ expectNo404 $ goPathQuery "/foo" "param" ["foo"] fQ+ expect404 $ goPathQuery "/foo" "param" ["foo"] fP++ where+ p gp = do+ mp <- gp "param"+ maybe pass+ (\s -> if s == "foo" then return () else pass)+ mp++ f = p getParam+ fQ = p getQueryParam+ fP = p getPostParam+++------------------------------------------------------------------------------+getBody :: Response -> IO L.ByteString+getBody r = liftM (L.fromChunks . (:[])) $ Test.getResponseBody r+++------------------------------------------------------------------------------+testWrites :: Test+testWrites = testCase "core/writes" $ do+ (_,r) <- go h+ b <- getBody r+ assertEqual "output functions" "Foo1Foo2Foo3" b+ where+ h :: Snap ()+ h = do+ addToOutput f+ writeBS "Foo2"+ writeLBS "Foo3"++ f str = do+ Streams.write (Just $ byteString "Foo1") str+ return str+++------------------------------------------------------------------------------+testURLEncode1 :: Test+testURLEncode1 = testCase "core/urlEncoding1" $ do+ let b = urlEncode "the quick brown fox~#"+ assertEqual "url encoding 1" "the+quick+brown+fox%7e%23" b+ assertEqual "fail" Nothing $ urlDecode "%"+++------------------------------------------------------------------------------+testURLEncode2 :: Test+testURLEncode2 = testProperty "core/urlEncoding2" prop+ where+ prop s = (urlDecode $ urlEncode s) == Just s+++------------------------------------------------------------------------------+testDir2 :: Test+testDir2 = testCase "core/dir2" $ do+ (_,resp) <- goPath "foo/bar" f+ b <- getBody resp+ assertEqual "context path" "/foo/bar/" b++ where+ f = dir "foo" $ dir "bar" $ do+ p <- liftM rqContextPath getRequest+ addToOutput $ \s -> do+ Streams.write (Just $ byteString p) s+ return s+++------------------------------------------------------------------------------+testIpHeaderFilter :: Test+testIpHeaderFilter = testCase "core/ipHeaderFilter" $ do+ (_,r) <- goIP f+ b <- getBody r+ assertEqual "ipHeaderFilter" "1.2.3.4" b+++ (_,r2) <- go f'+ b2 <- getBody r2+ assertEqual "ipHeaderFilter" "127.0.0.1" b2++ where+ f = do+ ipHeaderFilter+ ip <- liftM rqClientAddr getRequest+ writeBS ip++ f' = do+ ipHeaderFilter+ ip <- liftM rqRemoteAddr getRequest+ writeBS ip+++------------------------------------------------------------------------------+testMZero404 :: Test+testMZero404 = testCase "core/mzero404" $ do+ (_,r) <- go mzero+ b <- getBody r+ assertBool "mzero 404" ("<!DOCTYPE html" `L.isPrefixOf` b)+++testEvalSnap :: Test+testEvalSnap = testCase "core/evalSnap-exception" $ do+ rq <- mkZomgRq+ expectExceptionH (evalSnap f logerr tout rq >> return ())+ where+ f = do+ logError "zzz"+ v <- withResponse (return . rspStatus)+ liftIO $ assertEqual "evalSnap rsp status" 200 v+ finishWith emptyResponse+++------------------------------------------------------------------------------+testLocalRequest :: Test+testLocalRequest = testCase "core/localRequest" $ do+ rq1 <- mkZomgRq+ rq2 <- mkRequest "zzz/zz/z"++ let h = localRequest (const rq2) mzero++ (rq',_) <- go (h <|> return ())++ let u1 = rqURI rq1+ let u2 = rqURI rq'++ assertEqual "localRequest backtrack" u1 u2+++------------------------------------------------------------------------------+testRedirect :: Test+testRedirect = testCase "core/redirect" $ do+ (_,rsp) <- go (redirect "/foo/bar")++ b <- getBody rsp+ assertEqual "no response body" "" b+ assertEqual "response content length" (Just 0) $ rspContentLength rsp+ assertEqual "redirect path" (Just "/foo/bar") $ getHeader "Location" rsp+ assertEqual "redirect status" 302 $ rspStatus rsp+ assertEqual "status description" "Found" $ rspStatusReason rsp+++ (_,rsp2) <- go (redirect' "/bar/foo" 307)++ assertEqual "redirect path" (Just "/bar/foo") $ getHeader "Location" rsp2+ assertEqual "redirect status" 307 $ rspStatus rsp2+ assertEqual "status description" "Temporary Redirect" $+ rspStatusReason rsp2+++------------------------------------------------------------------------------+testCoverInstances :: Test+testCoverInstances = testCase "core/instances" $ do+#if !MIN_VERSION_transformers(0,6,0)+ coverErrorT+ coverListT+#endif+#if MIN_VERSION_transformers(0,4,0)+ coverExceptT+#endif+ coverRWST+ coverLRWS+ coverReaderT+ coverStateT+ coverLStateT+ coverWriterT+ coverLWriterT++ where+ snap :: MonadSnap m => m ()+ snap = liftSnap $ writeBS "OK"++ cover :: MonadSnap m => (m () -> Snap ()) -> IO ()+ cover runFunc = do+ !_ <- Test.runHandler (return ()) (runFunc snap)+ return ()++ rwst :: RWST () () () Snap () -> Snap ()+ rwst m = void $ runRWST m () ()++ lrwst :: LRWS.RWST () () () Snap () -> Snap ()+ lrwst m = void $ LRWS.runRWST m () ()++ wt :: WriterT () Snap () -> Snap ()+ wt m = void $ runWriterT m++ lwt :: LWriter.WriterT () Snap () -> Snap ()+ lwt m = void $ LWriter.runWriterT m++#if !MIN_VERSION_transformers(0,6,0)+ coverErrorT = cover (\m -> do+ (_ :: Either String ()) <- runErrorT m+ return ())+ coverListT = cover (void . runListT)+#endif+#if MIN_VERSION_transformers(0,4,0)+ coverExceptT = cover (\m -> do+ (_ :: Either String ()) <- runExceptT m+ return ())+#endif+ coverRWST = cover rwst+ coverLRWS = cover lrwst+ coverReaderT = cover (flip runReaderT ())+ coverStateT = cover (flip evalStateT ())+ coverLStateT = cover (flip LState.evalStateT ())+ coverWriterT = cover wt+ coverLWriterT = cover lwt+++------------------------------------------------------------------------------+testPathArgs :: Test+testPathArgs = testCase "core/pathArgs" $ do+ (_, rsp) <- goPath "%e4%b8%ad" m+ b <- getBody rsp+ assertEqual "pathargs url- and utf8-decodes" "ok" b++ Test.evalHandler (Test.get "/%zzzz" Map.empty) m2 >>= assertEqual "m2" True+ Test.evalHandler (Test.get "/z/foo" Map.empty) m3 >>= assertEqual "m3" "/z/"++ where+ m = pathArg f++ m2 = pathArg (\(_ :: Text) -> return False) <|> return True++ m3 = pathArg (\(_ :: Text) -> rqContextPath <$> getRequest)+++ f x = if x == ("\x4e2d" :: Text)+ then writeBS "ok"+ else writeBS $ "not ok: " `mappend` T.encodeUtf8 x++------------------------------------------------------------------------------+test304Fixup :: Test+test304Fixup = testCase "core/304fixup" $ do+ rsp1 <- Test.runHandler (return ()) h1+ assertEqual "code1" (rspStatus rsp1) 304+ assertEqual "cl1" Nothing (rspContentLength rsp1)+ Test.getResponseBody rsp1 >>= assertEqual "body1" ""+ assertBool "date" $ getHeader "date" rsp1 /= (Just "zzz")+ assertEqual "cl-header" Nothing $ getHeader "content-length" rsp1+ assertEqual "transfer-encoding" Nothing $ getHeader "transfer-encoding" rsp1++ where+ h1 = do let s = "this should get eaten"+ modifyResponse (setResponseCode 304 .+ setContentLength (toEnum $ S.length s) .+ setHeader "content-length" "zzz" .+ setHeader "transfer-encoding" "chunked" .+ setHeader "date" "zzz")+ writeBS s+++------------------------------------------------------------------------------+testChunkedFixup :: Test+testChunkedFixup = testCase "core/chunked-fixup" $ do+ rsp1 <- Test.runHandler (return ()) h1+ Test.getResponseBody rsp1 >>= assertEqual "body1" "OK"+ assertEqual "transfer-encoding" (Just "chunked")+ $ getHeader "transfer-encoding" rsp1+ assertEqual "baz" (Just "baz") $ getHeader "baz" rsp1+ assertEqual "foo" (Just "foo") $ getHeader "foo" rsp1++ where+ h1 = do modifyResponse $ setHeader "baz" "baz" .+ setHeader "transfer-encoding" "chunked" .+ setHeader "foo" "foo" .+ setHeader "bar" "bar"+ writeBS "OK"
+ test/Snap/Internal/Http/Types/Tests.hs view
@@ -0,0 +1,205 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE OverloadedStrings #-}++------------------------------------------------------------------------------+module Snap.Internal.Http.Types.Tests ( tests ) where+------------------------------------------------------------------------------+import Control.Parallel.Strategies (rdeepseq, using)+import Data.ByteString.Builder (byteString)+import qualified Data.ByteString.Char8 as S (concat)+import Data.ByteString.Lazy.Char8 ()+import Data.List (sort)+import qualified Data.Map as Map (empty, insert, lookup)+import Data.Time.Calendar (Day (ModifiedJulianDay))+import Data.Time.Clock (UTCTime (UTCTime))+import Snap.Internal.Http.Types (Cookie (Cookie), HasHeaders (headers, updateHeaders), Method (CONNECT, DELETE, GET, HEAD, Method, OPTIONS, PATCH, POST, PUT, TRACE), Request (rqCookies, rqIsSecure, rqContentLength, rqParams), Response (rspContentLength, rspStatus, rspStatusReason), addHeader, addResponseCookie, cookieToBS, deleteResponseCookie, emptyResponse, formatLogTime, getHeader, getResponseCookie, getResponseCookies, listHeaders, modifyResponseBody, modifyResponseCookie, rqModifyParams, rqParam, rqSetParam, setContentLength, setContentType, setResponseBody, setResponseCode, setResponseStatus)+import Snap.Internal.Parsing (urlDecode)+import qualified Snap.Test as Test (buildRequest, get, getResponseBody)+import qualified Snap.Types.Headers as H (lookup, set)+import qualified System.IO.Streams as Streams (write)+import Test.Framework (Test)+import Test.Framework.Providers.HUnit (testCase)+import Test.HUnit (assertBool, assertEqual)+import Text.Regex.Posix ((=~))++------------------------------------------------------------------------------+tests :: [Test]+tests = [ testTypes+ , testCookies+ , testCookieToBS+ , testUrlDecode+ , testFormatLogTime+ , testAddHeader+ , testHeaderOrd+ ]+++------------------------------------------------------------------------------+mkRq :: IO Request+mkRq = Test.buildRequest $ Test.get "/" Map.empty+++------------------------------------------------------------------------------+testHeaderOrd :: Test+testHeaderOrd = testCase "httpTypes/methodOrd" $ do+ let methods = [GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, CONNECT,+ PATCH, Method "Foo"]+ mapM_ (\m -> assertEqual "method" (compare m m) EQ) methods+ mapM_ (\m -> assertEqual "ord" (compare GET m) LT) $ tail methods+ assertEqual "ord2" LT (compare (Method "a") (Method "b"))++------------------------------------------------------------------------------+testFormatLogTime :: Test+testFormatLogTime = testCase "httpTypes/formatLogTime" $ do+ b <- formatLogTime 3804938++ let re = S.concat [ "^[0-9]{1,2}/[A-Za-z]{3}/[0-9]{4}:[0-9]{2}:[0-9]{2}"+ , ":[0-9]{2} (-|\\+)[0-9]{4}$" ]++ assertBool "formatLogTime" $ b =~ re+++------------------------------------------------------------------------------+testAddHeader :: Test+testAddHeader = testCase "httpTypes/addHeader" $ do+ defReq <- mkRq++ let req = addHeader "foo" "bar" $+ addHeader "foo" "baz" defReq+++ let x = getHeader "foo" req+ assertEqual "addHeader x 2" (Just "baz,bar") x+ assertEqual "listHeaders" [ ("foo","baz,bar")+ , ("Host", "localhost") ] $+ sort $ listHeaders req++ let hdrs = updateHeaders (H.set "zzz" "bbb") $ headers req+ assertEqual "listHeaders 2"+ [ ("foo", "baz,bar")+ , ("Host", "localhost")+ , ("zzz", "bbb") ]+ (sort (listHeaders $ headers hdrs))+++------------------------------------------------------------------------------+testUrlDecode :: Test+testUrlDecode = testCase "httpTypes/urlDecode" $ do+ assertEqual "bad hex" Nothing $ urlDecode "%qq"+++------------------------------------------------------------------------------+testTypes :: Test+testTypes = testCase "httpTypes/show" $ do+ defReq <- mkRq++ let req = rqModifyParams (Map.insert "zzz" ["bbb"]) $+ updateHeaders (H.set "zzz" "bbb") $+ rqSetParam "foo" ["bar"] $+ defReq++ let req2 = (addHeader "zomg" "1234" req)+ { rqCookies = [ cook, cook2 ]+ , rqIsSecure = True+ , rqContentLength = Just 10+ }++ let !a = show req `using` rdeepseq+ let !_ = show req2 `using` rdeepseq++ -- we don't care about the show instance really, we're just trying to shut+ -- up hpc+ assertBool "show" $ a /= b+ assertEqual "rqParam" (Just ["bar"]) (rqParam "foo" req)+ assertEqual "lookup" (Just ["bbb"]) (Map.lookup "zzz" $ rqParams req)+ assertEqual "lookup 2" (Just "bbb") (H.lookup "zzz" $ headers req)++ assertEqual "response status" 555 $ rspStatus resp+ assertEqual "response status reason" "bogus" $ rspStatusReason resp+ assertEqual "content-length" (Just 4) $ rspContentLength resp++ bd <- Test.getResponseBody resp+ assertEqual "response body" "PING" $ bd++ let !_ = show GET+ let !_ = GET == POST+ let !_ = headers $ headers defReq+ let !_ = show resp2 `using` rdeepseq++ assertEqual "999" "Unknown" (rspStatusReason resp3)++ where+ enum os = Streams.write (Just $ byteString "PING") os >> return os++ resp = addResponseCookie cook $+ setContentLength 4 $+ modifyResponseBody id $+ setResponseBody enum $+ setContentType "text/plain" $+ setResponseStatus 555 "bogus" $+ emptyResponse+ !b = show resp `using` rdeepseq++ resp2 = addResponseCookie cook2 resp++ resp3 = setResponseCode 999 resp2++ utc = UTCTime (ModifiedJulianDay 55226) 0+ cook = Cookie "foo" "bar" (Just utc) (Just ".foo.com") (Just "/") False False+ cook2 = Cookie "zoo" "baz" (Just utc) (Just ".foo.com") (Just "/") False False+++------------------------------------------------------------------------------+testCookieToBS :: Test+testCookieToBS = testCase "httpTypes/cookieToBS" $ do+ let [b0, b1, b2] = map cookieToBS [cookie0, cookie1, cookie2]+ assertEqual "cookie0" "foo=bar; HttpOnly" b0+ assertEqual "cookie1" "foo=bar; Secure" b1+ assertEqual "cookie2" "foo=bar; path=/; expires=Sat, 30 Jan 2010 00:00:00 GMT; domain=.foo.com; HttpOnly" b2+ where+ utc = UTCTime (ModifiedJulianDay 55226) 0+ cookie0 = Cookie "foo" "bar" Nothing Nothing Nothing False True+ cookie1 = Cookie "foo" "bar" Nothing Nothing Nothing True False+ cookie2 = Cookie "foo" "bar" (Just utc) (Just ".foo.com") (Just "/") False True+++testCookies :: Test+testCookies = testCase "httpTypes/cookies" $ do+ assertEqual "cookie" (Just cook) rCook+ assertEqual "cookie2" (Just cook2) rCook2+ assertEqual "cookie3" (Just cook3) rCook3+ assertEqual "empty response cookie3" (Just cook3) rCook3e+ assertEqual "removed cookie" Nothing nilCook+ assertEqual "multiple cookies" [cook, cook2] cks+ assertEqual "cookie modification" (Just cook3) rCook3Mod+ assertEqual "modify nothing" Nothing (getResponseCookie "boo" resp5)++ return ()++ where+ resp = addResponseCookie cook $+ setContentType "text/plain" $+ emptyResponse++ f !_ = cook3++ resp' = deleteResponseCookie "foo" resp+ resp'' = modifyResponseCookie "foo" f resp+ resp2 = addResponseCookie cook2 resp+ resp3 = addResponseCookie cook3 resp2+ resp4 = addResponseCookie cook3 emptyResponse+ resp5 = modifyResponseCookie "boo" id emptyResponse++ utc = UTCTime (ModifiedJulianDay 55226) 0+ cook = Cookie "foo" "bar" (Just utc) (Just ".foo.com") (Just "/") False True+ cook2 = Cookie "zoo" "baz" (Just utc) (Just ".foo.com") (Just "/") True False+ cook3 = Cookie "boo" "baz" Nothing Nothing Nothing False False++ rCook = getResponseCookie "foo" resp+ nilCook = getResponseCookie "foo" resp'+ rCook2 = getResponseCookie "zoo" resp2+ rCook3 = getResponseCookie "boo" resp3+ rCook3e = getResponseCookie "boo" resp4+ rCook3Mod = getResponseCookie "boo" resp''++ cks = getResponseCookies resp2
+ test/Snap/Internal/Parsing/Tests.hs view
@@ -0,0 +1,177 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}++------------------------------------------------------------------------------+module Snap.Internal.Parsing.Tests ( tests ) where+------------------------------------------------------------------------------+import Control.Applicative (many)+import Data.Attoparsec.ByteString.Char8 (IResult (..), char, string, (<?>))+import qualified Data.ByteString.Char8 as S (concat)+import qualified Data.Map as Map (fromList)+import Data.Word (Word8)+import Snap.Internal.Http.Types (Cookie (Cookie, cookieDomain, cookieExpires, cookieHttpOnly, cookieName, cookiePath, cookieSecure, cookieValue))+import Snap.Internal.Parsing (crlf, finish, fullyParse, fullyParse', pAvPairs, pHeaders, pQuotedString, pQuotedString', parseCookie, parseToCompletion, parseUrlEncoded, unsafeFromHex, unsafeFromNat, pTokens)+import Snap.Test.Common (expectExceptionH)+import System.Random (Random (random, randomR))+import Test.Framework (Test)+import Test.Framework.Providers.HUnit (testCase)+import Test.HUnit (assertEqual)+import Test.QuickCheck (Arbitrary (arbitrary), choose)+------------------------------------------------------------------------------++tests :: [Test]+tests = [ testAvPairs+ , testCookie+ , testHeaderParse+ , testQuotedString+ , testQuotedString'+ , testUnsafeFromHex+ , testUnsafeFromInt+ , testUrlEncoded+ , testFailParse+ , testTokens+ ]+++------------------------------------------------------------------------------+testAvPairs :: Test+testAvPairs = testCase "parsing/avpairs" $ do+ let x = fullyParse txt pAvPairs+ assertEqual "avpairs" (Right [ ("foo" , "bar")+ , ("bar" , "baz")+ , ("quux", "" )+ ]) x+ where+ txt = "foo = bar; bar = baz; quux"+++------------------------------------------------------------------------------+testCookie :: Test+testCookie =+ testCase "parsing/parseCookie" $ do+ assertEqual "cookie parsing" (Just [cv]) cv2+ let (Just [c]) = cv2+ -- stupid assertions to cover the accessors+ assertEqual "c1" (cookieName c) nm+ assertEqual "c2" (cookieValue c) v+ assertEqual "c3" (cookieExpires c) Nothing+ assertEqual "c4" (cookieDomain c) Nothing+ assertEqual "c5" (cookiePath c) Nothing+ assertEqual "c6" (cookieSecure c) False+ assertEqual "c7" (cookieHttpOnly c) False++ where+ cv = Cookie nm v Nothing Nothing Nothing False False+ cv2 = parseCookie ct++ nm = "foo"+ v = "bar"++ ct = S.concat [ nm , "=" , v ]+++------------------------------------------------------------------------------+testHeaderParse :: Test+testHeaderParse = testCase "parsing/headers" $ do+ let e = fullyParse txt pHeaders+ assertEqual "parse" (Right [("foo", "bar baz quux")]) e++ let f = fullyParse bad pHeaders+ assertEqual "bad parse" (Right []) f++ where+ txt = S.concat [ "foo: bar\r\n"+ , " baz\r\n"+ , " quux\r\n"+ ]++ bad = "%&^%&^*^(*&^*&^*%*&%^^#$"+++------------------------------------------------------------------------------+testQuotedString :: Test+testQuotedString = testCase "parsing/quoted-string" $ do+ let e = fullyParse txt pQuotedString+ assertEqual "q-s" (Right "foo\"bar\"baz") e++ where+ txt = "\"foo\\\"bar\\\"baz\""++------------------------------------------------------------------------------+testQuotedString' :: Test+testQuotedString' = testCase "parsing/quoted-string-utf8" $ do+ let e = fullyParse qdtext $ pQuotedString' (const True)+ assertEqual "q-s" (Right txt) e++ where+ txt = "\xd1\x82\xd0\xb5\xd1\x81\xd1\x82" -- "тест" as UTF-8+ qdtext = S.concat ["\"", txt, "\""]+++------------------------------------------------------------------------------+-- older random didn't have a Word8 instance.....+data WrappedWord8 = W { _unW :: Word8 }++instance Show WrappedWord8 where+ show (W w) = show w++instance Random WrappedWord8 where+ randomR (W a, W b) g = case randomR (fromIntegral a :: Int,+ fromIntegral b :: Int) g+ of (x, g') -> (W $! fromIntegral x, g')+ random = randomR (W minBound, W maxBound)++instance Arbitrary WrappedWord8 where+ arbitrary = choose (W minBound, W maxBound)+++------------------------------------------------------------------------------+testUnsafeFromHex :: Test+testUnsafeFromHex = testCase "parsing/unsafeFromHex" $ do+ expectExceptionH $ return $! ((unsafeFromHex "zz") :: Int)+ let x = unsafeFromHex "a"+ assertEqual "a" (10 :: Int) x+++------------------------------------------------------------------------------+testUnsafeFromInt :: Test+testUnsafeFromInt = testCase "parsing/unsafeFromNat" $ do+ expectExceptionH $ return $! ((unsafeFromNat "zz") :: Int)+ let x = unsafeFromNat "10"+ assertEqual "10" (10 :: Int) x+++------------------------------------------------------------------------------+testUrlEncoded :: Test+testUrlEncoded = testCase "parsing/urlEncoded" $ do+ let x = parseUrlEncoded "foo=h%20i&bar=baz+baz&baz=quux&baz=zzz&%zz"+ assertEqual "map" (Map.fromList [ ("foo", ["h i"])+ , ("bar", ["baz baz"])+ , ("baz", ["quux", "zzz"]) ]) x+++------------------------------------------------------------------------------+testFailParse :: Test+testFailParse = testCase "parsing/failParse" $ do+ let (Left a) = fullyParse "foo" ((string "bar" <?> "foo") <?> "bar")+ let (Left b) = fullyParse "foo" (fail "bar")+ let (Left c) = fullyParse "aaaaa" (many (char 'a') >> string "b")+ let f = const $ Partial f+ let (Left d) = fullyParse' (const f) (const f) "aaaaa" (string "b")+ let e@(Just "aaaa") = parseToCompletion (many (char 'a')) "aaaa"+ let (Left g) = fullyParse "\x09" crlf+ let (Done z (0::Int)) = finish $! Partial $! \s ->+ s `seq` Partial (\t -> t `seq` Done t 0)+++ return $! length a `seq` length b `seq` length c `seq` length d `seq` e+ `seq` length g `seq` z `seq` ()+++------------------------------------------------------------------------------+testTokens :: Test+testTokens = testCase "parsing/tokens" $ do+ assertEqual "without whitespace" (Right ["Foo","Bar"]) $+ fullyParse "Foo,Bar" pTokens+ assertEqual "with whitespace" (Right ["Foo","Bar"]) $+ fullyParse " Foo ,Bar " pTokens
+ test/Snap/Internal/Routing/Tests.hs view
@@ -0,0 +1,513 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}++module Snap.Internal.Routing.Tests+ ( tests ) where++------------------------------------------------------------------------------+import Control.Applicative ((<|>))+import Control.Exception (ErrorCall (..), throwIO)+import Control.Monad (liftM, unless)+import Control.Monad.Trans (liftIO)+import Data.ByteString (ByteString)+import qualified Data.ByteString as S (append, isPrefixOf)+import qualified Data.Map as Map (empty)+import Data.Maybe (fromJust)+import Snap.Internal.Core (Snap, getParam, getRequest, modifyRequest, pass)+import Snap.Internal.Http.Types (Request (rqContextPath, rqPathInfo), rqParam, rqSetParam)+import Snap.Internal.Routing (Route (NoRoute), route, routeEarliestNC, routeHeight, routeLocal)+import Snap.Test (evalHandler, get)+import Snap.Test.Common (expectExceptionH)+import Test.Framework (Test)+import Test.Framework.Providers.HUnit (testCase)+import Test.HUnit (assertEqual)+#if !MIN_VERSION_base(4,8,0)+import Control.Applicative ((<$>))+#endif+------------------------------------------------------------------------------+++tests :: [Test]+tests = [ testRouting1+ , testRouting2+ , testRouting3+ , testRouting4+ , testRouting5+ , testRouting6+ , testRouting7+ , testRouting8+ , testRouting9+ , testRouting10+ , testRouting11+ , testRouting12+ , testRouting13+ , testRouting14+ , testRouting15+ , testRouting16+ , testRouting17+ , testRouting18+ , testRouting19+ , testRouting20+ , testRouting21+ , testRouting22+ , testRouting23+ , testRouting24+ , testRouting25+ , testRouting26+ , testRouting27+ , testRouting28+ , testRouteLocal+ , testRouteUrlDecode+ , testRouteUrlEncodedPath+ , testRouteEmptyCapture+ , testRouteCaptureConflicts+ , testTrivials+ , testParamUnification+ , testFailedUrlDecode+ , testDirFallthrough+ ]+++------------------------------------------------------------------------------+go :: Snap a -> ByteString -> IO a+go m s0 = evalHandler (get s Map.empty) m+ where+ s = if S.isPrefixOf "/" s0+ then s0+ else S.append "/" s0+++------------------------------------------------------------------------------+routes :: Snap ByteString+routes = route [ ("foo" , topFoo )+ , ("foo/bar" , fooBar )+ , ("foo/bar/baz" , getRqPathInfo )+ , ("foo/:id" , fooCapture )+ , ("bar/:id" , fooCapture )+ , ("herp/:derp/" , getRqPathInfo )+ , ("nerp/:derp/" , getRqContextPath)+ , ("a b c d" , return "OK" )+ , ("bar/quux" , barQuux )+ , ("bar" , bar )+ , ("z/:a/:b/:c/d" , zabc ) ]+++------------------------------------------------------------------------------+routesLocal :: Snap ByteString+routesLocal = routeLocal [ ("foo/bar/baz" , getRqPathInfo )+ , ("bar" , pass )+ , ("quux/zzz" , getRqContextPath)+ ]+++------------------------------------------------------------------------------+routes2 :: Snap ByteString+routes2 = route [ ("" , topTop )+ , ("foo" , topFoo ) ]+++------------------------------------------------------------------------------+routes3 :: Snap ByteString+routes3 = route [ (":foo" , topCapture )+ , ("" , topTop ) ]+++------------------------------------------------------------------------------+routes4 :: Snap ByteString+routes4 = route [ (":foo" , pass )+ , (":foo" , topCapture )+ , (":qqq/:id" , fooCapture )+ , (":id2/baz" , fooCapture2 ) ]+++------------------------------------------------------------------------------+routes5 :: Snap ByteString+routes5 = route [ ("" , pass )+ , ("" , topTop ) ]+++------------------------------------------------------------------------------+routes6 :: Snap ByteString+routes6 = route [ (":a/:a" , dblA ) ]+++------------------------------------------------------------------------------+routes7 :: Snap ByteString+routes7 = route [ ("foo/:id" , fooCapture )+ , ("foo/:id/:id2" , fooCapture2)+ , ("fooo/:id/:id2" , fooCapture2)+ , ("foooo/bar/baz" , bar )+ , ("" , topTop ) ]+++------------------------------------------------------------------------------+routesEmptyCapture :: Snap ByteString+routesEmptyCapture = route [ ("foo/:id", fooCapture) ]+++------------------------------------------------------------------------------+topTop, topFoo, fooBar, fooCapture, getRqPathInfo, bar,+ getRqContextPath, barQuux, dblA, zabc, topCapture,+ fooCapture2 :: Snap ByteString++dblA = do+ ma <- getParam "a"++ unless (ma == Just "a a") pass+ return "ok"+++zabc = do+ ma <- getParam "a"+ mb <- getParam "b"+ mc <- getParam "c"++ unless ( ma == Just "a"+ && mb == Just "b"+ && mc == Just "c" ) pass++ return "ok"+++topCapture = do+ mp <- getParam "foo"+ maybe pass return mp++topTop = return "topTop"+topFoo = return "topFoo"+fooBar = return "fooBar"+fooCapture = liftM (head . fromJust . rqParam "id") getRequest+fooCapture2 = liftM (head . fromJust . rqParam "id2") getRequest+getRqPathInfo = liftM rqPathInfo getRequest+getRqContextPath = liftM rqContextPath getRequest+barQuux = return "barQuux"+bar = return "bar"+++ -----------+ -- Tests --+ -----------+++------------------------------------------------------------------------------+-- TODO more useful test names+testRouting1 :: Test+testRouting1 = testCase "route/1" $ do+ r1 <- go routes "foo"+ assertEqual "/foo" "topFoo" r1+++------------------------------------------------------------------------------+testRouting2 :: Test+testRouting2 = testCase "route/2" $ do+ r2 <- go routes "foo/baz"+ assertEqual "/foo/baz" "baz" r2+++------------------------------------------------------------------------------+testRouting3 :: Test+testRouting3 = testCase "route/3" $ do+ expectExceptionH $ go routes "/xsaxsaxsax"+++------------------------------------------------------------------------------+testRouting4 :: Test+testRouting4 = testCase "route/4" $ do+ r3 <- go routes "foo/bar"+ assertEqual "/foo/bar" "fooBar" r3+++------------------------------------------------------------------------------+testRouting5 :: Test+testRouting5 = testCase "route/5" $ do+ r4 <- go routes "foo/bar/baz/quux"+ assertEqual "/foo/bar/baz/quux" "quux" r4+++------------------------------------------------------------------------------+testRouting6 :: Test+testRouting6 = testCase "route/6" $ do+ r5 <- go routes "foo/bar/sproing"+ assertEqual "/foo/bar/sproing" "fooBar" r5+++------------------------------------------------------------------------------+testRouting7 :: Test+testRouting7 = testCase "route/7" $ do+ r <- go routes "bar"+ assertEqual "/bar" "bar" r+++------------------------------------------------------------------------------+testRouting8 :: Test+testRouting8 = testCase "route/8" $ do+ r2 <- go routes "bar/quux"+ assertEqual "/bar/quux" "barQuux" r2+++------------------------------------------------------------------------------+testRouting9 :: Test+testRouting9 = testCase "route/9" $ do+ r3 <- go routes "bar/whatever"+ assertEqual "/bar/whatever" "whatever" r3+++------------------------------------------------------------------------------+testRouting10 :: Test+testRouting10 = testCase "route/10" $ do+ r4 <- go routes "bar/quux/whatever"+ assertEqual "/bar/quux/whatever" "barQuux" r4+++------------------------------------------------------------------------------+testRouting11 :: Test+testRouting11 = testCase "route/11" $ do+ r1 <- go routes2 ""+ assertEqual "/" "topTop" r1+++------------------------------------------------------------------------------+testRouting12 :: Test+testRouting12 = testCase "route/12" $ do+ r1 <- go routes2 "foo"+ assertEqual "/foo" "topFoo" r1+++------------------------------------------------------------------------------+testRouting13 :: Test+testRouting13 = testCase "route/13" $ do+ r1 <- go routes3 "zzzz"+ assertEqual "/zzzz" "zzzz" r1+++------------------------------------------------------------------------------+testRouting14 :: Test+testRouting14 = testCase "route/14" $ do+ r1 <- go routes3 ""+ assertEqual "/" "topTop" r1+++------------------------------------------------------------------------------+testRouting15 :: Test+testRouting15 = testCase "route/15" $ do+ r1 <- go routes4 "zzzz"+ assertEqual "/zzzz" "zzzz" r1+++------------------------------------------------------------------------------+testRouting16 :: Test+testRouting16 = testCase "route/16" $ do+ r1 <- go routes5 ""+ assertEqual "/" "topTop" r1+++------------------------------------------------------------------------------+testRouting17 :: Test+testRouting17 = testCase "route/17" $ do+ r1 <- go routes "z/a/b/c/d"+ assertEqual "/z/a/b/c/d" "ok" r1+++------------------------------------------------------------------------------+testRouting18 :: Test+testRouting18 = testCase "route/18" $ do+ r1 <- go routes6 "a/a"+ assertEqual "/a/a" "ok" r1+++------------------------------------------------------------------------------+testRouting19 :: Test+testRouting19 = testCase "route/19" $ do+ r1 <- go routes7 "foo"+ assertEqual "/foo" "topTop" r1+++------------------------------------------------------------------------------+testRouting20 :: Test+testRouting20 = testCase "route/20" $ do+ r1 <- go routes7 "foo/baz"+ assertEqual "/foo/baz" "baz" r1+++------------------------------------------------------------------------------+testRouting21 :: Test+testRouting21 = testCase "route/21" $ do+ r1 <- go routes7 "foo/baz/quux"+ assertEqual "/foo/baz/quux" "quux" r1+++------------------------------------------------------------------------------+testRouting22 :: Test+testRouting22 = testCase "route/22" $ do+ r1 <- go routes7 "fooo/baz"+ assertEqual "/fooo/baz" "topTop" r1+++------------------------------------------------------------------------------+testRouting23 :: Test+testRouting23 = testCase "route/23" $ do+ r1 <- go routes7 "fooo/baz/quux"+ assertEqual "/fooo/baz/quux" "quux" r1+++------------------------------------------------------------------------------+testRouting24 :: Test+testRouting24 = testCase "route/24" $ do+ r1 <- go routes7 "foooo/bar/bax"+ assertEqual "/foooo/bar/bax" "topTop" r1+++------------------------------------------------------------------------------+testRouting25 :: Test+testRouting25 = testCase "route/25" $ do+ r1 <- go routes7 "foooo/bar/baz"+ assertEqual "/foooo/bar/baz" "bar" r1+++------------------------------------------------------------------------------+testRouting26 :: Test+testRouting26 = testCase "route/26" $ do+ r1 <- go routes4 "foo/bar"+ assertEqual "capture union" "bar" r1+++------------------------------------------------------------------------------+testRouting27 :: Test+testRouting27 = testCase "route/27" $ do+ r1 <- go routes4 "foo"+ assertEqual "capture union" "foo" r1+++------------------------------------------------------------------------------+testRouting28 :: Test+testRouting28 = testCase "route/28" $ do+ r1 <- go routes4 "quux/baz"+ assertEqual "capture union" "quux" r1+++------------------------------------------------------------------------------+testRouteUrlDecode :: Test+testRouteUrlDecode = testCase "route/urlDecode" $ do+ r1 <- go routes "herp/%7Bderp%7D/"+ assertEqual "rqPathInfo on urldecode" "" r1+ r2 <- go routes "foo/%7Bderp%7D/"+ assertEqual "urldecoded capture" "{derp}" r2+ r3 <- go routes "nerp/%7Bderp%7D/"+ assertEqual "rqContextPath on urldecode" "/nerp/%7Bderp%7D/" r3+++------------------------------------------------------------------------------+testRouteUrlEncodedPath :: Test+testRouteUrlEncodedPath = testCase "route/urlEncodedPath" $ do+ -- make sure path search urlDecodes.+ r1 <- go routes "a+b+c+d"+ assertEqual "urlEncoded search works" "OK" r1+++------------------------------------------------------------------------------+testRouteLocal :: Test+testRouteLocal = testCase "route/routeLocal" $ do+ r4 <- go routesLocal "foo/bar/baz/quux"+ assertEqual "/foo/bar/baz/quux" "foo/bar/baz/quux" r4+ expectExceptionH $ go routesLocal "bar"++ go routesLocal "quux/zzz" >>= assertEqual "context" "/"+++------------------------------------------------------------------------------+testRouteEmptyCapture :: Test+testRouteEmptyCapture = testCase "route/emptyCapture" $ do+ r <- go m "foo"+ assertEqual "empty capture must fail" expected r++ r2 <- go m "foo/"+ assertEqual "empty capture must fail" expected r2++ where+ expected = "ZOMG_OK"+ m = routesEmptyCapture <|> return expected+++------------------------------------------------------------------------------+testRouteCaptureConflicts :: Test+testRouteCaptureConflicts = testCase "route/captureConflicts" $ do+ go h1 "ok/ok/ok" >>= assertEqual "earliest non-capture/1" "ok"+ go h2 "ok/ok/ok" >>= assertEqual "earliest non-capture/2" "ok"+ go h3 "foo/ok/ok/ok" >>= assertEqual "earliest non-capture/3" "ok"+ go h4 "foo/ok/ok/ok" >>= assertEqual "earliest non-capture/4" "ok"+ go h5 "zz/aa/zz" >>= assertEqual "fallback" "fb1"+ go h6 "zz" >>= assertEqual "rightmost" "fb2"++ where+ puke = liftIO . throwIO . ErrorCall+ ok = return ("ok" :: String)+ fb1 = return ("fb1" :: String)+ fb2 = return ("fb2" :: String)++ -- rule: earliest non-capture+ h1 = route [ (":a/:b/:c", puke "h1")+ , (":b/:c/ok", ok )+ ]++ h2 = route [ (":a/:b/ok", ok )+ , (":b/:c/:d", puke "h2")+ ]++ -- same, with a prefix+ h3 = route [ ("foo/:a/:b/:c", puke "h1")+ , ("foo/:b/:c/ok", ok )+ ]++ h4 = route [ ("foo/:a/:b/ok", ok )+ , ("foo/:b/:c/:d", puke "h2")+ ]++ -- test fallback+ h5 = route [ ("", fb1)+ , (":a/aa/bb", puke "h5-1")+ , (":b/:c/bb", puke "h5-2")+ ]++ -- all else equal, rightmost wins+ h6 = route [ (":a", fb1), (":b", fb2) ]+++------------------------------------------------------------------------------+testParamUnification :: Test+testParamUnification = testCase "route/paramUnification" $ do+ go h1 "a/b" >>= assertEqual "++" ["0", "a", "b"]+ where+ h1 = do+ modifyRequest $ rqSetParam "a" ["0"]+ route [ (":a/:a", fromJust . rqParam "a" <$> getRequest) ]+++------------------------------------------------------------------------------+testFailedUrlDecode :: Test+testFailedUrlDecode = testCase "route/failedUrlDecode" $ do+ expectExceptionH $ go h1 "%zz"+ expectExceptionH $ go h2 "%zz"+ where+ h1 = route [(":a", return ())]+ h2 = route [("a/", return ())]+++------------------------------------------------------------------------------+testDirFallthrough :: Test+testDirFallthrough = testCase "route/dirFallthrough" $ do+ go m1 "a/a" >>= assertEqual "1" 1+ where+ m1 = route [ ("" , return (1::Int) )+ , ("a/a", pass )+ , ("a/a", pass )+ ]++------------------------------------------------------------------------------+testTrivials :: Test+testTrivials = testCase "route/trivials" $ do+ -- routeHeight and routeEarliestNC can't actually be called on NoRoute (it+ -- never appears in the children of captures or directories), so cover this+ -- case here+ assertEqual "trivials/routeHeight" 1 (routeHeight NoRoute)+ assertEqual "trivials/routeEarliestNC" 1 (routeEarliestNC NoRoute 1)
+ test/Snap/Test/Common.hs view
@@ -0,0 +1,143 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}+++module Snap.Test.Common+ ( coverEqInstance+ , coverOrdInstance+ , coverReadInstance+ , coverShowInstance+ , coverTypeableInstance+ , forceSameType+ , expectException+ , expectExceptionH+ , liftQ+ , eatException+ , waitabit+ , seconds+ ) where+------------------------------------------------------------------------------+import Control.Concurrent (threadDelay)+import Control.DeepSeq (deepseq)+import Control.Exception.Lifted (SomeException (..), catch, evaluate, try)+import Control.Monad (Monad ((>>), return), liftM, replicateM)+import Control.Monad.IO.Class (MonadIO (..))+import Control.Monad.Trans.Control (MonadBaseControl)+import qualified Data.ByteString as S+import Data.ByteString.Internal (c2w)+import qualified Data.ByteString.Lazy as L+import Data.Typeable (Typeable, typeOf)+import Prelude (Either (..), Eq (..), IO, Int, Num (..), Ord (..), Ordering (..), Read (..), Show (..), fail, map, seq, ($), (.), (^))+import Test.QuickCheck (Arbitrary (arbitrary), choose)+import Test.QuickCheck.Monadic (PropertyM)+import qualified Test.QuickCheck.Monadic as QC+------------------------------------------------------------------------------++------------------------------------------------------------------------------+instance Arbitrary S.ByteString where+ arbitrary = liftM (S.pack . map c2w) arbitrary++instance Arbitrary L.ByteString where+ arbitrary = do+ n <- choose(0,5)+ chunks <- replicateM n arbitrary+ return $ L.fromChunks chunks+++------------------------------------------------------------------------------+eatException :: (MonadBaseControl IO m) => m a -> m ()+eatException a = (a >> return ()) `catch` handler+ where+ handler :: (MonadBaseControl IO m) => SomeException -> m ()+ handler _ = return ()+++------------------------------------------------------------------------------+forceSameType :: a -> a -> a+forceSameType _ a = a+++------------------------------------------------------------------------------+-- | Kill the false negative on derived show instances.+coverShowInstance :: (Monad m, Show a) => a -> m ()+coverShowInstance x = a `deepseq` b `deepseq` c `deepseq` return ()+ where+ a = showsPrec 0 x ""+ b = show x+ c = showList [x] ""+++------------------------------------------------------------------------------+coverReadInstance :: (MonadIO m, Read a) => a -> m ()+coverReadInstance x = do+ liftIO $ eatException $ evaluate $ forceSameType [(x,"")] $ readsPrec 0 ""+ liftIO $ eatException $ evaluate $ forceSameType [([x],"")] $ readList ""+++------------------------------------------------------------------------------+coverEqInstance :: (Monad m, Eq a) => a -> m ()+coverEqInstance x = a `seq` b `seq` return ()+ where+ a = x == x+ b = x /= x+++------------------------------------------------------------------------------+coverOrdInstance :: (Monad m, Ord a) => a -> m ()+coverOrdInstance x = a `deepseq` b `deepseq` return ()+ where+ a = [ x < x+ , x >= x+ , x > x+ , x <= x+ , compare x x == EQ ]++ b = min a $ max a a+++------------------------------------------------------------------------------+coverTypeableInstance :: (Monad m, Typeable a) => a -> m ()+coverTypeableInstance a = typeOf a `seq` return ()+++------------------------------------------------------------------------------+expectException :: IO a -> PropertyM IO ()+expectException m = do+ e <- liftQ $ try m+ case e of+ Left (z::SomeException) -> (forceList $ show z) `seq` return ()+ Right _ -> fail "expected exception, didn't get one"+++------------------------------------------------------------------------------+expectExceptionH :: IO a -> IO ()+expectExceptionH act = do+ e <- try act+ case e of+ Left (z::SomeException) -> (forceList $ show z) `seq` return ()+ Right _ -> fail "expected exception, didn't get one"+++------------------------------------------------------------------------------+forceList :: [a] -> ()+forceList [] = ()+forceList (x:xs) = x `seq` forceList xs+++------------------------------------------------------------------------------+liftQ :: forall a m . (Monad m) => m a -> PropertyM m a+liftQ = QC.run+++------------------------------------------------------------------------------+waitabit :: IO ()+waitabit = threadDelay $ 2*seconds+++------------------------------------------------------------------------------+seconds :: Int+seconds = (10::Int) ^ (6::Int)+
+ test/Snap/Test/Tests.hs view
@@ -0,0 +1,449 @@+{-# LANGUAGE OverloadedStrings #-}++module Snap.Test.Tests+ ( tests+ ) where++------------------------------------------------------------------------------+import Control.Applicative (Applicative (..))+import Control.Exception (ErrorCall (..), evaluate)+import Control.Monad (MonadPlus (mzero), liftM)+import Control.Monad.IO.Class (liftIO)+import Control.Monad.Trans (lift)+import Data.ByteString.Char8 (ByteString)+import qualified Data.ByteString.Char8 as S+import Data.Functor (Functor (fmap, (<$)))+import qualified Data.Map as Map+import Data.Maybe (fromJust, isJust)+import Data.Text (Text)+import Data.Time.Clock (getCurrentTime)+import Prelude (Bool (True, False), IO, Int, Maybe (Just, Nothing), Monad (..), Ord (..), const, fail, fromIntegral, return, seq, show, ($), ($!), (*), (.))+import Snap.Core (Cookie (Cookie, cookieExpires), Method (DELETE, GET, HEAD, Method, PATCH, POST, PUT), Request (rqContentLength, rqContextPath, rqIsSecure, rqMethod, rqParams, rqPathInfo, rqPostParams, rqQueryParams, rqQueryString, rqURI, rqVersion), Snap, expireCookie, extendTimeout, getCookie, getHeader, getParam, logError, readCookie, redirect, runSnap, terminateConnection, writeBS)+import Snap.Internal.Http.Types (Request (..), Response (rspCookies))+import qualified Snap.Internal.Http.Types as T+import Snap.Internal.Test.RequestBuilder (FileData (FileData), MultipartParam (Files, FormData), RequestBuilder, RequestType (DeleteRequest, GetRequest, MultipartPostRequest, RequestWithRawBody, UrlEncodedPostRequest), addCookies, addHeader, buildRequest, delete, evalHandler, get, postMultipart, postRaw, postUrlEncoded, put, requestToString, responseToString, runHandler, setContentType, setHeader, setHttpVersion, setQueryStringRaw, setRequestPath, setRequestType, setSecure)+import Snap.Test (assert404, assertBodyContains, assertRedirect, assertRedirectTo, assertSuccess, getResponseBody)+import Snap.Test.Common (coverShowInstance, expectExceptionH)+import Snap.Util.FileUploads (defaultUploadPolicy, handleMultipart, partContentType, partFieldName, partFileName)+import qualified System.IO.Streams as Streams+import Test.Framework (Test)+import Test.Framework.Providers.HUnit (testCase)+import Test.HUnit (assertBool, assertEqual)+import Text.Regex.Posix ((=~))+------------------------------------------------------------------------------++------------------------------------------------------------------------------+tests :: [Test]+tests = [ testDefaultBuild+ , testRequestToString+ , testSetRequestType+ , testSetQueryString+ , testSetQueryStringRaw+ , testHeaders+ , testMisc+ , testMultipart+ , testPost+ , testFormEncoded+ , testToString+ , testAssert404+ , testAssertBodyContains+ , testAssertRedirect+ , testCookies+ , testTerminate+ , testTrivials+ , testHostName+ ]+++------------------------------------------------------------------------------+testDefaultBuild :: Test+testDefaultBuild = testCase "test/requestBuilder/defaultBuild" $ do+ req <- buildRequest $ setRequestType GetRequest+ Streams.toList (rqBody req) >>= assertEqual "body" []+ assertEqual "pathInfo" "" $ rqPathInfo req+ assertEqual "ctx" "/" $ rqContextPath req+ assertEqual "uri" "/" $ rqURI req+ assertEqual "qs" "" $ rqQueryString req+ assertEqual "p1" Map.empty $ rqParams req+ assertEqual "p2" Map.empty $ rqQueryParams req+ assertEqual "p3" Map.empty $ rqPostParams req++------------------------------------------------------------------------------+testFormEncoded :: Test+testFormEncoded = testCase "test/requestBuilder/postFormEncoded" $ do+ req <- buildRequest $ postUrlEncoded "/foo/bar"+ $ Map.fromList [("baz", ["qux"])]+ assertEqual "post params" [("baz", ["qux"])] $ Map.toList $ rqPostParams req+------------------------------------------------------------------------------+testSetRequestType :: Test+testSetRequestType = testCase "test/requestBuilder/setRequestType" $ do+ request1 <- buildRequest $ setRequestType GetRequest+ assertEqual "setRequestType/1" GET (rqMethod request1)++ request2 <- buildRequest $ setRequestType DeleteRequest+ assertEqual "setRequestType/2" DELETE (rqMethod request2)++ request3 <- buildRequest $ setRequestType $ RequestWithRawBody PUT "foo"+ assertEqual "setRequestType/3/Method" PUT (rqMethod request3)++ rqBody3 <- getRqBody request3+ assertEqual "setRequestType/3/Body" "foo" rqBody3++ request4 <- buildRequest $ setRequestType rt4+ assertEqual "setRequestType/4/Method" POST (rqMethod request4)+ -- will test correctness of multipart generation code in another test++ request5 <- buildRequest $ setRequestType $+ UrlEncodedPostRequest $ Map.fromList [("foo", ["foo"])]+ assertEqual "setRequestType/5/Method" POST (rqMethod request5)++ request6 <- buildRequest $ setRequestType $+ RequestWithRawBody (Method "MOVE") "foo"+ assertEqual "setRequestType/6/Method" (Method "MOVE") (rqMethod request6)++ request7 <- buildRequest $ setRequestType $ RequestWithRawBody PATCH "bar"+ assertEqual "setRequestType/7/Method" PATCH (rqMethod request7)++ request8 <- buildRequest $ setRequestType $ RequestWithRawBody HEAD ""+ assertEqual "setRequestType/8/Method" HEAD (rqMethod request8)++ where+ rt4 = MultipartPostRequest [ ("foo", FormData ["foo"])+ , ("bar", Files [fd4])+ ]++ fd4 = FileData "bar.txt" "text/plain" "bar"+++------------------------------------------------------------------------------+testSetQueryString :: Test+testSetQueryString = testCase "test/requestBuilder/testSetQueryString" $ do+ request <- buildRequest $ get "/" params+ assertEqual "setQueryString" params $ rqParams request+ assertEqual "queryString" "bar=bar&foo=foo&foo=foo2" $+ rqQueryString request++ where+ params = Map.fromList [ ("foo", ["foo", "foo2"])+ , ("bar", ["bar"]) ]+++------------------------------------------------------------------------------+testSetQueryStringRaw :: Test+testSetQueryStringRaw = testCase "test/requestBuilder/testSetQueryStringRaw" $ do+ request <- buildRequest $ do+ postUrlEncoded "/" $ Map.fromList [("foo", ["foo0"])]+ addCookies [c1, c2]+ setQueryStringRaw "foo=foo&foo=foo2&bar=bar"+ assertEqual "setQueryStringRaw" params $ rqParams request+ assertEqual "cookie" (Just "k=v; k2=v2") $ getHeader "cookie" request++ where+ c1 = Cookie "k" "v" Nothing Nothing Nothing False False+ c2 = Cookie "k2" "v2" Nothing Nothing Nothing False False+ params = Map.fromList [ ("foo", ["foo0", "foo", "foo2"])+ , ("bar", ["bar"]) ]+++------------------------------------------------------------------------------+testHeaders :: Test+testHeaders = testCase "test/requestBuilder/testHeaders" $ do+ request <- buildRequest $ do+ get "/" Map.empty+ setHeader "foo" "foo"+ addHeader "bar" "bar"+ addHeader "bar" "bar2"+ setContentType "image/gif" -- this should get deleted+ assertEqual "setHeader" (Just "foo") $ getHeader "foo" request+ assertEqual "addHeader" (Just "bar,bar2") $ T.getHeader "bar" request+ assertEqual "contentType" Nothing $ T.getHeader "Content-Type" request+ assertEqual "contentLength" Nothing $ rqContentLength request+ assertEqual "contentLengthHdr" Nothing $ getHeader "Content-Length" request++ request2 <- buildRequest $ put "/" "text/zzz" "zzz"+ assertEqual "contentType2" (Just "text/zzz") $+ T.getHeader "Content-Type" request2+ assertEqual "contentLength" (Just 3) $ rqContentLength request2+ assertEqual "contentLengthHdr" (Just "3") $+ getHeader "Content-Length" request2+++------------------------------------------------------------------------------+testMisc :: Test+testMisc = testCase "test/requestBuilder/testMisc" $ do+ request <- buildRequest $ do+ get "/" Map.empty+ setSecure True+ setRequestPath "/foo/bar"++ assertEqual "secure" True $ rqIsSecure request+ assertEqual "rqPathInfo" "foo/bar" $ rqPathInfo request+ assertEqual "rqURI" "/foo/bar" $ rqURI request+ assertEqual "rqContextPath" "/" $ rqContextPath request+ assertEqual "rqVersion" (1,1) $ rqVersion request++ body <- getRqBody request+ assertEqual "body" "" body++ request2 <- buildRequest $ do+ postRaw "/" "text/zzz" "zzz"+ setHttpVersion (1,0)++ body2 <- getRqBody request2+ assertEqual "body2" "zzz" body2++ assertEqual "contentType2" (Just "text/zzz") $+ T.getHeader "Content-Type" request2+ assertEqual "postRaw" POST $ rqMethod request2+ assertEqual "rqVersion2" (1,0) $ rqVersion request2++ request3 <- buildRequest $ do+ delete "/" Map.empty+ setSecure True+ setRequestPath "/foo/bar"++ assertEqual "secure" True $ rqIsSecure request3+ assertEqual "method" DELETE $ rqMethod request3+ assertEqual "rqPathInfo" "foo/bar" $ rqPathInfo request3+ assertEqual "rqURI" "/foo/bar" $ rqURI request3+ assertEqual "rqContextPath" "/" $ rqContextPath request3+ assertEqual "rqVersion" (1,1) $ rqVersion request3+++------------------------------------------------------------------------------+testMultipart :: Test+testMultipart = testCase "test/requestBuilder/testMultipart" $ do+ request0 <- buildRequest rq+ (request,rbody) <- peekRqBody request0+ assertEqual "content-length" (Just (fromIntegral $ S.length rbody)) $+ rqContentLength request++ (_,response) <- runSnap handler (const $ return $! ())+ (const $ return $! ())+ request+ body <- getResponseBody response+ assertEqual "body" "OK" body++ where+ partHandler pinfo stream = do+ let field = partFieldName pinfo+ let fn = partFileName pinfo+ let ct = partContentType pinfo+ body <- liftM S.concat $ Streams.toList stream+ return (field, fn, ct, body)++ expectedParts = [ ("bar", Just "bar1.txt", "text/plain", "bar")+ , ("bar", Just "bar2.txt", "text/zzz", "bar2")+ , ("baz", Just "baz.gif", "text/gif", "baz") ]++ handler = do+ parts <- handleMultipart defaultUploadPolicy partHandler+ fooParam <- getParam "foo"+ liftIO $ assertEqual "param" (Just "oof") fooParam++ quuxParams <- getParam "quux"+ liftIO $ assertEqual "quux" (Just "quux1 quux2") quuxParams+ liftIO $ assertEqual "parts" expectedParts parts+ writeBS "OK"++ rq = postMultipart "/" rt++ rt = [ ("foo", FormData ["oof"])+ , ("bar", Files [fb1, fb2])+ , ("baz", Files [fz])+ , ("zzz", Files [])+ , ("zz0", FormData [])+ , ("quux", FormData ["quux1", "quux2"])+ ]++ fb1 = FileData "bar1.txt" "text/plain" "bar"+ fb2 = FileData "bar2.txt" "text/zzz" "bar2"+ fz = FileData "baz.gif" "text/gif" "baz"+++------------------------------------------------------------------------------+testPost :: Test+testPost = testCase "test/requestBuilder/testPost" $ do+ request <- buildRequest $ do+ postUrlEncoded "/" $ Map.fromList [("foo", ["foo1", "foo2"])]++ body <- getRqBody request+ assertEqual "body" "foo=foo1&foo=foo2" body+ assertEqual "len" (Just (fromIntegral $ S.length body))+ (rqContentLength request)+ assertEqual "contentType" (Just "application/x-www-form-urlencoded") $+ getHeader "Content-Type" request+++------------------------------------------------------------------------------+testToString :: Test+testToString = testCase "test/requestBuilder/testToString" $ do+ rsp <- runHandler rq h+ http <- responseToString rsp+ body <- getResponseBody rsp+ out2 <- evalHandler rq h++ assertSuccess rsp+ assertEqual "Close" (Just "close") $ getHeader "connection" rsp+ assertEqual "HTTP body" "zzz" body+ assertBool "HTTP header" $ http =~ headRE+ assertBool "HTTP date" $ http =~ dateRE+ assertEqual "monadic result" 42 out2+ where+ rq = do postRaw "/" "text/zzz" "zzz"+ setHttpVersion (1,0)+ h = do writeBS "zzz"+ logError "zzz"+ extendTimeout 5+ return (42 :: Int)+ headRE = "HTTP/1.1 200 OK" :: ByteString+ dateRE = S.concat [ "date: [a-zA-Z]+, [0-9]+ [a-zA-Z]+ "+ , "[0-9]+ [0-9]+:[0-9]+:[0-9]+ GMT"+ ]+++------------------------------------------------------------------------------+testRequestToString :: Test+testRequestToString = testCase "test/requestBuilder/reqToString" $ do+ req1 <- buildRequest $ setRequestType GetRequest+ s1 <- requestToString req1+ assertBool "HTTP header" $ s1 =~ headRE++ req2 <- buildRequest $ do postRaw "/" "text/zzz" "zzz"+ setHttpVersion (1,0)+ s2 <- requestToString req2+ assertBool "HTTP header2" $ s2 =~ postHeadRE+ assertBool "HTTP cl" $ s2 =~ ("content-length: 3" :: ByteString)++ req3 <- buildRequest $ do postRaw "/" "text/zzz" "zzz"+ setHeader "transfer-encoding" "chunked"+ s3 <- requestToString req3+ assertBool "HTTP chunked" $ "3\r\nzzz\r\n0\r\n\r\n" `S.isSuffixOf` s3++ where+ headRE = "^GET / HTTP/1.1\r\n" :: ByteString+ postHeadRE = "^POST / HTTP/1.0\r\n" :: ByteString+++------------------------------------------------------------------------------+testAssert404 :: Test+testAssert404 = testCase "test/requestBuilder/testAssert404" $ do+ rsp <- runHandler (get "/" Map.empty) mzero+ assert404 rsp+ expectExceptionH $ assertSuccess rsp+ expectExceptionH $ assertBodyContains "fjlkdja" rsp+ expectExceptionH $ assertRedirectTo "/zzzzz" rsp+ expectExceptionH $ assertRedirect rsp++ rsp2 <- runHandler (get "/" Map.empty) (return ())+ assertSuccess rsp2+ expectExceptionH $ assert404 rsp2++++------------------------------------------------------------------------------+testAssertBodyContains :: Test+testAssertBodyContains =+ testCase "test/requestBuilder/testAssertBodyContains" $ do+ rsp <- runHandler (get "/" Map.empty) $ do+ writeBS "RESPONSE IS OK"+ assertBodyContains "NSE IS" rsp+++------------------------------------------------------------------------------+testAssertRedirect :: Test+testAssertRedirect = testCase "test/requestBuilder/testAssertRedirect" $ do+ rsp <- runHandler (get "/" Map.empty) $ redirect "/bar"+ assertRedirectTo "/bar" rsp+ assertRedirect rsp+ expectExceptionH $ assertRedirectTo "/zzzz" rsp+++------------------------------------------------------------------------------+testCookies :: Test+testCookies = testCase "test/requestBuilder/cookies" $ do+ evalHandler (get "/" Map.empty) (getCookie "foo")+ >>= assertEqual "cookie1" Nothing+ evalHandler (get "/" Map.empty >> addCookies [c1]) (getCookie "foo")+ >>= assertEqual "cookie2" (Just c1)+ evalHandler (get "/" Map.empty >> addCookies [c1]) (readCookie "foo")+ >>= assertEqual "cookie3" ("bar" :: Text)+ expectExceptionH $+ evalHandler (get "/" Map.empty >> addCookies [c2])+ ((readCookie "foo") :: Snap Int)+ expectExceptionH $+ evalHandler (get "/" Map.empty >> addCookies [c2])+ ((readCookie "bar") :: Snap Int)+ rsp <- runHandler (get "/" Map.empty) expire++ let h = Map.lookup "foo" (rspCookies rsp)+ assertBool "isJust" (isJust h)++ now <- getCurrentTime+ let tm = fromJust $ cookieExpires $ fromJust h+ assertBool "time" (tm < now)+ return $! show tm `seq` ()++ -- FIXME(greg): the following test currently fails because we're rendering+ -- the set-cookie headers in the server instead of in fixupResponse where+ -- it should be happening++ -- let h = getHeader "set-cookie" rsp+ -- assertBool "isJust" (isJust h)+ -- let (Just cookie) = h+ -- assertEqual "cookie" "" cookie+ where+ c1 = Cookie "foo" "bar" Nothing Nothing Nothing False False+ c2 = Cookie "foo" "zzzzz" Nothing Nothing Nothing False False++ expire = expireCookie c1+++------------------------------------------------------------------------------+testTerminate :: Test+testTerminate = testCase "test/requestBuilder/terminate" $+ expectExceptionH $+ evalHandler (return ()) (terminateConnection $ ErrorCall "foo")+++------------------------------------------------------------------------------+testTrivials :: Test+testTrivials = testCase "test/requestBuilder/trivials" $ do+ coverShowInstance (FormData [])+ coverShowInstance (Files [])+ coverShowInstance (FileData "" "" "")+ coverShowInstance GetRequest+ z <- buildRequest $ do+ (() <$ (pure () <* rb *> rb2)) >>= lift . evaluate+ x <- fmap (*4) (return (3 :: Int))+ lift $ assertEqual "fmap" 12 x++ expectExceptionH $ buildRequest $ fail "foo"+ z `seq` return ()++ where+ rb = (return ()) :: (Functor m, Monad m, Applicative m) => RequestBuilder m ()+ rb2 = (pure ()) :: (Functor m, Monad m, Applicative m) => RequestBuilder m ()+++------------------------------------------------------------------------------+testHostName :: Test+testHostName = testCase "test/requestBuilder/hostName" $ do+ request <- buildRequest $ do+ get "/" Map.empty+ setHeader "Host" "just.an.example.com"+ assertEqual "HostName" "just.an.example.com" $ rqHostName request+ assertEqual "Host header" (Just "just.an.example.com") $ getHeader "host" request+++------------------------------------------------------------------------------+getRqBody :: Request -> IO ByteString+getRqBody = liftM S.concat . Streams.toList . rqBody+++peekRqBody :: Request -> IO (Request, ByteString)+peekRqBody rq = do+ l <- Streams.toList $ rqBody rq+ b <- Streams.fromList l++ return (rq { rqBody = b }, S.concat l)
+ test/Snap/Types/Headers/Tests.hs view
@@ -0,0 +1,77 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE OverloadedStrings #-}++------------------------------------------------------------------------------+module Snap.Types.Headers.Tests (tests) where+------------------------------------------------------------------------------+import Data.CaseInsensitive as CI+import Data.List (sort)+import qualified Data.Set as Set+import Snap.Test.Common (coverShowInstance)+import qualified Snap.Types.Headers as H+import Test.Framework (Test)+import Test.Framework.Providers.HUnit (testCase)+import Test.HUnit (assertBool, assertEqual)+------------------------------------------------------------------------------++tests :: [Test]+tests = [ testTrivials+ , testToFrom+ , testFolds+ ]+++------------------------------------------------------------------------------+testToFrom :: Test+testToFrom = testCase "types/headers/toFrom" $ do+ let h = H.unsafeFromCaseFoldedList [("foo", "bar")]+ assertEqual "toFrom1" [("foo", "bar")] $ H.toList h+ assertEqual "toFrom2" [("foo", "bar")]+ $ H.unsafeToCaseFoldedList $ H.fromList [("Foo", "bar")]+ assertBool "member" $ H.member "Foo" h+ assertBool "not member" $ not $ H.member "Fooooo" h++ let h' = H.set "foo" "zzz" . H.set "zzz" "qqq" $ h+ assertEqual "set/lookup" (Just "zzz") $ H.lookup "Foo" h'+ assertEqual "set/lookupD1" "zzz" $ H.lookupWithDefault "000" "Foo" h'+ assertEqual "set/lookupD2" "000" $ H.lookupWithDefault "000" "Zoo" h'++ assertEqual "toSort" [("a", "b"), ("foo","zzz"), ("zzz","qqq")] $ sort+ $ H.unsafeToCaseFoldedList $ H.unsafeInsert "a" "b" h'+++------------------------------------------------------------------------------+testFolds :: Test+testFolds = testCase "types/headers/folds" $ do+ let h = H.fromList [("foo", "bar"), ("bar", "baz")]+ let r1 = Set.toAscList $ H.foldl' ffl Set.empty h+ let r2 = Set.toAscList $ H.foldr ffr Set.empty h+ let r3 = Set.toAscList $ H.foldedFoldl' fl Set.empty h+ let r4 = Set.toAscList $ H.foldedFoldr fr Set.empty h++ let r = ["bar", "baz", "foo"]+ assertEqual "r1" r r1+ assertEqual "r2" r r2+ assertEqual "r3" r r3+ assertEqual "r4" r r4++ where+ fl !s !k v = Set.insert k $ Set.insert v s+ fr !k v !s = Set.insert k $ Set.insert v s+ ffl !s !k v = Set.insert (CI.foldedCase k) $ Set.insert v s+ ffr !k v !s = Set.insert (CI.foldedCase k) $ Set.insert v s++------------------------------------------------------------------------------+testTrivials :: Test+testTrivials = testCase "types/headers/show" $ do+ let h = H.empty+ assertBool "null" $ H.null h+ assertEqual "lookupWithDefault" "ok" $ H.lookupWithDefault "ok" "foo" h+ assertEqual "foldr" () $ H.foldr (\_ _ x -> x) () $ H.insert "ok" "ok" h+ assertEqual "foldl'" () $ H.foldl' (\x _ _ -> x) () $ H.insert "ok" "ok" h+ assertEqual "foldedFoldr" () $+ H.foldedFoldr (\_ _ x -> x) () $ H.unsafeInsert "ok" "ok" h+ assertEqual "foldedFoldl'" () $+ H.foldedFoldl' (\x _ _ -> x) () $ H.unsafeInsert "ok" "ok" h+ assertBool "member" $ not $ H.member "foo" h+ coverShowInstance h
+ test/Snap/Util/CORS/Tests.hs view
@@ -0,0 +1,121 @@+{-# OPTIONS_GHC -fno-warn-warnings-deprecations #-}++{-# LANGUAGE CPP #-}+{-# LANGUAGE OverloadedStrings #-}++module Snap.Util.CORS.Tests (tests) where++------------------------------------------------------------------------------+import Data.ByteString.Char8 (ByteString)+import Data.CaseInsensitive (CI (..))+#if !MIN_VERSION_base(4,8,0)+import Data.Functor ((<$>))+#endif+import qualified Data.HashSet as HashSet+import qualified Data.Set as Set+import qualified Data.Text as Text+import qualified Data.Text.Encoding as Text+import Snap.Core (Method (..), getHeader, Response(..))+import Snap.Test (RequestBuilder, runHandler, setHeader, setRequestType, RequestType(..), setRequestPath)+import Snap.Util.CORS (applyCORS,CORSOptions(..),defaultOptions,HashableMethod(..))+import Test.Framework (Test)+import Test.Framework.Providers.HUnit (testCase)+import Test.HUnit (assertEqual,Assertion)+------------------------------------------------------------------------------+++------------------------------------------------------------------------------+tests :: [Test]+tests = [ testCORSSimple+ , testCORSOptions+ ]+++ ---------------+ -- Constants --+ ---------------++------------------------------------------------------------------------------+origin :: ByteString+origin = "http://origin.org"++++ -----------+ -- Tests --+ -----------++------------------------------------------------------------------------------+testCORSSimple :: Test+testCORSSimple = testCase "CORS/simple" $ do+ let testDefault meth = do+ r <- runHandler (mkMethReq meth) $+ applyCORS defaultOptions $ return ()+ checkAllowOrigin (Just origin) r+ checkAllowCredentials (Just "true") r+ checkExposeHeaders Nothing r+ mapM_ testDefault [GET,POST,PUT,DELETE,HEAD]++------------------------------------------------------------------------------+testCORSOptions :: Test+testCORSOptions = testCase "CORS/options" $ do+ let opts = applyCORS defaultOptions { corsAllowedMethods =+ return $ HashSet.singleton $ HashableMethod GET }+ r <- runHandler (mkMethReq OPTIONS+ >> setRequestMethod "GET"+ >> setRequestHeaders "X-STUFF, Content-Type") $+ opts $ return ()+ checkAllowOrigin (Just origin) r+ checkAllowCredentials (Just "true") r+ checkAllowHeaders (Just "Content-Type, X-STUFF") r+ checkAllowMethods (Just "GET") r+ ---------------------------------------------------------+ s <- runHandler (mkMethReq OPTIONS+ >> setRequestMethod "POST"+ >> setRequestHeaders "X-STUFF, Content-Type") $+ opts $ return ()+ checkAllowOrigin Nothing s+ checkAllowCredentials Nothing s+ checkAllowHeaders Nothing s+ checkAllowMethods Nothing s+++ ---------------+ -- Functions --+ ---------------++------------------------------------------------------------------------------+mkMethReq :: Method -> RequestBuilder IO ()+mkMethReq meth = do+ setRequestType $ RequestWithRawBody meth ""+ setRequestPath "/"+ setHeader "Origin" origin++checkHeader :: CI ByteString -> Maybe ByteString -> Response -> Assertion+checkHeader h v r = assertEqual ("Header " ++ show h) v (getHeader h r)++checkAllowOrigin :: Maybe ByteString -> Response -> Assertion+checkAllowOrigin = checkHeader "Access-Control-Allow-Origin"++checkAllowCredentials :: Maybe ByteString -> Response -> Assertion+checkAllowCredentials = checkHeader "Access-Control-Allow-Credentials"++checkExposeHeaders :: Maybe ByteString -> Response -> Assertion+checkExposeHeaders = checkHeader "Access-Control-Expose-Headers"++checkAllowHeaders :: Maybe ByteString -> Response -> Assertion+checkAllowHeaders v r =+ assertEqual "Header Access-Control-Allow-Headers"+ (getSet <$> v)+ (getSet <$> getHeader "Access-Control-Allow-Headers" r)+ where+ getSet = Set.fromList . Text.splitOn ", " . Text.decodeUtf8++checkAllowMethods :: Maybe ByteString -> Response -> Assertion+checkAllowMethods = checkHeader "Access-Control-Allow-Methods"++setRequestMethod :: ByteString -> RequestBuilder IO ()+setRequestMethod = setHeader "Access-Control-Request-Method"++setRequestHeaders :: ByteString -> RequestBuilder IO ()+setRequestHeaders = setHeader "Access-Control-Request-Headers"
+ test/Snap/Util/FileServe/Tests.hs view
@@ -0,0 +1,627 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}++module Snap.Util.FileServe.Tests+ ( tests ) where++------------------------------------------------------------------------------+import Control.Applicative ((<|>))+import Control.Monad (forM_, liftM)+import Data.ByteString (ByteString)+import qualified Data.ByteString.Char8 as S (break, breakEnd, drop, isInfixOf, pack)+import qualified Data.HashMap.Strict as HashMap (empty, fromList, toList)+import qualified Data.Map as Map (empty)+import Data.Maybe (fromJust, isJust)+import qualified Data.Text as T (unpack)+import Snap.Internal.Core (Snap, fixupResponse, pass, runSnap, writeBS)+import Snap.Internal.Http.Types (Request, Response (rspContentLength, rspStatus), getHeader, setHeader)+import Snap.Internal.Util.FileServe (DirectoryConfig (..), decodeFilePath, defaultMimeTypes, fancyDirectoryConfig, serveDirectory, serveDirectoryWith, serveFile, simpleDirectoryConfig)+import qualified Snap.Test as Test (buildRequest, get, getResponseBody, setQueryStringRaw)+import Snap.Test.Common (expectExceptionH)+import Test.Framework (Test)+import Test.Framework.Providers.HUnit (testCase)+import Test.HUnit (assertBool, assertEqual)+#if !MIN_VERSION_base(4,8,0)+import Control.Applicative ((<$>))+#endif+------------------------------------------------------------------------------+++------------------------------------------------------------------------------+tests :: [Test]+tests = [ testFooBin+ , testFooTxt+ , testFooHtml+ , testFooBinBinBin+ , testDirectoryPasses+ , test404s+ , testFsSingle+ , testFsCfgA+ , testFsCfgB+ , testFsCfgC+ , testFsCfgD+ , testFsCfgFancy+ , testRangeOK+ , testRangeBad+ , testMultiRange+ , testIfRange+ , testBadUrl+ , testDecodeFilePath+ ]+++------------------------------------------------------------------------------+expect404 :: IO Response -> IO ()+expect404 m = do+ r <- m+ assertBool "expected 404" (rspStatus r == 404)+++------------------------------------------------------------------------------+expect302 :: ByteString -> IO Response -> IO ()+expect302 p m = do+ r <- m+ assertBool "expected 302" (rspStatus r == 302)+ assertEqual "redir location"+ (Just p)+ (getHeader "location" r)+++------------------------------------------------------------------------------+runIt :: Snap a -> Request -> IO (Request, Response)+runIt m rq = do+ (rq', rsp) <- runSnap m d d rq+ rsp' <- fixupResponse rq rsp+ return (rq', rsp')+ where+ d = const $ return ()+++------------------------------------------------------------------------------+go :: Snap a -> ByteString -> IO Response+go m s = do+ rq <- mkRequest s+ liftM snd (runIt m rq)+++------------------------------------------------------------------------------+goIfModifiedSince :: Snap a -> ByteString -> ByteString -> IO Response+goIfModifiedSince m s lm = do+ rq <- mkRequest s+ let r = setHeader "if-modified-since" lm rq+ liftM snd (runIt m r)+++------------------------------------------------------------------------------+goIfRange :: Snap a -> ByteString -> (Int,Int) -> ByteString -> IO Response+goIfRange m s (start,end) lm = do+ rq <- mkRequest s+ let r = setHeader "if-range" lm $+ setHeader "Range"+ (S.pack $ "bytes=" ++ show start ++ "-" ++ show end)+ rq+ liftM snd (runIt m r)+++------------------------------------------------------------------------------+goRange :: Snap a -> ByteString -> (Int,Int) -> IO Response+goRange m s (start,end) = do+ rq' <- mkRequest s+ let rq = setHeader "Range"+ (S.pack $ "bytes=" ++ show start ++ "-" ++ show end)+ rq'+ liftM snd (runIt m rq)+++------------------------------------------------------------------------------+goMultiRange :: Snap a -> ByteString -> (Int,Int) -> (Int,Int) -> IO Response+goMultiRange m s (start,end) (start2,end2) = do+ rq' <- mkRequest s+ let rq = setHeader "Range"+ (S.pack $ "bytes=" ++ show start ++ "-" ++ show end+ ++ "," ++ show start2 ++ "-" ++ show end2)+ rq'+ liftM snd (runIt m rq)+++------------------------------------------------------------------------------+goRangePrefix :: Snap a -> ByteString -> Int -> IO Response+goRangePrefix m s start = do+ rq' <- mkRequest s+ let rq = setHeader "Range"+ (S.pack $ "bytes=" ++ show start ++ "-")+ rq'+ liftM snd (runIt m rq)+++------------------------------------------------------------------------------+goRangeSuffix :: Snap a -> ByteString -> Int -> IO Response+goRangeSuffix m s end = do+ rq' <- mkRequest s+ let rq = setHeader "Range"+ (S.pack $ "bytes=-" ++ show end)+ rq'+ liftM snd (runIt m rq)+++------------------------------------------------------------------------------+mkRequest :: ByteString -> IO Request+mkRequest uri = Test.buildRequest $ do+ Test.get pathPart Map.empty+ Test.setQueryStringRaw queryPart+ where+ (pathPart, queryPart) = breakQuery uri++ breakQuery s = (a, S.drop 1 b)+ where+ (a,b) = S.break (=='?') s+++------------------------------------------------------------------------------+fs :: Snap ()+fs = do+ x <- serveDirectory "test/data/fileServe"+ return $! x `seq` ()+++------------------------------------------------------------------------------+fsSingle :: Snap ()+fsSingle = do+ x <- serveFile "test/data/fileServe/foo.html"+ return $! x `seq` ()+++------------------------------------------------------------------------------+fsCfg :: DirectoryConfig Snap -> Snap ()+fsCfg cfg = do+ x <- serveDirectoryWith cfg "test/data/fileServe"+ return $! x `seq` ()+++------------------------------------------------------------------------------+testFooBin :: Test+testFooBin = testCase "fileServe/foo.bin" $ do+ r1 <- go fs "foo.bin"+ checkProps "fileServe/foo.bin/default" r1+ let !lm = fromJust $ getHeader "last-modified" r1++ go fs "foo.bin?blah=blah" >>= checkProps "fileServe/foo.bin/query-string"++ -- check last modified stuff+ r2 <- goIfModifiedSince fs "foo.bin" lm+ assertEqual "foo.bin 304" 304 $ rspStatus r2++ r3 <- goIfModifiedSince fs "foo.bin" "Wed, 15 Nov 1995 04:58:08 GMT"+ checkProps "fileServe/foo.bin/ifModifiedSince" r3++ where+ checkProps name r = do+ b <- Test.getResponseBody r++ assertEqual (name ++ "/contents") "FOO\n" b+ assertEqual (name ++ "/content-type")+ (Just "application/octet-stream")+ (getHeader "content-type" r)++ assertEqual (name ++ "/size") (Just 4) (rspContentLength r)++ assertBool (name ++ "/last-modified")+ (isJust $ getHeader "last-modified" r)++ assertEqual (name ++ "/accept-ranges")+ (Just "bytes")+ (getHeader "accept-ranges" r)+++------------------------------------------------------------------------------+testFooTxt :: Test+testFooTxt = testCase "fileServe/foo.txt" $ do+ go fs "foo.txt" >>= checkProps "fileServe/foo.txt/default"+ go fs "foo.txt?blah=blah" >>= checkProps "fileServe/foo.txt/query"++ where+ checkProps name r = do+ b <- Test.getResponseBody r++ assertEqual (name ++ "/contents") "FOO\n" b+ assertEqual (name ++ "/content-type")+ (Just "text/plain")+ (getHeader "content-type" r)++ assertEqual (name ++ "/size") (Just 4) (rspContentLength r)++ assertBool (name ++ "/last-modified")+ (isJust $ getHeader "last-modified" r)++ assertEqual (name ++ "/accept-ranges")+ (Just "bytes")+ (getHeader "accept-ranges" r)+++------------------------------------------------------------------------------+testFooHtml :: Test+testFooHtml = testCase "fileServe/foo.html" $ do+ go fs "foo.html" >>= checkProps "fileServe/foo.html/default"+ go fs "foo.html?bar=bar" >>= checkProps "fileServe/foo.html/query"++ where+ checkProps name r = do+ b <- Test.getResponseBody r++ assertEqual (name ++ "/contents") "FOO\n" b+ assertEqual (name ++ "/content-type")+ (Just "text/html")+ (getHeader "content-type" r)++ assertEqual (name ++ "/size") (Just 4) (rspContentLength r)++ assertBool (name ++ "/last-modified")+ (isJust $ getHeader "last-modified" r)++ assertEqual (name ++ "/accept-ranges")+ (Just "bytes")+ (getHeader "accept-ranges" r)+++------------------------------------------------------------------------------+testFooBinBinBin :: Test+testFooBinBinBin = testCase "fileServe/foo.bin.bin.bin" $ do+ go fs "foo.bin.bin.bin" >>= checkProps "fileServe/foo.bin.bin.bin"++ where+ checkProps name r = do+ b <- Test.getResponseBody r++ assertEqual (name ++ "/contents") "FOO\n" b+ assertEqual (name ++ "/content-type")+ (Just "application/octet-stream")+ (getHeader "content-type" r)++ assertEqual (name ++ "/size") (Just 4) (rspContentLength r)++ assertBool (name ++ "/last-modified")+ (isJust $ getHeader "last-modified" r)++ assertEqual (name ++ "/accept-ranges")+ (Just "bytes")+ (getHeader "accept-ranges" r)+++------------------------------------------------------------------------------+test404s :: Test+test404s = testCase "fileServe/404s" $ do+ expect404 $ go fs "jfldksjflksd"+ expect404 $ go fs "dummy/../foo.txt"+ expect404 $ go fs "/etc/password"++ coverMimeMap+++------------------------------------------------------------------------------+printName :: FilePath -> Snap ()+printName c = writeBS $ snd $ S.breakEnd (=='/') $ S.pack c+++------------------------------------------------------------------------------+cfgA, cfgB, cfgC, cfgD :: DirectoryConfig Snap+cfgA = DirectoryConfig {+ indexFiles = []+ , indexGenerator = const pass+ , dynamicHandlers = HashMap.empty+ , mimeTypes = defaultMimeTypes+ , preServeHook = \x -> x `seq` (return $! ())+ }++cfgB = DirectoryConfig {+ indexFiles = ["index.txt", "altindex.html"]+ , indexGenerator = const pass+ , dynamicHandlers = HashMap.empty+ , mimeTypes = defaultMimeTypes+ , preServeHook = \x -> x `seq` (return $! ())+ }++cfgC = DirectoryConfig {+ indexFiles = ["index.txt", "altindex.html"]+ , indexGenerator = printName+ , dynamicHandlers = HashMap.empty+ , mimeTypes = defaultMimeTypes+ , preServeHook = \x -> x `seq` (return $! ())+ }++cfgD = DirectoryConfig {+ indexFiles = []+ , indexGenerator = const pass+ , dynamicHandlers = HashMap.fromList [ (".txt", printName) ]+ , mimeTypes = defaultMimeTypes+ , preServeHook = \x -> x `seq` (return $! ())+ }+++------------------------------------------------------------------------------+testFsCfgA :: Test+testFsCfgA = testCase "fileServe/cfgA" $ do+ let gooo = go (fsCfg cfgA)++ -- Named file in the root directory+ gooo "foo.bin" >>= checkProps "cfgA1/1" "application/octet-stream"+ gooo "foo.bin?blah=blah" >>=+ checkProps "cfgA1/2" "application/octet-stream"++ -- Missing file in the root directory+ expect404 $ gooo "bar.bin"++ -- Named file in a subdirectory+ gooo "mydir2/foo.txt" >>= checkProps "cfgA1/subdir/1" "text/plain"+ gooo "mydir2/foo.txt?z=z" >>= checkProps "cfgA1/subdir/2" "text/plain"++ -- Missing file in a subdirectory+ expect404 $ gooo "mydir2/bar.txt"++ -- Request for directory with no trailing slash+ expect302 "/mydir1/" $ gooo "mydir1"++ -- Request for directory with no trailing slash, with query param+ expect302 "/mydir1/?z=z" $ gooo "mydir1?z=z"++ -- Request for directory with trailing slash, no index+ expect404 $ gooo "mydir1/"+ expect404 $ gooo "mydir2/"++ -- Request file with trailing slash+ expect404 $ gooo "foo.html/"+ expect404 $ gooo "mydir2/foo.txt/"++ where+ checkProps name ct r = do+ b <- Test.getResponseBody r++ assertEqual (name ++ "/contents") "FOO\n" b+ assertEqual (name ++ "/content-type")+ (Just ct)+ (getHeader "content-type" r)++ assertEqual (name ++ "/size") (Just 4) (rspContentLength r)++ assertBool (name ++ "/last-modified")+ (isJust $ getHeader "last-modified" r)++ assertEqual (name ++ "/accept-ranges")+ (Just "bytes")+ (getHeader "accept-ranges" r)+++------------------------------------------------------------------------------+testFsCfgB :: Test+testFsCfgB = testCase "fileServe/cfgB" $ do+ let gooo = go (fsCfg cfgB)++ -- Request for root directory with index+ rB1 <- gooo "mydir1/"+ bB1 <- Test.getResponseBody rB1++ assertEqual "B1" "INDEX\n" bB1+ assertEqual "B1 content-type"+ (Just "text/plain")+ (getHeader "content-type" rB1)++ -- Request for root directory with index, query+ rB2 <- gooo "mydir1/?z=z"+ bB2 <- Test.getResponseBody rB2++ assertEqual "B2" "INDEX\n" bB2+ assertEqual "B2 content-type"+ (Just "text/plain")+ (getHeader "content-type" rB2)+++ -- Request for root directory with alternate index+ rB3 <- gooo "mydir3/"+ bB3 <- Test.getResponseBody rB3++ assertEqual "B3" "ALTINDEX\n" bB3+ assertEqual "B3 content-type"+ (Just "text/html")+ (getHeader "content-type" rB3)++ -- Request for root directory with no index+ expect404 $ gooo "mydir2/"+++------------------------------------------------------------------------------+testFsCfgC :: Test+testFsCfgC = testCase "fileServe/cfgC" $ do+ let gooo = go (fsCfg cfgC)++ -- Request for root directory with index+ rC1 <- gooo "mydir1/"+ bC1 <- Test.getResponseBody rC1++ assertEqual "C1" "INDEX\n" bC1+ assertEqual "C1 content-type"+ (Just "text/plain")+ (getHeader "content-type" rC1)++ -- Request for root directory with index, query+ rC2 <- gooo "mydir1/?z=z"+ bC2 <- Test.getResponseBody rC2++ assertEqual "C2" "INDEX\n" bC2+ assertEqual "C2 content-type"+ (Just "text/plain")+ (getHeader "content-type" rC2)++ -- Request for root directory with generated index+ rC3 <- gooo "mydir2/"+ bC3 <- Test.getResponseBody rC3++ assertEqual "C3" "mydir2" bC3+++------------------------------------------------------------------------------+testFsCfgD :: Test+testFsCfgD = testCase "fileServe/cfgD" $ do+ -- Request for file with dynamic handler+ rD1 <- go (fsCfg cfgD) "mydir2/foo.txt"+ bD1 <- Test.getResponseBody rD1++ assertEqual "D1" "foo.txt" bD1+++------------------------------------------------------------------------------+testFsCfgFancy :: Test+testFsCfgFancy = testCase "fileServe/cfgFancy" $ do+ -- Request for directory with autogen index+ rE1 <- go (fsCfg fancyDirectoryConfig) "mydir2/"+ assertEqual "index-type" (Just "text/html; charset=utf-8")+ (getHeader "content-type" rE1)+ bE1 <- Test.getResponseBody rE1++ assertBool "autogen-sub-index" $+ "Directory Listing: /mydir2/" `S.isInfixOf` bE1+ assertBool "autogen-sub-parent" $+ "<a href='../'" `S.isInfixOf` bE1+ assertBool "autogen-sub-dir" $+ "<a href='dir/'" `S.isInfixOf` bE1+ assertBool "autogen-sub-file" $+ "<a href='foo.txt'" `S.isInfixOf` bE1++ -- Request for directory with autogen index+ rE2 <- go (fsCfg fancyDirectoryConfig) "mydir2/?z=z"+ bE2 <- Test.getResponseBody rE2++ assertBool "autogen-sub-index" $+ "Directory Listing: /mydir2/" `S.isInfixOf` bE2+ assertBool "autogen-sub-parent" $+ "<a href='../'" `S.isInfixOf` bE2+ assertBool "autogen-sub-file" $+ "<a href='foo.txt'" `S.isInfixOf` bE2++ rE3 <- go (fsCfg fancyDirectoryConfig) "mydir2/foo.txt"+ assertEqual "c-t" (Just "text/plain") (getHeader "content-type" rE3)+++------------------------------------------------------------------------------+testFsSingle :: Test+testFsSingle = testCase "fileServe/Single" $ do+ r1 <- go fsSingle "foo.html"+ b1 <- Test.getResponseBody r1++ assertEqual "foo.html" "FOO\n" b1+ assertEqual "foo.html content-type"+ (Just "text/html")+ (getHeader "content-type" r1)++ assertEqual "foo.html size" (Just 4) (rspContentLength r1)+++------------------------------------------------------------------------------+testRangeOK :: Test+testRangeOK = testCase "fileServe/range/ok" $ do+ r1 <- goRange fsSingle "foo.html" (1,2)+ assertEqual "foo.html 206" 206 $ rspStatus r1+ b1 <- Test.getResponseBody r1++ assertEqual "foo.html partial" "OO" b1+ assertEqual "foo.html partial size" (Just 2) (rspContentLength r1)+ assertEqual "foo.html content-range"+ (Just "bytes 1-2/4")+ (getHeader "Content-Range" r1)++ r2 <- goRangeSuffix fsSingle "foo.html" 3+ assertEqual "foo.html 206" 206 $ rspStatus r2+ assertEqual "c-l" (Just 3) (rspContentLength r2)+ assertEqual "c-l hdr" (Just "3") (getHeader "content-length" r2)+ b2 <- Test.getResponseBody r2+ assertEqual "foo.html partial suffix" "OO\n" b2++ r3 <- goRangePrefix fsSingle "foo.html" 2+ assertEqual "foo.html 206" 206 $ rspStatus r3+ b3 <- Test.getResponseBody r3+ assertEqual "foo.html partial prefix" "O\n" b3+++------------------------------------------------------------------------------+testMultiRange :: Test+testMultiRange = testCase "fileServe/range/multi" $ do+ r1 <- goMultiRange fsSingle "foo.html" (1,2) (3,3)++ -- we don't support multiple ranges so it's ok for us to return 200 here;+ -- test this behaviour+ assertEqual "foo.html 200" 200 $ rspStatus r1+ b1 <- Test.getResponseBody r1++ assertEqual "foo.html" "FOO\n" b1+++------------------------------------------------------------------------------+testRangeBad :: Test+testRangeBad = testCase "fileServe/range/bad" $ do+ r1 <- goRange fsSingle "foo.html" (1,17)+ assertEqual "bad range" 416 (rspStatus r1)+ assertEqual "bad range content-range"+ (Just "bytes */4")+ (getHeader "Content-Range" r1)+ assertEqual "bad range content-length" (Just 0) (rspContentLength r1)+ b1 <- Test.getResponseBody r1+ assertEqual "bad range empty body" "" b1++ r2 <- goRangeSuffix fsSingle "foo.html" 4893+ assertEqual "bad suffix range" 416 $ rspStatus r2+++------------------------------------------------------------------------------+coverMimeMap :: (Monad m) => m ()+coverMimeMap = Prelude.mapM_ f $ HashMap.toList defaultMimeTypes+ where+ f (!k,!v) = return $ case k `seq` v `seq` () of () -> ()+++------------------------------------------------------------------------------+testIfRange :: Test+testIfRange = testCase "fileServe/range/if-range" $ do+ r <- goIfRange fs "foo.bin" (1,2) "Wed, 15 Nov 1995 04:58:08 GMT"+ assertEqual "foo.bin 200" 200 $ rspStatus r+ b <- Test.getResponseBody r+ assertEqual "foo.bin" "FOO\n" b++ r2 <- goIfRange fs "foo.bin" (1,2) "Tue, 01 Oct 2030 04:58:08 GMT"+ assertEqual "foo.bin 206" 206 $ rspStatus r2+ b2 <- Test.getResponseBody r2+ assertEqual "foo.bin partial" "OO" b2++ r3 <- goIfRange fs "foo.bin" (1,24324) "Tue, 01 Oct 2030 04:58:08 GMT"+ assertEqual "foo.bin 200" 200 $ rspStatus r3+ b3 <- Test.getResponseBody r3+ assertEqual "foo.bin" "FOO\n" b3+++------------------------------------------------------------------------------+testBadUrl :: Test+testBadUrl = testCase "fileServe/badUrl" $ do+ expectExceptionH $ go (fs <|> error "foo") "%$z%%%%%%%%"+ expectExceptionH $ go (fs <|> error "foo") "//etc/passwd"+++------------------------------------------------------------------------------+testDirectoryPasses :: Test+testDirectoryPasses = testCase "fileServe/directory-passes" $ do+ expectExceptionH $ go (fs <|> error "foo") ""+ expectExceptionH $ go (fsCfg simpleDirectoryConfig <|> error "foo") ""+ r <- go (fsCfg simpleDirectoryConfig) "foo.txt"+ assertEqual "c-t" (Just "text/plain") (getHeader "content-type" r)+++------------------------------------------------------------------------------+testDecodeFilePath :: Test+testDecodeFilePath = testCase "fileServe/decodeFilePath" $ do+ forM_ table $ \(nm, inp, expected) -> do+ out <- (map fromEnum . T.unpack) <$> decodeFilePath inp+ assertEqual nm expected out+ where+ table = [ ("bad" , "\x00\xd8\x00\xd8" , [0, 0xd8, 0, 0xd8] )+ , ("bom" , "\xfe\xff" , [0xfe, 0xff] )+ , ("ok" , "ok" , [fromEnum 'o',+ fromEnum 'k'] )+ ]
+ test/Snap/Util/FileUploads/Tests.hs view
@@ -0,0 +1,1199 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}++module Snap.Util.FileUploads.Tests+ ( tests ) where++------------------------------------------------------------------------------+import Control.Applicative (Alternative ((<|>)), (<$>))+import Control.DeepSeq (deepseq)+import Control.Exception (ErrorCall (..), evaluate, throwIO)+import Control.Exception.Lifted (Exception (fromException, toException), catch, finally, throw)+import Control.Monad (Monad (return, (>>), (>>=)), liftM, void)+import Control.Monad.IO.Class (MonadIO (liftIO))+import Data.ByteString (ByteString)+import qualified Data.ByteString.Char8 as S+import qualified Data.ByteString.Lazy as L+import Data.IORef (atomicModifyIORef, newIORef, readIORef, writeIORef)+import Data.List (foldl', length)+import qualified Data.Map as Map+import Data.Maybe (Maybe (..), fromJust, maybe)+import qualified Data.Text as T+import Data.Typeable (Typeable)+import Prelude (Bool (..), Either (..), Eq (..), FilePath, IO, Int, Num (..), Show (..), const, either, error, filter, map, seq, snd, ($), ($!), (&&), (++), (.))+import Snap.Internal.Core (EscapeSnap (TerminateConnection), Snap, getParam, getPostParam, getQueryParam, runSnap)+import Snap.Internal.Http.Types (Request (rqBody), Response, setHeader)+import Snap.Internal.Util.FileUploads (BadPartException (..), FileUploadException (..), FormFile (..), PartDisposition (..), PartInfo (..), PolicyViolationException (..), allowWithMaximumSize, defaultFileUploadPolicy, defaultUploadPolicy, disallow, doProcessFormInputs, fileUploadExceptionReason, foldMultipart, getMaximumNumberOfFormInputs, getMinimumUploadRate, getMinimumUploadSeconds, getUploadTimeout, handleFileUploads, handleFileUploads, handleFormUploads, setMaximumFileSize, setMaximumFormInputSize, setMaximumNumberOfFiles, setMaximumNumberOfFormInputs, setMaximumSkippedFileSize, setMinimumUploadRate, setMinimumUploadSeconds, setProcessFormInputs, setSkipFilesWithoutNames, setUploadTimeout, storeAsLazyByteString, toPartDisposition, withTemporaryStore)+import qualified Snap.Test as Test+import Snap.Test.Common (coverEqInstance, coverShowInstance, coverTypeableInstance, eatException, expectExceptionH, seconds, waitabit)+import qualified Snap.Types.Headers as H+import System.Directory (createDirectoryIfMissing, doesFileExist, getDirectoryContents, removeDirectoryRecursive, removeFile)+import System.IO.Streams (RateTooSlowException)+import qualified System.IO.Streams as Streams+import System.Mem (performGC)+import System.Timeout (timeout)+import Test.Framework (Test)+import Test.Framework.Providers.HUnit (testCase)+import Test.HUnit (Assertion, assertBool, assertEqual, assertFailure)+------------------------------------------------------------------------------+++------------------------------------------------------------------------------+data TestException = TestException+ deriving (Show, Typeable)++instance Exception TestException++------------------------------------------------------------------------------+tests :: [Test]+tests = [ testFoldMultipart1+ , testFoldMultipart2+ , testFilePolicyViolation1+ , testFilePolicyViolation2+ , testEmptyNamePolicyViolation+ , testEmptyNameStore+ , testEmptyNameSkip+ , testTemporaryStore+ , testTemporaryStoreSafeMove+ , testSuccess1+ , testSuccess2+ , testSuccessUtf8Filename+ , testBadParses+ , testPerPartPolicyViolation1+ , testPerPartPolicyViolation2+ , testFormInputsPolicyViolation+ , testFormSizePolicyViolation+ , testNoFileName+ , testNoFileNameTooBig+ , testTooManyHeaders+ , testNoBoundary+ , testNoMixedBoundary+ , testWrongContentType+ , testSlowEnumerator+ , testSlowEnumerator2+ , testAbortedBody+ , testTrivials+ , testDisconnectionCleanup+ ]++------------------------------------------------------------------------------+testFoldMultipart1 :: Test+testFoldMultipart1 = testCase "fileUploads/fold1" $+ void $ go hndl mixedTestBody++ where+ hndl :: Snap ()+ hndl = do+ (params, files) <- foldMultipart defaultUploadPolicy hndl' []++ let fileMap = foldl' f Map.empty files+ liftIO $ assertEqual "2 params returned" 2 (length params)+ let [p1, p2] = params++ liftIO $ do+ let Just (a1, a2, a3) = Map.lookup "file1.txt" fileMap+ let Just (b1, b2, b3) = Map.lookup "file2.gif" fileMap+ assertEqual "file1 contents"+ ("text/plain", file1Contents)+ (a1, a2)+ assertEqual "file1 header 1"+ (Just "text/plain")+ (H.lookup "content-type" a3)+ assertEqual "file1 header 2"+ (Just "attachment; filename=\"file1.txt\"")+ (H.lookup "content-disposition" a3)++ assertEqual "file2 contents"+ ("image/gif", file2Contents)+ (b1, b2)+ assertEqual "file2 header 1"+ (Just "image/gif")+ (H.lookup "content-type" b3)+ assertEqual "file2 header 2"+ (Just "attachment; filename=\"file2.gif\"")+ (H.lookup "content-disposition" b3)++ assertEqual "field1 contents"+ ("field1", formContents1)+ p1++ assertEqual "field2 contents"+ ("field2", formContents2)+ p2++ f mp (fn, ct, x, hdrs) = Map.insert fn (ct,x,hdrs) mp++ hndl' partInfo istream acc = do+ let+ fn = fromJust $ partFileName partInfo+ ct = partContentType partInfo+ hdrs = partHeaders partInfo+ body <- S.concat <$> Streams.toList istream+ return (acc ++ [(fn, ct, body, hdrs)])++++------------------------------------------------------------------------------+testFoldMultipart2 :: Test+testFoldMultipart2 = testCase "fileUploads/fold2" $+ void $ go hndl mixedTestBody+ where+ policy = setProcessFormInputs False defaultUploadPolicy++ hndl = do+ (fields, fileCount) <- foldMultipart policy hndl' (0::Int)++ liftIO $ do+ assertEqual "num params" 4 fileCount+ assertEqual "num processed" 0 (length fields)++ hndl' !_ !_ !acc = return $ acc + 1++++------------------------------------------------------------------------------+testFilePolicyViolation1 :: Test+testFilePolicyViolation1 = testCase "fileUploads/filePolicyViolation1" $+ assertThrows (go hndl mixedTestBody) h+ where+ h e = assertIsFileSizeException e++ hndl = handleFormUploads defaultUploadPolicy+ (setMaximumFileSize 0 defaultFileUploadPolicy)+ (const storeAsLazyByteString)++++------------------------------------------------------------------------------+testFilePolicyViolation2 :: Test+testFilePolicyViolation2 = testCase "fileUploads/filePolicyViolation2" $+ assertThrows (go hndl mixedTestBody) h+ where+ h (PolicyViolationException r) =+ assertBool "correct exception"+ (T.isInfixOf "number of files exceeded the maximum" r)++ hndl = handleFormUploads defaultUploadPolicy+ (setMaximumNumberOfFiles 0 defaultFileUploadPolicy)+ (const storeAsLazyByteString)+++------------------------------------------------------------------------------+testEmptyNamePolicyViolation :: Test+testEmptyNamePolicyViolation = testCase "fileUploads/emptyNamePolicyViolation" $+ assertThrows (go hndl noFileNameTestBody) h+ where+ h e = assertIsFileSizeException e++ hndl = handleFormUploads defaultUploadPolicy+ (setSkipFilesWithoutNames True defaultFileUploadPolicy)+ (const storeAsLazyByteString)+++------------------------------------------------------------------------------+assertIsFileSizeException :: PolicyViolationException -> Assertion+assertIsFileSizeException (PolicyViolationException r) =+ assertBool "file size exception"+ (T.isInfixOf "File" r &&+ T.isInfixOf "exceeded maximum allowable size" r)+++------------------------------------------------------------------------------+testEmptyNameStore :: Test+testEmptyNameStore = testCase "fileUploads/emptyNameStore" $+ void $ go hndl noFileNameTestBody+ where+ hndl = do+ (inputs, files) <- handleFormUploads defaultUploadPolicy+ (setSkipFilesWithoutNames False defaultFileUploadPolicy)+ (const storeAsLazyByteString)+ liftIO $ do+ assertEqual "got both files" 2 (length files)+ let [f1, f2] = files+ assertEqual "file1 contents"+ (FormFile "files" $ L.fromChunks [file1Contents])+ f1+ assertEqual "file2 contents"+ (FormFile "files" $ L.fromChunks [file2Contents])+ f2+ assertEqual "inputs present" 2 (length inputs)+ return ()+++------------------------------------------------------------------------------+testEmptyNameSkip :: Test+testEmptyNameSkip = testCase "fileUploads/emptyNameSkip" $+ void $ go hndl noFileNameTestBody+ where+ hndl = do+ (inputs, files) <- handleFormUploads defaultUploadPolicy+ ( setMaximumSkippedFileSize 4000+ . setSkipFilesWithoutNames True+ $ defaultFileUploadPolicy )+ (const storeAsLazyByteString)+ liftIO $ do+ assertEqual "files skipped" 0 (length files)+ assertEqual "inputs present" 2 (length inputs)+ return ()+++------------------------------------------------------------------------------+testTemporaryStore :: Test+testTemporaryStore = testCase "fileUploads/temporaryStore" $+ harness "tempdir1" hndl mixedTestBody+ where+ hndl = do+ (fn1, fn2) <- withTemporaryStore "tempdir1" "upload" $ \store -> do+ (inputs, files) <- handleFormUploads defaultUploadPolicy+ defaultFileUploadPolicy+ (const store)+ liftIO $ do+ assertEqual "num files" 2 (length files)+ assertEqual "inputs present" 2 (length inputs)+ let [FormFile name1 fn1, FormFile name2 fn2] = files+ fc1 <- liftIO $ S.readFile fn1+ fc2 <- liftIO $ S.readFile fn2++ assertEqual "file1 content"+ ("files", file1Contents)+ (name1, fc1)++ assertEqual "file2 content"+ ("files", file2Contents)+ (name2, fc2)+ return (fn1, fn2)+ liftIO $ do+ ex1 <- doesFileExist fn1+ ex2 <- doesFileExist fn2+ assertEqual "file1 deleted" False ex1+ assertEqual "file2 deleted" False ex2+++------------------------------------------------------------------------------+testTemporaryStoreSafeMove :: Test+testTemporaryStoreSafeMove = testCase "fileUploads/temporaryStoreSafeMove" $+ harness "tempdir1" hndl mixedTestBody+ where+ hndl = do+ -- should not throw+ withTemporaryStore "tempdir1" "upload" $ \store -> do+ (_, files) <- handleFormUploads defaultUploadPolicy+ defaultFileUploadPolicy+ (const store)+ liftIO $ do+ assertEqual "num files" 2 (length files)+ let [FormFile _ fn1, FormFile _ fn2] = files+ removeFile fn1+ removeFile fn2++++------------------------------------------------------------------------------+testSuccess1 :: Test+testSuccess1 = testCase "fileUploads/success1" $+ harness tmpdir hndl mixedTestBody++ where+ tmpdir = "tempdir1"++ hndl = do+ xs <- handleFileUploads tmpdir+ defaultUploadPolicy+ (const $ allowWithMaximumSize 300000)+ hndl'++ let fileMap = foldl' f Map.empty xs+ p1 <- getParam "field1"+ p1P <- getPostParam "field1"+ p1Q <- getQueryParam "field1"+ p2 <- getParam "field2"++ liftIO $ do+ let Just (a1, a2, a3) = Map.lookup "file1.txt" fileMap+ let Just (b1, b2, b3) = Map.lookup "file2.gif" fileMap+ assertEqual "file1 contents"+ ("text/plain", file1Contents)+ (a1, a2)+ assertEqual "file1 header 1"+ (Just "text/plain")+ (H.lookup "content-type" a3)+ assertEqual "file1 header 2"+ (Just "attachment; filename=\"file1.txt\"")+ (H.lookup "content-disposition" a3)++ assertEqual "file2 contents"+ ("image/gif", file2Contents)+ (b1, b2)+ assertEqual "file2 header 1"+ (Just "image/gif")+ (H.lookup "content-type" b3)+ assertEqual "file2 header 2"+ (Just "attachment; filename=\"file2.gif\"")+ (H.lookup "content-disposition" b3)++ assertEqual "field1 contents"+ (Just formContents1)+ p1++ assertEqual "field1 POST contents" (Just formContents1) p1P+ assertEqual "field1 query contents" Nothing p1Q+ assertEqual "field2 contents" (Just formContents2) p2++ f mp (fn, ct, x, hdrs) = Map.insert fn (ct,x,hdrs) mp++ hndl' partInfo =+ either throw+ (\fp -> do+ x <- liftIO $ S.readFile fp+ let fn = fromJust $ partFileName partInfo+ let ct = partContentType partInfo+ let hdrs = partHeaders partInfo+ return (fn, ct, x, hdrs))+++------------------------------------------------------------------------------+testSuccess2 :: Test+testSuccess2 = testCase "fileUploads/success2" $+ harness tmpdir hndl mixedTestBody++ where+ tmpdir = "tempdir2"++ policy = setProcessFormInputs False defaultUploadPolicy++ hndl = do+ ref <- liftIO $ newIORef (0::Int)+ _ <- handleFileUploads tmpdir+ policy+ (const $ allowWithMaximumSize 300000)+ (hndl' ref)++ n <- liftIO $ readIORef ref+ liftIO $ assertEqual "num params" 4 n++ hndl' !ref !_ !_ = atomicModifyIORef ref (\x -> (x+1, ()))++ ------------------------------------------------------------------------------+testSuccessUtf8Filename :: Test+testSuccessUtf8Filename = testCase "fileUploads/utf8-filename" $+ harness tmpdir hndl utf8FilenameBody++ where+ tmpdir = "tempdir3"++ hndl = do+ xs <- handleFileUploads tmpdir+ defaultUploadPolicy+ (const $ allowWithMaximumSize 300000)+ hndl'++ liftIO $ assertEqual "filename" [filenameUtf8] xs++ hndl' pinfo _ = return $ fromJust $ partFileName pinfo+++------------------------------------------------------------------------------+testBadParses :: Test+testBadParses = testCase "fileUploads/badParses" $ do+ harness tmpdir hndl mixedTestBodyWithBadTypes+ where+ tmpdir = "tempdir_bad_types"++ hndl = do+ xs <- handleFileUploads tmpdir+ defaultUploadPolicy+ (const $ allowWithMaximumSize 300000)+ hndl'++ let fileMap = foldl' f Map.empty xs+ p1 <- getParam "field1"+ p1P <- getPostParam "field1"+ p1Q <- getQueryParam "field1"+ p2 <- getParam "field2"+ pBoo <- getParam "boo"++ liftIO $ do+ assertEqual "file1 contents"+ (Just ("text/plain", file1Contents))+ (Map.lookup "file1.txt" fileMap)++ assertEqual "file2 contents"+ (Just ("text/plain", file2Contents))+ (Map.lookup "file2.gif" fileMap)++ assertEqual "field1 param contents" (Just formContents1) p1+ assertEqual "field1 POST contents" (Just formContents1) p1P+ assertEqual "field1 query contents" Nothing p1Q+ assertEqual "field2 contents" Nothing p2+ assertEqual "boo contents" (Just "boo") pBoo++ f mp (fn, ct, x) = Map.insert fn (ct,x) mp++ hndl' partInfo =+ either throw+ (\fp -> do+ x <- liftIO $ S.readFile fp+ let fn = fromJust $ partFileName partInfo+ let ct = partContentType partInfo+ return (fn, ct, x))++++------------------------------------------------------------------------------+testPerPartPolicyViolation1 :: Test+testPerPartPolicyViolation1 = testCase "fileUploads/perPartPolicyViolation1" $+ harness tmpdir hndl mixedTestBody+ where+ tmpdir = "tempdir_pol1"++ hndl = do+ _ <- handleFileUploads tmpdir defaultUploadPolicy+ (const disallow)+ hndl'++ p1 <- getParam "field1"+ p2 <- getParam "field2"+++ liftIO $ do+ assertEqual "field1 contents"+ (Just formContents1)+ p1++ assertEqual "field2 contents"+ (Just formContents2)+ p2++ hndl' !_ e = either (\i -> show i `deepseq` return $! ())+ (const $ error "expected policy violation")+ e+++------------------------------------------------------------------------------+testPerPartPolicyViolation2 :: Test+testPerPartPolicyViolation2 = testCase "fileUploads/perPartPolicyViolation2" $+ harness tmpdir hndl mixedTestBody+ where+ tmpdir = "tempdir_pol2"++ hndl = handleFileUploads tmpdir defaultUploadPolicy+ (const $ allowWithMaximumSize 4)+ hndl'++ hndl' partInfo e = (if partFileName partInfo == Just "file1.txt"+ then ePass+ else eFail) e++ eFail = either (\i -> show i `deepseq` return ())+ (const $ error "expected policy violation")++ ePass = either (throw)+ (\i -> show i `deepseq` return ())++++------------------------------------------------------------------------------+testNoFileName :: Test+testNoFileName = testCase "fileUploads/noFileName" $+ (harness tmpdir hndl noFileNameTestBody)+ where+ tmpdir = "tempdir_noname"++ hndl = handleFileUploads tmpdir defaultUploadPolicy+ (const $ allowWithMaximumSize 400000)+ hndl'++ hndl' pinfo !_ = do+ assertEqual "filename" Nothing $ partFileName pinfo+ assertEqual "disposition" DispositionFile $ partDisposition pinfo+++------------------------------------------------------------------------------+testNoFileNameTooBig :: Test+testNoFileNameTooBig = testCase "fileUploads/noFileNameTooBig" $+ assertThrows (harness tmpdir hndl noFileNameTestBody) h+ where+ h !(e :: FileUploadException) = do+ let r = fileUploadExceptionReason e+ assertBool "correct exception"+ (T.isInfixOf "File" r &&+ T.isInfixOf "exceeded maximum allowable size" r)++ tmpdir = "tempdir_noname_toobig"++ hndl = handleFileUploads tmpdir defaultUploadPolicy+ (const $ allowWithMaximumSize 1)+ hndl'++ hndl' pinfo !e = do+ let (Left !x) = e+ coverShowInstance x+ assertEqual "filename" Nothing $ partFileName pinfo+ assertEqual "disposition" DispositionFile $ partDisposition pinfo+ throwIO x+++------------------------------------------------------------------------------+testFormSizePolicyViolation :: Test+testFormSizePolicyViolation =+ testCase "fileUploads/formSizePolicy" $+ assertThrows (harness tmpdir hndl mixedTestBody) h+ where+ h !(e :: FileUploadException) = do+ let r = fileUploadExceptionReason e+ assertBool "correct exception"+ (T.isInfixOf "form input" r &&+ T.isInfixOf "exceeded maximum permissible" r)++ tmpdir = "tempdir_formpol"++ policy = setMaximumFormInputSize 2 defaultUploadPolicy++ hndl = handleFileUploads tmpdir policy+ (const $ allowWithMaximumSize 4)+ hndl'++ hndl' xs _ = show xs `deepseq` return ()+++------------------------------------------------------------------------------+testFormInputsPolicyViolation :: Test+testFormInputsPolicyViolation =+ testCase "fileUploads/formInputsPolicy" $+ assertThrows (harness tmpdir hndl mixedTestBody) h+ where+ h !(e :: FileUploadException) = do+ let r = fileUploadExceptionReason e+ assertBool "correct exception"+ (T.isInfixOf "number of form inputs" r &&+ T.isInfixOf "exceeded maximum" r)++ tmpdir = "tempdir_formpol2"++ policy = setMaximumNumberOfFormInputs 0 defaultUploadPolicy++ hndl = handleFileUploads tmpdir policy+ (\x -> x `seq` allowWithMaximumSize 4000)+ hndl'++ hndl' xs _ = show xs `deepseq` return ()+++------------------------------------------------------------------------------+testNoBoundary :: Test+testNoBoundary = testCase "fileUploads/noBoundary" $+ expectExceptionH $+ harness' goBadContentType tmpdir hndl mixedTestBody+ where+ tmpdir = "tempdir_noboundary"++ hndl = handleFileUploads tmpdir+ defaultUploadPolicy+ (const $ allowWithMaximumSize 300000)+ hndl'++ hndl' xs _ = show xs `deepseq` return ()+++------------------------------------------------------------------------------+testNoMixedBoundary :: Test+testNoMixedBoundary = testCase "fileUploads/noMixedBoundary" $+ expectExceptionH $+ harness' go tmpdir hndl badMixedBody+ where+ tmpdir = "tempdir_mixednoboundary"++ hndl = handleFileUploads tmpdir+ defaultUploadPolicy+ (const $ allowWithMaximumSize 300000)+ hndl'++ hndl' xs _ = show xs `deepseq` return ()+++------------------------------------------------------------------------------+testWrongContentType :: Test+testWrongContentType = testCase "fileUploads/wrongContentType" $+ expectExceptionH $+ harness' goWrongContentType tmpdir hndl mixedTestBody+ where+ tmpdir = "tempdir_noboundary"++ hndl = handleFileUploads tmpdir+ defaultUploadPolicy+ (const $ allowWithMaximumSize 300000)+ hndl'+ <|> error "expect fail here"++ hndl' xs _ = show xs `deepseq` return ()+++------------------------------------------------------------------------------+testTooManyHeaders :: Test+testTooManyHeaders = testCase "fileUploads/tooManyHeaders" $+ assertThrows (harness tmpdir hndl bigHeadersBody) h+ where+ h (e :: BadPartException) = show e `deepseq` return ()++ tmpdir = "tempdir_tooManyHeaders"++ hndl = handleFileUploads tmpdir defaultUploadPolicy+ (const $ allowWithMaximumSize 4)+ hndl'++ hndl' xs _ = show xs `deepseq` return ()+++------------------------------------------------------------------------------+testAbortedBody :: Test+testAbortedBody = testCase "fileUploads/abortedBody" $+ expectExceptionH $+ harness' goAndAbort tmpdir hndl abortedTestBody+ where+ tmpdir = "tempdir_abort"++ hndl = handleFileUploads tmpdir defaultUploadPolicy+ (const $ allowWithMaximumSize 400000)+ hndl'++ hndl' xs _ = show xs `deepseq` return ()++++------------------------------------------------------------------------------+testSlowEnumerator :: Test+testSlowEnumerator =+ testCase "fileUploads/tooSlow" $+ assertThrows (harness' goSlowEnumerator tmpdir hndl mixedTestBody) h0+ where+ h0 (e :: EscapeSnap) = do+ let (TerminateConnection se) = e+ (me :: Maybe RateTooSlowException) = fromException se+ maybe (throw e) h me++ h (e :: RateTooSlowException) = coverShowInstance e++ tmpdir = "tempdir_tooslow"++ policy = setMinimumUploadRate 200000 $+ setMinimumUploadSeconds 2 $+ defaultUploadPolicy++ hndl = handleFileUploads tmpdir policy+ (const $ allowWithMaximumSize 400000)+ hndl'++ hndl' xs _ = show xs `deepseq` return ()+++------------------------------------------------------------------------------+testSlowEnumerator2 :: Test+testSlowEnumerator2 =+ testCase "fileUploads/tooSlow2" $+ assertThrows (harness' goSlowEnumerator tmpdir hndl mixedTestBody) h0+ where+ h0 (e :: EscapeSnap) = do+ let (TerminateConnection se) = e+ (me :: Maybe RateTooSlowException) = fromException se+ maybe (throw e) h me++ h (e :: RateTooSlowException) = e `seq` return ()++ tmpdir = "tempdir_tooslow2"++ policy = setUploadTimeout 2 defaultUploadPolicy++ hndl = handleFileUploads tmpdir policy+ (const $ allowWithMaximumSize 400000)+ hndl'++ hndl' xs _ = show xs `deepseq` return ()+++------------------------------------------------------------------------------+testTrivials :: Test+testTrivials = testCase "fileUploads/trivials" $ do+ assertEqual "" False $ doProcessFormInputs policy+ assertEqual "" 1000 $ getMinimumUploadRate policy+ assertEqual "" 1000 $ getMinimumUploadRate defaultUploadPolicy+ assertEqual "" 5 $ getMinimumUploadSeconds policy+ assertEqual "" 9 $ getUploadTimeout policy++ let pvi = PolicyViolationException ""+ coverTypeableInstance pvi+ evaluate $ ((fromJust $+ fromException (toException pvi)) :: PolicyViolationException)+ evaluate $ ((fromJust $+ fromException (toException pvi)) :: FileUploadException)+ let !_ = policyViolationExceptionReason pvi++ let bpi = BadPartException ""+ coverTypeableInstance bpi+ let !_ = badPartExceptionReason bpi++ coverShowInstance $ WrappedFileUploadException $ BadPartException ""+ coverShowInstance $ PartInfo "" Nothing "" DispositionFile (H.empty)+ coverShowInstance $ toPartDisposition ""+ coverEqInstance $ DispositionOther ""++ let !gfui = WrappedFileUploadException $ BadPartException ""+ evaluate $ fileUploadExceptionReason gfui++ void $ evaluate+ $ getMaximumNumberOfFormInputs+ $ setMaximumNumberOfFormInputs 2 policy++ where+ policy = setProcessFormInputs False $+ setMinimumUploadRate 1000 $+ setMinimumUploadSeconds 5 $+ setUploadTimeout 9 $+ defaultUploadPolicy+++------------------------------------------------------------------------------+testDisconnectionCleanup :: Test+testDisconnectionCleanup = testCase "fileUploads/disconnectionCleanup" $ do+ runTest `finally` removeDirectoryRecursive tmpdir+ where+ runTest = do+ eatException $ removeDirectoryRecursive tmpdir+ createDirectoryIfMissing True tmpdir+ rq <- mkDamagedRequest mixedTestBody+ eatException $ liftM snd (runIt hndl rq)+ performGC+ dirs <- liftM (filter (\x -> x /= "." && x /= "..")) $+ getDirectoryContents tmpdir+ assertEqual "files should be cleaned up" [] dirs+++ tmpdir = "tempdirC"+ hndl = handleFileUploads tmpdir+ defaultUploadPolicy+ (const $ allowWithMaximumSize 300000)+ hndl'++ hndl' _ _ = return ()+++------------------------------------------------------------------------------+harness :: FilePath -> Snap a -> ByteString -> IO ()+harness = harness' go+++------------------------------------------------------------------------------+harness' :: (Snap a -> ByteString -> IO Response)+ -> FilePath+ -> Snap a+ -> ByteString+ -> IO ()+harness' g tmpdir hndl body = (do+ createDirectoryIfMissing True tmpdir+ !_ <- g hndl body+ return ()) `finally` removeDirectoryRecursive tmpdir+++------------------------------------------------------------------------------+assertThrows :: Exception e => IO a -> (e -> IO ()) -> IO ()+assertThrows act handle = catch action handle+ where+ action = act >> assertFailure "Expected exception to be thrown"+++------------------------------------------------------------------------------+mkRequest :: ByteString -> IO Request+mkRequest body = Test.buildRequest $ Test.postRaw "/" ct body+ where+ ct = S.append "multipart/form-data; boundary=" boundaryValue+++------------------------------------------------------------------------------+mkDamagedRequest :: ByteString -> IO Request+mkDamagedRequest body = do+ req <- Test.buildRequest $ Test.postRaw "/" ct ""++ e <- newIORef False >>= Streams.makeInputStream . enum++ return $! req { rqBody = e }++ where+ ct = S.append "multipart/form-data; boundary=" boundaryValue+ enum ref = do+ x <- readIORef ref+ if x+ then throw TestException+ else do writeIORef ref True+ return $! Just $! S.take (S.length body - 1) body+++------------------------------------------------------------------------------+go :: Snap a -> ByteString -> IO Response+go m s = do+ rq <- mkRequest s+ liftM snd (runIt m rq)+++------------------------------------------------------------------------------+goBadContentType :: Snap a -> ByteString -> IO Response+goBadContentType m s = do+ rq <- mkRequest s+ let rq' = setHeader "Content-Type" "multipart/form-data" rq+ liftM snd (runIt m rq')+++------------------------------------------------------------------------------+goWrongContentType :: Snap a -> ByteString -> IO Response+goWrongContentType m s = do+ rq <- mkRequest s+ let rq' = setHeader "Content-Type" "text/plain" rq+ liftM snd (runIt m rq')+++------------------------------------------------------------------------------+goSlowEnumerator :: Snap a -> ByteString -> IO Response+goSlowEnumerator m s = do+ rq <- mkRequest s+ e <- Streams.fromGenerator slowInput+ let rq' = rq { rqBody = e }+ mx <- timeout (20*seconds) (liftM snd (runIt m rq'))+ maybe (error "timeout") return mx++ where+ body = S.unpack s++ slowInput = f body+ where+ f [] = return ()+ f (x:xs) = do+ liftIO waitabit+ Streams.yield $ S.singleton x+ f xs+++------------------------------------------------------------------------------+goAndAbort :: Snap a -> ByteString -> IO Response+goAndAbort m s = do+ rq <- mkRequest s+ e <- Streams.fromGenerator generator+ let rq' = rq { rqBody = e }+ mx <- timeout (20*seconds) (liftM snd (runIt m rq'))+ maybe (error "timeout") return mx++ where+ generator = do+ Streams.yield s+ liftIO $ throwIO+ $ ErrorCall "For in that sleep of death what dreams may come."+++------------------------------------------------------------------------------+runIt :: Snap a -> Request -> IO (Request, Response)+runIt m rq = runSnap m d bump rq+ where+ bump !f = let !_ = f 1 in return $! ()++ d :: forall a . Show a => a -> IO ()+ d = \x -> show x `deepseq` return ()+++------------------------------------------------------------------------------+-- TEST DATA++formContents1 :: ByteString+formContents1 = "form contents 1"++formContents2 :: ByteString+formContents2 = "form contents 2 zzzzzzzzzzzzzzzzzzzz"++file1Contents :: ByteString+file1Contents = "foo"++file2Contents :: ByteString+file2Contents = "... contents of file2.gif ..."++filenameUtf8 :: ByteString+filenameUtf8 = "\xd1\x82\xd0\xb5\xd1\x81\xd1\x82.png" -- "тест.png" as UTF-8++boundaryValue :: ByteString+boundaryValue = "fkjldsakjfdlsafldksjf"++subBoundaryValue :: ByteString+subBoundaryValue = "zjkzjjfjskzjzjkz"++crlf :: ByteString+crlf = "\r\n"+++------------------------------------------------------------------------------+mixedTestBody :: ByteString+mixedTestBody =+ S.concat+ [ "--"+ , boundaryValue+ , crlf+ , "content-disposition: form-data; name=\"field1\"\r\n"+ , crlf+ , formContents1+ , crlf+ , "--"+ , boundaryValue+ , crlf+ , "content-disposition: form-data; name=\"field2\"\r\n"+ , crlf+ , formContents2+ , crlf+ , "--"+ , boundaryValue+ , crlf+ , "content-disposition: form-data; name=\"files\"\r\n"+ , "Content-type: multipart/mixed; boundary="+ , subBoundaryValue+ , crlf+ , crlf+ , "--"+ , subBoundaryValue+ , crlf+ , "Content-disposition: attachment; filename=\"file1.txt\"\r\n"+ , "Content-Type: text/plain\r\n"+ , crlf+ , file1Contents+ , crlf+ , "--"+ , subBoundaryValue+ , crlf+ , "Content-disposition: attachment; filename=\"file2.gif\"\r\n"+ , "Content-type: image/gif\r\n"+ , "Content-Transfer-Encoding: binary\r\n"+ , crlf+ , file2Contents+ , crlf+ , "--"+ , subBoundaryValue+ , "--\r\n"+ , "--"+ , boundaryValue+ , "--\r\n"+ ]++------------------------------------------------------------------------------+mixedTestBodyWithBadTypes :: ByteString+mixedTestBodyWithBadTypes =+ S.concat+ [ "--"+ , boundaryValue+ , crlf+ , "content-type: ;\x01;\x01;\x01;\r\n"+ , "content-disposition: form-data; name=\"field1\"\r\n\r\n"+ , formContents1+ , crlf+ , "--"+ , boundaryValue+ , crlf+ , "content-disposition: form-data;\x01;;;\x01 name=\"field2\"\r\n"+ , crlf+ , formContents2+ , crlf+ , "--"+ , boundaryValue+ , crlf+ , "content-disposition: \x01\x01\x01\x01\r\n"+ , "Content-type: multipart/mixed; boundary="+ , subBoundaryValue+ , crlf+ , crlf+ , "--"+ , subBoundaryValue+ , crlf+ , "Content-disposition: attachment; filename=\"file1.txt\"\r\n"+ , "Content-Type: ;\x01;\x01;\x01;\r\n"+ , crlf+ , file1Contents+ , crlf+ , "--"+ , subBoundaryValue+ , crlf+ , "Content-disposition: attachment; filename=\"file2.gif\"\r\n"+ , "Content-type: ;\x01;\x01;\x01\r\n"+ , "Content-Transfer-Encoding: binary\r\n"+ , crlf+ , file2Contents+ , crlf+ , "--"+ , subBoundaryValue+ , "--\r\n"+ , "--"+ , boundaryValue+ , crlf+ , "Content-type: multipart/mixed; \x01\x01;;\x01;\r\n"+ , "Content-disposition: form-data; name=boo\r\n"+ , crlf+ , "boo"+ , crlf+ , "--"+ , boundaryValue+ , "--\r\n"+ ]+++------------------------------------------------------------------------------+badMixedBody :: ByteString+badMixedBody =+ S.concat+ [ crlf+ , "--"+ , boundaryValue+ , crlf+ , "content-disposition: form-data; name=\"field1\"\r\n"+ , crlf+ , formContents1+ , crlf+ , "--"+ , boundaryValue+ , crlf+ , "content-disposition: form-data; name=\"field2\"\r\n"+ , crlf+ , formContents2+ , crlf+ , "--"+ , boundaryValue+ , crlf+ , "content-disposition: form-data; name=\"files\"\r\n"+ , "Content-type: multipart/mixed"+ , crlf+ , crlf+ , "--"+ , subBoundaryValue+ , crlf+ , "Content-disposition: attachment; filename=\"file1.txt\"\r\n"+ , "Content-Type: text/plain\r\n"+ , crlf+ , file1Contents+ , crlf+ , "--"+ , subBoundaryValue+ , crlf+ , "Content-disposition: attachment; filename=\"file2.gif\"\r\n"+ , "Content-type: image/gif\r\n"+ , "Content-Transfer-Encoding: binary\r\n"+ , crlf+ , file2Contents+ , crlf+ , "--"+ , subBoundaryValue+ , "--\r\n"+ , "--"+ , boundaryValue+ , "--\r\n"+ ]+++------------------------------------------------------------------------------+bigHeadersBody :: ByteString+bigHeadersBody =+ S.concat (+ [ "--"+ , boundaryValue+ , crlf+ , "content-disposition: form-data; name=\"field1\"\r\n" ]+ +++ map (\i -> S.pack ("field_" ++ show i ++ ": bar\r\n")) [1..40000::Int]+ +++ [ crlf+ , formContents1+ , crlf+ , "--"+ , boundaryValue+ , "--\r\n"+ ])+++------------------------------------------------------------------------------+noFileNameTestBody :: ByteString+noFileNameTestBody =+ S.concat+ [ "--"+ , boundaryValue+ , crlf+ , "content-disposition: form-data; name=\"field1\"\r\n"+ , crlf+ , formContents1+ , crlf+ , "--"+ , boundaryValue+ , crlf+ , "content-disposition: form-data; name=\"field2\"\r\n"+ , crlf+ , formContents2+ , crlf+ , "--"+ , boundaryValue+ , crlf+ , "content-disposition: form-data; name=\"files\"\r\n"+ , "Content-type: multipart/mixed; boundary="+ , subBoundaryValue+ , crlf+ , crlf+ , "--"+ , subBoundaryValue+ , crlf+ , "Content-disposition: file\r\n"+ , "Content-Type: text/plain\r\n"+ , crlf+ , file1Contents+ , crlf+ , "--"+ , subBoundaryValue+ , crlf+ , "Content-disposition: file\r\n"+ , "Content-type: image/gif\r\n"+ , "Content-Transfer-Encoding: binary\r\n"+ , crlf+ , file2Contents+ , crlf+ , "--"+ , subBoundaryValue+ , "--\r\n"+ , "--"+ , boundaryValue+ , "--\r\n"+ ]+++------------------------------------------------------------------------------+abortedTestBody :: ByteString+abortedTestBody =+ S.concat [ "--"+ , boundaryValue+ , crlf+ , "content-disposition: form-data; name=\"field1\"\r\n"+ , crlf+ , formContents1+ , crlf+ , "--"+ , boundaryValue+ , crlf+ , "content-disposition: form-data; name=\"field2\"\r\n"+ , "fdjkljflsdkjfsd"+ ]+++------------------------------------------------------------------------------+utf8FilenameBody :: ByteString+utf8FilenameBody =+ S.concat+ [ "--"+ , boundaryValue+ , crlf+ , "Content-Disposition: form-data; name=\"file\"; filename=\""+ , filenameUtf8+ , "\"\r\n"+ , "Content-Type: image/png\r\n"+ , crlf+ , file2Contents+ , crlf+ , "--"+ , boundaryValue+ , "--\r\n"+ ]
+ test/Snap/Util/GZip/Tests.hs view
@@ -0,0 +1,391 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE Rank2Types #-}+{-# LANGUAGE ScopedTypeVariables #-}++module Snap.Util.GZip.Tests+ ( tests ) where+------------------------------------------------------------------------------+import qualified Codec.Compression.GZip as GZip+import qualified Codec.Compression.Zlib as Zlib+import Control.Monad (replicateM)+import Data.Bits ((.&.))+import qualified Data.ByteString as B+import Data.ByteString.Builder (byteString)+import Data.ByteString.Char8 (ByteString)+import qualified Data.ByteString.Char8 as S+import qualified Data.ByteString.Lazy.Char8 as L+import Data.CaseInsensitive (CI)+import Data.Word (Word8)+import Snap.Core (Request, Response, Snap, getHeader, modifyResponse, runSnap, setContentType, setHeader, setResponseBody, writeBS)+import qualified Snap.Test as Test+import Snap.Test.Common (coverTypeableInstance, expectException, expectExceptionH, liftQ)+import Snap.Util.GZip (BadAcceptEncodingException, noCompression, withCompression)+import qualified System.IO.Streams as Streams+import System.Random (randomIO)+import Test.Framework (Test)+import Test.Framework.Providers.HUnit (testCase)+import Test.Framework.Providers.QuickCheck2 (testProperty)+import Test.HUnit (assertEqual)+import qualified Test.HUnit as H+import Test.QuickCheck (Arbitrary (arbitrary))+import Test.QuickCheck.Monadic (PropertyM, assert, forAllM, monadicIO)+#if !MIN_VERSION_base(4,8,0)+import Control.Applicative ((<$>))+#endif+------------------------------------------------------------------------------++------------------------------------------------------------------------------+tests :: [Test]+tests = [ testIdentity1+ , testIdentity1_charset+ , testIdentity2+ , testIdentity3+ , testIdentity4+ , testIdentity5+ , testNoHeaders+ , testNoAcceptEncoding+ , testAcceptEncodingBad+ , testNopWhenContentEncodingSet+ , testCompositionDoesn'tExplode+ , testGzipLotsaChunks+ , testNoCompression+ , testBadHeaders+ , testTrivials+ ]+++------------------------------------------------------------------------------+gzipHdrs, xGzipHdrs, xGzipHdrs2, badHdrs, deflateHdrs, xDeflateHdrs+ :: (CI ByteString, ByteString)++gzipHdrs = ("Accept-Encoding", "deflate,froz,gzip,glorble, x-gzip" )+xGzipHdrs = ("Accept-Encoding", "x-gzip;q=1.0" )+xGzipHdrs2 = ("Accept-Encoding", "x-gzip;q=1" )+badHdrs = ("Accept-Encoding", "*&%^&^$%&%&*^\023" )+deflateHdrs = ("Accept-Encoding", "deflate" )+xDeflateHdrs = ("Accept-Encoding", "x-deflate" )+++------------------------------------------------------------------------------+mkNoHeaders :: IO Request+mkNoHeaders = Test.buildRequest $ return ()+++------------------------------------------------------------------------------+mkGzipRq :: IO Request+mkGzipRq = Test.buildRequest $ uncurry Test.setHeader gzipHdrs+++------------------------------------------------------------------------------+mkXGzipRq :: IO Request+mkXGzipRq = Test.buildRequest $ uncurry Test.setHeader xGzipHdrs+++------------------------------------------------------------------------------+mkXGzip2Rq :: IO Request+mkXGzip2Rq = Test.buildRequest $ uncurry Test.setHeader xGzipHdrs2+++------------------------------------------------------------------------------+mkDeflateRq :: IO Request+mkDeflateRq = Test.buildRequest $ uncurry Test.setHeader deflateHdrs+++------------------------------------------------------------------------------+mkXDeflateRq :: IO Request+mkXDeflateRq = Test.buildRequest $ uncurry Test.setHeader xDeflateHdrs+++------------------------------------------------------------------------------+mkBadRq :: IO Request+mkBadRq = Test.buildRequest $ uncurry Test.setHeader badHdrs+++------------------------------------------------------------------------------+seqSnap :: Snap a -> Snap a+seqSnap m = do+ !x <- m+ return $! x `seq` x+++------------------------------------------------------------------------------+goGeneric :: IO Request -> Snap a -> IO (Request, Response)+goGeneric mkRq m = do+ rq <- mkRq+ runSnap (seqSnap m) d d rq++ where+ d = (const $ return ())+++goGZip, goDeflate, goXGZip, goXGZip2 :: Snap a -> IO (Request,Response)+goNoHeaders, goXDeflate, goBad :: Snap a -> IO (Request,Response)++goGZip = goGeneric mkGzipRq+goDeflate = goGeneric mkDeflateRq+goXGZip = goGeneric mkXGzipRq+goXGZip2 = goGeneric mkXGzip2Rq+goXDeflate = goGeneric mkXDeflateRq+goBad = goGeneric mkBadRq+goNoHeaders = goGeneric mkNoHeaders+++------------------------------------------------------------------------------+noContentType :: L.ByteString -> Snap ()+noContentType body = modifyResponse $ setResponseBody e+ where+ e s = do+ Streams.writeList (map byteString $ L.toChunks body) s+ return s++------------------------------------------------------------------------------+withContentType :: ByteString -> L.ByteString -> Snap ()+withContentType ct body = modifyResponse $+ setResponseBody e . setContentType ct+ where+ e s = do+ Streams.writeList (map byteString $ L.toChunks body) s+ return s+++------------------------------------------------------------------------------+textPlain :: L.ByteString -> Snap ()+textPlain = withContentType "text/plain"+++------------------------------------------------------------------------------+binary :: L.ByteString -> Snap ()+binary = withContentType "application/octet-stream"+++------------------------------------------------------------------------------+testNoHeaders :: Test+testNoHeaders = testProperty "gzip/noheaders" $+ monadicIO $+ forAllM arbitrary prop+ where+ prop :: L.ByteString -> PropertyM IO ()+ prop s = liftQ $ do+ -- if there's no content-type, withCompression should be a no-op+ (!_,!rsp) <- goNoHeaders (seqSnap $ withCompression+ $ noContentType s)+ assertEqual "" Nothing $ getHeader "Content-Encoding" rsp+ assertEqual "" Nothing $ getHeader "Vary" rsp+ body <- Test.getResponseBody rsp++ assertEqual "" s $ L.fromChunks [body]+++------------------------------------------------------------------------------+testNoAcceptEncoding :: Test+testNoAcceptEncoding = testProperty "gzip/noAcceptEncoding" $+ monadicIO $+ forAllM arbitrary prop+ where+ prop :: L.ByteString -> PropertyM IO ()+ prop s = liftQ $ do+ -- if there's no accept-encoding, withCompression should be a no-op+ (!_,!rsp) <- goNoHeaders (seqSnap $ withCompression+ $ textPlain s)+ assertEqual "" Nothing $ getHeader "Content-Encoding" rsp+ assertEqual "" Nothing $ getHeader "Vary" rsp++ body <- Test.getResponseBody rsp+ assertEqual "" s $ L.fromChunks [body]+++------------------------------------------------------------------------------+testAcceptEncodingBad :: Test+testAcceptEncodingBad = testCase "gzip/acceptEncodingBad" $ do+ expectExceptionH $ Test.runHandler (Test.setHeader "Accept-Encoding" "$")+ snap+ expectExceptionH $ Test.runHandler+ (Test.setHeader "Accept-Encoding" "gzip;q=^") snap+ where+ snap = withCompression $ do+ modifyResponse $ setHeader "Content-Type" "text/plain"+ writeBS "ok"+++------------------------------------------------------------------------------+testIdentity1 :: Test+testIdentity1 = testProperty "gzip/identity1" $ monadicIO $ forAllM arbitrary prop+ where+ prop :: L.ByteString -> PropertyM IO ()+ prop s = liftQ $ do+ (!_,!rsp) <- goGZip (seqSnap $ withCompression $ textPlain s)+ assertEqual "" (Just "gzip") $ getHeader "Content-Encoding" rsp+ assertEqual "" (Just "Accept-Encoding") $ getHeader "Vary" rsp++ body <- Test.getResponseBody rsp+ let s1 = GZip.decompress $ L.fromChunks [body]+ assertEqual "" s s1+++------------------------------------------------------------------------------+testIdentity1_charset :: Test+testIdentity1_charset = testProperty "gzip/identity1_charset" $+ monadicIO $ forAllM arbitrary prop+ where+ prop :: L.ByteString -> PropertyM IO ()+ prop s = liftQ $ do+ (!_,!rsp) <- goGZip (seqSnap $ withCompression $+ withContentType "text/plain; charset=utf-8" s)+ assertEqual "" (Just "gzip") $ getHeader "Content-Encoding" rsp+ assertEqual "" (Just "Accept-Encoding") $ getHeader "Vary" rsp++ body <- Test.getResponseBody rsp+ let s1 = GZip.decompress $ L.fromChunks [body]+ assertEqual "" s s1+++------------------------------------------------------------------------------+testIdentity2 :: Test+testIdentity2 = testProperty "gzip/identity2" $ monadicIO $+ forAllM arbitrary prop+ where+ prop :: L.ByteString -> PropertyM IO ()+ prop s = liftQ $ do+ (!_,!rsp) <- goDeflate (seqSnap $ withCompression $ textPlain s)++ assertEqual "" (Just "deflate") $ getHeader "Content-Encoding" rsp+ assertEqual "" (Just "Accept-Encoding") $ getHeader "Vary" rsp++ body <- Test.getResponseBody rsp+ let s' = Zlib.decompress $ L.fromChunks [body]+ assertEqual "" s s'+++------------------------------------------------------------------------------+testIdentity3 :: Test+testIdentity3 = testProperty "gzip/identity3" $ monadicIO $ forAllM arbitrary prop+ where+ prop :: L.ByteString -> PropertyM IO ()+ prop s = liftQ $ do+ (!_,!rsp) <- goGZip (seqSnap $ withCompression $ binary s)+ body <- Test.getResponseBody rsp+ assertEqual "identify" s $ L.fromChunks [body]+++------------------------------------------------------------------------------+testIdentity4 :: Test+testIdentity4 = testProperty "gzip/identity4" $ monadicIO $ forAllM arbitrary prop+ where+ prop :: L.ByteString -> PropertyM IO ()+ prop s = liftQ $ do+ (!_,!rsp) <- goXGZip (seqSnap $ withCompression $ textPlain s)+ assertEqual "encoding" (Just "x-gzip") (getHeader "Content-Encoding" rsp)+ body <- Test.getResponseBody rsp+ let s1 = GZip.decompress $ L.fromChunks [body]+ assertEqual "identity" s s1++ (!_,!rsp2) <- goXGZip2 (seqSnap $ withCompression $ textPlain s)+ assertEqual "encoding" (Just "x-gzip") (getHeader "Content-Encoding" rsp2)+ body2 <- Test.getResponseBody rsp2+ let s2 = GZip.decompress $ L.fromChunks [body2]+ assertEqual "identity2" s s2+++------------------------------------------------------------------------------+testIdentity5 :: Test+testIdentity5 = testProperty "gzip/identity5" $ monadicIO $ forAllM arbitrary prop+ where+ prop :: L.ByteString -> PropertyM IO ()+ prop s = liftQ $ do+ (!_,!rsp) <- goXDeflate (seqSnap $ withCompression $ textPlain s)++ assertEqual "" (Just "x-deflate") $ getHeader "Content-Encoding" rsp+ body <- Test.getResponseBody rsp+ let s2 = Zlib.decompress $ L.fromChunks [body]+ assertEqual "gzip" s s2+++------------------------------------------------------------------------------+testBadHeaders :: Test+testBadHeaders = testProperty "gzip/bad headers" $ monadicIO $ forAllM arbitrary prop+ where+ prop :: L.ByteString -> PropertyM IO ()+ prop s = expectException $ do+ (!_,!rsp) <- goBad (seqSnap $ withCompression $ textPlain s)+ Test.getResponseBody rsp+++------------------------------------------------------------------------------+testNopWhenContentEncodingSet :: Test+testNopWhenContentEncodingSet =+ testProperty "gzip/testNopWhenContentEncodingSet" $+ monadicIO $+ forAllM arbitrary prop+ where+ prop :: L.ByteString -> PropertyM IO ()+ prop s = do+ (!_,!rsp) <- liftQ $ goGZip $ f s+ assert $ getHeader "Content-Encoding" rsp == Just "identity"++ f !s = seqSnap $ withCompression $ do+ modifyResponse $ setHeader "Content-Encoding" "identity"+ textPlain s+++------------------------------------------------------------------------------+testCompositionDoesn'tExplode :: Test+testCompositionDoesn'tExplode =+ testProperty "gzip/testCompositionDoesn'tExplode" $+ monadicIO $+ forAllM arbitrary prop+ where+ prop :: L.ByteString -> PropertyM IO ()+ prop s = liftQ $ do+ (!_,!rsp) <- goGZip (seqSnap $+ withCompression $+ withCompression $+ withCompression $ textPlain s)++ assertEqual "" (Just "gzip") $ getHeader "Content-Encoding" rsp++ c <- Test.getResponseBody rsp+ let s1 = GZip.decompress $ L.fromChunks [c]+ assertEqual "composition" s s1+++------------------------------------------------------------------------------+testGzipLotsaChunks :: Test+testGzipLotsaChunks = testCase "gzip/lotsOfChunks" prop+ where+ prop = do+ a <- genRandom 12000+ let s = L.take 120000 $ L.cycle $ L.fromChunks [a, B.reverse a]+ (!_,!rsp) <- goGZip (seqSnap $ withCompression $ textPlain s)++ body <- Test.getResponseBody rsp+ let s1 = GZip.decompress $ L.fromChunks [body]+ H.assertEqual "streams equal" s s1++ genRandom n = B.pack <$> replicateM n randomWord8++ t8 c = toEnum $ c .&. 0xff++ randomWord8 :: IO Word8+ randomWord8 = t8 <$> randomIO+++------------------------------------------------------------------------------+testNoCompression :: Test+testNoCompression = testProperty "gzip/noCompression" $+ monadicIO $ forAllM arbitrary prop+ where+ prop :: L.ByteString -> PropertyM IO ()+ prop s = liftQ $ do+ (!_,!rsp) <- goGZip (seqSnap $ withCompression $+ (noCompression >> textPlain s))+ assertEqual "" (Just "identity") $ getHeader "Content-Encoding" rsp+ body <- Test.getResponseBody rsp+ assertEqual "body matches" (S.concat $ L.toChunks s) body+++------------------------------------------------------------------------------+testTrivials :: Test+testTrivials = testCase "gzip/trivials" $ do+ coverTypeableInstance (undefined :: BadAcceptEncodingException)
+ test/Snap/Util/Proxy/Tests.hs view
@@ -0,0 +1,164 @@+{-# OPTIONS_GHC -fno-warn-warnings-deprecations #-}++{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE OverloadedStrings #-}++module Snap.Util.Proxy.Tests (tests) where++------------------------------------------------------------------------------+import Control.Monad.State.Strict (modify)+import Data.ByteString.Char8 (ByteString)+import qualified Data.ByteString.Char8 as S+import Data.CaseInsensitive (CI (..))+import qualified Data.Map as Map+import Snap.Core (Request (rqClientAddr, rqClientPort), Snap, rqRemotePort, withRequest)+import Snap.Test (RequestBuilder, evalHandler, get, setHeader)+import Snap.Test.Common (coverEqInstance, coverOrdInstance, coverReadInstance, coverShowInstance)+import Snap.Util.Proxy (ProxyType (NoProxy, X_Forwarded_For), behindProxy)+import Test.Framework (Test)+import Test.Framework.Providers.HUnit (testCase)+import Test.HUnit (assertEqual)+------------------------------------------------------------------------------+++------------------------------------------------------------------------------+tests :: [Test]+tests = [ testNoProxy+ , testForwardedFor+ , testTrivials+ ]+++ ---------------+ -- Constants --+ ---------------++------------------------------------------------------------------------------+initialPort :: Int+initialPort = 9999++initialAddr :: ByteString+initialAddr = "127.0.0.1"+++ -----------+ -- Tests --+ -----------++------------------------------------------------------------------------------+testNoProxy :: Test+testNoProxy = testCase "proxy/no-proxy" $ do+ a <- evalHandler (mkReq $ forwardedFor ["4.3.2.1"])+ (behindProxy NoProxy reportRemoteAddr)+ p <- evalHandler (mkReq $ forwardedFor ["4.3.2.1"] >> xForwardedPort [10903])+ (behindProxy NoProxy reportRemotePort)+ assertEqual "NoProxy leaves request alone" initialAddr a+ assertEqual "NoProxy leaves request alone" initialPort p++ --------------------------------------------------------------------------+ b <- evalHandler (mkReq $ xForwardedFor ["2fe3::d4"])+ (behindProxy NoProxy reportRemoteAddr)+ assertEqual "NoProxy leaves request alone" initialAddr b++ --------------------------------------------------------------------------+ c <- evalHandler (mkReq $ return ())+ (behindProxy NoProxy reportRemoteAddr)+ assertEqual "NoProxy leaves request alone" initialAddr c+++------------------------------------------------------------------------------+testForwardedFor :: Test+testForwardedFor = testCase "proxy/forwarded-for" $ do+ (a,p) <- evalHandler (mkReq $ return ()) handler+ assertEqual "No Forwarded-For, no change" initialAddr a+ assertEqual "port" initialPort p++ --------------------------------------------------------------------------+ (b,q) <- evalHandler (mkReq $ forwardedFor [ip4]) handler+ assertEqual "Behind 5.6.7.8" ip4 b+ assertEqual "No Forwarded-Port, no port change" initialPort q++ --------------------------------------------------------------------------+ (c,_) <- evalHandler (mkReq $ xForwardedFor [ip4, ip6]) handler+ assertEqual "Behind 23fe::d4" ip6 c++ --------------------------------------------------------------------------+ (d,r) <- evalHandler (mkReq $ xForwardedFor [ip6, ip4] >> xForwardedPort [20202, port]) handler+ assertEqual "Behind 5.6.7.8" ip4 d+ assertEqual "port change" port r++ where+ handler = behindProxy X_Forwarded_For $ do+ !a <- reportRemoteAddr+ !p <- reportRemotePort'+ return $! (a,p)++ ip4 = "5.6.7.8"+ ip6 = "23fe::d4"+ port = 10101+++------------------------------------------------------------------------------+testTrivials :: Test+testTrivials = testCase "proxy/trivials" $ do+ coverShowInstance NoProxy+ coverReadInstance NoProxy+ coverEqInstance NoProxy+ coverOrdInstance NoProxy+++ ---------------+ -- Functions --+ ---------------++------------------------------------------------------------------------------+mkReq :: RequestBuilder IO () -> RequestBuilder IO ()+mkReq m = do+ get "/" Map.empty+ modify $ \req -> req { rqClientAddr = initialAddr+ , rqClientPort = initialPort+ }+ m+++------------------------------------------------------------------------------+reportRemoteAddr :: Snap ByteString+reportRemoteAddr = withRequest $ \req -> return $ rqClientAddr req+++------------------------------------------------------------------------------+reportRemotePort :: Snap Int+reportRemotePort = withRequest $ \req -> return $ rqClientPort req+++------------------------------------------------------------------------------+-- Cover deprecated rqRemotePort+reportRemotePort' :: Snap Int+reportRemotePort' = withRequest $ \req -> return $ rqRemotePort req+++------------------------------------------------------------------------------+forwardedFor' :: CI ByteString -- ^ header name+ -> [ByteString] -- ^ list of "forwarded-for"+ -> RequestBuilder IO ()+forwardedFor' hdr addrs =+ setHeader hdr $ S.intercalate ", " addrs+++------------------------------------------------------------------------------+forwardedFor :: [ByteString]+ -> RequestBuilder IO ()+forwardedFor = forwardedFor' "Forwarded-For"+++------------------------------------------------------------------------------+xForwardedFor :: [ByteString]+ -> RequestBuilder IO ()+xForwardedFor = forwardedFor' "X-Forwarded-For"+++------------------------------------------------------------------------------+xForwardedPort :: [Int] -- ^ list of "forwarded-port"+ -> RequestBuilder IO ()+xForwardedPort ports =+ setHeader "X-Forwarded-Port" $ S.intercalate ", " $ map (S.pack . show) $ ports
+ test/TestSuite.hs view
@@ -0,0 +1,45 @@+module Main where++import Test.Framework (defaultMain, testGroup)++------------------------------------------------------------------------------+import qualified Snap.Core.Tests+import qualified Snap.Internal.Http.Types.Tests+import qualified Snap.Internal.Parsing.Tests+import qualified Snap.Internal.Routing.Tests+import qualified Snap.Test.Tests+import qualified Snap.Types.Headers.Tests+import qualified Snap.Util.FileServe.Tests+import qualified Snap.Util.FileUploads.Tests+import qualified Snap.Util.GZip.Tests+import qualified Snap.Util.Proxy.Tests+import qualified Snap.Util.CORS.Tests+++------------------------------------------------------------------------------+main :: IO ()+main = defaultMain tests+ where+ tests = [ testGroup "Snap.Internal.Http.Types.Tests"+ Snap.Internal.Http.Types.Tests.tests+ , testGroup "Snap.Internal.Routing.Tests"+ Snap.Internal.Routing.Tests.tests+ , testGroup "Snap.Core.Tests"+ Snap.Core.Tests.tests+ , testGroup "Snap.Internal.Parsing.Tests"+ Snap.Internal.Parsing.Tests.tests+ , testGroup "Snap.Types.Headers.Tests"+ Snap.Types.Headers.Tests.tests+ , testGroup "Snap.Util.FileServe.Tests"+ Snap.Util.FileServe.Tests.tests+ , testGroup "Snap.Util.FileUploads.Tests"+ Snap.Util.FileUploads.Tests.tests+ , testGroup "Snap.Util.GZip.Tests"+ Snap.Util.GZip.Tests.tests+ , testGroup "Snap.Util.Proxy.Tests"+ Snap.Util.Proxy.Tests.tests+ , testGroup "Snap.Util.CORS.Tests"+ Snap.Util.CORS.Tests.tests+ , testGroup "Snap.Test.Tests"+ Snap.Test.Tests.tests+ ]
+ test/data/fileServe/mydir1/index.txt view
@@ -0,0 +1,1 @@+INDEX
+ test/data/fileServe/mydir2/dir/foo.txt view
@@ -0,0 +1,1 @@+FOO
+ test/data/fileServe/mydir2/foo.txt view
@@ -0,0 +1,1 @@+FOO
+ test/data/fileServe/mydir3/altindex.html view
@@ -0,0 +1,1 @@+ALTINDEX
− test/runTestsAndCoverage.sh
@@ -1,62 +0,0 @@-#!/bin/sh--set -e--if [ -z "$DEBUG" ]; then- export DEBUG="testsuite"-fi--SUITE=./dist/build/testsuite/testsuite--export LC_ALL=C-export LANG=C--rm -f testsuite.tix--if [ ! -f $SUITE ]; then- cat <<EOF-Testsuite executable not found, please run:- cabal configure -ftest-then- cabal build-EOF- exit;-fi--./dist/build/testsuite/testsuite -j4 -a1000 $*--DIR=dist/hpc--rm -Rf $DIR-mkdir -p $DIR--EXCLUDES='Main-Data.CIByteString-Snap.Internal.Debug-Snap.Internal.Iteratee.Debug-Snap.Iteratee.Tests-Snap.Internal.Http.Parser.Tests-Snap.Internal.Http.Server.Tests-Snap.Internal.Http.Types.Tests-Snap.Internal.Iteratee.Tests-Snap.Internal.Routing.Tests-Snap.Types.Tests-Snap.Util.FileServe.Tests-Snap.Util.GZip.Tests-Text.Snap.Templates.Tests-Snap.Test.Common'--EXCL=""--for m in $EXCLUDES; do- EXCL="$EXCL --exclude=$m"-done--hpc markup $EXCL --destdir=$DIR testsuite >/dev/null 2>&1--rm -f testsuite.tix--cat <<EOF--Test coverage report written to $DIR.-EOF
− test/snap-core-testsuite.cabal
@@ -1,55 +0,0 @@-name: snap-core-testsuite-version: 0.1.1-build-type: Simple-cabal-version: >= 1.6--Flag portable- Description: Compile in cross-platform mode. No platform-specific code or- optimizations such as C routines will be used.- Default: False--Executable testsuite- hs-source-dirs: ../src suite- main-is: TestSuite.hs-- if flag(portable) || os(windows)- cpp-options: -DPORTABLE- else- c-sources: ../cbits/timefuncs.c- include-dirs: ../cbits- build-depends: bytestring-mmap >= 0.2.1 && <0.3-- build-depends:- QuickCheck >= 2.3.0.2,- attoparsec >= 0.8.1 && < 0.9,- base >= 4 && < 5,- bytestring,- bytestring-nums,- bytestring-show >= 0.3.2 && < 0.4,- cereal >= 0.3 && < 0.4,- containers,- deepseq >= 1.1 && <1.2,- directory,- dlist >= 0.5 && < 0.6,- filepath,- HUnit >= 1.2 && < 2,- iteratee >= 0.3.1 && < 0.4,- ListLike >= 1 && < 2,- MonadCatchIO-transformers >= 0.2 && < 0.3,- monads-fd,- old-locale,- old-time,- parallel >= 2.2 && <2.3,- pureMD5 == 2.1.*,- regex-posix >= 0.94.4 && <0.95,- test-framework >= 0.3.1 && <0.4,- test-framework-hunit >= 0.2.5 && < 0.3,- test-framework-quickcheck2 >= 0.2.6 && < 0.3,- text >= 0.10 && <0.11,- time,- transformers,- unix-compat,- zlib- - ghc-options: -O2 -Wall -fhpc -fwarn-tabs -funbox-strict-fields -threaded- -fno-warn-unused-do-bind
− test/suite/Snap/Internal/Http/Types/Tests.hs
@@ -1,130 +0,0 @@-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE OverloadedStrings #-}-module Snap.Internal.Http.Types.Tests- ( tests ) where--import Control.Monad-import Control.Parallel.Strategies-import Data.ByteString.Char8 (ByteString)-import Data.ByteString.Lazy.Char8 ()-import Data.IORef-import Data.Iteratee (stream2stream, run)-import Data.List (sort)-import qualified Data.Map as Map-import Data.Time.Calendar-import Data.Time.Clock-import Prelude hiding (take)-import Test.Framework-import Test.Framework.Providers.HUnit-import Test.HUnit hiding (Test, path)-import Text.Regex.Posix--import Snap.Internal.Http.Types-import Snap.Iteratee (enumBS, fromWrap)--tests :: [Test]-tests = [ testTypes- , testUrlDecode- , testFormatLogTime- , testAddHeader ]---mkRq :: IO Request-mkRq = do- enum <- newIORef (SomeEnumerator return)- return $ Request "foo" 80 "foo" 999 "foo" 1000 "foo" False Map.empty- enum Nothing GET (1,1) [] "" "/" "/" "/" "" Map.empty---testFormatLogTime :: Test-testFormatLogTime = testCase "formatLogTime" $ do- b <- formatLogTime 3804938-- let re = ("^[0-9]{1,2}/[A-Za-z]{3}/[0-9]{4}:[0-9]{2}:[0-9]{2}:[0-9]{2} (-|\\+)[0-9]{4}$"- :: ByteString)-- assertBool "formatLogTime" $ b =~ re---testAddHeader :: Test-testAddHeader = testCase "addHeader" $ do- defReq <- mkRq-- let req = addHeader "foo" "bar" $- addHeader "foo" "baz" defReq--- let x = getHeader "foo" req- assertEqual "addHeader x 2" (Just "bar baz") x---testUrlDecode :: Test-testUrlDecode = testCase "urlDecode" $ do- assertEqual "bad hex" Nothing $ urlDecode "%qq"---testTypes :: Test-testTypes = testCase "show" $ do- defReq <- mkRq-- let req = rqModifyParams (Map.insert "zzz" ["bbb"]) $- updateHeaders (Map.insert "zzz" ["bbb"]) $- rqSetParam "foo" ["bar"] $- defReq-- let req2 = (addHeader "zomg" "1234" req) { rqCookies = [ cook, cook2 ] }-- let !a = show req `using` rdeepseq- let !_ = show req2 `using` rdeepseq-- -- we don't care about the show instance really, we're just trying to shut- -- up hpc- assertBool "show" $ a /= b- assertEqual "rqParam" (Just ["bar"]) (rqParam "foo" req)- assertEqual "lookup" (Just ["bbb"]) (Map.lookup "zzz" $ rqParams req)- assertEqual "lookup 2" (Just ["bbb"]) (Map.lookup "zzz" $ headers req)- assertEqual "cookie" (Just ["foo=bar; path=/; expires=Sat, 30-Jan-2010 00:00:00 GMT; domain=.foo.com"]) cookieHeader-- assertEqual "cookie2" (Just ["foo=bar; path=/; expires=Sat, 30-Jan-2010 00:00:00 GMT; domain=.foo.com", "foo=baz; path=/; expires=Sat, 30-Jan-2010 00:00:00 GMT; domain=.foo.com"]) (liftM sort cookieHeader2)-- assertEqual "cookie3" (Just ["foo=baz"]) cookieHeader3-- assertEqual "response status" 555 $ rspStatus resp- assertEqual "response status reason" "bogus" $ rspStatusReason resp- assertEqual "content-length" (Just 4) $ rspContentLength resp- -- run response body- bd <- (rspBodyToEnum $ rspBody resp) stream2stream >>= run- assertEqual "response body" "PING" (fromWrap bd)-- let !_ = show GET- let !_ = GET == POST- let !_ = headers $ headers defReq-- let !_ = show resp2 `using` rdeepseq--- return ()-- where- resp = addCookie cook $- setContentLength 4 $- modifyResponseBody id $- setResponseBody (enumBS "PING") $- setContentType "text/plain" $- setResponseStatus 555 "bogus" $- emptyResponse- !b = show resp `using` rdeepseq-- resp2 = addCookie cook2 resp- resp3 = addCookie cook3 emptyResponse--- utc = UTCTime (ModifiedJulianDay 55226) 0- cook = Cookie "foo" "bar" (Just utc) (Just ".foo.com") (Just "/")- cook2 = Cookie "foo" "baz" (Just utc) (Just ".foo.com") (Just "/")- cook3 = Cookie "foo" "baz" Nothing Nothing Nothing-- cookieHeader = Map.lookup "Set-Cookie" $ headers resp- cookieHeader2 = Map.lookup "Set-Cookie" $ headers resp2- cookieHeader3 = Map.lookup "Set-Cookie" $ headers resp3-
− test/suite/Snap/Internal/Routing/Tests.hs
@@ -1,301 +0,0 @@-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE ScopedTypeVariables #-}--module Snap.Internal.Routing.Tests- ( tests ) where--import Control.Exception-import Control.Monad-import Data.ByteString (ByteString)-import qualified Data.ByteString as B-import Data.IORef-import qualified Data.Map as Map-import Data.Maybe-import Test.Framework-import Test.Framework.Providers.HUnit-import Test.HUnit hiding (Test, path)---import Snap.Internal.Http.Types-import Snap.Internal.Routing-import Snap.Internal.Types-import Snap.Iteratee hiding (head)--tests :: [Test]-tests = [ testRouting1- , testRouting2- , testRouting3- , testRouting4- , testRouting5- , testRouting6- , testRouting7- , testRouting8- , testRouting9- , testRouting10- , testRouting11- , testRouting12- , testRouting13- , testRouting14- , testRouting15- , testRouting16- , testRouting17- , testRouting18- , testRouting19- , testRouting20- , testRouting21- , testRouting22- , testRouting23- , testRouting24- , testRouting25- , testRouting26- , testRouting27- , testRouting28- , testRouteLocal ]--expectException :: IO a -> IO ()-expectException m = do- e <- try m- case e of- Left (z::SomeException) -> (show z) `seq` return ()- Right _ -> assertFailure "expected exception, didn't get one"---mkRequest :: ByteString -> IO Request-mkRequest uri = do- enum <- newIORef $ SomeEnumerator return-- return $ Request "foo" 80 "foo" 999 "foo" 1000 "foo" False Map.empty- enum Nothing GET (1,1) [] "" uri "/"- (B.concat ["/",uri]) "" Map.empty--go :: Snap a -> ByteString -> IO a-go m s = do- req <- mkRequest s- run $ evalSnap m (const $ return ()) req---routes :: Snap ByteString-routes = route [ ("foo" , topFoo )- , ("foo/bar" , fooBar )- , ("foo/bar/baz" , fooBarBaz )- , ("foo/:id" , fooCapture)- , ("bar/:id" , fooCapture)- , ("bar/quux" , barQuux )- , ("bar" , bar )- , ("z/:a/:b/:c/d" , zabc ) ]--routesLocal :: Snap ByteString-routesLocal = routeLocal [ ("foo/bar/baz" , fooBarBaz )- , ("bar" , pass ) ]--routes2 :: Snap ByteString-routes2 = route [ ("" , topTop )- , ("foo" , topFoo ) ]--routes3 :: Snap ByteString-routes3 = route [ (":foo" , topCapture )- , ("" , topTop ) ]--routes4 :: Snap ByteString-routes4 = route [ (":foo" , pass )- , (":foo" , topCapture )- , (":qqq/:id" , fooCapture )- , (":id2/baz" , fooCapture2 ) ]--routes5 :: Snap ByteString-routes5 = route [ ("" , pass )- , ("" , topTop ) ]--routes6 :: Snap ByteString-routes6 = route [ (":a/:a" , dblA ) ]--routes7 :: Snap ByteString-routes7 = route [ ("foo/:id" , fooCapture )- , ("foo/:id/:id2" , fooCapture2)- , ("fooo/:id/:id2" , fooCapture2)- , ("foooo/bar/baz" , bar )- , ("" , topTop ) ]---topTop, topFoo, fooBar, fooCapture, fooBarBaz, bar, barQuux :: Snap ByteString-dblA, zabc, topCapture, fooCapture2 :: Snap ByteString--dblA = do- ma <- getParam "a"-- unless (ma == Just "a a") pass- return "ok"---zabc = do- ma <- getParam "a"- mb <- getParam "b"- mc <- getParam "c"-- unless ( ma == Just "a"- && mb == Just "b"- && mc == Just "c" ) pass-- return "ok"---topCapture = do- mp <- getParam "foo"- maybe pass return mp--topTop = return "topTop"-topFoo = return "topFoo"-fooBar = return "fooBar"-fooCapture = liftM (head . fromJust . rqParam "id") getRequest-fooCapture2 = liftM (head . fromJust . rqParam "id2") getRequest-fooBarBaz = liftM rqPathInfo getRequest-barQuux = return "barQuux"-bar = return "bar"---- TODO more useful test names--testRouting1 :: Test-testRouting1 = testCase "routing1" $ do- r1 <- go routes "foo"- assertEqual "/foo" "topFoo" r1--testRouting2 :: Test-testRouting2 = testCase "routing2" $ do- r2 <- go routes "foo/baz"- assertEqual "/foo/baz" "baz" r2--testRouting3 :: Test-testRouting3 = testCase "routing3" $ do- expectException $ go routes "/xsaxsaxsax"--testRouting4 :: Test-testRouting4 = testCase "routing4" $ do- r3 <- go routes "foo/bar"- assertEqual "/foo/bar" "fooBar" r3--testRouting5 :: Test-testRouting5 = testCase "routing5" $ do- r4 <- go routes "foo/bar/baz/quux"- assertEqual "/foo/bar/baz/quux" "quux" r4--testRouting6 :: Test-testRouting6 = testCase "routing6" $ do- r5 <- go routes "foo/bar/sproing"- assertEqual "/foo/bar/sproing" "fooBar" r5--testRouting7 :: Test-testRouting7 = testCase "routing7" $ do- r <- go routes "bar"- assertEqual "/bar" "bar" r--testRouting8 :: Test-testRouting8 = testCase "routing8" $ do- r2 <- go routes "bar/quux"- assertEqual "/bar/quux" "barQuux" r2--testRouting9 :: Test-testRouting9 = testCase "routing9" $ do- r3 <- go routes "bar/whatever"- assertEqual "/bar/whatever" "whatever" r3--testRouting10 :: Test-testRouting10 = testCase "routing10" $ do- r4 <- go routes "bar/quux/whatever"- assertEqual "/bar/quux/whatever" "barQuux" r4--testRouting11 :: Test-testRouting11 = testCase "routing11" $ do- r1 <- go routes2 ""- assertEqual "/" "topTop" r1--testRouting12 :: Test-testRouting12 = testCase "routing12" $ do- r1 <- go routes2 "foo"- assertEqual "/foo" "topFoo" r1--testRouting13 :: Test-testRouting13 = testCase "routing13" $ do- r1 <- go routes3 "zzzz"- assertEqual "/zzzz" "zzzz" r1--testRouting14 :: Test-testRouting14 = testCase "routing14" $ do- r1 <- go routes3 ""- assertEqual "/" "topTop" r1--testRouting15 :: Test-testRouting15 = testCase "routing15" $ do- r1 <- go routes4 "zzzz"- assertEqual "/zzzz" "zzzz" r1--testRouting16 :: Test-testRouting16 = testCase "routing16" $ do- r1 <- go routes5 ""- assertEqual "/" "topTop" r1--testRouting17 :: Test-testRouting17 = testCase "routing17" $ do- r1 <- go routes "z/a/b/c/d"- assertEqual "/z/a/b/c/d" "ok" r1--testRouting18 :: Test-testRouting18 = testCase "routing18" $ do- r1 <- go routes6 "a/a"- assertEqual "/a/a" "ok" r1--testRouting19 :: Test-testRouting19 = testCase "routing19" $ do- r1 <- go routes7 "foo"- assertEqual "/foo" "topTop" r1--testRouting20 :: Test-testRouting20 = testCase "routing20" $ do- r1 <- go routes7 "foo/baz"- assertEqual "/foo/baz" "baz" r1--testRouting21 :: Test-testRouting21 = testCase "routing21" $ do- r1 <- go routes7 "foo/baz/quux"- assertEqual "/foo/baz/quux" "quux" r1--testRouting22 :: Test-testRouting22 = testCase "routing22" $ do- r1 <- go routes7 "fooo/baz"- assertEqual "/fooo/baz" "topTop" r1--testRouting23 :: Test-testRouting23 = testCase "routing23" $ do- r1 <- go routes7 "fooo/baz/quux"- assertEqual "/fooo/baz/quux" "quux" r1--testRouting24 :: Test-testRouting24 = testCase "routing24" $ do- r1 <- go routes7 "foooo/bar/bax"- assertEqual "/foooo/bar/bax" "topTop" r1--testRouting25 :: Test-testRouting25 = testCase "routing25" $ do- r1 <- go routes7 "foooo/bar/baz"- assertEqual "/foooo/bar/baz" "bar" r1--testRouting26 :: Test-testRouting26 = testCase "routing26" $ do- r1 <- go routes4 "foo/bar"- assertEqual "capture union" "bar" r1--testRouting27 :: Test-testRouting27 = testCase "routing27" $ do- r1 <- go routes4 "foo"- assertEqual "capture union" "foo" r1--testRouting28 :: Test-testRouting28 = testCase "routing28" $ do- r1 <- go routes4 "quux/baz"- assertEqual "capture union" "quux" r1--testRouteLocal :: Test-testRouteLocal = testCase "routeLocal" $ do- r4 <- go routesLocal "foo/bar/baz/quux"- assertEqual "/foo/bar/baz/quux" "foo/bar/baz/quux" r4- expectException $ go routesLocal "bar"
− test/suite/Snap/Iteratee/Tests.hs
@@ -1,406 +0,0 @@-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}--module Snap.Iteratee.Tests- ( tests ) where--import qualified Control.Exception as E-import Control.Exception hiding (try, assert)-import Control.Monad-import Control.Monad.Identity-import qualified Data.ByteString as S-import qualified Data.ByteString.Lazy.Char8 as L-import Data.Monoid-import Data.Int-import Data.IORef-import Data.Iteratee.WrappedByteString-import Data.Word-import Prelude hiding (drop, take)-import Test.Framework -import Test.Framework.Providers.QuickCheck2-import Test.QuickCheck-import qualified Test.QuickCheck.Monadic as QC-import Test.QuickCheck.Monadic hiding (run)-import Test.Framework.Providers.HUnit-import qualified Test.HUnit as H--import Snap.Iteratee-import Snap.Test.Common ()---liftQ :: forall a m . (Monad m) => m a -> PropertyM m a-liftQ = QC.run---expectException :: IO a -> PropertyM IO ()-expectException m = do- e <- liftQ $ E.try m- case e of- Left (z::SomeException) -> (show z) `seq` return ()- Right _ -> fail "expected exception, didn't get one"---tests :: [Test]-tests = [ testEnumBS- , testEnumLBS- , testBuffer- , testBuffer2- , testBuffer3- , testBuffer4- , testBufferChain- , testBufferChainEscape- , testUnsafeBuffer- , testUnsafeBuffer2- , testUnsafeBuffer3- , testUnsafeBuffer4- , testUnsafeBuffer5- , testTakeExactly1- , testTakeExactly2- , testTakeExactly3- , testTakeNoMoreThan1- , testTakeNoMoreThan2- , testTakeNoMoreThan3- , testCountBytes- , testCountBytes2- ]--testEnumBS :: Test-testEnumBS = testProperty "enumBS" prop- where- prop :: S.ByteString -> Bool- prop s = (S.concat $ L.toChunks $ fromWrap $ runIdentity (run iter)) == s- where- iter = runIdentity $ enumBS s stream2stream--testEnumLBS :: Test-testEnumLBS = testProperty "enumLBS" prop- where- prop :: L.ByteString -> Bool- prop s = fromWrap (runIdentity (run iter)) == s- where- iter = runIdentity $ enumLBS s stream2stream---testBuffer :: Test-testBuffer = testProperty "testBuffer" $- monadicIO $ forAllM arbitrary prop- where- prop s = do- pre (s /= L.empty)-- (i,_) <- liftQ $ bufferIteratee stream2stream- iter <- liftQ $ enumLBS s' i- x <- liftQ $ run iter-- QC.assert $ fromWrap x == s'- where- s' = L.take 20000 $ L.cycle s---testBuffer2 :: Test-testBuffer2 = testCase "testBuffer2" prop- where- prop = do- (i,_) <- bufferIteratee $ drop 4 >> stream2stream-- s <- enumLBS "abcdefgh" i >>= run >>= return . fromWrap- H.assertEqual "s == 'efgh'" "efgh" s---testBuffer3 :: Test-testBuffer3 = testProperty "testBuffer3" $- monadicIO $ forAllM arbitrary prop- where- prop s = do- pre (s /= L.empty)- (i,_) <- liftQ $ bufferIteratee (ss >>= \x -> drop 1 >> return x)- iter <- liftQ $ enumLBS s' i- x <- liftQ $ run iter-- QC.assert $ fromWrap x == (L.take 19999 s')- where- s' = L.take 20000 $ L.cycle s- ss = joinI $ take 19999 stream2stream---testBuffer4 :: Test-testBuffer4 = testProperty "testBuffer4" $- monadicIO $ forAllM arbitrary prop- where- prop s = do- (i,_) <- liftQ $ bufferIteratee (stream2stream >> throwErr (Err "foo"))- i' <- liftQ $ enumLBS s i- expectException $ run i'-- (j,_) <- liftQ $ bufferIteratee (throwErr (Err "foo") >> stream2stream)- j' <- liftQ $ enumLBS s j- expectException $ run j'- - k <- liftQ $ enumErr "foo" j- expectException $ run k---testBufferChain :: Test-testBufferChain = testProperty "testBufferChain" $- monadicIO $ forAllM arbitrary prop- where- prop s = do- pre (s /= L.empty)-- (j,_) <- liftQ $ bufferIteratee stream2stream- (i,_) <- liftQ $ bufferIteratee j- iter <- liftQ $ enumLBS s' i- x <- liftQ $ run iter-- QC.assert $ fromWrap x == s'- where- s' = L.take 20000 $ L.cycle s---testBufferChainEscape :: Test-testBufferChainEscape = testProperty "testBufferChainEscape" $- monadicIO $ forAllM arbitrary prop- where- prop s = do- pre (s /= L.empty)-- (j,esc) <- liftQ $ bufferIteratee stream2stream- (i,_) <- liftQ $ bufferIteratee j-- liftQ $ writeIORef esc True-- iter <- liftQ $ enumLBS s' i- x <- liftQ $ run iter-- QC.assert $ fromWrap x == s'- where- s' = L.take 20000 $ L.cycle s---copyingStream2stream :: Iteratee IO (WrappedByteString Word8)-copyingStream2stream = IterateeG (step mempty)- where- step acc (Chunk (WrapBS ls))- | S.null ls = return $ Cont (IterateeG (step acc)) Nothing- | otherwise = do- let !ls' = S.copy ls- let !bs' = WrapBS $! ls'- return $ Cont (IterateeG (step (acc `mappend` bs')))- Nothing-- step acc str = return $ Done acc str---bufferAndRun :: Iteratee IO a -> L.ByteString -> IO a-bufferAndRun ii s = do- i <- unsafeBufferIteratee ii- iter <- enumLBS s i- run iter---testUnsafeBuffer :: Test-testUnsafeBuffer = testProperty "testUnsafeBuffer" $- monadicIO $ forAllM arbitrary prop- where- prop s = do- pre $ s /= L.empty- x <- liftQ $ bufferAndRun copyingStream2stream s'- assert $ fromWrap x == s'-- where- s' = L.take 20000 $ L.cycle s---testUnsafeBuffer2 :: Test-testUnsafeBuffer2 = testCase "testUnsafeBuffer2" prop- where- prop = do- i <- unsafeBufferIteratee $ drop 4 >> copyingStream2stream-- s <- enumLBS "abcdefgh" i >>= run >>= return . fromWrap- H.assertEqual "s == 'efgh'" "efgh" s---testUnsafeBuffer3 :: Test-testUnsafeBuffer3 = testProperty "testUnsafeBuffer3" $- monadicIO $ forAllM arbitrary prop- where- prop s = do- pre $ s /= L.empty- x <- liftQ $ bufferAndRun (ss >>= \x -> drop 1 >> return x) s'-- assert $ fromWrap x == (L.take 19999 s')- where- s' = L.take 20000 $ L.cycle s- ss = joinI $ take 19999 copyingStream2stream---testUnsafeBuffer4 :: Test-testUnsafeBuffer4 = testProperty "testUnsafeBuffer4" $- monadicIO $ forAllM arbitrary prop- where- prop s = do- i <- liftQ $- unsafeBufferIteratee (copyingStream2stream >> throwErr (Err "foo"))- i' <- liftQ $ enumLBS s i- expectException $ run i'-- j <- liftQ $- unsafeBufferIteratee (throwErr (Err "foo") >> copyingStream2stream)- j' <- liftQ $ enumLBS s j- expectException $ run j'- - k <- liftQ $ enumErr "foo" j- expectException $ run k---testUnsafeBuffer5 :: Test-testUnsafeBuffer5 = testProperty "testUnsafeBuffer5" $- monadicIO $ forAllM arbitrary prop- where- prop s = do- pre $ s /= L.empty- x <- liftQ $ bufferAndRun copyingStream2stream s- assert $ fromWrap x == s---testTakeExactly1 :: Test-testTakeExactly1 = testProperty "short stream" $- monadicIO $ forAllM arbitrary prop- where- prop :: L.ByteString -> PropertyM IO ()- prop s = do- expectException $ doIter >>= run >>= return . fromWrap-- where- doIter = enumLBS s (joinI (takeExactly (n+1) stream2stream))-- n = fromIntegral $ L.length s---testTakeExactly2 :: Test-testTakeExactly2 = testProperty "exact stream" $- monadicIO $ forAllM arbitrary prop- where- prop :: L.ByteString -> PropertyM IO ()- prop s = do- e <- liftQ $ doIter >>= run >>= return . fromWrap- assert $ e == s-- where- doIter = enumLBS s (joinI (takeExactly n stream2stream))-- n = fromIntegral $ L.length s---testTakeExactly3 :: Test-testTakeExactly3 = testProperty "long stream" $- monadicIO $ forAllM arbitrary prop- where- prop :: L.ByteString -> PropertyM IO ()- prop s = do- e <- liftQ $ doIter >>= run >>= return . fromWrap- assert $ e == L.take (fromIntegral n) s-- where- doIter = enumLBS s (joinI (takeExactly n stream2stream))-- n = fromIntegral $ L.length s---testTakeNoMoreThan1 :: Test-testTakeNoMoreThan1 = testProperty "takeNoMore: short stream" $- monadicIO $ forAllM arbitrary prop- where- prop :: L.ByteString -> PropertyM IO ()- prop s = do- s' <- liftQ $ doIter >>= run >>= return . fromWrap-- assert $ s == s'-- where- doIter = enumLBS s (joinI (takeNoMoreThan (n+1) stream2stream))-- n = fromIntegral $ L.length s---testTakeNoMoreThan2 :: Test-testTakeNoMoreThan2 = testProperty "takeNoMore: exact stream" $- monadicIO $ forAllM arbitrary prop- where- prop :: L.ByteString -> PropertyM IO ()- prop s = do- e <- liftQ $ doIter >>= run >>= return . fromWrap- assert $ e == s-- where- doIter = enumLBS (L.concat ["", s])- (joinI (takeNoMoreThan n stream2stream))-- n = fromIntegral $ L.length s---testTakeNoMoreThan3 :: Test-testTakeNoMoreThan3 = testProperty "takeNoMoreLong" $- monadicIO $ forAllM arbitrary prop- where- prop :: (Int64,L.ByteString) -> PropertyM IO ()- prop (m,s) = do- v <- liftQ $ enumLBS "" (joinI (takeNoMoreThan 0 stream2stream)) >>= run- assert $ fromWrap v == ""-- if (L.null s || m == 0)- then liftQ $ do- !_ <- doIter >>= run- return ()- else expectException $ doIter >>= run >>= return . fromWrap-- - where- doIter = enumLBS s (joinI (takeNoMoreThan (n-abs m) stream2stream))- n = L.length s---testCountBytes :: Test-testCountBytes = testProperty "count bytes" $- monadicIO $ forAllM arbitrary prop- where- prop :: L.ByteString -> PropertyM IO ()- prop s = do- (!_,n1) <- f (countBytes (return ()))- (!_,n2) <- f (countBytes stream2stream)-- assert $ n1 == 0- assert $ n2 == n-- expectException $ g erriter- expectException $ enumEof erriter >>= run- -- where- erriter = countBytes $ throwErr $ Err "foo"- g iter = enumLBS s iter >>= run- f = liftQ . g- n = L.length s---testCountBytes2 :: Test-testCountBytes2 = testProperty "count bytes" $- monadicIO $ forAllM arbitrary prop- where- prop :: L.ByteString -> PropertyM IO ()- prop s = do- pre $ L.length s > 4- (n1,s') <- f iter-- assert $ n1 == 4- assert $ fromWrap s' == L.drop 4 s-- where- f i = liftQ $ enumLBS s i >>= run- iter = do- (!_,m) <- countBytes $ drop' 4- x <- stream2stream- return (m,x)
− test/suite/Snap/Test/Common.hs
@@ -1,27 +0,0 @@-{-# LANGUAGE FlexibleInstances #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}---module Snap.Test.Common where--import Control.Monad-import qualified Data.ByteString as S-import qualified Data.ByteString.Lazy as L-import Data.ByteString.Internal (c2w)-import Data.Iteratee.WrappedByteString-import Data.Word-import Test.QuickCheck---instance Show (WrappedByteString Word8) where- show (WrapBS s) = show s--instance Arbitrary S.ByteString where- arbitrary = liftM (S.pack . map c2w) arbitrary--instance Arbitrary L.ByteString where- arbitrary = do- n <- choose(0,5)- chunks <- replicateM n arbitrary- return $ L.fromChunks chunks-
− test/suite/Snap/Types/Tests.hs
@@ -1,418 +0,0 @@-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE ScopedTypeVariables #-}--module Snap.Types.Tests- ( tests ) where--import Control.Applicative-import Control.Concurrent.MVar-import Control.Exception (SomeException)-import Control.Monad-import Control.Monad.CatchIO-import Control.Monad.Trans (liftIO)-import Control.Parallel.Strategies-import Data.ByteString.Char8 (ByteString)-import qualified Data.ByteString.Char8 as S-import qualified Data.ByteString.Lazy.Char8 as L-import Data.IORef-import Data.Iteratee-import Data.Text ()-import Data.Text.Lazy ()-import qualified Data.Map as Map-import Prelude hiding (catch)-import Test.Framework-import Test.Framework.Providers.HUnit-import Test.Framework.Providers.QuickCheck2-import Test.HUnit hiding (Test, path)--import Snap.Internal.Types-import Snap.Internal.Http.Types-import Snap.Iteratee-import Snap.Test.Common ()---tests :: [Test]-tests = [ testFail- , testAlternative- , testEarlyTermination- , testRqBody- , testTrivials- , testMethod- , testDir- , testCatchIO- , testWrites- , testParam- , testURLEncode1- , testURLEncode2- , testDir2- , testIpHeaderFilter- , testMZero404- , testEvalSnap- , testLocalRequest- , testRedirect ]---expectException :: IO () -> IO ()-expectException m = do- r <- (try m :: IO (Either SomeException ()))- let b = either (\e -> show e `using` rdeepseq `seq` True)- (const False) r- assertBool "expected exception" b---expect404 :: IO (Request,Response) -> IO ()-expect404 m = do- (_,r) <- m- assertBool "expected 404" (rspStatus r == 404)---expectNo404 :: IO (Request,Response) -> IO ()-expectNo404 m = do- (_,r) <- m- assertBool ("expected 200, got " ++ show (rspStatus r))- (rspStatus r /= 404)---mkRequest :: ByteString -> IO Request-mkRequest uri = do- enum <- newIORef $ SomeEnumerator return-- return $ Request "foo" 80 "127.0.0.1" 999 "foo" 1000 "foo" False Map.empty- enum Nothing GET (1,1) [] "" uri "/"- (S.concat ["/",uri]) "" Map.empty--mkRequestQuery :: ByteString -> ByteString -> [ByteString] -> IO Request-mkRequestQuery uri k v = do- enum <- newIORef $ SomeEnumerator return-- let mp = Map.fromList [(k,v)]- let q = S.concat [k,"=", S.concat v]-- return $ Request "foo" 80 "foo" 999 "foo" 1000 "foo" False Map.empty- enum Nothing GET (1,1) [] "" uri "/"- (S.concat ["/",uri,"?",q]) q mp---mkZomgRq :: IO Request-mkZomgRq = do- enum <- newIORef $ SomeEnumerator return-- return $ Request "foo" 80 "127.0.0.1" 999 "foo" 1000 "foo" False Map.empty- enum Nothing GET (1,1) [] "" "/" "/" "/" "" Map.empty---mkIpHeaderRq :: IO Request-mkIpHeaderRq = do- rq <- mkZomgRq- return $ setHeader "X-Forwarded-For" "1.2.3.4"- $ deleteHeader "X-Forwarded-For"- $ setHeader "X-Forwarded-For" "1.2.3.4" rq---mkRqWithBody :: IO Request-mkRqWithBody = do- enum <- newIORef $ SomeEnumerator (enumBS "zazzle")- return $ Request "foo" 80 "foo" 999 "foo" 1000 "foo" False Map.empty- enum Nothing GET (1,1) [] "" "/" "/" "/" ""- Map.empty---testCatchIO :: Test-testCatchIO = testCase "catchIO" $ do- (_,rsp) <- go f- (_,rsp2) <- go g-- assertEqual "catchIO 1" (Just "bar") $ getHeader "foo" rsp- assertEqual "catchIO 2" Nothing $ getHeader "foo" rsp2-- where- f :: Snap ()- f = (block $ unblock $ throw NoHandlerException) `catch` h-- g :: Snap ()- g = return () `catch` h-- h :: SomeException -> Snap ()- h e = e `seq` modifyResponse $ addHeader "foo" "bar"--go :: Snap a -> IO (Request,Response)-go m = do- zomgRq <- mkZomgRq- run $ runSnap m (\x -> return $! (show x `using` rdeepseq) `seq` ()) zomgRq---goIP :: Snap a -> IO (Request,Response)-goIP m = do- rq <- mkIpHeaderRq- run $ runSnap m (const $ return ()) rq---goPath :: ByteString -> Snap a -> IO (Request,Response)-goPath s m = do- rq <- mkRequest s- run $ runSnap m (const $ return ()) rq---goPathQuery :: ByteString- -> ByteString- -> [ByteString]- -> Snap a- -> IO (Request,Response)-goPathQuery s k v m = do- rq <- mkRequestQuery s k v- run $ runSnap m (const $ return ()) rq---goBody :: Snap a -> IO (Request,Response)-goBody m = do- rq <- mkRqWithBody- run $ runSnap m (const $ return ()) rq---testFail :: Test-testFail = testCase "failure" $ expect404 (go pass)---setFoo :: ByteString -> Snap ()-setFoo s = do- modifyResponse (addHeader "Foo" s)- fmap id $ pure ()- pure id <*> (liftIO $ return ())---testAlternative :: Test-testAlternative = testCase "alternative" $ do- (_,resp) <- go (pass <|> setFoo "Bar")- assertEqual "foo present" (Just "Bar") $ getHeader "Foo" resp-- (_,resp2) <- go (fail ""- <|> fail2- <|> setFoo "Bar"- <|> setFoo "Baz")- assertEqual "alternative chooses correct branch"- (Just ["Bar"]) $ getHeaders "Foo" resp2-- where- fail2 :: Snap ()- fail2 = pass >>= \_ -> return ()---sampleResponse :: Response-sampleResponse = addHeader "Foo" "Quux" $ emptyResponse---testEarlyTermination :: Test-testEarlyTermination = testCase "early termination" $ do- (_,resp) <- go (finishWith sampleResponse >>= \_ -> setFoo "Bar")- assertEqual "foo" (Just ["Quux"]) $ getHeaders "Foo" resp---testRqBody :: Test-testRqBody = testCase "request bodies" $ do- mvar1 <- newEmptyMVar- mvar2 <- newEmptyMVar-- _ <- goBody $ f mvar1 mvar2-- v1 <- takeMVar mvar1- v2 <- takeMVar mvar2-- assertEqual "rq body" "zazzle" v1- assertEqual "rq body 2" "" v2-- (_,rsp) <- goBody g- bd <- getBody rsp-- assertEqual "detached rq body" "zazzle" bd--- where- f mvar1 mvar2 = do- getRequestBody >>= liftIO . putMVar mvar1- getRequestBody >>= liftIO . putMVar mvar2-- g = transformRequestBody return---testTrivials :: Test-testTrivials = testCase "trivial functions" $ do- (rq,rsp) <- go $ do- req <- getRequest- putRequest $ req { rqIsSecure=True }- putResponse $ setResponseStatus 333 "333" sampleResponse- r <- getResponse- liftIO $ assertEqual "rsp status" 333 $ rspStatus r- !_ <- localRequest (\x -> x {rqIsSecure=False}) $ do- q <- getRequest- liftIO $ assertEqual "localrq" False $ rqIsSecure q- return ()-- logError "foo"- writeText "zzz"- writeLazyText "zzz"-- let req' = updateContextPath 0 req- let cp1 = rqContextPath req- let cp2 = rqContextPath req'-- liftIO $ assertEqual "updateContextPath 0" cp1 cp2-- withRequest $ return . (`seq` ())- withResponse $ return . (`seq` ())--- return ()-- b <- getBody rsp- let !_ = show b `using` rdeepseq--- let !_ = show NoHandlerException `seq` ()-- assertEqual "rq secure" True $ rqIsSecure rq- assertEqual "rsp status" 333 $ rspStatus rsp---testMethod :: Test-testMethod = testCase "method" $ do- expect404 $ go (method POST $ return ())- expectNo404 $ go (method GET $ return ())---testDir :: Test-testDir = testCase "dir" $ do- expect404 $ goPath "foo/bar" (dir "zzz" $ return ())- expectNo404 $ goPath "foo/bar" (dir "foo" $ return ())- expect404 $ goPath "fooz/bar" (dir "foo" $ return ())- expectNo404 $ goPath "foo/bar" (path "foo/bar" $ return ())- expect404 $ goPath "foo/bar/z" (path "foo/bar" $ return ())- expectNo404 $ goPath "" (ifTop $ return ())- expect404 $ goPath "a" (ifTop $ return ())---testParam :: Test-testParam = testCase "getParam" $ do- expect404 $ goPath "/foo" f- expectNo404 $ goPathQuery "/foo" "param" ["foo"] f- where- f = do- mp <- getParam "param"- maybe pass- (\s -> if s == "foo" then return () else pass)- mp---getBody :: Response -> IO L.ByteString-getBody r = liftM fromWrap ((rspBodyToEnum $ rspBody r) stream2stream >>= run)---testWrites :: Test-testWrites = testCase "writes" $ do- (_,r) <- go h- b <- getBody r- assertEqual "output functions" "Foo1Foo2Foo3" b- where- h :: Snap ()- h = do- addToOutput $ enumBS "Foo1"- writeBS "Foo2"- writeLBS "Foo3"---testURLEncode1 :: Test-testURLEncode1 = testCase "url encoding 1" $ do- let b = urlEncode "the quick brown fox~#"- assertEqual "url encoding 1" "the+quick+brown+fox%7e%23" b- assertEqual "fail" Nothing $ urlDecode "%"---testURLEncode2 :: Test-testURLEncode2 = testProperty "url encoding 2" prop- where- prop s = (urlDecode $ urlEncode s) == Just s---testDir2 :: Test-testDir2 = testCase "dir2" $ do- (_,resp) <- goPath "foo/bar" f- b <- getBody resp- assertEqual "context path" "/foo/bar/" b-- where- f = dir "foo" $ dir "bar" $ do- p <- liftM rqContextPath getRequest- addToOutput $ enumBS p---testIpHeaderFilter :: Test-testIpHeaderFilter = testCase "ipHeaderFilter" $ do- (_,r) <- goIP f- b <- getBody r- assertEqual "ipHeaderFilter" "1.2.3.4" b--- (_,r2) <- go f- b2 <- getBody r2- assertEqual "ipHeaderFilter" "127.0.0.1" b2-- where- f = do- ipHeaderFilter- ip <- liftM rqRemoteAddr getRequest- writeBS ip---testMZero404 :: Test-testMZero404 = testCase "mzero 404" $ do- (_,r) <- go mzero- let l = rspContentLength r- b <- getBody r- assertEqual "mzero 404" "404" b- assertEqual "mzero 404 length" (Just 3) l---testEvalSnap :: Test-testEvalSnap = testCase "evalSnap exception" $ do- rq <- mkZomgRq- expectException (run $ evalSnap f- (const $ return ())- rq >> return ())- where- f = do- logError "zzz"- v <- withResponse (return . rspHttpVersion)- liftIO $ assertEqual "evalSnap rsp version" (1,1) v- finishWith emptyResponse---testLocalRequest :: Test-testLocalRequest = testCase "localRequest" $ do- rq1 <- mkZomgRq- rq2 <- mkRequest "zzz/zz/z"-- let h = localRequest (const rq2) mzero-- (rq',_) <- go (h <|> return ())-- let u1 = rqURI rq1- let u2 = rqURI rq'-- assertEqual "localRequest backtrack" u1 u2----testRedirect :: Test-testRedirect = testCase "redirect" $ do- (_,rsp) <- go (redirect "/foo/bar")-- assertEqual "redirect path" (Just "/foo/bar") $ getHeader "Location" rsp- assertEqual "redirect status" 302 $ rspStatus rsp- assertEqual "status description" "Found" $ rspStatusReason rsp--- (_,rsp2) <- go (redirect' "/bar/foo" 307)-- assertEqual "redirect path" (Just "/bar/foo") $ getHeader "Location" rsp2- assertEqual "redirect status" 307 $ rspStatus rsp2- assertEqual "status description" "Temporary Redirect" $ rspStatusReason rsp2
− test/suite/Snap/Util/FileServe/Tests.hs
@@ -1,264 +0,0 @@-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE ScopedTypeVariables #-}--module Snap.Util.FileServe.Tests- ( tests ) where--import Control.Monad-import Data.ByteString (ByteString)-import qualified Data.ByteString.Char8 as S-import qualified Data.ByteString.Lazy.Char8 as L-import Data.IORef-import qualified Data.Map as Map-import Data.Maybe-import Prelude hiding (take)-import Test.Framework-import Test.Framework.Providers.HUnit-import Test.HUnit hiding (Test, path)--import Snap.Internal.Http.Types-import Snap.Internal.Types-import Snap.Util.FileServe-import Snap.Iteratee--tests :: [Test]-tests = [ testFs- , testFsSingle- , testRangeOK- , testRangeBad- , testMultiRange- , testIfRange ]---expect404 :: IO Response -> IO ()-expect404 m = do- r <- m- assertBool "expected 404" (rspStatus r == 404)---getBody :: Response -> IO L.ByteString-getBody r = liftM fromWrap ((rspBodyToEnum $ rspBody r) stream2stream >>= run)---go :: Snap a -> ByteString -> IO Response-go m s = do- rq <- mkRequest s- liftM snd (run $ runSnap m (const $ return ()) rq)---goIfModifiedSince :: Snap a -> ByteString -> ByteString -> IO Response-goIfModifiedSince m s lm = do- rq <- mkRequest s- let r = setHeader "if-modified-since" lm rq- liftM snd (run $ runSnap m (const $ return ()) r)---goIfRange :: Snap a -> ByteString -> (Int,Int) -> ByteString -> IO Response-goIfRange m s (start,end) lm = do- rq <- mkRequest s- let r = setHeader "if-range" lm $- setHeader "Range"- (S.pack $ "bytes=" ++ show start ++ "-" ++ show end)- rq- liftM snd (run $ runSnap m (const $ return ()) r)---goRange :: Snap a -> ByteString -> (Int,Int) -> IO Response-goRange m s (start,end) = do- rq' <- mkRequest s- let rq = setHeader "Range"- (S.pack $ "bytes=" ++ show start ++ "-" ++ show end)- rq'- liftM snd (run $ runSnap m (const $ return ()) rq)---goMultiRange :: Snap a -> ByteString -> (Int,Int) -> (Int,Int) -> IO Response-goMultiRange m s (start,end) (start2,end2) = do- rq' <- mkRequest s- let rq = setHeader "Range"- (S.pack $ "bytes=" ++ show start ++ "-" ++ show end- ++ "," ++ show start2 ++ "-" ++ show end2)- rq'- liftM snd (run $ runSnap m (const $ return ()) rq)---goRangePrefix :: Snap a -> ByteString -> Int -> IO Response-goRangePrefix m s start = do- rq' <- mkRequest s- let rq = setHeader "Range"- (S.pack $ "bytes=" ++ show start ++ "-")- rq'- liftM snd (run $ runSnap m (const $ return ()) rq)---goRangeSuffix :: Snap a -> ByteString -> Int -> IO Response-goRangeSuffix m s end = do- rq' <- mkRequest s- let rq = setHeader "Range"- (S.pack $ "bytes=-" ++ show end)- rq'- liftM snd (run $ runSnap m (const $ return ()) rq)---mkRequest :: ByteString -> IO Request-mkRequest uri = do- enum <- newIORef $ SomeEnumerator return- return $ Request "foo" 80 "foo" 999 "foo" 1000 "foo" False Map.empty- enum Nothing GET (1,1) [] "" uri "/"- (S.concat ["/",uri]) "" Map.empty--fs :: Snap ()-fs = do- x <- fileServe "data/fileServe"- return $! x `seq` ()--fsSingle :: Snap ()-fsSingle = do- x <- fileServeSingle "data/fileServe/foo.html"- return $! x `seq` ()---testFs :: Test-testFs = testCase "fileServe/multi" $ do- r1 <- go fs "foo.bin"- b1 <- getBody r1-- assertEqual "foo.bin" "FOO\n" b1- assertEqual "foo.bin content-type"- (Just "application/octet-stream")- (getHeader "content-type" r1)-- assertEqual "foo.bin size" (Just 4) (rspContentLength r1)-- assertBool "last-modified header" (isJust $ getHeader "last-modified" r1)- assertEqual "accept-ranges header" (Just "bytes")- (getHeader "accept-ranges" r1)-- let !lm = fromJust $ getHeader "last-modified" r1-- -- check last modified stuff- r2 <- goIfModifiedSince fs "foo.bin" lm- assertEqual "foo.bin 304" 304 $ rspStatus r2-- r3 <- goIfModifiedSince fs "foo.bin" "Wed, 15 Nov 1995 04:58:08 GMT"- assertEqual "foo.bin 200" 200 $ rspStatus r3- b3 <- getBody r3- assertEqual "foo.bin 2" "FOO\n" b3-- r4 <- go fs "foo.txt"- b4 <- getBody r4-- assertEqual "foo.txt" "FOO\n" b4- assertEqual "foo.txt content-type"- (Just "text/plain")- (getHeader "content-type" r4)- - r5 <- go fs "foo.html"- b5 <- getBody r5-- assertEqual "foo.html" "FOO\n" b5- assertEqual "foo.html content-type"- (Just "text/html")- (getHeader "content-type" r5)- - r6 <- go fs "foo.bin.bin.bin"- b6 <- getBody r6-- assertEqual "foo.bin.bin.bin" "FOO\n" b6- assertEqual "foo.bin.bin.bin content-type"- (Just "application/octet-stream")- (getHeader "content-type" r6)-- expect404 $ go fs "jfldksjflksd"- expect404 $ go fs "dummy/../foo.txt"- expect404 $ go fs "/etc/password"-- coverMimeMap---testFsSingle :: Test-testFsSingle = testCase "fileServe/Single" $ do- r1 <- go fsSingle "foo.html"- b1 <- getBody r1-- assertEqual "foo.html" "FOO\n" b1- assertEqual "foo.html content-type"- (Just "text/html")- (getHeader "content-type" r1)-- assertEqual "foo.html size" (Just 4) (rspContentLength r1)---testRangeOK :: Test-testRangeOK = testCase "fileServe/range/ok" $ do- r1 <- goRange fsSingle "foo.html" (1,2)- assertEqual "foo.html 206" 206 $ rspStatus r1- b1 <- getBody r1-- assertEqual "foo.html partial" "OO" b1- assertEqual "foo.html partial size" (Just 2) (rspContentLength r1)- assertEqual "foo.html content-range"- (Just "bytes 1-2/4")- (getHeader "Content-Range" r1)-- r2 <- goRangeSuffix fsSingle "foo.html" 3- assertEqual "foo.html 206" 206 $ rspStatus r2- b2 <- getBody r2- assertEqual "foo.html partial suffix" "OO\n" b2-- r3 <- goRangePrefix fsSingle "foo.html" 2- assertEqual "foo.html 206" 206 $ rspStatus r3- b3 <- getBody r3- assertEqual "foo.html partial prefix" "O\n" b3---testMultiRange :: Test-testMultiRange = testCase "fileServe/range/multi" $ do- r1 <- goMultiRange fsSingle "foo.html" (1,2) (3,3)-- -- we don't support multiple ranges so it's ok for us to return 200 here;- -- test this behaviour- assertEqual "foo.html 200" 200 $ rspStatus r1- b1 <- getBody r1-- assertEqual "foo.html" "FOO\n" b1---testRangeBad :: Test-testRangeBad = testCase "fileServe/range/bad" $ do- r1 <- goRange fsSingle "foo.html" (1,17)- assertEqual "bad range" 416 (rspStatus r1)- assertEqual "bad range content-range"- (Just "bytes */4")- (getHeader "Content-Range" r1)- assertEqual "bad range content-length" (Just 0) (rspContentLength r1)- b1 <- getBody r1- assertEqual "bad range empty body" "" b1-- r2 <- goRangeSuffix fsSingle "foo.html" 4893- assertEqual "bad suffix range" 416 $ rspStatus r2---coverMimeMap :: (Monad m) => m ()-coverMimeMap = Prelude.mapM_ f $ Map.toList defaultMimeTypes- where- f (!k,!v) = return $ case k `seq` v `seq` () of () -> ()---testIfRange :: Test-testIfRange = testCase "fileServe/range/if-range" $ do- r <- goIfRange fs "foo.bin" (1,2) "Wed, 15 Nov 1995 04:58:08 GMT"- assertEqual "foo.bin 200" 200 $ rspStatus r- b <- getBody r- assertEqual "foo.bin" "FOO\n" b-- r2 <- goIfRange fs "foo.bin" (1,2) "Tue, 01 Oct 2030 04:58:08 GMT"- assertEqual "foo.bin 206" 206 $ rspStatus r2- b2 <- getBody r2- assertEqual "foo.bin partial" "OO" b2-- r3 <- goIfRange fs "foo.bin" (1,24324) "Tue, 01 Oct 2030 04:58:08 GMT"- assertEqual "foo.bin 200" 200 $ rspStatus r3- b3 <- getBody r3- assertEqual "foo.bin" "FOO\n" b3
− test/suite/Snap/Util/GZip/Tests.hs
@@ -1,386 +0,0 @@-{-# LANGUAGE BangPatterns #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE ScopedTypeVariables #-}--module Snap.Util.GZip.Tests- ( tests ) where--import qualified Codec.Compression.GZip as GZip-import qualified Codec.Compression.Zlib as Zlib-import Control.Exception hiding (assert)-import qualified Data.ByteString.Lazy.Char8 as L-import Data.Digest.Pure.MD5-import Data.IORef-import Data.Iteratee-import qualified Data.Map as Map-import Data.Serialize-import Test.Framework-import Test.Framework.Providers.QuickCheck2-import Test.QuickCheck-import qualified Test.QuickCheck.Monadic as QC-import Test.QuickCheck.Monadic hiding (run)-import Test.Framework.Providers.HUnit-import qualified Test.HUnit as H--import Snap.Types-import Snap.Internal.Http.Types-import Snap.Iteratee-import Snap.Test.Common ()-import Snap.Util.GZip----------------------------------------------------------------------------------tests :: [Test]-tests = [ testIdentity1- , testIdentity2- , testIdentity3- , testIdentity4- , testIdentity5- , testNoHeaders- , testNoAcceptEncoding- , testNopWhenContentEncodingSet- , testCompositionDoesn'tExplode- , testGzipLotsaChunks- , testBadHeaders- , testIterateeException ]----------------------------------------------------------------------------------expectException :: IO a -> PropertyM IO ()-expectException m = do- e <- liftQ $ try m- case e of- Left (z::SomeException) -> (show z) `seq` return ()- Right _ -> fail "expected exception, didn't get one"----------------------------------------------------------------------------------liftQ :: forall a m . (Monad m) => m a -> PropertyM m a-liftQ = QC.run----------------------------------------------------------------------------------gzipHdrs, xGzipHdrs, badHdrs, compressHdrs, xCompressHdrs, emptyHdrs :: Headers-emptyHdrs = Map.empty-gzipHdrs = setHeader "Accept-Encoding" "froz,gzip, x-gzip" emptyHdrs-xGzipHdrs = setHeader "Accept-Encoding" "x-gzip;q=1.0" emptyHdrs-badHdrs = setHeader "Accept-Encoding" "*&%^&^$%&%&*^\023" emptyHdrs-compressHdrs = setHeader "Accept-Encoding" "compress" emptyHdrs-xCompressHdrs = setHeader "Accept-Encoding" "x-compress" emptyHdrs-----------------------------------------------------------------------------------mkNoHeaders :: IO Request-mkNoHeaders = do- enum <- newIORef $ SomeEnumerator return-- return $ Request "foo" 80 "foo" 999 "foo" 1000 "foo" False emptyHdrs- enum Nothing GET (1,1) [] "" "/" "/" "/" "" Map.empty---mkGzipRq :: IO Request-mkGzipRq = do- enum <- newIORef $ SomeEnumerator return-- return $ Request "foo" 80 "foo" 999 "foo" 1000 "foo" False gzipHdrs- enum Nothing GET (1,1) [] "" "/" "/" "/" "" Map.empty--mkXGzipRq :: IO Request-mkXGzipRq = do- enum <- newIORef $ SomeEnumerator return-- return $ Request "foo" 80 "foo" 999 "foo" 1000 "foo" False xGzipHdrs- enum Nothing GET (1,1) [] "" "/" "/" "/" "" Map.empty-----------------------------------------------------------------------------------mkCompressRq :: IO Request-mkCompressRq = do- enum <- newIORef $ SomeEnumerator return-- return $ Request "foo" 80 "foo" 999 "foo" 1000 "foo" False compressHdrs- enum Nothing GET (1,1) [] "" "/" "/" "/" "" Map.empty--mkXCompressRq :: IO Request-mkXCompressRq = do- enum <- newIORef $ SomeEnumerator return-- return $ Request "foo" 80 "foo" 999 "foo" 1000 "foo" False xCompressHdrs- enum Nothing GET (1,1) [] "" "/" "/" "/" "" Map.empty-----------------------------------------------------------------------------------mkBadRq :: IO Request-mkBadRq = do- enum <- newIORef $ SomeEnumerator return-- return $ Request "foo" 80 "foo" 999 "foo" 1000 "foo" False badHdrs- enum Nothing GET (1,1) [] "" "/" "/" "/" "" Map.empty---------------------------------------------------------------------------------seqSnap :: Snap a -> Snap a-seqSnap m = do- !x <- m- return $! x `seq` x----------------------------------------------------------------------------------goGeneric :: IO Request -> Snap a -> IO (Request, Response)-goGeneric mkRq m = do- rq <- mkRq- run $! runSnap (seqSnap m) (const $ return ()) rq--goGZip, goCompress, goXGZip :: Snap a -> IO (Request,Response)-goNoHeaders, goXCompress, goBad :: Snap a -> IO (Request,Response)--goGZip = goGeneric mkGzipRq-goCompress = goGeneric mkCompressRq-goXGZip = goGeneric mkXGzipRq-goXCompress = goGeneric mkXCompressRq-goBad = goGeneric mkBadRq-goNoHeaders = goGeneric mkNoHeaders---------------------------------------------------------------------------------noContentType :: L.ByteString -> Snap ()-noContentType s = modifyResponse $ setResponseBody (enumLBS s)----------------------------------------------------------------------------------textPlainErr :: L.ByteString -> Snap ()-textPlainErr s = modifyResponse $- setResponseBody (enumLBS s >. enumErr "blah") .- setContentType "text/plain"---textPlain :: L.ByteString -> Snap ()-textPlain s = modifyResponse $- setResponseBody (enumLBS s) .- setContentType "text/plain"----------------------------------------------------------------------------------binary :: L.ByteString -> Snap ()-binary s = modifyResponse $- setResponseBody (enumLBS s) .- setContentType "application/octet-stream"----------------------------------------------------------------------------------testNoHeaders :: Test-testNoHeaders = testProperty "gzip/noheaders" $- monadicIO $- forAllM arbitrary prop- where- prop :: L.ByteString -> PropertyM IO ()- prop s = do- -- if there's no content-type, withCompression should be a no-op- (!_,!rsp) <- liftQ $ goNoHeaders (seqSnap $ withCompression- $ noContentType s)- assert $ getHeader "Content-Encoding" rsp == Nothing- assert $ getHeader "Vary" rsp == Nothing- let body = rspBodyToEnum $ rspBody rsp-- c <- liftQ $- body stream2stream >>= run >>= return . fromWrap-- assert $ s == c----------------------------------------------------------------------------------testNoAcceptEncoding :: Test-testNoAcceptEncoding = testProperty "gzip/noAcceptEncoding" $- monadicIO $- forAllM arbitrary prop- where- prop :: L.ByteString -> PropertyM IO ()- prop s = do- -- if there's no content-type, withCompression should be a no-op- (!_,!rsp) <- liftQ $ goNoHeaders (seqSnap $ withCompression- $ textPlain s)- assert $ getHeader "Content-Encoding" rsp == Nothing- assert $ getHeader "Vary" rsp == Nothing- let body = rspBodyToEnum $ rspBody rsp-- c <- liftQ $- body stream2stream >>= run >>= return . fromWrap-- assert $ s == c----------------------------------------------------------------------------------testIdentity1 :: Test-testIdentity1 = testProperty "gzip/identity1" $ monadicIO $ forAllM arbitrary prop- where- prop :: L.ByteString -> PropertyM IO ()- prop s = do- (!_,!rsp) <- liftQ $ goGZip (seqSnap $ withCompression $ textPlain s)- assert $ getHeader "Content-Encoding" rsp == Just "gzip"- assert $ getHeader "Vary" rsp == Just "Accept-Encoding"- let body = rspBodyToEnum $ rspBody rsp-- c <- liftQ $- body stream2stream >>= run >>= return . fromWrap-- let s1 = GZip.decompress c- assert $ s == s1----------------------------------------------------------------------------------testIdentity2 :: Test-testIdentity2 = testProperty "gzip/identity2" $ monadicIO $ forAllM arbitrary prop- where- prop :: L.ByteString -> PropertyM IO ()- prop s = do- (!_,!rsp) <- liftQ $ goCompress (seqSnap $ withCompression $ textPlain s)-- assert $ getHeader "Content-Encoding" rsp == Just "compress"- assert $ getHeader "Vary" rsp == Just "Accept-Encoding"- let body = rspBodyToEnum $ rspBody rsp-- c <- liftQ $- body stream2stream >>= run >>= return . fromWrap-- let s' = Zlib.decompress c- assert $ s == s'----------------------------------------------------------------------------------testIdentity3 :: Test-testIdentity3 = testProperty "gzip/identity3" $ monadicIO $ forAllM arbitrary prop- where- prop :: L.ByteString -> PropertyM IO ()- prop s = do- (!_,!rsp3) <- liftQ $ goGZip (seqSnap $ withCompression $ binary s)- let body3 = rspBodyToEnum $ rspBody rsp3-- s3 <- liftQ $- body3 stream2stream >>= run >>= return . fromWrap-- assert $ s == s3----------------------------------------------------------------------------------testIdentity4 :: Test-testIdentity4 = testProperty "gzip/identity4" $ monadicIO $ forAllM arbitrary prop- where- prop :: L.ByteString -> PropertyM IO ()- prop s = do- (!_,!rsp) <- liftQ $ goXGZip (seqSnap $ withCompression $ textPlain s)- assert $ getHeader "Content-Encoding" rsp == Just "x-gzip"- let body = rspBodyToEnum $ rspBody rsp-- c <- liftQ $- body stream2stream >>= run >>= return . fromWrap-- let s1 = GZip.decompress c- assert $ s == s1----------------------------------------------------------------------------------testIdentity5 :: Test-testIdentity5 = testProperty "gzip/identity5" $ monadicIO $ forAllM arbitrary prop- where- prop :: L.ByteString -> PropertyM IO ()- prop s = do- (!_,!rsp2) <- liftQ $ goXCompress (seqSnap $ withCompression $ textPlain s)-- assert $ getHeader "Content-Encoding" rsp2 == Just "x-compress"- let body2 = rspBodyToEnum $ rspBody rsp2-- c2 <- liftQ $- body2 stream2stream >>= run >>= return . fromWrap-- let s2 = Zlib.decompress c2- assert $ s == s2----------------------------------------------------------------------------------testBadHeaders :: Test-testBadHeaders = testProperty "gzip/bad headers" $ monadicIO $ forAllM arbitrary prop- where- prop :: L.ByteString -> PropertyM IO ()- prop s = expectException $ do- (!_,!rsp) <- goBad (seqSnap $ withCompression $ textPlain s)- let body = rspBodyToEnum $ rspBody rsp-- body stream2stream >>= run >>= return . fromWrap----------------------------------------------------------------------------------testNopWhenContentEncodingSet :: Test-testNopWhenContentEncodingSet =- testProperty "gzip/testNopWhenContentEncodingSet" $- monadicIO $- forAllM arbitrary prop- where- prop :: L.ByteString -> PropertyM IO ()- prop s = do- (!_,!rsp) <- liftQ $ goGZip $ f s- assert $ getHeader "Content-Encoding" rsp == Just "identity"-- f !s = seqSnap $ withCompression $ do- modifyResponse $ setHeader "Content-Encoding" "identity"- textPlain s----------------------------------------------------------------------------------testCompositionDoesn'tExplode :: Test-testCompositionDoesn'tExplode =- testProperty "gzip/testCompositionDoesn'tExplode" $- monadicIO $- forAllM arbitrary prop- where- prop :: L.ByteString -> PropertyM IO ()- prop s = do- (!_,!rsp) <- liftQ $ goGZip (seqSnap $- withCompression $- withCompression $- withCompression $ textPlain s)-- assert $ getHeader "Content-Encoding" rsp == Just "gzip"-- let body = rspBodyToEnum $ rspBody rsp-- c <- liftQ $- body stream2stream >>= run >>= return . fromWrap-- let s1 = GZip.decompress c- assert $ s == s1----------------------------------------------------------------------------------testGzipLotsaChunks :: Test-testGzipLotsaChunks = testCase "gzip/lotsOfChunks" prop- where- prop = do- let s = L.take 120000 $ L.fromChunks $ frobnicate "dshflkahdflkdhsaflk"- (!_,!rsp) <- goGZip (seqSnap $ withCompression $ textPlain s)- let body = rspBodyToEnum $ rspBody rsp-- c <- body stream2stream >>= run >>= return . fromWrap-- let s1 = GZip.decompress c- H.assertBool "streams equal" $ s == s1-- - -- in order to get incompressible text (so that we can test whether the- -- gzip thread is streaming properly!) we'll iteratively md5 the source- -- string- frobnicate s = let s' = encode $ md5 $ L.fromChunks [s]- in (s:frobnicate s')----------------------------------------------------------------------------------testIterateeException :: Test-testIterateeException = testProperty "gzip/iterateeException" $- monadicIO $ forAllM arbitrary prop- where- prop :: L.ByteString -> PropertyM IO ()- prop s = expectException $ do- (!_,!rsp) <- goGZip (seqSnap $ withCompression $ textPlainErr s)- let body = rspBodyToEnum $ rspBody rsp- body stream2stream >>= run >>= return . fromWrap
− test/suite/TestSuite.hs
@@ -1,28 +0,0 @@-module Main where--import Test.Framework (defaultMain, testGroup)--import qualified Snap.Types.Tests-import qualified Snap.Internal.Http.Types.Tests-import qualified Snap.Internal.Routing.Tests-import qualified Snap.Iteratee.Tests-import qualified Snap.Util.FileServe.Tests-import qualified Snap.Util.GZip.Tests---main :: IO ()-main = defaultMain tests- where tests = [- testGroup "Snap.Internal.Http.Types.Tests"- Snap.Internal.Http.Types.Tests.tests- , testGroup "Snap.Internal.Routing.Tests"- Snap.Internal.Routing.Tests.tests- , testGroup "Snap.Types.Tests"- Snap.Types.Tests.tests- , testGroup "Snap.Iteratee.Tests"- Snap.Iteratee.Tests.tests- , testGroup "Snap.Util.GZip.Tests"- Snap.Util.GZip.Tests.tests- , testGroup "Snap.Util.FileServe.Tests"- Snap.Util.FileServe.Tests.tests- ]