pax_global_header00006660000000000000000000000064135771525770014535gustar00rootroot0000000000000052 comment=e9ab14a4437640656117aed30a1ee5b6227f07de ocsigenserver-2.16.0/000077500000000000000000000000001357715257700145015ustar00rootroot00000000000000ocsigenserver-2.16.0/.gitignore000066400000000000000000000015001357715257700164650ustar00rootroot00000000000000*.cmi *.cmx *.cmo *.cma *.cmxa *.cmxs *.o *.a *.annot *.cmt *.cmti *~ Makefile.config src/baselib/dynlink_wrapper.ml src/baselib/ocsigen_config.ml src/http/http_lexer.ml src/http/http_parser.ml src/http/http_parser.mli src/server/ocsigenserver src/server/ocsigenserver.opt src/extensions/ocsipersist.mli src/extensions/ocsipersist-dbm/ocsidbm src/extensions/ocsipersist-dbm/ocsidbm.opt src/extensions/ocsipersist-dbm/ocsipersist.mli src/extensions/ocsipersist-sqlite/ocsipersist.mli src/files/META src/files/META.ocsigenserver src/extensions/files/META src/extensions/files/META.ocsigenserver_ext ocsigenserver.conf.sample ocsigenserver.conf.opt.sample local/var/log/access.log local/var/log/errors.log local/var/log/warnings.log local/var/lib/ocsidb local/etc/ocsigenserver.conf local/etc/ocsigenserver.conf.opt doc/api-html _opam ocsigenserver-2.16.0/.jenkins.sh000066400000000000000000000005551357715257700165610ustar00rootroot00000000000000opam pin add --no-action ocsigenserver . opam install camlzip opam install --deps-only ocsigenserver opam install --verbose ocsigenserver do_build_doc () { make -C doc clean make -C doc doc make -C doc wikidoc cp -rf doc/api-wiki/*.wiki ${API_DIR} cp -rf doc/manual-wiki/*.wiki ${MANUAL_SRC_DIR} } do_remove () { opam remove --verbose ocsigenserver } ocsigenserver-2.16.0/.merlin000066400000000000000000000002701357715257700157670ustar00rootroot00000000000000PKG lwt PKG lwt_ssl PKG findlib PKG react PKG pcre PKG ssl PKG cryptokit PKG netstring PKG netstring-pcre PKG ipaddr PKG tyxml PKG xml-light PKG camlzip PKG dynlink S src/** B src/** ocsigenserver-2.16.0/.ocp-indent000066400000000000000000000000521357715257700165370ustar00rootroot00000000000000normal with=0 syntax=lwt mll max_indent=2 ocsigenserver-2.16.0/.travis.yml000066400000000000000000000004741357715257700166170ustar00rootroot00000000000000language: c sudo: required install: wget https://raw.githubusercontent.com/ocaml/ocaml-ci-scripts/master/.travis-opam.sh script: bash -ex .travis-opam.sh env: - OCAML_VERSION=4.03 PACKAGE=ocsigenserver - OCAML_VERSION=4.05 PACKAGE=ocsigenserver - OCAML_VERSION=4.06 PACKAGE=ocsigenserver os: - linux - osx ocsigenserver-2.16.0/CHANGES000066400000000000000000000747061357715257700155120ustar00rootroot00000000000000==== 2.16.0 (2019-12-20) ==== * compatibility with lwt.5.0: fix nested Lwt_main.run ==== 2.15.0 (2019-08-16) ==== * Ocsipersist-pgsql: fix iter_table function ==== 2.12.0 (2019-07-10) ==== * more fine-grained verbosity control * disable profiling by default * compatibility with ssl.0.5.8 * documentation ==== 2.11.0 (2018-12-19) ==== * Ocsipersist-pgsql: no longer hold a database connection for a long time ==== 2.10 (2018-12-18) ==== * Compatibility with OCaml 4.07 * Compatibility with Lwt 4.x * Replace tyxml.parser with xml-light * Gracefully respond to invalid command line options. ==== 2.9.0 (2018-02-01) ==== * Compatibility with OCaml 4.06 * Compatibility with Lwt 3.0.0 * No longer use Camlp4 * More robust Ocsipersist DB connection handling * Various small fixes and improvements ==== 2.8.0 (2016-11-24) ==== * PostgreSQL Ocsipersist backend * Compatibility with TyXML 4.0.x * Export OpenSSL options through configuration file * Various small fixes and improvements ==== 2.7.0 (2016-05-12) ==== * Fix content type selection for XML content * Send gzip trailer in Deflatemod * Log more details about SSL accept errors * Support the Content-Disposition header * Optimize buffering ==== 2.6.0 (2015-07-21) ==== * Fix cryptographic-safe string generation * Fix performance bug in Deflatmod * Fix max-age cache control directive in Staticmod * Resend cache information with 304 (not modified) responses * Expose Ocsigen_request_info interface. * Wait for error message to be logged on shutdown (#81) * Lots of fixes, UTF-8-ization, cosmetics, etc. ==== 2.5.0 (2014-09-30) ==== * New "sethttpcode" option in outputfilter * Adding "continue" option in rewritemod * use Lwt_unix.fork instead of Unix.fork * Xhtml -> Html5 ==== 2.4.0 (2014-04-14) ==== * Features/Changes ** Use ipaddr ** Support "Expect: 100-continue" * BugFixes ** fix PID file (#24) ==== 2.3.1 (2014-04-14) ==== * fix VERSION ==== 2.3.0 (2014-01-25) ==== * Features/Changes ** Support for DELETE and PUT http method ** Support syslog logs ** option for ipv4/v6 in ocsigen_http_client * BugFixes ** Prevent exit when an Lwt.async task raise an exception ** fix HTTP bytes range (#8) ==== 2.2.0 (2012-12-07) ==== * Relative filenames when not running as daemon * Small change in ocsigen_lib: encoding of parameters with '~' * fix Ocsigen_http_client.get_url (and other) first "/" was missing (close #311) * Installation: Do not try to chown files to a different user * Fix error on make logrotate * redirectmod: fixing default to permanent (as written in manual) * Minor additions in the API ===== 2.1 ===== ===== 2.0.4 (2012-06-02) ===== * Rename and split Ocsigen_pervasives into Ocsigen_lib and Ocsigen_lib_base * Adapt to lower-case module names in TyXML ===== 2.0.4 (2012-03-08) ===== * Fix licencing problems of staticmod icons * Server: ** Add a notification for connections closed by the client ===== 2.0.3 (2012-01-31) ===== * Staticmod: add a "cache" option * Cgimod: fix use of old $1 notation instead of \1 * Fix problem with etags, post and safari * Add charset to content served as something/xml * Licence of polytable and ocsigen_cache changed from GPL to LGPL ===== 2.0.2 (2011-11-18) ===== * Server: ** The command line options where not parsed when the server was not compiled with -linkall. ** Improve error messages when the log firectory is not present. ** Add a Cross-Origin Resource Sharing extension. * Libraries: ** Allow Ocsigen_sender.{Html5,Xhtml}_content to choose the returned content type (close #53). ** Ocsigen_extensions.get_port: do not return the actual request port if an Host HTTP header is present. ===== 2.0.1 (2011-09-28) ===== * Directory listing: do not use URL encoding for displaying the current path in HTML. ===== 2.0 (2011-09-20) ===== * Small fixes in documentation. ===== 2.0-rc1 (2011-07-15) ===== * Fix installation without dbm * Option to compile without preemptive threads * Get rid of C stubs: they are now included in Ocaml standard library * Log files are opened with the right UID ===== 1.91 (2011-04-08) ===== * Split the ocsigen package in three : tyxml, ocsigenserver and eliom * Rename into ocsigenserver * Revproxy, Accesscontrol: Handling {{{X-Forwarded-For}}} and {{{X-Forwarded-Proto}}} headers * HTTP parser rewritten ===== 1.90 ===== * New module {{{Eliom_client}}} for client/server Eliom programs using js_of_ocaml. * Eliom client: calling a service from client side code. * Eliom: Services taking Caml values as parameters. * Eliom: services sending Caml values. * Eliom: new implementation of forms and links to be compatible with client side programs. * Eliom: sp parameter has been removed (now using Lwt's thread storage) * Eliom: {{{Eliom_predefmod}}} renamed {{{Eliom_output}}} * Eliom: New module {{{Eliom_output.Customize}}} to create your own register functions from another registration module * Eliom: Module {{{Eliom_sessions}}} replaced by {{{Eliom_state}}}, {{{Eliom_request_info}}} and {{{Eliom_config}}}. * Eliom: new implementation of user cookies. * Eliom: Client process cookies. Like usual browser cookies but for one client side process. * Eliom: Client process server side state data. Like session data but for one client side process. * Eliom: Client process session services. * Eliom: Session group data. Like session data but for all sessions in a session group. * Eliom: Session group services. * Eliom: session replaced by a more general concept of "server side state". States have a scope: client process, session or group of sessions. * Eliom: session tables and request cache now deprecated, replaced by //Eliom references// * Eliom client: Possible to call another service without stopping the client side program, with bookmark support and back button support. * New extension Comet to allow server -> client communication. * Eliom: client/server communication channels. * Eliom: client/server reactive programming using React. * Eliom client: syntax extension for separating client and server code. * Eliom: New module Eliom_output.Eliom_appl for registering pages that belong to the same Eliom application. * Eliom client: syntax extension and wrapping/unwrapping mechanism to access server side values in client side code. * Eliom client: Relinking the DOM on client side after loading a (portion of) page. This allows to use directly nodes created on server side in client side code. * Eliom client: XHR redirections for Eliom applications. * Eliom: safe unmarshaling of caml values sent by client side programs * Xhtml: Xhtml5 support * Atom module and Pubsubhubbub * OpenID support * SVG module * Documentation: New tutorial * Documentation: New Eliom manual * //and many other things ...// ===== 1.3.4 ===== * Eliom: Now supporting list of lists in service parameters ===== 1.3.3 (2010-06-13) ===== * Eliom: Fix some typos in Eliom's tutorial stylesheet * Server: Fix usage of {{{accept_n}}} to avoid file descriptor leakage * XHTML: Adding missing elements and attributes in XHTML.M * Cleaning Ocsigen_cache * Eliom: Fixing actions with uploads ===== 1.3.2 (2010-04-30) ===== * Add dummy findlib packages ocsigen.xhtml*, that depend on ocsigen_xhtml*, for compatibility with previous versions. These packages might be removed in a future (major) release. * Port to Lwt 2.1.0 ===== 1.3.1 (2010-04-23) ===== * Split out ocsigen_xhtml findlib package * Configuration file: when no protocol is specified in {{{}}}, listen on IPv6 (if possible) and IPv4 (always) ===== 1.3.0 (2010-01-22) ===== * Server: Each request now has a polymorphic data table (called //request cache//), where you can store the data you want to keep during the whole page generation. * Eliom: actions now return {{{()}}}. Use the request cache to send information to fallbacks. * Eliom: CSRF-safe coservices * Eliom: the number of sessions without group by IP address is now limited * Eliom: efficient implementation of limitation of sessions by groups (or now IP) for large limits * Eliom: the number of anonymous coservices by session is now limited * Eliom: the number of anonymous coservices without session by IP address is now limited * Eliom: now possible to unregister services * Eliom: New (very) experimental module {{{Eliom_obrowser}}} to use Eliom with Obrowser * Eliom: Now possible to have constant parts in suffixes to allow URLS like {{{/param1/something/param2}}} * Eliom: services with optional suffixes * Eliom: form towards a service with suffix: it is now possible to choose whether you want the redirection towards the suffix form or not * Eliom: lists and sets in suffixes * Eliom: Now possible to create services sending custom HTTP header values or customizing the content-type * Eliom: New notion: "Non localized parameters". Can be sent to any service. * Eliom: changing the order of parameters for user type form widgets * Eliom: changing the order of parameters for user type form widgets * Eliom: Eliom_tools.menu and hierarchical_menu now compare the URL strings (was: compare the service) * Eliom: textarea now take a string (was pcdata) * Eliom: The type of the iterator for lists in parameters has changed * Eliom: New options in configuration file to set session timeouts * Server: now possible to give the config file name to reload server command * Server: now possible to do a "graceful shutdown" of the server using the "shutdown" server command * Server: now possible to add custom commands for the command pipe * Server: EXPERIMENTAL now possible to observe HTTP headers before sending the result * Xhtmlpp: the parsing now fails if a quotation for an Xhtml element contains superfluous elements. (This can cause the parsing of previously incorrect code to fail) * Staticmod/Eliom: attempting to access a file whose name contains a NULL character will result in a 403. * Server: HTTP headers containing newlines are now properly escaped. * Server: correct missing xmlns in Xhtml DTD * Server: now send last-modified and etag headers when returning a 403 * Server: Now accepting several requests at a time (as explained in "Accept()able strategies" by Tim Brecht & all) * Rewritemod: more rewriting possibilities (still very basic) * Eliom menus are now more robust when finding which item is active * Fixed handling of incorrectly-sized multipart requests. Thanks to Mauricio Fernandez for noticing the bug * Upload directory and maximum file size can now be configured on a per-site basis * Renamed the field of Ocsigen_http_frame.t * Javascript events support in Xhtml.M ; Thanks to john @ 5070.info for the patch ===== 1.2.2 (2009-10-17) ===== * Add react and lwt.unix to the list of built-in findlib packages ===== 1.2.1 (2009-09-26) ===== * Lwt 2.0 compatibility: ** Adapt to Lwt.t/Lwt.u splitting ** fix Makefile to deal with lwt.unix findlib package * Fix ocsipersist-dbm Makefile * Fix for pcre-ocaml 6.0.0 * Fix typo regarding --stubdir in configure script ===== 1.2.0 (2009-03-25) ===== * Native code version now compiled by default * Now possible to link extensions and Eliom modules statically, for example to use a native code server on platforms where native dynlink is not supported * Server: Partial requests implemented (Range HTTP header) * Build C stubs into a shared library so that bytecode executables may be not linked in custom mode; new {{{--stubdir}}} option in {{{configure}}} script * Eliom: non-attached services now called "named non-attached coservices" and created using {{{Eliom_services.new_coservice'}}} with the optional {{{name}}} parameter * Eliom: now possible to create named attached coservices using the optional {{{name}}} parameter * Eliom: now possible to write libraries for Eliom sites, loaded inside {{{}}}, but not generating any page * Eliom and server: EXPERIMENTAL now possible to make extensions that can use Eliom's data * XHTML.M's pretty printer: now possible to set the DOCTYPE manually * Eliom: now possible to set manually the DOCTYPE when registering an XHTML.M service * Redirectmod and Revproxy: now possible to do more complex rewriting * Accesscontrol: add support for {{{}}} and {{{}}} conditions * Config file: {{{aliases}}} attribute now called {{{hostfilter}}} * Revproxy and Redirectmod: now possible to filter on server, port and protocol * New extension extendconfiguration to allow dynamic changes in the configuration (mimetypes, charsets, followsymnlink, directory listing, ...) * Server: new module {{{LocalFiles}}} factoring the common parts for sending static files (with Eliom and staticmod for example), while checking that the files can safely be sent. * Now possible to use XHTML pretty printers without Ocsigen, using the {{{xhtmlpretty.cma}}} library * Add {{{Ocsigen_lib.register_exn_printer}}}, better error messages * Now possible to use the same configuration file in native code and in bytecode (.cmo/.cma filenames are internally translated to .cmxs) * Signature of Ocsigen_extensions.register_extension is now more complete and more lightweight * Userconf: the options set in the local .userconf file are kept in the enclosing {{{}}} tag * Server: possibility to ignore or to supply an alternative command-line * Ocsigen_http_client: timeout when the distant server does not exists * OCaml versions < 3.10 are not supported anymore * Extensions are now much more strict w.r.t. the syntax of configuration files * Staticmod: accessing a directory for which indexing is disallowed returns an error 404 (instead of a 403 previously) ===== 1.1.0 (2008-07-15) ===== * Lwt removed (now distributed separately) * {{{XHTML.M}}} pretty printer: fixing pretty printing of empty tags (for browser compatibility) * Eliom_duce: New pretty printer for XHTML fixing pretty printing of empty tags * Eliom: secure sessions, secure services, (absolute) https links/forms, and using secure cookies * Eliom: Adding special "void action", without any parameters * Eliom: {{{Eliom_predefmod.Redirections}}} now called {{{Eliom_predefmod.String_redirection}}}, and adding new module {{{Eliom_predefmod.Redirection}}} that use GET services without parameters as data type. * Eliom and XHTML.M: Small changes of types in interfaces * Eliom: New session ID generator * Eliom: Adding types {{{int32}}} and {{{int64}}} for parameters and forms * Eliom: Adding functions {{{lwt_get_form}}} and {{{lwt_post_form}}} for creating forms using cooperative functions * Eliom and Staticmod: now possible to give GET parameters to static pages * Eliom: Bugfix in Makefiles for native code version * Eliom forms: Adding missing types in interfaces * Eliom_tools: current page is now optional in menus * Userconf and Eliom: there was a bug when loading both Eliom and Userconf together * Reverse Proxy: Now sending content length when available * Web server: The default content-type is now {{{application/octet-stream}}} * Creating and installing a cma file for all Ocsigen libraries not installed elsewhere * Ocsipersist-dbm: fixing bug when removing data * Deflatemod: fixing memory leak * And small bugfixes in XHTML.M, Eliom, ... ===== 1.0.0 (2008-04-01) ===== * Config file: findlib integration * Eliom and Ocsigen: changing namespace convention for modules * Access control: simplification of config file syntax * Eliom: Allowing (module dependent) parameters for registration functions * New xhtml output without pretty printing * Web server: Bugfix in HTTP/1.0 with keep-alive * Reverse proxy: Bugfix GET parameters were wrong * Reverse proxy: Bugfix memory consumption when the connection was closed by client ===== 0.99.5 (2008-01-11) ===== * Revproxy: pipelining of requests * Access control: simplification, generalization of filters and syntax changes in config file * Eliom: EXPERIMENTAL session groups * Eliom: non-attached services * Eliomduce: new functor {{{SubXhtml}}} for creating registration modules * Eliomduce: new module Eliomducetools with same features as Eliomtools, but for Eliomduce * Web server: now possible to split the configuration file into several files using the {{{}}} option. * Web server: now possible to have {{{}}} option inside another {{{}}} in configuration files, and the the first one is optional * Web server: EXPERIMENTAL user configuration files, with restricted possibilities (for security reasons) * Web server: IPv6 support * Deflatemod: now possible to filter on file extensions * Eliom: new option to keep GET non-attached parameters or not when doing a POST form towards a non-attached coservice. * Eliom: bugfix path of session cookies * Eliom: bugfix POST non-attached coservices called from a page with non-attached GET parameters were not found. * Lwt: now catching exceptions raised after timeouts * Cgimod: bufixes in path handling * Web server: bugfix - some files were not closed ===== 0.99.4 (2007-11-21) ===== * Ocsigen: Changes in the extension mechanism. The extensions are not tried in the order in which they are loaded any more, but in the order in which the options are specified for each site in the configuration file. * New experimental extension: access control * A few small enhancements ** Eliom: internal cookie management rewritten (+ bugfix) ** Eliom: Small changes in function names ** Eliom: now retry all extensions after actions (not only Eliom) ** Eliom: cleaning {{{Eliommod}}} interface ** Ocsigen server: Internal changes in server (removing "send" functions, debug messages lazier) ** Lwt: Adding a few functions in {{{Lwt_chan}}} interface ** Staticmod: Allowing to personalize default error pages for HTTP errors ** Ocsipersist (dbm and sqlite): better handling of database errors ** XHTML: New pretty printer for xhtml using streams (up to 15% speedup on requests) ** XHTML: Allowing any value for {{{}}} rel attribute (for example {{{shortcut icon}}}). ===== 0.99.3 (2007-11-07) ===== * Ocsigen: New module Deflatemod to compress data before sending to the browser. * Ocsigen: EXPERIMENTAL - New module Revproxy (reverse proxy). * Eliom: New session mechanism, with the ability to name the sessions and thus have several sessions for the same site. * Eliom: Now possible to have one site with session inside a subdirectory of another one. * Lwt: New module {{{Lwt_timeout}}} to implement timeouts, new module {{{Lwt_chan}}}, new module {{{Lwt_mutex}}}, new function {{{Lwt_unix.abort}}} to make all threads waiting on a file descriptor abort with an exception. * Ocsigen: New implementation of lots of Web server internals. Better management of Ocsigen's streams, file descriptors, exceptions, timeouts ... * A lot of enhancements and bug fixes: ** Eliom: Single {{{}}} in forms, by Stéphane Dalmas * EXPERIMENTAL: The Web server is now extensible. It means that you can add modules (like Apache modules) for generating pages, filters of requests, extensions of config files. For now there are two modules, one for static pages, and one for dynamic pages. The only changes for users is that they need to dynlink staticmod.cmo and ocsigenmod.cma from the configuration file. The syntax of config file for modules and staticdir also changed. * It is now possible to specify the encoding of characters for each sub-site. * Now usable with Ocamlnet 2.2 or 1.1.2. * EXPERIMENTAL: If OCamlDuce is installed on your system, you can now use it to do the type-checking of your pages (see the documentation). Warning: This is broken with OCamlDuce 3.09.2 patch level 2. You need at least OCamlDuce 3.09.3 patch level 1. * Removing Ocsimore from the default distribution. That version of Ocsimore is not supported anymore. Ocsimore has been rewritten from scratch by Piero Furiesi. ===== 0.5.1 (2006-12-14) ===== * Bugfix Konqueror: Multipart forms with now work correctly with Konqueror * Bugfix Internet Explorer: getting around a bug of Internet Explorer while displaying page * Bugfix NetBSD: Makefile * Bugfix Debian for HPPA, Mips, Alpha: Makefile * Bugfix: preemptive.cmi was missing in the installation directory * Adding manpage (S. Mimram) * Adding logrotate configuration * Daemon mode: adding setsid and redirect stdout/stderr to /dev/null. Closing stdin. ===== 0.5.0 (2006-11-23) ===== * HTTP 1.1 improved (HEAD, ETag, keep-alive implemented, If-Modified-Since, ...) * HTTPS support * Pipelining of requests * Server can listen on several ports * Multiple servers: you can now define several servers in the config file. * Virtual hosts: filtering on hostnames/ports (with wildcards) * Asynchronous file upload with multipart support * Large file transfer improved. * MIME types are now parsed from a file * Changes in the syntax of config file * Accessors for server parameters. Use ({{{get_user_agent sp}}}) instead of {{{sp.user_agent}}}. * Page generation is now using {{{Lwt}}} cooperative threads, to make it possible the generation of several pages at the same time. Practically, add {{{Lwt.return}}} before the page you want to send, and use cooperative input/output functions. * Typing errors of parameters are now catchable. * {{{static_dir}}} is now a function * Most of global references have been removed. You now need to give sp as parameter to {{{register_for_session}}}, {{{static_dir}}}, {{{close_session}}}, etc. * Links and forms now take {{{server_params}}} instead of {{{current_url}}} ({{{sp}}} is shorter than {{{sp.current_url}}}) * EXPERIMENTAL: Use of preemptive threads for non cooperative libraries ({{{detach}}} function). * EXPERIMENTAL: The {{{Ocsigen}}} module now contains a functor {{{Make}}} to allows the use of several ways of generating XHTML. The default way (using {{{XHTML.M}}} or the syntax extension) can be used by doing {{{open Ocsigen.Xhtml}}}. There is also an untyped xhtml generation module called {{{Ocsigen.Text}}}. * EXPERIMENTAL: extension of forms. * Reorganisation of the code * Bugfixes in makefiles * Bugfix: escaping of strings in xhtml with syntax extension (thanks to David Mentre) ===== 0.4.0 (2006-06-06) ===== * Full reimplementation of the core using Generalized Algebraic Data Types, * {{{_int}}}, {{{_string}}}, etc. are now called {{{int}}}, {{{string}}}, etc. * The type {{{_http_params}}} is now called {{{server_params}}}, * Services functions now all take 3 parameters, one for server parameters, one for GET parameters, and one for POST parameters. Note that {{{**}}} is used to create **pairs** and not tuples. * The {{{a}}} and {{{post_form}}} functions now take a fix number of parameters, corresponding to GET parameters. * //URLs// are now called //services//, * //state URLs// are now called //auxiliary services//, * {{{register_post_url}}} does not exist anymore. use {{{register_service}}} instead (idem for all other {{{register_post_*}}} functions). * Changes for prefix URLs * Small changes for actions * EXPERIMENTAL: sums, bool and list types for services and forms * small bugfixes ===== 0.3.27 (2006-04-27) ===== * Change the way to get server parameters ===== 0.3.26 ===== * Load unsafe modules * Other small changes ===== 0.3.25-2 (2006-02-24) ===== * Small bugfix for 64 bits processors * bugfix for static files * {{{action_link}}} is now called {{{action_a}}} ===== 0.3.24 (2006-02-07) ===== * More documentation * Change types {{{internal_url}}} and {{{external_service}}} to use polymorphic variants ===== 0.3.23 (2006-02-07) ===== * Better handling of static files and "403 Forbidden" message ocsigenserver-2.16.0/COPYING000066400000000000000000000654371357715257700155530ustar00rootroot00000000000000This program is released under the LGPL version 2.1 (see the text below) with the additional exemption that compiling, linking, and/or using OpenSSL is allowed. As a special exception to the GNU Library General Public License, you may also link, statically or dynamically, a "work that uses the Library" with a publicly distributed version of the Library to produce an executable file containing portions of the Library, and distribute that executable file under terms of your choice, without any of the additional requirements listed in clause 6 of the GNU Library General Public License. By "a publicly distributed version of the Library", we mean either the unmodified Library, or a modified version of the Library that is distributed under the conditions defined in clause 3 of the GNU Library General Public License. This exception does not however invalidate any other reasons why the executable file might be covered by the GNU Library General Public License. GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! ocsigenserver-2.16.0/LICENSE000066400000000000000000000004131357715257700155040ustar00rootroot00000000000000The Ocsigen application core, and other portions of the official Ocsigen distribution not explicitly licensed otherwise, are licensed under the GNU LESSER GENERAL PUBLIC LICENSE with openssl linking exception -- see the 'COPYING' file in this directory for details. ocsigenserver-2.16.0/Makefile000066400000000000000000000125431357715257700161460ustar00rootroot00000000000000include Makefile.config ### Building .PHONY: all byte opt doc all: ${MAKE} -C src all byte: ${MAKE} -C src byte opt: ${MAKE} -C src opt doc: $(MAKE) -C doc ### Testing and debugging : local execution and toplevel ### .PHONY: run.local run.opt.local top run.local: byte CAML_LD_LIBRARY_PATH=src/server:$(CAML_LD_LIBRARY_PATH) src/server/${PROJECTNAME} -c local/etc/${PROJECTNAME}.conf run.opt.local: opt CAML_LD_LIBRARY_PATH=src/server:$(CAML_LD_LIBRARY_PATH) src/server/${PROJECTNAME}.opt -c local/etc/${PROJECTNAME}.conf top: cd src/server && ${MAKE} top ### Cleaning ### clean: clean.local ${MAKE} -C src clean clean.local: -rm -f $(PROJECTNAME)-*.tar.gz distclean: clean.local ${MAKE} -C src distclean -make -C doc clean -rm Makefile.config -rm -f *~ \#* .\#* ### Installation #### .PHONY: install partialinstall reinstall uninstall purge.files install.files install.META: $(MAKE) -C src install install.META.byte: $(MAKE) -C src install.byte install.META.opt: $(MAKE) -C src install.opt reinstall: uninstall install reinstall.byte: uninstall install.byte reinstall.opt: uninstall install.opt install: install.META install.files @echo @echo "## Run \"make doc\" and \"make install.doc\" to build and install the ocamldoc." install.byte: install.META.byte install.files install.opt: install.META.opt install.files # BB If install is not run by root but OCSIGENUSER, OCSIGENGROUP is somebody # BB different, make files universally accessible, we cannot chown. INSTALL_CAN_PUT_PERMISSIONS=yes INSTALL_USER_GROUP=-o $(OCSIGENUSER) -g "$(OCSIGENGROUP)" INSTALL_MOD_660=660 INSTALL_MOD_644=644 INSTALL_MOD_755=755 INSTALL_MOD_770=770 INSTALL_MOD_750=750 USERNAME=$(shell whoami) ifneq ($(shell id -u), 0) ifneq ($(OCSIGENUSER), $(USERNAME)) INSTALL_CAN_PUT_PERMISSIONS=no endif ifneq ($(shell groups ${USERNAME}|grep -q ${OCSIGENGROUP}; echo $$?), 0) INSTALL_CAN_PUT_PERMISSIONS=no endif endif ifeq ($(INSTALL_CAN_PUT_PERMISSIONS), no) INSTALL_USER_GROUP= INSTALL_MOD_660=666 INSTALL_MOD_644=666 INSTALL_MOD_755=777 INSTALL_MOD_770=777 INSTALL_MOD_750=777 endif install.files: @echo INSTALL_CAN_PUT_PERMISSIONS: ${INSTALL_CAN_PUT_PERMISSIONS} ## Command pipe $(INSTALL) -m ${INSTALL_MOD_755} -d $(dir $(TEMPROOT)$(COMMANDPIPE)) [ -p $(TEMPROOT)$(COMMANDPIPE) ] || \ { mkfifo -m ${INSTALL_MOD_660} $(TEMPROOT)$(COMMANDPIPE); \ if [ "${INSTALL_CAN_PUT_PERMISSIONS}" = yes ]; \ then $(CHOWN) -R $(OCSIGENUSER):"$(OCSIGENGROUP)" $(TEMPROOT)$(COMMANDPIPE); \ fi; } ## Configuration files $(INSTALL) -m ${INSTALL_MOD_755} -d $(TEMPROOT)$(CONFIGDIR)/conf.d ${INSTALL} -m ${INSTALL_MOD_644} ${PROJECTNAME}.conf.sample $(TEMPROOT)$(CONFIGDIR)/ [ -f $(TEMPROOT)$(CONFIGDIR)/$(PROJECTNAME).conf ] || \ { $(INSTALL) -m ${INSTALL_MOD_644} $(PROJECTNAME).conf.sample \ $(TEMPROOT)$(CONFIGDIR)/$(PROJECTNAME).conf; } -mv $(TEMPROOT)$(CONFIGDIR)/mime.types $(TEMPROOT)$(CONFIGDIR)/mime.types.old ## Log directory $(INSTALL) -m ${INSTALL_MOD_644} src/files/mime.types $(TEMPROOT)$(CONFIGDIR) $(INSTALL) -d -m ${INSTALL_MOD_755} ${INSTALL_USER_GROUP} $(TEMPROOT)$(LOGDIR) ## Static files $(INSTALL) -d -m ${INSTALL_MOD_755} ${INSTALL_USER_GROUP} $(TEMPROOT)$(STATICPAGESDIR) $(INSTALL) -d -m ${INSTALL_MOD_750} ${INSTALL_USER_GROUP} $(TEMPROOT)$(DATADIR) $(INSTALL) -m ${INSTALL_MOD_644} ${INSTALL_USER_GROUP} \ local/var/www/*.html $(TEMPROOT)$(STATICPAGESDIR) $(INSTALL) -d -m ${INSTALL_MOD_755} ${INSTALL_USER_GROUP} \ $(TEMPROOT)$(STATICPAGESDIR)/ocsigenstuff $(INSTALL) -m ${INSTALL_MOD_644} ${INSTALL_USER_GROUP} \ local/var/www/ocsigenstuff/*.png local/var/www/ocsigenstuff/*.css \ $(TEMPROOT)$(STATICPAGESDIR)/ocsigenstuff $(INSTALL) -d -m ${INSTALL_MOD_755} $(TEMPROOT)$(MANDIR) $(INSTALL) -m ${INSTALL_MOD_644} src/files/${PROJECTNAME}.1 $(TEMPROOT)$(MANDIR) uninstall: -make -C doc uninstall -rm -f $(TEMPROOT)$(CONFIGDIR)/$(PROJECTNAME).conf.sample -rm -f $(TEMPROOT)$(MANDIR)/${PROJECTNAME}.1 -rm -f $(TEMPROOT)$(COMMANDPIPE) -rmdir --ignore-fail-on-non-empty $(TEMPROOT)$(CONFIGDIR)/conf.d -rmdir --ignore-fail-on-non-empty $(TEMPROOT)$(CONFIGDIR) -rmdir --ignore-fail-on-non-empty $(TEMPROOT)$(LOGDIR) -rmdir --ignore-fail-on-non-empty $(TEMPROOT)$(DATADIR) -rmdir --ignore-fail-on-non-empty $(TEMPROOT)$(MANDIR) -$(MAKE) -C src uninstall purge: purge.files uninstall purge.files: -rm -f $(TEMPROOT)$(CONFIGDIR)/mime.types $(TEMPROOT)$(CONFIGDIR)/mime.types.old -rm -f $(TEMPROOT)$(CONFIGDIR)/$(PROJECTNAME).conf -rm -f $(patsubst local/var/www/ocsigenstuff/%, \ $(TEMPROOT)$(STATICPAGESDIR)/ocsigenstuff/%, \ $(wildcard local/var/www/ocsigenstuff/*)) -rmdir --ignore-fail-on-non-empty $(TEMPROOT)$(STATICPAGESDIR)/ocsigenstuff -rm -f $(patsubst local/var/www/%, \ $(TEMPROOT)$(STATICPAGESDIR)/%, \ $(wildcard local/var/www/*.html)) -rmdir --ignore-fail-on-non-empty $(TEMPROOT)$(STATICPAGESDIR) install.doc: ${MAKE} -C doc install ### Install logrotate configuration files ### .PHONY: logrotate logrotate: $(INSTALL) -m 755 -d $(TEMPROOT)/etc/logrotate.d cat src/files/logrotate.in \ | sed s%LOGDIR%$(LOGDIR)%g \ | sed s%USER%$(OCSIGENUSER)%g \ | sed s%GROUP%"$(OCSIGENGROUP)"%g \ | sed s%_COMMANDPIPE_%$(COMMANDPIPE)%g \ > $(TEMPROOT)/etc/logrotate.d/$(PROJECTNAME) ### .PHONY: depend depend: ${MAKE} -C src depend ocsigenserver-2.16.0/Makefile.dist000066400000000000000000000021011357715257700170750ustar00rootroot00000000000000 ## ## Usage: ## ## If the released version is tagged in the main repository, use: ## ## make -f Makefile.dist ## ## If the tag has not been pushed, use: ## ## make -f Makefile.dist REPO=${PWD} ## ## otherwise, use: ## ## make -f Makefile.dist REPO=${PWD} VERSION=master ## #VERSION?=$(shell grep Version: _oasis | cut -d ' ' -f 2) VERSION=$(shell cat VERSION) REPO?=https://github.com/ocsigen/ocsigenserver all: dist sign dist: @rm -rf ocsigenserver-${VERSION} \ ocsigenserver-${VERSION}.tar.gz \ ocsigenserver-${VERSION}.tar.gz.asc git clone --local -b ${VERSION} ${REPO} ocsigenserver-${VERSION} # oasis -C ocsigenserver-${VERSION} setup # sed -i "s/SETUP := setup-dev.exe/SETUP := setup.exe/" \ # ocsigenserver-${VERSION}/Makefile cd ocsigenserver-${VERSION} && rm -rf .git .gitignore Makefile.dist tar cvzf ocsigenserver-${VERSION}.tar.gz ocsigenserver-${VERSION} @rm -rf ocsigenserver-${VERSION} sign: ocsigenserver-${VERSION}.tar.gz.asc ocsigenserver-${VERSION}.tar.gz.asc: ocsigenserver-${VERSION}.tar.gz gpg --armor -b $^ .PHONY: dist sign ocsigenserver-2.16.0/Makefile.options000066400000000000000000000023541357715257700176370ustar00rootroot00000000000000BYTEDBG := OPTDBG := THREAD := -thread ifeq "$(DEBUG)" "YES" BYTEDBG += -g OPTDBG += -g endif ifeq "$(ANNOT)" "YES" BYTEDBG += -bin-annot OPTDBG += -bin-annot endif ifeq "$(PROFILING)" "YES" BYTEDBG := ${BYTEDBG} OPTDBG += -p endif ## ${SERVER_PACKAGE} is not only used to build the 'ocsigenserver' executable ## but also to generate src/baselib/ocsigen_config.ml and src/files/META BASE_PACKAGE := lwt ipaddr bytes SERVER_PACKAGE := lwt_ssl \ bytes \ lwt.unix \ lwt_log \ ipaddr \ netstring \ netstring-pcre \ findlib \ cryptokit \ tyxml \ xml-light \ dynlink \ INITPACKAGE := \"$(shell ${OCAMLFIND} query -p-format -recursive \ -separator '\";\"' ${SERVER_PACKAGE})\"; \ \"${PROJECTNAME}.commandline\"; \ \"${PROJECTNAME}.polytables\"; \ \"${PROJECTNAME}.cookies\"; \ \"${PROJECTNAME}.baselib\"; \ \"${PROJECTNAME}.http\"; \ \"${PROJECTNAME}\"; \ ocsigenserver-2.16.0/README.md000066400000000000000000000102211357715257700157540ustar00rootroot00000000000000Ocsigen server ------------------------------------------------------------------ Requirements: ============= Compilers: * ocaml (need version >= 3.12.1) * a C compiler (tested with gcc-4.4.5) Libraries: * findlib * react (tested with 0.9.3) * ocamlssl (tested with 0.4.6) * lwt (need version >= 2.4.2, with react and ssl support) * ocamlnet (tested with 3.6, with netstring, netstring-pcre and netsys) * pcre-ocaml (tested with 6.2.5) * cryptokit (tested with 1.6) * ocaml-text (tested with 0.6) * tyxml (need version 3) * ipaddr (need version >= 2.1) * ocamlsqlite3 (tested with 2.0.2) OR * pgocaml (tested with 2.3) OR * dbm (tested with 1.0) Optional libraries: * camlzip (tested with 1.04) Ocsigenserver supports both dbm and sqlite3. Note well, that dbm isn't part of the distribution of OCaml>=4 any more, but an external package. If OCaml 3.12.1 and the needed libraries (findlib/react/lwt...) are not installed on your computer and not available on your favorite linux distribution, you may use the Ocsigen bunble GODI to install them automatically, see: http://ocsigen.org/install#bundle http://godi.camlcity.org/godi/index.html To run the native code version of ocsigen server, you may need to generate cmxs files for the libraries you need, if they are not included in your distribution, see: http://ocsigen.org/ocsigenserver/manual/misc ------------------------------------------------------------------ Build instructions: =================== * run "sh configure [options]" to generate 'Makefile.config' - For the full list of options, run "sh configure --help". * verify that 'Makefile.config' suits to your needs. * run "make" to compile * run "make install" as root to install * [optional] run "make logrotate" as root to install logrotate configuration files in /etc/logrotate.d * [optional] run "make doc" to build the ocamldoc * [optional] run "make install.doc" as root to install the ocamldoc * run "make uninstall" to uninstall (almost) everything * run "make purge" to uninstall everything (even configuration files) ------------------------------------------------------------------ Local testings: =============== * run "make run.local" or "make run.opt.local" in the ocsigen source directory. * open http://localhost:8080/index.html in your browser * if it does not work, look at the logs (see 'local/var/log/' in the ocsgigen source directory) or run ocsigen with options -v or -V (verbose and debug mode). ------------------------------------------------------------------ Authors: ======== * Vincent Balat (project leader, Web server, Ocsigenmod, Eliom, Eliom client, Staticmod, XHTML syntax extension, documentation, Ocsimore, extension mechanism, Ocsidbm, Ocsipersist with DBM, ...) * Jérôme Vouillon (Lwt, Web server, js_of_ocaml, O'Closure, ...) * Boris Yakobowski (Ocsimore, module Extendconfiguration, Ocsigen server...) * Benjamin Canou (O'Browser) * Jérémie Dimino (Lwt) * Raphaël Proust (Ocsforge, Eliom client, Comet) * Stéphane Glondu (Configuration file, Findlib integration, access control, HTTP authentication, Debian package, ...) * Gabriel Kerneis (XHTML syntax extension for OCaml 3.10, Ocsipersist with SQLite, CGI module, forms in Eliom, deflatemod, ...) * Denis Berthod (HTTP protocol, Web server) * Grégoire Henry (safe unmarshalling of client data) * Pierre Chambart (Comet) * Jaap Boender (Ocsimore, NetBSD and Godi packages) * Gabriel Scherer (Macaque) * Gabriel Cardoso (O'Closure) * Jean-Henri Granarolo (Ocsforge) * Simon Castellan (HTML5, OpenID, SVG) * Piero Furiesi (Ocsimore) * Thorsten Ohl (most of the functions generating XHTML (xML and xHTML modules)) * Mauricio Fernandez (Xhtmlcompact, static linking of extensions and Eliom modules) * Nataliya Guts (Web server, HTTPS) * Archibald Pontier (Atom, Pubsubhubbub) * Jérôme Velleine (CGI module) * Charles Oran (O'Closure) * Pierre Clairambault (Lwt_lib, Gentoo package, configure script, ...) * Cécile Herbelin (HTML5, Benchmarks) ocsigenserver-2.16.0/VERSION000066400000000000000000000000041357715257700155430ustar00rootroot000000000000002.9 ocsigenserver-2.16.0/configure000077500000000000000000000340561357715257700164200ustar00rootroot00000000000000#! /bin/sh # Adapted from the ocamlnet configure script. ####################################################################### # Helpers: test_binary () { # $1: the name of the binary echo -n "Checking for $1 ... " if which "$1" >/dev/null 2>/dev/null; then echo "found" return 0 else echo "not found" return 1 fi } fail_binary () { echo echo "Required command '$1' not found!" [ -z "$2" ] || echo "===> $2" exit 1 } check_binary () { # $1: the name of the binary # $2: an URL if test_binary $1; then return else fail_binary $1 $2 fi } test_library () { # $1: the name of the library (findlib) echo -n "Checking for $1 ... " if ocamlfind query $1 >/dev/null 2>/dev/null; then echo "found" return 0 else echo "not found" return 1 fi } fail_library () { echo echo "Required library $1 not found!" [ -z "$2" ] || echo "===> $2" exit 1 } check_library () { # $1: the name of the library (findlib) # $2: an URL if test_library $1; then return else fail_library $1 "$2" fi } ####################################################################### # Defaults #--- Options --- # value 0: off # value 1: on # defaults: set_defaults () { name="ocsigenserver" enable_natdynlink=1 enable_debug=1 enable_annot=1 enable_profiling=0 with_sqlite=1 with_pgsql=1 with_camlzip=1 with_dbm=1 prefix="/usr/local" bindir="" logdir="" libdir="" mandir="" docdir="" sysconfdir="/etc/\$(PROJECTNAME)" staticpagesdir="/var/www/\$(PROJECTNAME)" uploaddir="/tmp" datadir="/var/lib/\$(PROJECTNAME)" temproot="" root="" ocsigen_user="www-data" ocsigen_group="www-data" } set_defaults my_pwd=`dirname $0` version=`head -n 1 $my_pwd/VERSION` full_pwd=`pwd` ######################################################################## ## Option parsing ## Which options exist? eoptions for enable/disable, woptions for with/without: eoptions="debug annot profiling natdynlink" woptions="pgsql sqlite dbm camlzip" print_options () { for opt in $eoptions; do e="o=\$enable_$opt" eval "$e" uopt=`echo $opt | sed -e 's/_/-/g'` if [ $o -gt 0 ]; then echo " --enable-$uopt" else echo " --disable-$uopt" fi done for opt in $woptions; do e="o=\$with_$opt" eval "$e" uopt=`echo $opt | sed -e 's/_/-/g'` if [ $o -gt 0 ]; then echo " --with-$uopt" else echo " --without-$uopt" fi done case "$bindir" in "") bindir2="/bin";; *) bindir2=$bindir;; esac case "$logdir" in "") logdir2="/var/log/";; *) logdir2=$logdir;; esac case "$libdir" in "") libdir2="\$(shell \${OCAMLFIND} printconf destdir)";; *) libdir2=$libdir;; esac case "$mandir" in "") mandir2="/share/man/man1";; *) mandir2=$mandir;; esac case "$docdir" in "") docdir2="/share/doc/\$(PROJECTNAME)";; *) docdir2=$docdir;; esac echo " --name $name" echo " --root $root" echo " --temproot $temproot" echo " --prefix $prefix" echo " --ocsigen-user $ocsigen_user" echo " --ocsigen-group $ocsigen_group" echo " --bindir $bindir2" echo " --libdir $libdir2" echo " --sysconfdir $sysconfdir" echo " --mandir $mandir2" echo " --docdir $docdir2" echo " --logdir $logdir2" echo " --staticpagesdir $staticpagesdir" echo " --uploaddir $uploaddir" echo " --datadir $datadir" echo " --commandpipe $commandpipe" } usage () { set_defaults cat <<_EOF_ >&2 usage: ./configure [ options ] --enable-debug, --disable-debug Enable/disable debug output --enable-annot, --disable-annot Enable/disable .cmt{,i} files generation --enable-profiling, --disable-profiling Enable/disable profiling --enable-natdynlink, --disable-natdynlink Enable/disable nativecode dynamic linking --with-sqlite, --without-sqlite Compile ocsipersist with SQLite for persistent storage --with-pgsql, --without-pgsql Compile ocsipersist with PostgreSQL for persistent storage --with-dbm, --without-dbm Compile ocsipersist with DBM for persistent storage --with-camlzip, --without-camlzip Compile deflatemod extension, requires camlzip --name NAME The name of the server binary and ocamlfind package. --ocsigen-user NAME The name of the ocsigen user --ocsigen-group NAME The name of the ocsigen group --root DIR Root directory to install the package, every other options are relatives to this path. (usually /) --temproot DIR Temporary root directory to install the package (usually always "" but for package makers) --prefix DIR Subdirectory where to install binaries and libs (usually /usr or /usr/local) --bindir DIR Install binaries into this directory --libdir DIR Common directory for Ocsigen server's libraries --sysconfdir DIR Create configuration files in this directory --mandir DIR Install man pages in this directory --docdir DIR Install documentation in this directory --logdir DIR Use this directory for Ocsigen's logs --datadir DIR The directory for data written by the server --staticpagesdir DIR Install default static pages in this directory --uploaddir DIR By default, files will be uploaded in this directory --commandpipe FILE The name of the named pipe used to command the server Defaults are: _EOF_ print_options >&2 exit 1 } check_eopt () { for x in $eoptions; do if [ "$x" = "$1" ]; then return 0 fi done echo "Unknown option: $1" >&2 exit 1 } check_wopt () { for x in $woptions; do if [ "$x" = "$1" ]; then return 0 fi done echo "Unknown option: $1" >&2 exit 1 } echo "\n\tWelcome to Ocsigen server, version $version.\n" >&2 while [ "$#" -gt 0 ]; do case "$1" in --enable-*) opt=`echo "$1" | sed -e 's/--enable-//' -e 's/-/_/g'` check_eopt "$opt" eval "enable_$opt=2" shift ;; --disable-*) opt=`echo "$1" | sed -e 's/--disable-//' -e 's/-/_/g'` check_eopt "$opt" eval "enable_$opt=-1" shift ;; --with-*) opt=`echo "$1" | sed -e 's/--with-//' -e 's/-/_/g'` check_wopt "$opt" eval "with_$opt=2" shift ;; --without-*) opt=`echo "$1" | sed -e 's/--without-//' -e 's/-/_/g'` check_wopt "$opt" eval "with_$opt=-1" shift ;; --root) root="$2" shift shift ;; --temproot) temproot="$2" shift shift ;; --prefix) prefix="$2" shift shift ;; --bindir) bindir="$2" shift shift ;; --logdir) logdir="$2" shift shift ;; --libdir) libdir="$2" shift shift ;; --mandir) mandir="$2" shift shift ;; --docdir) docdir="$2" shift shift ;; --staticpagesdir) staticpagesdir="$2" shift shift ;; --sysconfdir) sysconfdir="$2" shift shift ;; --uploaddir) uploaddir="$2" shift shift ;; --datadir) datadir="$2" shift shift ;; --name) name="$2" shift shift ;; --ocsigen-user) ocsigen_user="$2" shift shift ;; --ocsigen-group) ocsigen_group="$2" shift shift ;; --commandpipe) commandpipe="$2" shift shift ;; *) echo "Unknown option: $1" >&2 usage esac done case "$bindir" in "") bindir="$prefix/bin";; esac case "$logdir" in "") logdir="/var/log/\$(PROJECTNAME)";; esac case "$libdir" in "") libdir="\$(shell \${OCAMLFIND} printconf destdir)";; *) libdir=$root$libdir esac case "$mandir" in "") mandir="$prefix/share/man/man1";; esac case "$docdir" in "") docdir="$prefix/share/doc/\$(PROJECTNAME)";; esac case "$commandpipe" in "") commandpipe="/var/run/\$(PROJECTNAME)_command";; esac check_binary ocamlc "See: http://www.ocaml.org/" check_ocamlversion () { echo -n "Checking for OCaml version... " version=`ocamlc -version` echo $version n1=`echo $version | sed 's/^\([0-9][0-9]*\)\..*$/\1/'` n2=`echo $version | sed 's/^[0-9][0-9]*\.\([0-9][0-9]*\)\..*$/\1/'` # n3=`echo $version | sed 's/^[0-9][0-9]*\.[0-9][0-9]*\.\([0-9][0-9]*\)\..*$/\1/'` if [ $n1 -eq 3 ] && [ $n2 -lt 12 ]; then echo; echo "OCaml >= 3.12 is required. Aborting."; exit 1; fi } check_ocamlversion check_binary ocamlfind "See: http://projects.camlcity.org/projects/findlib.html" check_library react "See: http://erratique.ch/software/react" check_library ssl "See: http://sourceforge.net/projects/savonet/files/ocaml-ssl" check_library lwt "See: http://ocsigen.org/lwt" check_library lwt.unix "Missing support for 'unix' in lwt." check_library lwt_react "See: http://ocsigen.org/lwt" check_library lwt_ssl "See: http://ocsigen.org/lwt" check_library lwt_log "See: http://ocsigen.org/lwt" check_library netstring \ "See ocamlnet: http://projects.camlcity.org/projects/ocamlnet.html" check_library netstring-pcre \ "See ocamlnet: http://projects.camlcity.org/projects/ocamlnet.html" check_library netsys \ "See ocamlnet: http://projects.camlcity.org/projects/ocamlnet.html" check_library pcre "See: http://ocaml.info/home/ocaml_sources.html" check_library cryptokit "See: http://pauillac.inria.fr/~xleroy/software.html#cryptokit" check_library tyxml "See: http://ocsigen.org/tyxml/" check_library xml-light "See: https://github.com/ncannasse/xml-light" # Check PostgreSQL case "$with_pgsql" in 1) if test_library pgocaml; then with_pgsql=1; else with_pgsql=0; fi;; 2) check_library pgocaml "https://github.com/darioteixeira/pgocaml";; esac # Check Sqlite3 case "$with_sqlite" in 1) if test_library sqlite3; then with_sqlite=1; else with_sqlite=0; fi;; 2) check_library sqlite3 "See: http://ocaml.info/home/ocaml_sources.html";; esac # Check dbm if [ "$with_dbm" -gt 0 ]; then if test_library dbm; then echo -n elif [ "$with_dbm" -gt 1 ]; then fail_library dbm else with_dbm=0 fi fi # Check Camlzip if [ "$with_camlzip" -gt 0 ]; then if test_library camlzip; then zipname=camlzip elif test_library zip; then zipname=zip elif [ "$with_camlzip" -gt 1 ]; then fail_library camlzip "https://forge.ocamlcore.org/projects/camlzip/" else with_camlzip=0 fi fi # Check rlwrap or ledit if test_binary rlwrap; then rlwrap=rlwrap elif test_binary ledit; then rlwrap=ledit else rlwrap= fi ###################################################################### # Summary echo echo "Effective options:" print_options echo #Convert 0/1 values to YES/NO if [ $enable_debug -gt 0 ] ; then enable_debug="YES" else enable_debug="NO" fi if [ $enable_annot -gt 0 ] ; then enable_annot="YES" else enable_annot="NO" fi if [ $enable_profiling -gt 0 ] ; then enable_profiling="YES" else enable_profiling="NO" fi if [ $enable_natdynlink -gt 0 ] ; then enable_natdynlink="YES" else enable_natdynlink="NO" fi if [ $with_dbm -gt 0 ] ; then with_dbm="YES" else with_dbm="NO" fi if [ $with_sqlite -gt 0 ] ; then with_sqlite="YES" else with_sqlite="NO" fi if [ $with_pgsql -gt 0 ] ; then with_pgsql="YES" else with_pgsql="NO" fi if [ $with_camlzip -gt 0 ] ; then with_camlzip="YES" else with_camlzip="NO" fi ocamlinclude=`ocamlfind printconf stdlib` ###################################################################### # Write Makefile.conf echo "Writing Makefile.config" cat <<_EOF_ > $my_pwd/Makefile.config # The name of the server, the ocamlfind package, etc. PROJECTNAME := $name #### External binaries #### OCAMLFIND := ocamlfind OCAMLLEX := ocamllex OCAMLMKLIB:= ocamlmklib CHOWN := chown CHMOD := chmod INSTALL := install CC := gcc # optional RLWRAP := $rlwrap ### Options ### # User who will run Ocsigen server (not root) (eg, for debian, www-data) # (This user must exist on your system) OCSIGENUSER := $ocsigen_user # group who will run Ocsigen server (not root) (eg, for debian, www-data) # (This group must exist) OCSIGENGROUP := $ocsigen_group # Do you want deflatemod? YES/NO (requires camlzip) CAMLZIP:=$with_camlzip # The name of camlzip package: CAMLZIPNAME:=$zipname # Do you want ocsipersist with sqlite? YES/NO OCSIPERSISTSQLITE:=$with_sqlite # Do you want ocsipersist with pgsql? YES/NO OCSIPERSISTPGSQL:=$with_pgsql # Do you want ocsipersist with dbm? YES/NO OCSIPERSISTDBM:=$with_dbm # Do you want debugging information (-g) ? YES/NO DEBUG:=$enable_debug # Do you want annot files (-bin-annot) ? YES/NO ANNOT:=$enable_annot # Profiling (always put NO here - but if you want to debug ocsigen): PROFILING:=$enable_profiling ### Paths ### # Temporary root directory to install the package (usually always "" but for package makers) TEMPROOT := $temproot # Do you want to use dynamic linking for native code? YES/NO NATDYNLINK:=$enable_natdynlink # The directory for ocsigen server (binary): BINDIR := $root$bindir # The directory for ocsigen manpage: MANDIR := $root$mandir # Where to install the libraries LIBDIR := $libdir # ocsigen's logs: LOGDIR := $root$logdir # Config files: CONFIGDIR := $root$sysconfdir # Where to put static pages: STATICPAGESDIR := $root$staticpagesdir # Where to put dynamic data: DATADIR := $root$datadir # Default directory for file upload: UPLOADDIR := $root$uploaddir # Where to put Ocsigen documentation: DOCDIR := $root$docdir # The name of the named pipe used to command the server COMMANDPIPE := $root$commandpipe # The source directory SRC := $full_pwd include \$(SRC)/Makefile.options _EOF_ ###################################################################### # Finish echo echo echo "Please check Makefile.config." echo echo "You can now compile Ocsigen Server by invoking:" echo echo " make" echo " make doc" echo echo "You may want to test the server before installation:" echo echo " make run.local" echo " make run.opt.local" echo echo "Finally, if you want system-wide install, (become root if needed and) do:" echo echo " make install" echo " make install.doc" echo ocsigenserver-2.16.0/doc/000077500000000000000000000000001357715257700152465ustar00rootroot00000000000000ocsigenserver-2.16.0/doc/Makefile000066400000000000000000000017621357715257700167140ustar00rootroot00000000000000include ../Makefile.config include ../src/Makefile.filelist OCAMLDOC := ${OCAMLFIND} ocamldoc ODOC_WIKI := odoc_wiki.cma LIBS := -package lwt,lwt_log,lwt_ssl,tyxml,ssl,netstring,netstring-pcre,ipaddr,xml-light \ ${addprefix -I ../src/, baselib http server extensions } all: doc wikidoc doc: api-html/index.html api-html/index.html: indexdoc $(addprefix ../src/,$(DOC) $(PLUGINS_DOC)) mkdir -p api-html $(OCAMLDOC) ${LIBS} -d api-html -intro indexdoc -html $(addprefix ../src/,$(DOC) $(PLUGINS_DOC)) wikidoc: api-wiki/index.wiki api-wiki/index.wiki: indexdoc $(addprefix ../src/,$(DOC) $(PLUGINS_DOC)) mkdir -p api-wiki $(OCAMLDOC) ${LIBS} -d api-wiki -intro indexdoc -colorize-code \ -i $(shell ocamlfind query wikidoc) -g ${ODOC_WIKI} \ $(addprefix ../src/,$(DOC) $(PLUGINS_DOC)) install: ${INSTALL} -d -m 755 $(TEMPROOT)$(DOCDIR) $(INSTALL) -m 644 api-html/* $(TEMPROOT)$(DOCDIR) uninstall: -rm -Rf $(TEMPROOT)$(DOCDIR) clean: -rm -f api-html/* api-wiki/* -rm -f *~ \#* .\#* ocsigenserver-2.16.0/doc/indexdoc000066400000000000000000000007351357715257700167730ustar00rootroot00000000000000{1 Ocsigen server - API reference} {2 Persistent data, writing in the logs, configuration file extension, polymorphic tables} {!modules: Ocsigen_lib Ocsigen_messages Ocsigen_parseconfig Polytables Ocsigen_cache Ocsipersist Ocsigen_config } {2 Extending Ocsigen Server} {!modules: Ocsigen_extensions Ocsigen_local_files Ocsigen_headers Ocsigen_senders Ocsigen_http_frame Ocsigen_http_client Ocsigen_http_com Ocsigen_stream Ocsigen_comet Authbasic } {2 Indexes} {!indexlist} ocsigenserver-2.16.0/format.sh000077500000000000000000000003241357715257700163270ustar00rootroot00000000000000#!/bin/bash echo "# Removing tabs" find ./ -regex "^\.\(/[a-zA-Z0-9_-.]*\)*.ml[il]?" -exec sed -i 's/ //g' {} \; echo "# Indent files" find ./ -regex "^\.\(/[a-zA-Z0-9_-.]*\)*.ml[il]?" -exec ocp-indent -i {} \; ocsigenserver-2.16.0/local/000077500000000000000000000000001357715257700155735ustar00rootroot00000000000000ocsigenserver-2.16.0/local/var/000077500000000000000000000000001357715257700163635ustar00rootroot00000000000000ocsigenserver-2.16.0/local/var/www/000077500000000000000000000000001357715257700172075ustar00rootroot00000000000000ocsigenserver-2.16.0/local/var/www/index.html000066400000000000000000000000601357715257700212000ustar00rootroot00000000000000

It works !

ocsigenserver-2.16.0/local/var/www/ocsigenstuff/000077500000000000000000000000001357715257700217065ustar00rootroot00000000000000ocsigenserver-2.16.0/local/var/www/ocsigenstuff/AUTHORS000066400000000000000000000014741357715257700227640ustar00rootroot00000000000000Oxygen Icon Theme has been developed by The Oxygen Team. Art Directors: Nuno F. Pinheiro David Vignoni Naming Coordinator Jakob Petsovits Designers: David J. Miller David Vignoni Johann Ollivier Lapeyre Kenneth Wimer Nuno F. Pinheiro Riccardo Iaconelli David J. Miller Thanks to: Lee Olson: Contributed drawing used in application-x-bittorent icon. Marco Aurélio "Coré": Improved audio-input-microphone icon. Matthias Kretz: Contributed "audio-input-line" device icon. Mauricio Piacentini : game icons mashup Erlend Hamberg: "text-x-haskell" mimetype icon. ocsigenserver-2.16.0/local/var/www/ocsigenstuff/COPYING000066400000000000000000000227241357715257700227500ustar00rootroot00000000000000The Oxygen Icon Theme Copyright (C) 2007 Nuno Pinheiro Copyright (C) 2007 David Vignoni Copyright (C) 2007 David Miller Copyright (C) 2007 Johann Ollivier Lapeyre Copyright (C) 2007 Kenneth Wimer Copyright (C) 2007 Riccardo Iaconelli and others This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . Clarification: The GNU Lesser General Public License or LGPL is written for software libraries in the first place. We expressly want the LGPL to be valid for this artwork library too. KDE Oxygen theme icons is a special kind of software library, it is an artwork library, it's elements can be used in a Graphical User Interface, or GUI. Source code, for this library means: - where they exist, SVG; - otherwise, if applicable, the multi-layered formats xcf or psd, or otherwise png. The LGPL in some sections obliges you to make the files carry notices. With images this is in some cases impossible or hardly useful. With this library a notice is placed at a prominent place in the directory containing the elements. You may follow this practice. The exception in section 5 of the GNU Lesser General Public License covers the use of elements of this art library in a GUI. kde-artists [at] kde.org ----- GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. ocsigenserver-2.16.0/local/var/www/ocsigenstuff/back.png000066400000000000000000000013371357715257700233200ustar00rootroot00000000000000PNG  IHDRj sRGB pHYsϐtIME  \*5PLTE}}зɾ«Ϭβ𚚞 >,tRNS-::::::;;AABCCCDEpbKGDf,%IDATW[`з6,ljC$*?O. jlyQ'DLֵZ7*F-6uͺFS'm_kl6(J.i4Up>Mr `.Ǡ^smzV$IT ru@z ^I PBkVRy`u4 dV.Ki % OQ#ѷ46]9IENDB`ocsigenserver-2.16.0/local/var/www/ocsigenstuff/cdimage.png000066400000000000000000000020431357715257700240040ustar00rootroot00000000000000PNG  IHDRĴl;sRGBbKGD pHYs cytIME =6IDAT8˭k\U}=sfLgr/ZZ}A7AQ9'DYljE%ЇC ih;L&s?g0SmE\/Z\.A߂ hxx\zUtR"@Hc4[.vO9GeEZo΋Fk@JVK51H K Z::Gk#%R*6:pw29cZ+Xk89uXϧY[optM 'ڠBk1c.9Ze|Fyxah}qcmB!ƚQhR=`c K 9KlDm)x:xEdIv6 E^)3T5>gy1R<Ȳ,cFG !A ux]*%<ϑRl6T dyA##cH!͠BEchl6Z|0HSwy8PJ 44=["#2Μ>M"աgCgZRrKQlxֲoJt_gJ)RBզ{Akkz=66VmdSgݓed9snJUڵ/}| @c_O-"~M'ARVk (vyO>syE1$I8J0 H0\ŗ_]nڄw;7s)I Io0tssW>~گTn,ܢIp;˗/3Msc!Rrw~v{zNdՕƍ_e^:\Yvn޼>zӉ kȀE`U]lD!zIENDB`ocsigenserver-2.16.0/local/var/www/ocsigenstuff/deb.png000066400000000000000000000022001357715257700231400ustar00rootroot00000000000000PNG  IHDRĴl;sRGBbKGD pHYs cytIME 0-YIDAT8˭klU3QDTjՄ тEmTÈaL (D0"IE v)[+1䛜|yrwOE èTu=}aP7 T J!!`J!8 H)v${PTEQ*2c)4@ムU-T00qeBm=tYY85լ^^7>B4 b>"<8E>]]]=#(*.eY0qu&Y`(b)^ E(bqM|^l@X, mzK m\cp)>VzE 0-T:egʞIR52Bu3PTx(|zPPȉsY;igYnp|^^@*m5mN|WWӼv9t YLlU-3pu̘_!gD\<{|`P%.)$n# ssxiӦ8Cb*ѲbכWa{/-_Iutä$3`=t9;6Kw k8voa< Fmɥ˖=Oc7^hd q]rH|2s1iaY&ñc'܅?:,p Bm[d ::cMMC f.H&ͻI"4f2ttt$]IĿD+ⶱull6j,ۦĺX!$GC\J}jvݿ.[9Fy0 ekkYk]׿&;IENDB`ocsigenserver-2.16.0/local/var/www/ocsigenstuff/dvi.png000066400000000000000000000016351357715257700232030ustar00rootroot00000000000000PNG  IHDRĴl;sRGBbKGD pHYs cytIME +$["IDAT8˭k\E?3ĻUiw)>5iI/DJJQ ">ڒ E̺j^XCBeInsma{3~QAE@._(8+C=`j$DqR 5J'kA=ǎq;j5:kkkxD'yȑ>gscCVVVDDwsX|zܺySsO{f~BAt:{ĉ\zc"E<}JARK.AX l7?VqZR0H_P:;n0#"+WAy)r"RH{ iI3Oy2nZួ .xxD,CE\)>P#RRS߷_Jo/A{abkkk<63^ Aq|GgK11Οq£VzC>K33s=t&jHdIq1&ny9.wN^+(N?a ay9~ҥY2lDDΞ9#RY\| CY]]ɍCa@ 1ޥVa%s4yj199˗gW^Mv[wT(o澰a},L2Ϻ+MIENDB`ocsigenserver-2.16.0/local/var/www/ocsigenstuff/folder_open.png000066400000000000000000000010771357715257700247150ustar00rootroot00000000000000PNG  IHDRĴl;sBIT|d pHYsvv}ՂtEXtSoftwarewww.inkscape.org<IDATxՓ=kA<%'I; 6~!e>mA K"hr$)~ O 6hK Xڦt{n޾63>Ж[MqbIƇ]#e|q:k>6ZȱӃ4Zc;Ib>$_L} 5x~fx3}$[8<Ռ6 Z%ذgOة8Ҷ(2SEr3.X,8u Y"HUq.O)/HCMu[VsuH>_m ͅI .E7@KКe Sl d$2`.ИIS_Gof%f [S1YʼnN"#"JFt1ض |}]mM M6QF! QC>*qK)-cC\KJ#^ ~Q)5j Y( +vmD)<laU[O<&/?' gr\÷Ɖ ^l۾H)/?BήgKě`mKaȈedJ4|&)%2 8qT0رַ`R'u9L؊H-l }J3>|$I@5[gv.mc;ITlBvH*f4RI|ϣR.s>S*N, &v7ٹaBcj% 4T+{u"4@)Fml>;^mU|ٳA| sȶw};PJSBJUE2 Nu썢Rŭ--HJj7o0ΗKF/,T#S FIENDB`ocsigenserver-2.16.0/local/var/www/ocsigenstuff/image.png000066400000000000000000000016401357715257700234770ustar00rootroot00000000000000PNG  IHDRĴl;sRGBbKGD pHYs cytIME 97k IDAT8OhU1m&fZNl3T0QD+`,Vh;\BDu[] PQvT`8IRb4fu1X;Z+<{;5RT*d-(U1;uOevufX$X~} Ldo?N`T0j \W|rhO[!撚u9:2I(eū7)/2qT1.svN$ctdZsvfjyz-~/[E&/m$*#{5sOrjg_ܹK?z$5Ĩ**T]qϰ;X 7Ǥ ^}C@EUDATAl+-$J>y.fsWf =AQT,EŢ6" u(<֞;ї;&n}9೚ϱqMu/ \ch3G.--BP̳|Ôުm \¦"UqǩtzbږmC@E(]釶kE#QBDAH^CCDx:wh; XHi*FA!Zo&];<8}0_=H. c/~'EUQcP4u_~dǀWRy̶,APPT-tǏTGF\"^ PQiUhaÀ=|ѧGF<|WX?x4Z?W(f1upX\~9Z ~%IENDB`ocsigenserver-2.16.0/local/var/www/ocsigenstuff/ocsigen5.png000066400000000000000000001220041357715257700241270ustar00rootroot00000000000000PNG  IHDRX[ pHYs\F\FCAtIME 2):,tEXtCommentCreated with The GIMPd%n IDATx}yչYYWQ$w jܢ&5FQH47&13\ KLATT@afﭶ}Ꞟaf7LOSSϻkV8*V!  J9c2!w|BٶݲL"/y ]E֏x \8ΧJL[iJb#AU#ðRz>.2e%\R87|3缲kTb|~ pq]tqB2n L e;@ƺB./A1.)5-!QPST*RUiXS1POmnw5y,F(qʔ)k~z޼yb@9r00âIS:I$i;I IBYʽ,\xqŚ?wy(G]]N0Cܷd:eӁӾy䥡+~ر_|q;@*V݌PX 6rJuI!B U ޗW BJ|`3̊A80J}eA.\1.ho;SM*VAYi2qA?B/)uK)Br̞!!)ZCg=5;C?Q"qvuOP% V {˘>*+H*6tZ12&ܦܦN`M e28z9۔Ȧ@0y#F#Pf(C,u~WdW\qEG{'vUS*V!BpC9&!qLxz`aNX 0NQ ;5͜tɅd<]9F+@_Zx˲yVR 1:H\wM-0G{PƁ@ HcL 3b)͵#UlAQzg&}R>2YG(\Lg熺p(AG_JL&}h/h5k*J*6nLe,Z>.b[)!ŵ9bG KbNa%ݡW`_)VJ2gC%ڤ~p@F?ͥbD(iT{Rd ƹ䂄de38HWV//۞/*ʹYD́zR@?yЋ\*I:2,FG sʉ#=x@!׿^zR 2D( :2.h̲ ))uq,i2nmG`a`6e5 -A9Yk;*A43tA iUH@JjbU5h,Dxgk_ZG[reSSSegXtrd Y>8E%/ӅCI:B-f =6cˉ}$9De{CPTJ9#LCݴOj PPuMu5PPp@S1BSq}Mtx@8}aÆk4K/Te*VA Ӝڄ[۔ٔKn'e k3 h*VQ")*vȏ@ngD| N/ NCJ֨FHXK,JbcMUP*/PܦK5i+ F~brFYjUeǩX. vI3 Loz=:EAiiݒϜL.fuYrg|NI~<1ޏrΣa Bȹ@` aF}$RHY[w&:c(:0xe>l:@FDk=O# $4mj4|Г\EPĴܯ[P5yDAaB۔AAGy¯wivänٴa&9I#A%$!qӦE5+ a3C=}'bt0;4Ș-&ܴiSˢL+S5!+]<#aw7xT}EУ‰<6~u\Bt:QHkJyG=MҞٜє'͢ LZ6U AG܃!>;E w\ĉ Rf MɑA;]݀7}H7-(Ym)pW~t0w g")lݛ[OE 1@x[r\24>'SN-hׯl=DLq_c̲iSÅ@ӦY*}Mu8.h4nׄ1,94|"D =(ws_!U!@t{l I3*H@3,-:Dp}QT Nad2MԲpAfZtGMe1U fm3)H@UR6lJ9_ԃP H||&]1+!3x0\w(D+LQKCjjjZZZ2cl۶m \nݺW_}/`M8N:uj[U( YmJdM 65,jԉNLverdtPSq8QU nkNh02 eXx{Æ ڍ((̋ ھ dNЋzNQ$:#!\UQ2mmڱj]sK+<4X]u4cT3AO8Z3i=j  5jTGkooG{]{d/\2WwzʢxODʹ%&փܥ"GG**Nv"mq.4 J+1]R yW" mq}OC!Ç/hD% aÆݞ<ƍ+3{qq=6\` "^,Aud V0 D(X2m%V@Sʛ% !r $-r`$+]QVdo&%B(I*((DBpANQe"9A>'ST:DT04,mڛ|Ѯ̴缺*9\rզv^3 /F&-91Ckjjʹ@M1 }/+3;`\ a$M* Bs讚eHMD kЫAUUe=tAm> sPfrh Sf|^ N+c mەiO@ PVfPN($\&xO7n:]&lt%z,ݧjҜ (U!B{ Wf'e֔I!H1*Ӟ`_O22cNv weEutR˃@ʩ  u慜~ӢPP j{M'`>laoE*+pHa(Ίu׎9X,VJnT4=*3w(z((`$ 6,jXRPX傜 TU jBaI5L[iݎpPBPAw2W 55<0LsmdvU0ƕű'XUUR+{J 2 {(hXT7IڤiMD0mjfnOM3-E570FFGjBV ap L[Mma#5MA! ٌ?i 0B Fܚ2*(8TPQn5M,=n:1ӦM*sU^":`y'Gԉ f#ceEe32P\(n`wAcW͜6zĺP@VWH =Ҟl P0[?r!R6Qʓi--V 0| +)o-N0,=Ģ3F+sw((մ= ԰DALQђegNQBUq84*Fv.`MU4cTa@7@.mSٍ&/>*@8pj76z7|{cƌꪫ,XPy0*/ x=%d(06`I|Lڅ@ #EdxN^&Ȁ̑؄moJZ6 b&%cNeS˦~'oe@X<cTl[(Zh 7'HI&M6-;"(en]ƸJ I|((KF\:A_Lȑ)I.Tp9r*Uh>LE NQ&(w(n>#uÍ 梠&*:jAӢ@X+DX#!@jQ,ڤ'haemX=wѼl. % m a:`d@I#! Ah$LVJW*80F ƹEFCZ4 ei֣t:]FQY|X9R0'52IipI<)Q7X4`$bƹەER0 FqN9C$d ( .dcT[ Kvm̘1%X  z1^P7<)Qvc`N6zMŚ_z!Pf41)HRNCaB 3΁1n0*L[  ,cAq+VrAv*30bMM7ϋ1,fԔP7; 4T0CMUB%TCA5PTqӢBI>A!C"*ڑ.mq;5@h2@)mmmmkk3 Bx' JiKK˶mZZZi1iUUUcƌ5jTyᢶnݺ^{ @mq˲@ CHb1v R#ʜP|AAf*9U~d1 V3 JP VA(顖= CZ(Z!e%Ri9+p",0Li8:3j%B(~$3m1 h8P#a-AUS&\7 '̅  ؄BqHjΝ;_y7|sʕyNhtԩG}9s9+ڒN_{_vy^uUv'ɷ~7Xrg}}ΕºI&p3gΜ={ɓ{(@y1;١uڄ(% lj : jT떖Mvl4ݾZE8׌>ź݈M/EA5U hX2p.eaqm v}}'9ga2㚪`G0[o⍶/Knd$~ a56f5cFJ E B8ZᚊCA*Ţ0 .N0 "B4S}ǿe;Sm_W_|Eo4#7~w,'o;+[}Z[[}_/b<^_ @/Ozdދ:u^o{@?y11';)pܡR> Pb^*Scjڼm/W#c+Pl}1$EP`84J(JP# M-6|5o3y|{ T0|#1:s`'4%X5qUfTaJ8ZuU&P `ش=iͶOYi "U媓 G!Ci*nHC'N_+I^}kvժU={wܱhѢj%Kna͛7駟ލ'HZ7x-[&݉96ЀstҟWPH-ZTDg_(p>:AF}(hڎX?tݡaDw IDATI0|(wmXpe=*5u.DJQuUд)BHA0) iѰVQT_lm;;}ܴyzrF7ۦ֩#SyZ@)jc`mU TڒVK7;f*mMB(s!8s9@6l$C5NwUkaÆioݺuѢEK,bsw~g)ꯩϥu͛7oܸqݺuk֬ٸqc^f͚/*ל}_~_y晊 cR۶ۛb1RC݈CzyW>|Rëpbd^e;e 2!lز->hRWu󻓏],  2JDf$%7ܲ6޶sVC}[o۷w釘޾e! (9zy2 6 !EYT'ɓC-ryIq C7nhfY:ꨣ]Ow^Jov衇<[nܹR+oA;6Bvرv>@U[[opg⋃z?W^yea#Guuua|ׯ/:lSSӷH$E˲L.c96/ۭ u 2/YDL\"ne3B31iH&򟛧}z4KԵ/;yq8dƏ׼KqX v2=n,cR.ci2&ґ Յ$@E!j}5k5ٳl_+-irHLrccI'e˖؂ ;s/>3tҟ@X^q~6!mvr)/>#9!ڵkӟ.Y3-N؆PY׃@BIXniQ7SN(5Edвyy^_h06fX 2jVR|?z-I}w"!ʹ(3g((wqؖ߸Q{[JÿFmg-ʄ"Ӧi(1!*X$aT| C{`wJ-ZQ‘#Gq[ZZZZZ \qe]p]iӞx≽޻N;N{w[nwFg?O~R`17ߜҀN:o۬Y^6, P7S:PAG8^Pӏ47(ȹh'/G`^3/pDY5!MlZ :uP}q0BJ=; L~=-Wq-u[\qzyqLeӴ0UABaҔALڔ1ƙȨxZu݄iCW(:u$?gy{G\ys',{ݕF3Cy5bĈ~ԨQsQGrΝ;w #7o^CCC{{{{{{[[ۮ] l-][o-pe]x\q;v{ 裲 uiI.Y{^*i ׿;W:Ι3gΜ9?_WdEz'MiZ_ϰeYwqGW\" J9rYgn_ky"oll+ $U544yEǹ曗,YRJȰ+k܁@/T03i٭$d]7M&9s ypIG^3n&~o}^|գ,]T 0v9b *ѐTʗGҶӬ1CQ3+zӎ͉%v~)m^h0 E!5zD'\,cрaQʸ`"2 ' ^P&]m$GA6p͚5eWqFĐ瞅 E??s=w}a͛7_@rgvwzPS"A+Auݖv=Vp i0`I_Hٰi(: P\pB_@ 0&5~Q=(́3+]wmҤIӧOOG?*yw_W9 _WXٜf2¼ u̘1]!@O?tҥ9G;sڢYÆ{s2bv#rhnk$11(i^ -IŕJAݺ3ӝ)"ɪAι, [0RF)SB!D@gNB @STY!B袋.*mڴK.)wUWn3a„a4 &m۶uwڱcz3v*Fv۶c>f}V/zLSS!u@9z.:AEA cyRA@Jzޏ>z=ᄏ0~̜9E"I\Nh:˲N;.+=y'v"8(lÆ E)/_5%rl½/#Ʀ4yC usjC檒z%#єn+Wikm@hS=} Dy"MQAuB)M"rҡLdќGa [[]ME-1MAaT@Bʧ+gCH7|/zuוЃ!SLU\h5559O>}q7s;,ZźkMMMřGANhjU&cW ( >J\P_Ʉc^qNu`DP`iǶUE PJ (Y4B, !,%R0Ur,_twEj Lw},x@J"Ӧ.^LNދmR@ D!һt}b B<gܸq{n=\Ѻ'|ǡP]]] +VHWxܸq3f̘9sGqAE" h3-X?32@TؔQʥWD޲ %JeYCDP$+hao[ZIxv>Y,}>21m= j(z˳m m7CĮ|ΐb<JP#]`1;˖-87|sۖfOQΘ1ֻc7o޼y BiӦ}_=9~+eq$|s:E-||eb9Mv~ 4bDeqn.n;{yz֢KȮ iݮLB8s%BPU1d@e]_js/-qtZ@GJϳt`0tcS١>wh& h* G߼NR@N;HYj#/O/eτb  L FM,29L]UEA)" kJ]u6R_jS hiV(BP+GJ'w* u{{s/Zw:Uk\Jxuu=\$N2a 99显^:ꨣ֮][6~3f=~X* 0 ܱi gͿ"4I$;i& 2M%A$РsJ(ȗψ3Plc<I#*qp:_%!\v\P7 5X8T!UUA$`Tk=mYTU??ʺ_B@j*t$ "ɴuG|-:BH]f*x.\pP Dꫯ= W.Oc?9suֹs{#F _gyǼ<9s2חɘdY܋L;;hselK}Px`a ka-\mI&"ybBdJ֠XQ$j55n4 @kíwq!DJ҆8PUp.vH¦^ӨHXT0G"av [ouܹ@E+lgqF_8)BB3^z/_ti{u~ ,XdI:Yg=}+W> z{Fm]eilܶw gi' JQ{yY^fɟFԠE@J`+,lmOWW4SƄ( A@kG Qb2:XDݸ=IJBQp(vZ6l ثrAu.|l14yD  /*{!AyA&L]|b)Z5teՏ>矿xO?9 (fjƍ_w9KXU4Ƅdqommttt銘ra߾>" dL*țZf`\Gب=e\D!,T0JvbhW&8 "fMciz7a& Ȁ|p5 ^v^昄%K444yᢲdr'Y/~s=f%*'gLqC瞢OooYG?쳦I,,Jt|g:?V4~xZ"m'n:AMİa1@%Áﯫ tcL [lٴ#OhQʚRiD(@  k656% 염V!EeB9Y_>ԩS,X03,ܤMw]D>KX,imf(ʼy͛g믿^/^|E!ĞYM d .{xG?ZTYpu㌉{M~#FJokp۷v}79g_vo-Di#en]`Vu#_w` DJH!1SwMX)As )"!D(K S誔\pQ=5zq/^|bGJ 'ttt s(5k֬YeZ7x7-[gק_Au͵׍5(9Ŷs./]]~zks):_~y(}3z?{N:ҥ(۷oCѡtU (>w}^gҔC}o}Rny8PnQb:BD׫V\Q/4g/eҭ+3@ BEE@*X l*4e ;[65B\y 7Ξ=_|o~k;Wbm)CM;蠃,X /lܸ?ApL&x] e9ck8nnYhǝz7ȴAS:I^,Щ lf9 G1Tk0  '`MQ04FS0 hJ8u$MB 'A$\M7Cm^xacc… wc_ .jw?R.+1駟njj>|_( IDATdE}zرcy}Ee^ӫN :rqc/~-j' RɄN1U "꺺a0z”EkFP)]r2^PJ z lZ6]Q{_t1hjMmFqinj6i6me!QnA>WWA?+]m"̴aQY6O0eФ(&R:h$]uP Yv*LL7y#T^i/Áyf.D~[m[X; ׄ#!*(3>*Vq!֔ظ-TEv7 &E k*UCkKP!>c=~){i+V8O:5krzjՆ1>SK9nB ܔiDO8\A_?dWSuW]#f=.%vR'I$u; $mҴI &5-*sa\cL"c Z "֌9[75{#MѰvaQWƪ#PʹiU4!-0BD$تH@Qp(N]]] Zꪐ뻤M)}Lr-0eYO>' qʋ&uļDJ d ~I iꆃI 65΂nGIx"+-.U}ʿm=\R TGs(0hZֱum=oDK6BPSa涒0FH`ȪL?2 kRsg:뢋.:c{4w}˕4(_msvava+V(aW]vل >ax<^gyǿΛxEWEܿ~y]_퉔-~N<ˎQ ^|@uC1FXYrr9=82.:'Z0ž`Z[fL!do#ƸnBp*R,dM ´YʤMN; Tò-24]yGyN8cg̘VUU]>iرcڵ+VXl;K!;?uQQ xsNINtk_ҥKO>!V'Mt ' ,E;&/~sR`\!sR$>e26j$qܵkgW(Xo=?لrƼRLх|BhΙ ?|v2zS7-{-8ռ[C~RwOæ,ԶoބkOTX@+_F諓vNr!#! L[5F̓%Dڴ-ܮuJۜ$*d oK.p`pԨQƍ1bDmmm$QsNu0!E"n `gqG%io%SFaÆw^]@6n(VJ#e4s:Dž/3:h9I~+pn31ņ)JmBlF %661S˜ͨqAha5TmS7lwyL( ҭuܡʜnT_.{j _c5u!ݴM҅Yk*`0B0F2$ZS-sΘ >*h*qaYT)>iӦM6(ӟ7n)Gu>϶}-<裋/>3 :cfRzE1ϟ߭AoO~%;UJ೭>]??maos|^QnŇ_|X)գ> GEճDtVyg/LƇ8\pWxvV M\k݋[VȼS3 Hd5fg4l qòBmqO&B\X6-U+2.dt/Ţp`X4Tq0O[>{zmvM7kΟ?ԨQ͛={0vت*!D2lll\zo /lڴIQ__]w>gnm]tg}x9!K.Yn]WOSA0-[}˺u뤳(9/?oZRy׶~^ӎm: ׌k%սh{-zQӧJ޾uqFEQ[-z?G3*ٜm!,{X"-q#utL0g&U@ɟ=Ɠfmu؍;xm i*m hJ$7!ojk%o|7pôi?\z_nkǎ=C\ve}B⌱og뮻; 6\~导Jߪj@<JدZի'|/ӓ UX.֎Uh\p_+lo!гq~Hh߲["F{Ӻ֕. Gpz0ŷ"%2;R&vmtLeԈRΘ{!9ƅ!Xw`ٴ#TD%97,ڑ g<Pׅ"Au$tQ&Lws%;veY7x≲׭[7f̘;t:]]]ݕ>gϞ~B!)eG)moo_lҥK{챮p=Cַ l۶l+H$];w[w}w_຃>*#!e%']/Cr=ʉ[GH -ߝ½|ľf3>Ӿue]Yi6uFtb$͟\4ӥW Ռkц}@X a%X8ɦ"P#F?o(BNhB#!@Jzr9)aBXl!k(eM9Ma7Fƍ%LpKZbG#1S7w /<_oLύY 2GW|~Q)^ӌܽk+θ;X5 !_εE"=ڔo羥umr6rNδ9?D5Ct-: h PDʲ 5JY4P0BB#T*UF)-Q!!o|{1q\{ʴ@"R,,rȎ[%N'$7iNn'qȲHV (`It`Sgwvvwv $H8?339v\lSǹ`BmlV*/H*j!J*XMCEHҫ ӗҍ3Ԯnoũӥ<ٹK_qrlvh_u6pBR%%BMDY&5u bYjJZxDoNZRʉi{:k3&qs:cij )2)dx<~=s=O<֭[mۖakmmlr_]>!t=-o>|(x__ߧ>___ᆳ[zw;?oB^c< *<&sVsw) !c 2F )%nzg䧇/=|xbbb&̇a-Zz7nܸyt7o۷o9$ɻ{0>>c=O?ge1xwΰYC˽Y4R=쾪Mś.HAȬ='cLQ&0E KgR^bonqvdG1501 c81)Ar)<])`\99W;>H#Tp<! Ԓ`Bht2?>w=N)vQPhi !1s5}B'<Q<1B.D# ^zѣG?~ӧOMNNNOOgB.cLnf4TmѢE]]]K,YtiOOO2Yq4 gΜym۶o߾cǎ iUwhf*\lo뮻rCCCvڳg?~̙l6G]x{{{ooʕ+7mt77.v>a0:bgZ/P'hUUZ+mP Yc0T{K)d*Qk5)$P-B4SCͭR R&={9W BZ;;:dw9Rr0&:5b2"ͽe?SM2n9.S_ RJ"|!$BRl;2k+8<ԩe=M6c O99ͻFD*$f\D--BΌN#Lg2o}0ސMJ|ER(<e! U ~v0BhՖ,ԉ^ʊ ?հT)wy?wY D\\J6J)0R 0ӧ^33U\[޸^@qyZj1FcB."|1uZbGs*"LD%ͫ6!<Rع{Qϴ)ʸ[15`:XJd<[ c^؃J'cK௲B絨t"eSAG_y V2+ (@徘8 _O,O=K6W56үi#Mۤӧw{s xNfᖾG(bj*uiD`e --DT煶t4%t![=D^U owos, 5< B$BcQl4bjK jT6͈ԯRJO `e L U18=&B#E_H1&3v*a!\HP!=0. ̈́5`{焽b<(j[=Yv{g4 dx^ ?)J`/?Bahֲ~0!~=Ҽd葧lCpx(84`Q=-Ri#(gYlX^v!8bjy˻˛":l&E<\W#K+ɯ:g?_Q qձNjUkwn Fm1F BqN.7 Qb$bj񨞊1Ks.bB2p#ĥ@YшQpBʉ|K*)ern;ʗJ$q{ \@ybZ,kWv| U} Ka{AL о)Jɨ*WfkOm[|U}(\@?܈" OЕ8 *ʅc-5=.TvX`B0cT\xstߒq1:Xe#Υlu\v):9 \ެ0/H *tRICRRWꢐQ'Qy> Q!d5UydX()̵z.W }f$e%i=khmk_d]t(u"B1(Ɣb]#)F1`1B0@"V j !e6fJC g0Lt`O|RL-J<"e ZQ RR ~>s *a/䫙:Zo׈u̪9jÞFLt. ȕr瀺]V<62yTO!S:̯d@ &X|S@.seFBVKhj Rg07{ `n73c͎¤} 70B`x"Yą\(]%swX65p*DA39 D @x99ѮUyFYaHQ dFHJ&o$f!$>Cx##!DŽq+Ŗ&:FP킯h.,;]p%M|}ڧOm<_p$!Ul/7l0w`'Nt E#F B1!&_" RUS x.+q1R`b\R .~zY ctvӋZz2n&b2mkfrN6BN,\;!;z8s e⿊?]}5W+ )Y<(xʗB>,)+6`@Q:O[Wqg~0W됹[8|zs/~k[yy ?N }Ռw,9Wk\CH"j0!!L )RNK! .&l.)ũŞ0FUJ)B~<)0Ĉ, `Qy}pQBhklZՑͻ;Led5Nsgm$a9z%<ͯNfrpOS.H&"%'+ԸpQmo,~ YMdij.ԫ *]*i|UXIJѾ*䯾 x 0'?7SNn3>7U#ںY+-CFtecJX}!4s΅q %#ө:ΕYt`E?r.$VD *1B:.]!!DR¹@!.3q!⬈`4#Zz˄ll&59J : ;hv {Z*)DRZ.OJr%@`XqH5zig*j3":ph'T65lL. )ޟ{?/}{f Wub +ճ3bW0 ihq%ЉQS'FT=.$cq$Xq1Y=MNm++b$bVHl*wP GXD7 ĸ۞QR'm{7}+MF,u9&blT,LjT8 6+8^a=+qkI?bjy0/8lڮ/SDz@0B&cFSjNQll*LPoaGƳsK"G#Q1.rTh14kDPJ@1<% Tp+5+yΘl r39 V"ڕ̂SY礨f)p 񰣪.] 8r 0!3rOP3k]m5$0󜌓-Ľ3 >%~}+:5`ta#ЉehDKDA\Om` $cJ(lt^^i3E$RE/!e4#͑֔k$W(ƞ'rAPvM FHXH鸜ٜ+/=5A鬃 B@ q=Tle7˺ob[cx7U-uR7WU^-xҩ: xª2ëL)`fS;|U}7r+_fC;3ujԩs8*Lof% ш8LM!JӠ1KODdLt9.5E #%FyїdNYS K- ɜ-ezWSQP xhWB%ϫ[j$JhJ_v=ufze?|ֳCIbњ75^O(QF4;S?BGb]æN}iRLp1TmŊ l#ݒڮ`ޤ!.æF29vT#lމXhVy!BETQ Ŕ`f`*]sx<U3`M#R[W&>fFԩ%%2J`+SI` [mfႳr Djz2$TUPhh'+ CUbR-J1b\NelTb]bTPr!;.S٧BJ;] :R@ظ63Jb .xbuJ0+ыXK / ؂ 6(&TL텬kRBF| }K2[ 0ȺTRuKV^ɫLyN27[؛5]ovkjX+ 0(cBb])ENqy(RB *r+xys\1p|=CsKȋPiX87%"SA#b Rfrrb11qP11+,A5n k 2%Ј3n>FCA{ܟ !k$bi\Ms;1(B' |XJq0@a!zlpr;YW۔u&> +jk\T*]Ƴ v΃J-?KYWFBQ˚cݺ u*)UW˹Q {.7[)Wu1,_@J:5y\݂\UcK!Ez-vYC:%FdZ>E4`LMQC-D@Kq@̓\ح"ٜݢh\2Y%-&| (ټV,bT^͒/-_5%br:S=0+acw3);WJYd5b{ -22PDQRƢ*>k(58'CY]Zz!D,}D(!\ͥhxP}ky^x0;/؛G[+q@\q]ËBxp<8cǕ#,7[(VKE,^I)/hDlvzYp, h9Q%@vsyWI"fz!AUaIBS2-*e\#ts%Ƙ`tiTb,B.  *8%$9GAG s5 T%* r UI *T~IUMVÙѱUX>P 5PWe-bhW|^Պ$4 fY[7Ps-,$)YQM e}$Q~_5B!q1E (hz6_%s1BAMCp(cq/ä2zghVgh#{VO,TPK +LeBie:~dI}kŪ\ AsmdSKVaA` .mUH-k xu y { WGˏPҌ @Ƴt[ݢ\|票|˔(F#S X{<]AE[06,_Tv8(hbaJiPCNuP !DŽj<)BJTSdܰ #5rƅZ& RPPM.Sm#5J 艨4C#|b @V5wCaBHBePˠ`slOpW!:YS)S] XC` JZѽTʡ! `u\5qPǶ9pB4̊}h3l=To-y|:#$R"5˲@Z.P(.~ JQSעi#˂ 8GPiBI ]# 4hD\Ʌ`󸬨AņA=Og9д6-A.Bhİ]OE(XYZ2f$e`{\ȉ=ᒀO,B$dsqC#b!;+eBЭ,ܴ:ECmu|5} cX?C+ qXV%@M5ӕfQ.-h0B(0|TΟ"*+0F*[ N"+R3xD3uL %H9 r.\(R&}rਔ2w1F)@ 0_)#)L&B%c$KRi4hQ-bP]..FhORMSBT%P 2% UjF5ZS}糊eY\jkuQLn d;d%)薍9@!\ XݬۅBKm.`˜X(EPY)*A)ʣz !9F ^cVRrE#o{*%#=ӠQSKDT\!zĥt=zܣ*:RJBtֱ]NU Ah8`S@1kD/ٙz*ec151p\VXBSV:-THF1&}bK@*w<ԔZx:VR)\g ePCHl& ҀY '@rFnm3)8rYs Wϝԝ?]ZK5[lz {Rf.j)Փ1pRraͦA^}% c\ܢ O>cA#*V.ORs?,:qbHT$H%OF-!G&9R-&#ƸtEu`39'fR);o{N1A ?oz |VإUJ@ǥA) `{N9 k4+rΦ.G~Nc^Q0^qu xɘ{;M #AW/9[ћ%?Ù8<}ڎ-m?{-S?x䢞ˢoْܳBN?uЉ)7NL;/)U*n;Ww')[\wwoXm{ټ!w]sՊֶRQlupg#JPC'F "I0"~Ch @˔ Hi1pe.hh.㱈ќQ25JD%M_(  Qq ySU.g{eot$ƅ/HI؄*F9w7拵]OmεM8t }s!D.0] Y玁{cjr(ecySwݰ#Nѡc# W\s9;/ÿ}H^n L0Vn;TP~sE{nz7PC aѱ_9u]gs􅗙%bp=/?^SӅMUR ʬ.BKx@yL0.XU%U8./HȼdL֔Ք04J Mgv)A&uRJPY751{Zc`;66 wpn_h{SɳG'tɘޜ4o |iY.R_=5?z|dP4lN*6Q5}=q}\;~&SotN%,ƅ"elg %5}Mo?>gשȁDӔ&Y!mmQ̹s2+9K" ~sM#˒q2h"7YmMlӣ?y!X2@ .Sabl޳zqxNL@ tVRG8i}&hTF=9V~12ar3Үę|p51m?go1s:#wzj-WwuDΌW6ey-$j<}Mg ɸE)V!u:AWk|LR\A V[(KgyNM! \EVșB1Y.`ɊI/*U4nk2iLHˠ:%ccϓ+5qd;̱FO,ǝ: YU?>A,932䬠q59q@<B,8r/òɃ':g֛;[͟-a\׶SM\ݽayS?zhq7Yw9|rNzxټ{r8ۮ޴{n9,oܳne:˽T IDATzc/6,jscfY6t޾yg`b=w56ruדj|k\͸O@$Ԏ!hMYyigk?3vnt!Լ_&TSb_WN͖DT; (hn_}ck7jO¯X,ye2v/iM'fxTQ,|amэ+y|çҧG!Wܰ~ȩx~I_'n[vz4ϖzh Rq}N?s4W~/~6}b+Zr!E3c^>5]zS1@x%﹑C mJ[Lo_yfw;jZ[UKǯm7R:}S݉OW^ S͑_yw6%/=w@dQyϚgwy}pݷMS?uKvBG߾Cwx[Ɵ o^d"q!v:s-W-m>|r?=81mǣWw}{ A׭_95dܴ'>CO ϔI>8'|;n?56UbĤ[o{>w|ph] N 6I ;}nיG'nodavd|گ9aNg/`wp⽷<=4;rjzh1u:ݰ~4׬jW_ڕp\~j$\HŌmOrjޑ׫xnYlٶQQRvƇUf*n|CQ03Z_}c==ý<]׬n_vtz>zhM-ᄆ-{;=ޱOo>z:wp$ՂJY4+CW&5܉۳]VbE> OL?gvyecJӚ2?mػVwD;玁ٯ"y $kҦG?'OZovMX(-_|`CC#ٱB7sF'''f\}igw*Xy`Nر?rT0a\{3&݉UOJC=w c)A%˜<(1O^ĭ!A^Xs[o\P1is_>8Tw =O־CLk{0BɆ/9Qni`htӆΩsοa ѧ?鿶ʖJNd9aԪ y^:c\sl6 "3*I…,vr.N._nҸ;?p讃ׯ[-}869q6wZǝ{x(&)13jiɿ)AȚٞ&B-}~=dog|gSu-K)n΍zR sdnsPKk5J^=rtXDNN}ra ~d֫翼vJ7ᾮĻ,D>Oe.uM 3jiVp踬E0(՛O_BE`{Kw^vu7~ښ/쩷ɸnmTA꾦4Um\66U{2jI}{cSׯxBH?}ZkJTG=ۇjݲt %. 8t4[ŇNL{N ]4upbkdg.d&nX)aO-X* $)a4'o?z(u}(6[ݴ~ѷ=lqzCOs|xalҹ^tlْ{ XD_׶/bUoFRe-~Boz}pp#,B )F`,Ƀǧ^z\?в`ӿJYwUo4Q| KY6쟸" uamaaQ;/ÃkNp6+{S'fY1GQЇ޺7Yiqx@}#5#SM+2yhnez07Ք0j:-kq\u\|E:y+{~N6l{8?&*Io]SvUz |yKuuUMG | ;q_@2}׿8 GO42o~c Ѷ, [}VkB~{~7n<5mdlK l)ޔ뉧wBVK uFڛ#CiBpgKdEoc+Bw,wۇԗnd'_SXD[|M}_[KZ˚i밡TM#F0f^v@MCs=/]mO DO !"!CW6ٯ_T*C*Om(Tc%UY$k*Aɼ%\q#[LRFl;p|6)+(bjB/_yv?p|) (7 .\}1!] )7SL<012tզ2дe/F7h=tbqЫZںe-;b̰cN"3kS'6zp !WI/VyOG ]H".]:[KſC'f}gk9R\5-Is㊶?.p|ҏnX޺W,ل-ڱsi4hK*kIE,SOŭKZS֎Nn4(buZr6}x\i=b #=J'>3D-ܿ$k9j/xg#]!89<ӌkW>9]ۂcd2߿8wmԪ8v&Ӌ1ySgVmMY=KeЁ$wjE ?7'޹|Œ3;O^ڔ{꘥Jgy:\m\=IˠmM֯tʫW/NF--b_}ߺ>4W`3K'66`{*u˦.Yڇ^f3=qHōO!kg+*s˦.5I7Xw'~m+Q4;RwW;:2hB ?a^wl^7?UK?θ8w|]T׈wa5[|9U7l;V"ի| J꾦sLFe=I)^qe)a^jѦU 6]8ߵ@Sʄi|34qyӜKBhMtX窈ᅧQ) L|m+1樓QQEXY:K崁79o_qeDm7-]ޓSDF뗷\? _;|_~ho&˗;瑃+Z~Mװ[ýXJ8?}eaz@=POǧBZZ"m)p:AVDhy~ 긼5e}ronjLJNX'}ě]*C=G_=4O\;vxᄉ+='|EnnkȦg33>ݼ}灱+\^}US]CK,|zɢ_{:/tQ̸PJlKY͑oi2bj4Q R#6gw9'̢<!=&>F(l_V#fiy3W#"@S[~mOeS_cB9qcJJfڠGiI 4d\ e4KD0 CoZ%ֻªeUMKҘU|Mr.[Q!t88o@D2_5E 'zﻭmCONuޣmLꮖ,x0.,A5i<I 'l.ђJl ECk9Ծ v}I&sp<~>y=sSm`ۮW_m?) c'\Ȩs!Q\HT9 @Ũ"BﺩQ<1U=VݴJ}mW?QB`h4oyCh:H b(%ÂQ)j]UDt׮8v&qؙ;oY1ȄFI! rY ca,1w.zǐBd~QKerSK{iIgqbB . 0XfxL $Itfl؉\JP;uBr) %hLg ǻH'Tz%%٬cYzTlܙ Y[h"fD{\.Ya%#YM#~c+ 1…0¸4cC'_9YpXOGٺUN1CAemG mbuRuFryb4r -B*"n5'nG}.0%x[ٱ3zx0 ʸhPȏ|@/sE jI̮x*nύO@}1(Ŗ@)`ښ&˶ڔobB02T^FX .Pրj!6ɣX5P$Fr{=1"#& \z!ũE]GY S8D9Pr)ˆf1#jRJթ*o{q{f8| 6 @|>F g_(3]U94EreEkS " H鮪;Xc&5כXvmhGUѾ\E\6Sʅ:GY߯GGx`f6w_ۿ zOWxGR+߾:kfuMLH{弭e\C"Įڪ1Ƙ_u6Pצ.N~s o_Aa7jygb1Ƙ_h7NvHC|o;:@ft5xjrqDawN-1j=`=Ѽow,,{3f91M1= v{jѲ\8W^ M 4(b5+,6o͚RdBc” nhכxa>kYd^]ݤn$+ݻnjrƂc̣X-B6|Yy.k2TY)E0|)Nϩyɢ~7nTXc9D,λ&YX89 "LYTwޓsH5Ytz7(uD2e#XC*|wa7 1Ҩm7sv.;}u)xj6<ֻ>IDAT3D0xQ,]+6@pnw,E'_X4_۾;~1Ƙsɧ'TUކQPUaeQpآ9~fam;)z'"r(*a vm?=/Ŭżu{4j1朼#/EP+ kR=kFY4N<< 3+~._p{뗫|_7q|K"ҵ̇c 9K u#218RD֐GUޤWoWT":渤ނc9wTTUTD(uh;ì")s.Ѩލ8rT8w˥u 0NcΉYCfUU Z wHXP 5z4A9K @E6CJ&loAh1 K :ATEIYDt SNjAmbX^?Zt]d.f Bc1$j (+֡aznc^:=\qk>^*7qiHSy`u1Ƙ3x]* oOB"IIENDB`ocsigenserver-2.16.0/local/var/www/ocsigenstuff/pdf.png000066400000000000000000000021641357715257700231700ustar00rootroot00000000000000PNG  IHDRĴl;sRGBbKGD pHYs cytIME xIDAT8˅MhTWyLjTbDB"XˊK]DEB1 \ ܈ЄV,(tU1R*&$ڗ1.&3h\.ssn_6̲,%uoOkeDV߁mG1DZq,n$?7RuT$IXժ$q8#mi0S [9γglڵ|g'ol^°F&|G@DNej҅ y,"mۤi, 1ƠVɰTRd zo&ûa*׮b Y@a~=ֹs <1;0@#$A{^PKX#Rr:; ð_| C`&ڻ?n(D)RIbupp&'Yy'&纘b1l A& CwzoBكھto̟:IkKm z >\ܕ+޸ӨL$Ik5ݻWqttxZ fT._B lc `q~SwW ٳTA';Kvngǿ?"v)}*Jn7,%RR Wך;ggюof퇻Da~="RZTQ:Xc9֚W~`P #E.-AXi gϠ;oEaN;7M +@e©M\ QD^UK.\:7leqqFIη"251昊!B Ü3|<77j Jfs3g(yAJeXPw& ^=k-< ]^|w6DD]677iZc4%f~~V۷OOnWdeeex4@5Lg8~h3s_ :rX*IENDB`ocsigenserver-2.16.0/local/var/www/ocsigenstuff/readme.png000066400000000000000000000015611357715257700236540ustar00rootroot00000000000000PNG  IHDRĴl;sRGBbKGD pHYs cytIME  IDAT8˝Mo[Es]R'@Y$HlDE"U3 ,Z`яHn8ݱ"bUM&"~ܙsX؄Q#ts@ټ^ŧ@׏l6+dzmhWF99G6Pl95/T ^)fZy{™խ[c@ "|LJkeVUd, Ki)jvNdYD@$;S L1${U>xB/Qm ǰIm /-] j,/a"#lb1I2y; de2-}8i>-|=H<maWHj\ALL5^:F0L8@cTH U%F4Ո1p՚' =|B;B'K6UBxJHRtGZj@Lk ~T>n0>HwVV|UZ;=GffcLgU_ρ/JqA?tDMn1*Gtȟ| xʒ``퍍os\{-7xQ4he5f*}#D4f@翛P(NY`d&j)!@D%ъ R  PJyvSvtMӮ:}{m -nFׯ` <ۭ 4rc?-L4]0zǶQB@. |M2^<#sc+w/k"Y[K 455].!0 #gVd,ujX̪M]LA @\ֱsֶ-N2c$DUtnx`˯, F8hPܓx~خ"6C0(.Tk n_R}F2D)xX \dž0s\ޙ(d'dSYY;ot>V,k` <` gq}ݒ5GQI#+ټn o͙8Njkhj6sϿpmɕRUXKuU ]0-%Iҙa ŢD`||X7B Eűm4RvEJ&cQ 4)eQ*(SWg}~ ]㶺8UνB Iv!z{3۟&<ɛ;ڑA@Hvnm`B޽J׮:ߝ:޲e0΍L98iFF~BXd`` BGc*#F;[, |dy6"0 R>9zt{BЛZM4MJTOOϯ7W-{ @f}>"p-KIENDB`ocsigenserver-2.16.0/local/var/www/ocsigenstuff/scalable/000077500000000000000000000000001357715257700234545ustar00rootroot00000000000000ocsigenserver-2.16.0/local/var/www/ocsigenstuff/scalable/application-pdf.svgz000066400000000000000000000165221357715257700274470ustar00rootroot00000000000000\kƕ_|`Ev18 DžFbhzۿ>ܢDJbc4K"Xu9ު?,OfX]F]Tj/V]EMWrj]Qvkcտm[nw{uyf6͇7U]7o>|SU]mw wT.e{ӮvKˋ>cOl}s^mwʛ6{J%sTҘ5j7oEn5JK\kChgu -wa_lwY{;f.j1?jhJVӛv{;}y1}5$_?wW;R5j%UmrN{bz{\0U};nǏ_nvUyorYkv׋e[ڼi/o+fC3%[N/˛M{ no% ޘ>߮>x?DݤG+< +|o5L"5\1͏|*J+A[݃n7(A{- A ʶe[j׳rJ=\ƞw4ޘmLrOhnOzKy٬1ʹmm[EƑfӭ&a=m{ƣ mOC >nZF,q)+jJm߉t=t޼6ɧ_ (NE9ECxw_H6:l` e.J2u6.hڬmTȃ {tn1(y@Wj77zף6(L4/B.vV$4>Y$o^hB[2@:wOcޛњu=5֞o/9ǟ->̤+}MRC? j=/ċ;Άgo7ni۴y+f=0BM$xer լRV6MIhٛAhTxhXޥRUkdf5<bIRKZQIO|>W0=q>窶 ɘ n&2Z&@d;Yy>ǦbU4CF'e'rMtbXONItpeߣtlК=Z_/5޷6l<+22 lhrL1|eBSWmja _ͫ{v`zBzYt[?^ ʭ6#Cnm˻ͨ?0)@3`J~5d h_M9mG NkRɆgXFhLڝi94 $?#i(p^ڣ$r~$zpW~h9QunyUÏ鱆SV>g{9vL_S׏AT(  @Lo 66ڞaG.g6R_vgDIJCchbplcr:FAZsVA 8o z^8KWof}4נ:ԡx42+D__!6NEgOL.iyMU{w:jxJ3lR* IeAC' @̞A)fÛnD^+ e Wx*N|F9"24G! 5~8ꗿ ӁiAlr8kUڝQYgP*?X R{zTz4RC4~ n cp7B᷃X#^ʯZI̩cX7(-Qrdf({5/P,P/X3u^h3t~Qxj$'DX@j͜GU5StZƘ F&*׻c>U%PSNoECGO=~henQ݅ͼ/wt~{;>p]_~ן1^odwzDm h/bvɇӫ{12Ygm6>xq~ eۏ6=mW5…Qn1ok+ g}qw9HsN/atmma~?]6c/n?!4:W>7ڬoi owCO.ɼmfafk_p\ȝ$xz>0ri.O+'1r7,>-*PwRqWBٮWˇU!V7(^5Lz^]oib2W"9]$h'F!=o&_Ֆ葶lj|ҞB5!Hcusv7Ow%&o?@7Iwpრ> {f F8\8LZi˞ml͂7]}X.F=t[I-.س-^A}8} Ti].ו>zv`}o]Lo'4Bے,B\ѼeݮT\Z%I\ \nwMjVsuQ1֯\Mʒkb!W39bΙ s,A8; .FP or*8.&ktx WQ Υ`*Ӳ NRulQPl˨ MiFC]M9Yu9ǠٴA'c̷X<$ї0GQ#@|䓜kǎ&[Iφ5a>5GvoW#PP"mEIU B۔чYD\6T6.[[|CJ%)|idIy)5*fX'e.lRyAU ~CiA<2JYt),5r J[ѫX蓳4p*{"1輊S9Vp]6Y7)tqߋUCTM.z3(Pu_KaRYʜ&K$ss;E JΤaɑe\]`뀑7g.i GKm|5hLjL>UJr"pIԉ IHrW Ȉ5~S%-2e. uY b -M~jD#.ۺUaqE1ѾA@ `$;<݀25&)Q) JToP k05Br [ BNA~Xָ A^1*/\; (sy)nSnBi,?P w'"&\0 mv+@x5*I9 H+F0dn E̡|%Ex7!s3^:)2==Z)SA%q1E[Ǔtt`K|cQ.=Yo0/uW"^(kC5 _b_>F(8'HʏA0$P"\'g1ܿCW6:ҐP?/zC* z:#14"NO@r.iK䜆A,/ /2b\Εu術mωgad'\Y; w25aD g!q4-a P!qߢ)|P1˦HŔ"g+0H9ad*uk µ/R$ta= s4&]tʒ-b-jN=`n)q"BSB: @rȝck$xQyatp/nD2$yWvru,lc38v?aP4be_SDFVW1mMv&h!y;j`QMD$!v9Я"M<-64uAc64nx>dMWbQ1J]vZ9Bºθs@~uEۛNflO\Kzڞ I ['es:t s@!%ILe-#sWqFi M܎ITA(LLftbhs~}74hH 1 Iy`u)W鐩P 'MщD@ ԉG;'7 gP!ň627LQNiA66{[1Ί0EdbDϙGTɃvrA{% qh(G4لnLJB\D^g! 8D#dlԙ0`S=T=iTK0< ӣ| P*0Ǯh!2DivȌQ(q;d7?u/]=H}:үMWݦ[>S0g8s 77&pRhdbE[ICo8Z6;|ix)1vC,'ZLX ­ё`N`_fu]p#bOD8$䘋UÈcɴZ>.4aIi:QDtc!R `a-?;H$32Tҏa\rTV<vaMB%!`RG0 ,# _`$aIAz4@w⻝u{; LEu2h1Ud l-Dn' UA $⏖zaȝ2rLK3;j!ezF+i+"H#\{g -C 6aPvU $"0lBܞJHAB[r>v7 B'$- QȐ5&f9`jЀoTX"Lfx' ˛h3-xZaѣNRa$]ݨb5Q4\_Y9E2SɊ2 kDސŨ#Bw{Ý>>0 IbQYza&EkR^S\SPf06e]Jk~\- |b(cӝH+`^`MP1H2 })qH%8#'$Q`C˱`.Yr;183z q lFg=@Br@0-|,\bb4 UDKoC4<`(A0YwHևaT5U(M"`5Y֗F"lYBx|nq>1dkzrpvdͪ8]Χty4Uւ\8Oidi.#BZȎku aGM[nR \" 琙<\1 )3nXD5-a+bSb{Yşp9kLvfDS'*IiĒ+V`:KLSZ&$EX$D ~b^E3>O I KƐCs\ ČB+IzZd~pF[f mgG KJg&k3,qA{JCA["vΚ=%#0(ynidž5\Ogp!tmbb KɂKSd)9F"Xe=Z`9-c P^ cnA 6u?IBEgE 6y781e$Ș4g=r ˄iDa:Tkl#1e1" c$񬹚N{C#Á91f8H`j  M͢,D-vn}Hf`dd 2Ҝ Az<}^Yz1I? EdBLW ҽetXoMr%gl(DkeE)n rT?O BE@ `?2$q J;J ]O1ח)fk#,~ E%Z:Jԟ<; &K KN4bz )0#& 1i1W*Q-9lYH3rB&n(y|\Z%;j̐-OlJ,yp aza.7d֥8ƒRtBJ+eHd"tF9tm[:4p;ړ"72h|"9[4B1]L0̒=H!h/+ 5 fu<%5.Xng[ eWP;41_+/3aΫFijSW@+>f|n7,buu}Kr\}٩{t>hf8؈qqA _ߗƍDKVV*3ݿ<Ϫϓr:xkuUMÇWZFaa~/? ϋh5LW{r<KyaVݦ6GJ%sVژ!j _VaUUƳmj}#O۽oº^?-Ɠ;9&wCU߮n[ͬ鷳$q4,#$q2q=}^TmM4~REIu9Wt? r>jn/OO<&QL5gy,՝ o<=t6)]_Ob/p:]f6OV_h6~=s}`z;~}IJ]{+|mW 5NYօ0I)Pk2Za1bCzfIV7j۬7JP^mww BʖI=g܍Q@[}U]謯n =1ۘz[h;MvF9mVn'v'YƞnƓЂ"q<\c7M{42 @Hۏ Hoz00B۔)ti9Yt>e9٫a "{~ZL~ ҤuN> >_){TS>7W)`0:S J}2UZ:^6k;xv;Aܪ0~p-Z%_1TjM"^z &T4g-U!ZYX'4Y['oD t=8Xz|w3zCweOxjg>nФ}|+uMR-[mxsrvzpiyQͺUJk3Hq{[c.BTTC_ZegVɻ[*UC HjYm@;/F$Tj1isj| 1sv2fI-t@2plǦb5L5՘6 4:);PЈ U}r CqIWhh+kNm/5;4q0@P84(wD0)eC30|5&Yvs2i1IGLڡNǤm>F@0]@{%3Dwե~G쳅S0+kcCf"vh.8qʸ٣#*ْX~3n^=&{1pˍc <1&L@<ظIdi4dxTx\& HC9@XCWʹNA xW|sVn2l^ij/5H|̪_׃l-uaZ١NP! H- lW*@|^d8J XFlO5EG.-YH?؃;c"4!| 2ZGՎkto40NJzs~/8'waK~Dq.At/ҶW5XPjS6Z4חk;v~sayZ>^;o\BSNyj)@iMJ_h,؆H4@ IA!z  i6\nk r\1?On!wVVqrEr[wyR i:*;`mC{CtYl5*vQY{T2_XTv`*T 0hs[c :kEk:DӦE5Co[OEK3W袅TcA#ŧΕmNv4?a_f߱Y<K!҂v{uǽuɎ-={C$htQi.jNl24ζwx95;zF:uP^.5 xZTf AQ') P#!@Oӫ=RCYRi0Cbƥl:RU:ttbFM‹E;0Ӿ]IB^Bк:g𦝽WLRs  ZCǻFklǂTfsmd-k-6ǥع,ϣ:g-HɖyBB[!aeu>hKA+D!\v]a&E`E!u] ^){;%2qwZr ji{rcۧtlnR-m丈b0ϝ߆x _vGe+>zp<1(03xYNT[Z*9/a"R|f<1S b\\- ,bVl`36;gX@ *[KsiB<7 Y(z ʈ0 ` dLe(*sa=9mL _ b* Iʠ?]\S6-m{u_\"{“FYbocoTl"x\ E缭sodWKݝ<43m8";<w>DQ8d1龺li鯨`;4B4K~"У)2kWS_h| اxϙpJNzJSqceڤZV]Uk_{5pBϕġn~wS5*g'lEx ?crnXTm4`됔 \7]SWCfnmh5D,sIu2ZY/ YL1W{Յ^Rn%W'RMUXf%.FDں2!h4)cM,4nC)3U ]6>RT `ciGi>HuV[1G52O  ; lMtM1v(qwq6yqL[9;P ƥF*?!1.Z9,s-ck.#D];wa?|& 3Y33pL/}٨}$z^xBWĞIc:` 6ۛP0Nv#ɌAxG<s[ 96V6["y.n6.JcqTTtN׃E`Z ny$C;lHu<ϥIt'ϋu8= %&cH2h{iA;u-b u"QdyW\sNC4=#;rT;guތnҴmBONͨ}+ =# d|P'mx 乽fݪV?=?$x,|?Sv{9a=u8:;j&k5YO@_3>ʻZy3Q%=cC+WTqX?zү$9R%[YklA2U1;+==Q\cSMe3THjZ'k"ҫ{wm t oFE}>{u>q2w}JءVXdc6Nۧ0M3/~*iO+F_'F Z6lʖ_>,d>>ΧUA4,gÛS/sكLSc= xG ްqa6̀GO鯓۞E3]*N/&ʅ?MVÇmBMa!fօ_*Cz]mQUMŒ۝R¸Om.M Iِ5-/t{^teӗMr;:}OgΆFzJd5F[*.1A/y{o|-k`;-=EFD?6:?frM}A)_ IKkfu=fk՚5ׇ0L@U~67P,?=Ch nl 0z>(A8wN!:W= 2ۻ _\wrJ⦧^lEWٔ%*Dr)EϼI*F("c ;2MڥסgwT1=X&逅fln6R(F4(8;2:-5SߺR]d#Ǿ 8ű+ Q5:jF/JKzI}r .A{ 2J6~ [<>jԈf _;{k'~iu)j)tuy*̰N}lIi)\nd$n hM:m²f栲qE5nQ$E/p5EKIдmJ,Dw,wpRq6$[?W4{SMF).ZCwjb5*2T zXj3nfjL0 dc Sާ )4nvς*@Ư"FhZZAEbZEe[ϥHD@&kaJtY_m-2H`R`3]_Zyad7{[ʝۣ%=F8)]lR)9'$s(E&$līSb:[dqbH[zZJ'J@RM&V9jqS.M>I<SV$yVl9I.QnQR^+hVEHuwQp ͦ+fh1NG'hKd 3+5LJBۍd.Z<B3Ϊ6sn1I1[Zè]?&΢7՛>ĂrΥ,z͆ML;*Ɍs6$D  .k(_le do;qS7Aө/mS2^VcLdp)[H9< AKn&17 /!xtfX%ܧ@Yu\Mm,dPt>;Q ߈Şt)6'o" &Y၍А@.%F у5:hq?+!ĂiQefH'ל^r Ĵ6~t:.ظj=$eeT$kE႑,I `!3gR`i6Z&kSrzC:B*}F$PP"Lm2MAhBv|MFF/Ʉ1Fn((w1A0JopDVX \DtM t3IR 镰/|b+2 뼖,I iD5l.0+I7?\\hAjpPD#1dzՊ^^m&[Sl/'(1@SbP! >53nJT)ʒjciLef*MArqz cHFm@R\ ԉԫ6-L\]KFRtJ597\!6-naSe Uh%3σ"пnU@!p7ɣTC@6cyjo_BژoBΠcXGǘ7AfE>6B]iYymHUsⳏNx*m4xF账46v9m9Fc$2SYc5(fŁ=/anceAg֓e7^Vm9rRI{dIP95px©N]亐w@P&!Y ILG uJIŨ OŲ`7bTQbЃg&w&R|כ}R>V1grTɆ$[LC9>Yد匿b6?oݺl oGˏbK_KٹZ ;R/y yHaEdu$dMЭ$9" s,/<*" BdLO)6/Jt'Qc>E i}g0C+Nl;`Y Bd#p.iiB?6'y/E<3|gb~cWyM {YN(t}qr=czrQ$ {PI $2Q{ 8Ij;dI: F_ ksyA(!^e]A8ac rhzm1q<=iæE0( ?醴?RʯB;)eF6] .K \'(ϼ䈼;ϒz%K{R倕1%`ϔLVZbA`rF:YS "w%SLC @/ލA࠱ނ'2+/c:j !ɼbUMW´6>״'}vw;)}[zEG1s&:n`}&24; H߁ I{0/H\z&?U: NTH-]8޹$_zVo!jtMUr/V9rsegodp.N޲}ŜӏU0hVJ>êSEQӟ7e](yw_7V$~ 8Oc[gw,w~7,VqU4B<:08f#,V҄wO#{%´f:f^Q)hI[F +Cg,NQ+衩\fTHZ)2 .C)V'hZl52w>VoX7$aEuJFQdJ9]2r3Sܦ5=-寐1eEdR%PAW;.*2xiWn[2.bYcgP|B*Z ϥ.w5kmldH`ʑR{N16q"*]06EPC6cp35v٦KS?}=~ i,Yb,#h8:eYz3+ o`:Fn[$C`QG09/JٷT$c^b;sT:p3 ##o<̱C /w{2w8D Bׁ7%&{]he[PjAyU-ԯu$@]sB!~UwԱ VXShR3D#K x)o;P[ռt""ܨv൪% mJv!hO*BTm|\xCژX@lIG "h֦70b/mJZvGebᡰ+H@l3mrDUǨo`lZ7". [&}e ZhV^0L9h$hE1Q#x'%ِ>%&Eb6!ZΖTTC\.李dHЛ I H`m$NʙEXĠbN#=ՁD8M&Ḃa/s iBO $'E6.#{)a0/1dfI04X̤PCjyXQC\2KLIk*>5`p+u%֒ Gt| <6'p(0!d(sS-٨-jFZHph@bNn$CpJJdzƗ)x$I^c;ՒBZ -S'C1PiltG"{4=ZyC@*S`#Ӿ@;y {IWGfg6!eQ"TcVacIѳ.3d]St%Dr4Wŭ!MuPIrLL#K.[f"eI$wcs%Me:l@elg kʋS#y:uWD]Ai 3IrN()|9>ɟx/潅N3pNh扥 &r?u}<H^5戙qi;Ol7[5($M7̓"sV$xE}}՞DH9s,z;QGvocsigenserver-2.16.0/local/var/www/ocsigenstuff/scalable/application-rtf.svgz000066400000000000000000002723431357715257700274760ustar00rootroot00000000000000}YJ{ KQF^5s@H $І;"@~\X2#sSuO);fgnM>~{,o )+;$aە!;I}Om~}^?9A`YOoo8EPsQ?S?J߾wwg4ʇ;_.s5 P8 ,qWvYewOomu+0 })Rl(6ڡ7|p_uӿ {IO}%vtfU>0|?@cߟZz^DRp4E>y|OnVmuWir<}p^WǺ7,gρov??w??oyQ G w/nmޅI|4ݧ~NR-f]wK,C,]?{~P%> ۅTr%BxWYOe?*}AǮU<ɋ?p)*!w*| `(ax~)E~2ÐC>C27ş]uס>UvG' 붓 x0 }Cc}MeKXmmJRJhJUo Xϗee9"1s,S4/K3s՘^{$ES[R$,c wP;yOiNQOX*ධpX\]`SqP7傛 xŠd>vȧs4=$jY?7tQotOFݑo7nՇ߮c^n?jv}V??:>$XHx+Lޣ˅wt ^^ܹuqZRxSSXq ,E`8r!=q;K{#h@WpD"w8yAI%P  P X Xuϰ8NqvK)8䎸 2~Oñ|X@>4è9K rCHʰ/@CC{[BE#=ŒpG¦,pWrpo@pֆ{8.qwc"8t/J|}Q=ǰ /#ɒBZ_1h_W㧬s18F#wϑ4qsF~`9hģ3#8^ЧV/w'8}}e? V|eV w_g^) E& 6" i@|D;S<\^ei'!_i8hq np  ;mع$NG >w"6^GCoPQH.@|P¡*'Ea?'1$ WIVVI WGU̗/{ r6`67B*4Ф?ގ>J 1=?{'`{/6n*~ O(+_1b{kuaꏿ d!_8 W䳁#G`8lG}cz*_X=}"HԞ~ #БI7{a}e?ʿD1dBflC4Gp27@8/p4kQ 0w4,1': !5c~!$&\?4YVp?E!bA#D\!_>+fW; F>iɌXfﳀu|nR8 9u*AE yK = H"p= `,A*P @6p:r_1Շ|0>|O6C C ݠ 7TzRZ5opGU"ȏ@']y8,M>A6$\,zxS=UY,G߈n`*2 -WN8LbB5E =7R4R:Lyp?toVKPP|Djo<ϫpA܌+@3,u3h٩FSb?6^r$9ȾamG?Z=v^U˼Jq|A8wG䏦8028xFMF8AqSu1{-bu`o?g| ɧoA{؞o{~)O@3esP )fx5m0C ~7fH!-~bfHBj̐_CZ(6i1C m睘!V3n̐ߍRm#ccb ijd2l73s3C9n螤? ߗ&ZݹZn`S73^?[x3$אMZ~13$kgj̐^CZH6i1C{F~3$15Bo_8n̐`ߍ,#ò_B;2~ ۲[{bI/'B~ ȧ*D@u~ soZ?#L`2iq ñ+X9]@\y8C\Į:?jx2w'~O LI脕=?4gв_;n'>k{šgUn%.OsR˭׌Ed?Q|3(j[ac q+zKŭ^"84_ŸduV8>Wn/ŭpmzS gI1jmKĭp1n&nE2\wfyw[{#`܊®XanYd/dڿo ;n+VO/.=b$ ]J'y;.u_kܓgne{$a) *^pvB4{$4Ks,? @3 {9god Bp'svZ}XIfv >IX{4FkQ43 a]x9\%N,TmC}s"^"aPap s4,Cr,(G7h52o&=s/7FaK2-M+Cٕٹq4`3FK ݌Ьkܭ/sXoeNlЋ2?J v+KHvT,v67cfKQӏ}ћeݛeی(@KCk K2c $x|ך_ăƼ~pk&?f <^boڦF_X荍Þn7OwŽ_ڑ~b?^Ϯ|yί_Oq7ֻsoOyO6B{A<||Ї-nk:6y樿y;n!ͷ<z8ggy_￶Gee9|pk?oMN0{ENr{GL_1In-_| pob#|8G/:> '菙{ Ot3Qr_^xRqܕ7"qs/o<%Z^Pv8vvvHIKE NSzv#%y=/lI}&$|yC_^'F$ٟ~J}H@I80_99 ĠG!8F 'n4SCFM>w ЄB!,5A;dOYƾsp9xK7'<9D X_Ї0zL w;jZM$D^#ġG{x83tytx:=I}'Ky*$pWi.S98ALHDewCxI?O^~Wӻ w99ϋVoJY]e]0}\}2LWuzݨr^L|G1R'F{>_mA:!w?VO2Sy.4rw3AVgc!ߙ$CyǙǸ6S&?cwo%*N\v IT>Eȟ=S:o#gnN&Jo?sN_~8?́ݽyI묧,S5U#D 'ӋOq _pG=n)AwM%aLyHt{^;wctNu:#!)~:|=5N˟ G>1I3~3GyOxJ& 0?$< s]]ꇽFM{Zʰ 9BK%)|{+B读vݏ݄CƏQ_FrKʷ3p!~h|ïW& N/P{q_?py<" ˽`y6^_~ʷO9]V()gɯ`ʐz]m)&,\2*(Hh8#BC们 x24ǎ^3 fI?Ӻo w뾙}HϭfT{}fN|)-NM)o9}tO-º?HK~ uE5}⟧-6/K,L!'=ӶZCkMa=GQzn%W ~^gTp:w$ΰ _j[&uCh)wX% ^wTK1|M]ro3'.6 #Y!r 0atۅ+r;{;p{;)+ 7S7|׵,ߙ߁zHDq1T7T{?GΓYϲ4A[CW}y?x>/I[_AG|*!+N,o{|q:d\{7O'Q?jX(ͳ-+N'4]ߴO̳+c>M~@J򼾊2^?𼈂Fy~?y]~|<ۇ7<"ڑ:/<χگ ;XM` FC1ߌ&l$w Mr<4zcNEk`YZZZe6Ce d9—̐x<`s)ȼrSG=ψOm5jW̄ˌUh}3$ @ "ȸjxvX4ԡHY#^nٙkD2,M.89_9~D5KEbMJ\Z )GC2#5%9,u+hL;nC7IY O◡bNG0*#krk>ϫJ\djO9XQjrʷd/hNQcu4qaTKY3̜,&H\xbV cL ci:ahN9p_0l)ܠf'=^NU._Frv|H UYbm2KsM+5ge7NRmYGNl;熙Dײ4D`9!I>cc8";*T3y,jc0X.o!ŢWz~>\j#tPI'j6?2Ȭյ 4h T(\ R10.arAN< iOa#lw9V;j{eQ#*béu+&̷T`iݒnGZ0S!91{:s\ 'd^Vc1z.ͱ[;5뉟jeZPQN]CpkKjcw`cj^%#|Љ໳3e/5ҸlTM/!R&y+DxFEyEfN9M7K>sb cjq-Uh\He8Px4~nC9f^D`k)]4&kȩvٌ3) qjʏ4TeeņQN>v`" "UV(nh[gRcacj;FzAtɒ<ͺ%-R :i6œ9]\<"`yqKj s`)67Ô*"x y8 ЕDj9]=dl47m=JG^flWtfNm]d ƅ(k9 uaD.l@,mv f9Luk-hLr(5h{ulk}NNԓ1XmUILT 龓oEɪ}fC"1BECD6NQFq(k&2 !6#.y9Nj ˁ1)'FjJ~TPʻdi(9fǩi5`(ͺ9:HŸ2se!KHhYe! 67|^ZElW ?utThTy8C-I7s5! Pf77Emؗ~!49q+vAC n- $P^Wֹn6Ȋqh9i4 yR''ف|ؓQWd!tvF{ITU*^z9yT8΁ Cz7G20S-CnmɋdG5C9{ʁL!AU]+? a&=̊%i?Ket`SF X!+IlΣޭ :X &G1.CXU181OĊ8WA"QD@o j~teVF%[gX崜&?^jG8"Wij'+IM:):r6Q-!bmɷm٧UY^nGem թVxebr/DmY{i/.hqTˏ2L59_Ns44U٦ڨIă3ph AHJ O̺]ZpTZ :W u½np'XhNO'Use6DIp.Zt"i:;ڙC eUT58r[WXsۘjԜgoly߬s. 4yu12ݥRYa&&ju& zgubCmdnM"aԎ܊뺚pҫ]Zk''9+OkSbFIhSm\LQp+9m(ue3ImrOFEPܺҙv 1Xt86]ظ>S +CG'hOM'zz^'<3|j D6HC4CGwVeQb3[\RZkeQyd+а5yAlx#{Yr;ld4BN4(CZKSM x-O%Ѳ٢Hke'֒xt9Nu}4hҖpxx1hz%9&( $n0ڝFIn&"F9թγ1:SS/7!sab`Ih̭_=~8]ujsۨ;0uQ-E+6DTJ9U-O2%GxpI x݈+k[UjD9۴Ck*8hGdf*sl'Kfya[be -E8Fbcc2qѲOPhzρk$Rf@:xݙQZ-'5&Ǵ݀={!R z4X cY,zBS\uj,tM}C=8 $TL(V d{1^i7yfcsƹ 7s0-HI P P@7]' t0zHҢ3j췔<nȬ{.m!|/ȋ]pfCG.:>)!Ux0n/b2F&T_/SsS˩#dLdi 3b9]o܊7&h^ąrPx#a.%Kq/Gi֮Xg۬1sأmf55Q8j=?_ F w[:]LgtMMӲfeSO0xE'#ip@k4@kT0 Z ڊ: $8ꑚ[FRŲ*u:lm8}R;S6p&ٮFė8az5/l( 0ݞ="]Wa ]<= S5yF4)^dZc4!U~6AU@`1&B .Pfg^Hظuxg*)U ڪJ{|ÇaGYκlD W fY.6ĞCnjtDɍ̬\1y}K-ԐimN~'t c,ב 2Y#mҚeMʄL92l3kU*rB2K)nu&ϚC:x@a犃 Bu;xO'wAm(ʫH=lǩDuY- DW\l[xeXT Ur=>M-n3ƺe-:qOC-B$}r\#!/=syN8ӢECEk.l`Ǖ%N'.ɴclǂN;8UvŶ( 3= !R("4(y]gj"kI){md,xeF젢;h.J`e# hb.|a7=EMY['` ɮS*(խlybēk˝U,u14!KIK+] 0-qYu]͋cl*om 'Ƽȳ{Ɇ@uyojPN[-ta̡gI%4xȥaI-xRWI0>^8.:WV'h%Z=k+WXFKL$Wd\ T4=a7i#YpmheE49W=ƌ(3׋t D %M^&Y9Z*Tk%/!JMpAC&Ge/ӓR܆/G"m-a aC9fy卨:ީg20@j Yͳq(jǓnN l.hi .M9 ^1.qFJ;^v遊i,U,wlCIgʅ{f>5EvQU${EJ9u;PGNDi'9t)R D$W?vudd9w=? JnhRSҸT,c7_O }-sd"fdҾc&;(UIn _XKVIA;q59NBdIB4A!FCc")W6fts&4Kko|ܜ^̚ȍn9~CS>qHU5 rɆvQ 2yĴBB]ytV9aA2̴&)MLJ V>խdlZu0ט{ȲJ؏l=<{*v358X@'Bv曻 _Q:B#fSӬݕ x =H3{sNXQllydj?1y4zjFʡe.Y-m2/E5iZ 1x^δNC*$exv Ьt$ 9T\kz=hfvE4YOxX,j[._8M4uM=<,/>P(%9mxt|6Z,ٔbPq!ɂњhHQ.&*l qbʗ*i56%iF!c ɟ9 g.ѭj:&AVsI'kد!r59uB J_&,%I>r?imZz4Ì0B\n n0#֜%r"Yj^@7xzvfܯjx!.R;.Rbgj(S1R-ojorm2P&,q,\T:By?Yq][h3f0.k!/z3rlnA0rAOKUqF~AVNvwYc$q,UX$_gHUu3hpʥV* B((„[6vP6=fLzOyG3H)VLlnyU2/5@p&]D-BXIuFZo"f^%I*dѲhALyG qD#sb5b09e:F\Yy4!˘sK(Bpk 8=#,B^#@pƼ@dpbdn}l[\ԑK5㚁]UwtDn1Q]%Qhp-dmdM]9X[r$CI䥷4do-4% q8HHbIj+}mX.F>"&8);l~kj-9v 7^#XFLjdhh@]uѾ^,Tg3!%QoCg17vpTneٯf@"|2sUSK7lĒ'٤GgJ&&y۞ EsISY`@Q$+oi}`ܣkl̊45Xe+34!f^{Hi 5s_P3ɜ=;Uj5r(,ބ^0wژ5OG4mf` }3Ҳ9.m2xl Yc>6tj?j!2Y>]Y5)YXi5.Bŕ[aSXΙNT,#n7ۣ rdGSu|1k!_%a<.{(@eXKhkt; F 4Npb!Gܽ:v@LGe~F@ 5Q6EMr.U<"kQvn"M%x\xa3#h̙$fZF:W(Sj s@j+g^M-Df6^;dS"t;m 6n(CRr(\DgC[@;_X=X[#BfHcl<%#3..͡; piaP*FevܺzqqqhTA,8lh{#:adη,XLXT;@5K!*"g'|U+ٺ>24GԸmq戮%S2(dWqzY(lĒ0(t}8, g#t#7ڣQWe$}l Z*:m::+>P(0@Cu mI}^cȡm)4Lb hޅE!7hARyv2OM|@Mb+J ,Ϻ2y!+bi+#/H(2?~JjFv,wH8* ? @wMv-B&t+QnBvJӄ K PyJxϏ#|z;(p 2ѩ"C^YG4 vz1[Š>M(EE[ggRcBn4!x )~u ekpVdzca!ҟlߙ3Nr|(ɯ-o9Mɔ؁r:Al%23ٌIh5/Aw&7@@?+WAIS&ȪHn^zx^&d"2E*YʒT_01DO/;6PP߈2}3̳!SNB8"knu_̺}..k9<捂g]דO(cs[ dRo(l YyuZPG$ry~:8I4+G*cy;}BsZŻcגDb"9b=nI_TDCnN磖J?^HLs:}N;b҃I4ͽq:aC+eVpi2q'"R(5uhsLL|=~wZR'R`tF3h,@Mlk4Vv-ղS#I $0ܹ[K׭dWia <ѳ ^v®=7,;ĺbd:/K=siʯќ轓"`n^dnV;:G'ok.q 6]C*NEWogB~gPERV M4b߼]|% 28{=欝z̳ _;Y4f}@K<YI7nc%~Jšf q;BrFd^8c ;.LIbjX-6/pVf{% }y}ߊ´_oBbMή!U`ի>]K輱 2Y{Plhgʊ#~]r6ْS D~Rw7]챺4*8Ćo0ȸ̒?"&t  ȡiM6ݸo^ë7:)L K8^V͆dA.xm?Mv"):cեMy"E4}d~t1" PV@ 36\t<3dkMBЉ&e'X ^.vR?@t/XLmU-3.ϕMr=sԦ.F&gv#m! "a(ZR;++wՀ.iUq4Dl` '`at=Z ouyxf9U ʣ144/?vHSru$HL^kYrTy+APR28 +BEPW%'x18zƵÌx`6MRSss۫BmoHcPȨ7+LP3d G֝:>,@l $S4]wh~kʃe2I{{47i$Ȥ@b6Wk.#Dlo[PЁ>DKZ,oZx},PN)~vp /@،Fa)_U(*cU2Ѿ?w7ؕL!lEU /7 iagZ!ޠD %XUӞ?*Q] NGNNgB2z=7U^>4*d0o%b v)ĐI}HUw;S|xJ  Š] -Hg} rmwȆPՏ2pQRZ35 7f !pYHۣ#ohqrۜ`: Xq8 GOYtס: 8"= pZCU1B-&mN1EH2cu=Uw ,荋 T'wn;=ND5*~U@"4i"w}rx,ec~ jI2 缵;aJs(gfa?@,"NS@&3BĿth4I $Zx}.[U9JRaa[PiNbr0bs46 }d\!\];r4@Qt}4Oyxߺ ㈥ٹfodi#AprJdÊ|dz `3pA] { Y_+f (TbEDҖ̂tmKo}$-tcBgL3|uʛI%ӅŒ\ A'ۓgL{{_2qr±iʻs Q4NM:>ցas6sZ}s; }FȡOuRCc`Kի:Xa5Tan )  }gG>=.5W v^f<'޽/J E;5ߏ_l\J_|ke7\!*(`eigtë 4v:4HaWfylpuPG܁-zLaݭݨ\BJ??HG `͜tbms|rM.BVLԭZo㣇Ԡ8,!q,3e0TGu+:t#=0|D/9F=IσY&W$-L$ٜR:xB\vWZoic,Q0-Fg3qkڠAǮ/) 8=.;HLMdͭa}7:֥_b@6gz=8|%@zmsaIF8GE]ͻ_V̟N^QC~_H-nV}P`h !>k88]j+\m6ɳ9D2&Y [C!7v.I/Mw,uwl? K6܅TŃ*?ߧ/t:EmCf,Aw:m/RMBFx}aXFa-dʥN˽a [kM讚 O5>inD}dGy+moabj":g"[wӂK I4^y7JJ( OzEd%Cf΃Ish)ILOy$D:s4=\y0E飗sj%ԔM9 5H:b~e ȰX3I]ml5"YyĻkJ* Oyx]FK\{&Y,WAh!{X:~Hz)UXܔ|R2^yqߚo(\ʪ61)3d94~3mB__kX%8{aN&-]a](Xid)%eo[J3#_p*:#3/3 |+{_^$:/.#{V1=aj^c_菿&o~ OĻD˒ح0C"ڐ_I~[0E %{#*mʉC*̎dx(A]I!UUwtBcݼ%DM!A9T'Tt;f%hK dQN^a tf .kUz ofJ->^cR!xAy_ 9Zz'?IRwy?ĀbtOk!cXfkO2)G_:2@VH+05!7{|5{w̐k}aW[EϿ`NLà)ԫXӲ+/_%-OU?MdnXPM=]90qR'TR, PYfi`w[a٧SE@ՙt+zE1:S'1|H=熸\;IxV8nB c'{v09 aL"y699oZxR*;mɍ)C} c9J7|:%e͇eaPҘy'wsu3n/'/ơe貋ok;pm(0\Nݑ2gj k.8D]g ጃ$#~ ןt&^dL|:r-Յ>! YgA#H*)F]Pshݒ~yh,= n} (*pYٕ%JOvU>2^]À:i3@U!1~S'Ž}Rl=\XͰY0S9Ds xW'n;kp# X>œX)/1 ,|#UP:ɢlC:mcLԄ-Rά|GhiвZV>f&f_7fjFQшkrtJww$خwږ z#L?c6:zl=pᱨOv{'5S+#I^lfc0<{ T(C}~_e|682-yP\_;2<,>ꟗaE@,qSMm#BGgE!L.)>\4]P\?J5´eo$Ih^2Ou>Y²K2`<\ԕE@ "zyr ws.0l+7E>4A}x31_!] j12z#ͩ?/F(MVZkƋvq}yـÁ qlZ>pWnI$ bp8Q(m&܈_EN6Go4qۧLq9H6#*54TH-kiMOP O47՗8x9'oB1x{ q%2խ$hTd%p=g~Zox5/3CSfh4ɜ=r֦>AVVa+҈q,"#540mn[`R My轢l g_^Zp x.LIWD q2m4]&4ێU0h (em㠧dy Ƴfwb0)u>ΰQedwTL[pR,5Y3G 7!6Ggƕk UZeF D<ߨjJ"RnDLd\ OKF?r'DV'q52h(>ֈ2\3'@Ҹ,B Ip>cȨLa 08Az8,;VN+䬿}۹zA`? ̈́$ɬ|%aN̈ UOf͙uA̦/ERC;Z7wwfϜfjC^8mL!KS}mh}ZLv~!@(IVNz#E&[W)ei|LԈ"&g4^oB&ڊX9󂠩7(N':=iqFj{ վUN~u.r|3".QW_St 묇GԆdm]Šh Շ zo3-Fm ϐ\xg?X:"ƽ\sp޺KB%ܛSE_#^ICGaD9xS ^+v\~aȁ@r#abmT$~Pň1t+F1}*C<4,ɏo(N:F jB⹚j` \.9%o[=!<5_G~:ygo7}s>s;ɘ+ӗ'`x7LpzI'(8P:r:{f$/ ޣÐg6$͸Zp\aCg$אlȓK ~:VdfcQ GEgVwڮ|Z1V t<-稞̽QRz_?([b  k ",)Mb Quʝ`Bؾ#H?;-Y ȍaEYo먹j2KZNT뼖 K[nj>,QZbϗ^KYc!ͷI N>PN)hf EP^CVnJm`~ }hEZ^ߚ̻hGR2=: H%V/hu|_^\\?GRdgp[BNV(liXN@m/mߐCAT}Ty-kpBp IY&YѼq{X{5˄lI{}|{E$B{>kvڠ'ՊYI]/2GL|*uz:N͂AJoxTVL\XUO\zuLs4ވ漀QT €Y"㡼^ _dE.wc7{zJnU.v}E1!{?֭6&Պ"n 3mt?I2' 4 8ˍcsWg{fT,@6_giͦ0__Y.#>+ҜszQdjԀsGՐ@=s&ͤIǶt8Bǃj-ݹ{#Q0b8OfToY?:)2Bю WpW}I iF{ZQ+uEQWzL=Kڙ Uc]WǾL:勬(HxEIiMFM&ToV+dio\KQM$cPpF9$[W̭9|&)`s醎vV qD:d!f;0 k_Nh5nc@7a>\vI6p6-<xxXZΝeu V83c3&X"b/;7CC:W)c6i'扷RP,NwpE9fI`miX6'F1#Ne%VN5\;o|p:as~zI#E$`-ZY;Trݡ!<-PaSF3:EcpBߑң5)cQfBSd\zGS$=LufJ  ?,M tCebn#l5&r嫴˫}M C!Lԗap8+_}Z&~%9_Vy07!!~3HnWQR}mRbi/;BSsF0C> G# 7gh& CMR :g8AGP8g)MҝpBwYQq)af/i\6:1!>e0{2wPL]yDd$xnJ^ N[3{"MDZ Ο<l_E,V:rGaL\|ZDjxdH`m9q\P!/™`:iceX0_g/hKԾWYP_?*Vm#2M{ɳnUz:l40޴U-kܗhFRj85sVMC8՚z7~4k\KdEuܪlK,:9AK3O_UL25`hsoJˆ4!XJz*m%H:<_H((tIZ`bߏ%7ь1ptK`kAD[A%OW!IYtX|MM7l]*2O63~e Kv F0 .V]@'ҿo: t1--Bw u>LPq qKY=P%H&:쥭tk- e!j/ݑZ/XGjaM=Sj1y.B+M!4%F2<$_|b>[@ `wۼ$8-)1EmʅLgZ q<>"9 A1G A ҅gaOw#{DB!ˠ ,B-e%R=Lt~ }ԫL}3ƢUSt48sdӢd0Fϸ#ӮK!<'re,RaC_9@irV\ "Nɜ}#W̍OB~"}MD7RW?rd6W7T,}~m=i9fD'4%kۮ{o"vQ 8+pYw:൉<+32*S$0?eCDVhsvT?E]<Wujv>g\ZTK\YΛ%bݪԟM!åcYD˟X59,ʌKAX@t}/o9Rl?<)`h9^@EJW쯤ۼM\`>j p9UAKXolvR&_L ]a]hvBaJC/ Z3wA|fpXB iNi $Ja{$)運PB l0TWqT%}*zDp:؎3rT 诰gpNx?z朡QUe%/{qBؗGDɬC_ 0hlt2 `Hx'LBg?F/=y̸kj .D3Z  hiJ?Xu,AD PVfwLT%}1=޿ۧR__/ZVImUZ I5#y_:w6_(wk!3\ =70)̇2+s>9%{ EE)y3! ~on2~%O[ׅ &DtX .8CK֥Oj,Il}T{BPpƧY;K-%_KEa/Umtu[jY(B.sv 1 @QeMOb5_X j M=L -PbvAzfE%Q~ϵ4oվ( {П'i Sso: ZS/< ?Y{9^HXF!H$=Ƚ\"KoD5ЇJkaAuw k,鑗|Δ}CFz 5:fgH< g=[ z읷=|ήqW$kZˎAH W2!HМ1"ےp1!Mx+iDQOo,䟿ݹU*8=s6mGKۆ=$~]*muW9ڽqzo='fHZи|Z|rݟkUOΥ qnF_鼠8d)gJϭGA1Yۘ"_{1ڎdU\1rn{ELVc9Ko1) `l>7onlFi]bҚ~pUIzʁ+%y͹sb@r2"'Fdߑ kzy=YKtp⇉ĕݟT^j =;̾?YX^ޯ/W O8]f1w/*|eR0M_~bpO'hsѪg9h~&8nH$BBzl+]`6tK1z> B45[w" tҳ-lbl^Pp?ιbTNi5W4WDz`)gZgsiN~U?&ƥJheԽB{MdܼH;POxo 4 $By\罿>5 C7Ki<Ke-EjmI[m<"sG-,e 7 i"˴E``64&<2 Zk4I }ʓ?&=kt䉿n[!8$o{K ޸3ߦ,+ -FO;MACe~t Q/e8qNp`5ѣd@0OKG@ t03 s)Oigӆ\(vWeok03i~ڎD ? Wv F}8ʼ~ 2)7ET5íܼb\ 4(8^08O0ik tSRn[έATqCyΒV^&LB2K"h)U]㳌壮Ji;v<x`@yHVtkN:"-~h)qWRX:6n^};9XuW{ "s'<ݱK+ 6WƌQH Z%SfIUu1>'d9MZ%/=дVrz?!+@a'f|tG68D@8l7Gx=hR&qz'>3R~/? X&VPf?f{% w..!y\ʌT0ZٽR`>3 "'Df넙E &NO釴P^V[9tqKoTHGH`w,KR $CM ep: gI}U' eK w@uA4f§K+ӲۍͧQ<\Uħi_Eb\tU=S7%6/\j ~.O%ʝTHruWSCWa F!-SEy?ԧ9龭 :|iTa:QWzࡱ2Et jHFv*] fU#~B1C&\8,W /4{[&A=5oÂ}PcIzkpFl`w<2 "߶!Pt'{,Q! %`xMH9ܑIdmFk;ȞЯ\>Zds=mv 3H}URW&_%<ͫ\D֎ b pb2:ڴK.S=yXsNJՎ›g"]@y^4]۽.T|wUCaoĢTtvOa b5@4fė: ,Z?h_x]DXay9{֖S=.읂1[''ZT& !Ky1=*$? _װ+t_@ԟǓO{*<ǽwJ@`*,@F^6.xe\/bET* z.R8(17Q\epq~(Gzަ= /+3QůsPS`aE^)4o_ZM {#Y$c 4->G򧆙M@SvD7O!gMnÅ+b> hRr yp tw h^wU@N迡k΍`U@6pP%@_u)z>y&{dX_'RX)MZ[vVpC&>c7$J!xjLJZs[Nkڞ5|#(B}b -vCj) @X>CZOi kygʈ˵Xu+B7XSO?;|X\ ɚ*T Vxd%Ǻ6~TC( 6әwA%]ߴW=C m߂IUޠJ{9rbd^m-сqН#U]A3Q߶ ˆ8D" E`}[IS5D P>p]\݈i8+^]`1 VD6Dp"xDVh62Dy#4‡v_2 tЁ5;鿉o1U[738%n}̣ #Y(}~Pe_G~ynQo-F9;^E./BEyͻ~ė+Q:]v AAHHUL+xb1 *|>+$Ѫߦ&Ef ׯm+_1Jd #?Ϧcd[U)%;m<#zp+^Qv27gA=SȕOA jXwאOiay86NON_SZO 3 8ˬļ@s7RȳSo N8ߨVRCۨʎ 7:()-~]t qqkh+#9?VjL@`ߟy#gwL3jK|RR'ѠD%hJKS1yo'-:WVKܪ~"VG~4Z=a#n;tq4g',{dWz3\FVղ w1 ?uWӕ2( !~&4h^ôշǧIxA,s_r1;IUF|X=3rϬbGP 1% q:Ti̫wK\_r͋XˋR8G7y292Q:`_[uzT2NvrM]`]}uW?Xr%:QÁY}RHR[0%s&p6,hM%++O9m&|i)o3":vǭPcd`5#8*KTpS*\R.- e|lb10;) YX(EPpn ^ >zI e7}k$xkI8#.z>BvFio}aVή'Q2u%NuT߂5`-ڪ+Ť;Isځr|wn9^r1 oTF95G74E N[_Z5h:87جLaH! k[6iN/SL{rS 몈n~I Es L^x~>pPnςMÂ.2xS^R{D#Р>_5/d5ZPS.LVp.jjr&V{ }Wz%C>=#>olPySZ :Ιjb1]x9"?e ̭|yÿA;{Fa!ѿc W=V!y#'אc9%+0L6Q2t~ ZD< gf] "HFl}^˔s3U@:qt^4=>)T߰&uY}Etg h۹ eS'"ʄ$Q-߮Dijy5]-mpʆ*2HGuks bI`V` ZIP|0UlA0]1e~>ܖɳN5ɷoքvϿ-rP*?NDE[]c:ι VСdHLH$bC ͔tBl5c0&V3K T9}\T2 rjX!䘳\~k'L e{5tI}Tu67'`hU\[wi~TiZ Sq^rħ*%Qj;/wN)%:EPiPӼ*$鹭1=w}'0k=SUy>k'ĉeStkk<,4wY\tUfm:!PԤ OȅDP6R*`dq^@.D/0\D?ևGv|cA_6F'^G uKkѿ$|iJ Z6g$^⎥4YP%MHan#|Gt$sb % dU0x #S 'Ǡ̡->̉HVnv|y/H_M+:(.,4yHξo qwׂEZ2¸4HG `K3}KcM>dX T4GF :p&f9fС/ڡ2A23/yEquҢDKd#oD9KW.kcچuy}FE,p>Jf~*ۄL~<[w97}_H_=l_kIυ-(bаQ[wC#i.%;?NwpwMh~UssۨL0/$$dߏ5[ayߵIK}%Ufi >IqK)6)EI440C*#:g$WseH ցw!N{C^-Ô߾0,3pAb$J=fN {y+ju-$z22D3{VUI"iH?YK R >E)n~.Aȫu}KK3oM)7}DT)8 9<Ao)Ew-S0lw/xy͠\a&4TO\ˁ>O&: aQ%T'kz WvI|RU{ w#BB5B_9.uDpr_(8,\DM.u#"A} ? &>,ovChV\S_Rj挊xl@TDa^c$^=H"Lr' EHuBdJVB1~(_.Z։c+ W,ٜ 0z\$R6Z%ud  $_p9bƕʜ|Ü2rH!|(FhjaHxDm gشiѭ7v('䉩$'ܵyaeA H^ȃ?D"//*eY4F@PUn6^6gWH9G! =WGTϳO PgG9^iXM}js\HI@XҚَz,B}U0HQ¡-:W$:r 3}~K Ș6S ˜?DB{-<("L{n2>rg}lŨG<] K,ocly9Y QF4qy-zH_ۣ25qطc Kw5[/fCO]CqgSxiFV=?/C W:u'o>AF_߲HÁ^ƌl^KSٱ8I^.8+ {(FL}9"$*GLa.vF6r1$'`M}i1tcn=u71|z>[i+B3%)kҜx8_1P( 2hl@o Na$[ZDZ/A3L10QS'7#ӯxD&27D̩1b-MA&գRf9^0{ LB~q6'dj\M&z62 GNV=o&P.6І{{K!3fX .yJJǭVF8GF3ƞ<)#`7W3zn.@szpgSʯB,OplrII%B[lҮ Y۞^e,0G9'W#Gѣo4h0[ߍ2 ~ZKe?dF`>zJ .Zf{=ώK{!>\Hr&_F0t(q@ N,qd%0K_`3|)ջU$m[Pk>9IR5ǃ}2{* `Y֖Q)YT'(OWiZ{W|_6Ԏc& Q.U(D֘z48/i VS%uu@Q(y$GSDiai[[/A=QLC8} /*HI&3=&8clwhvP˺N>"R6ʶ@z!|m*W}"I/6V/kSםk~Gi^ӘV}-%9^ FaבZ{)<-UN\oSw `[ [NpMlA*X$U5_7/U{bTz 6L!o>b_*%ooTtlo^cEZU/L J޷m-)$j2v"kFTh՗^-|2 -UZDN~t5h(:a~!u )m_YK ?@0Ev 7[$V2hH 0Zo{}Bۂ#1mg%i!/ ?|c$,,ecm+=x(&bZ=kpd6(DJp9#}r@IetT\ow[X&/tHcYo"a` nkK@XYmWjլ 9[Jc6kY $S^yDrl%TG?B̼n/#nd8H[tY=gZ2Lhfq>߉x<[5&__zC7q[2c(#08뗾0zGaO}HX#Xx]Rm6+HLDirW}PYsYۈz5>f7ȧG>0RbDVJs'\U !>Plqvv{TqUGx+24E磖"u8Q凲Mck"27=%?$Xx~^} Zb[UI}FYבz}|dgK6Ukucrb +\* ȁbA3 ї#=Wq7oq:ةwILC摕CƸ߱rO"%2MPI#=z~wKމf;?+Hvl5[%c@qҺ?܏: sN!55O늫W?Fcm)s.2TBjچ}]$!\Jj\8TȊIhR2:GZ_R6=I=mXoғ^%A?C84{\i׳bN 1>:$R}ȏQ&[ Ɠg" v a_Wf'Jrž`pzΙYuPD R QYNj%a oZc8^o>.EЂ u%gDc %r ޓ-SEQbuˇ2͎dv|8c!“︆ۮGF*X]%c V|dV%6΍ĭ"9i1C!6J!\zxDOE>\Y}=$E@JsfôzIWCGx ֒7%̈ePAu-.3v,fe?_[oQ8DܸSZr]@D)}I炑J,Y9\n+jP !)p2H08?Hn0=F^G~A5rt[̌ۈhV,vk: \nH؈l1tUqe\鿰Ö v A?+.A2NNקxƘYi[:԰'ɦ}/F$oQ\Vz\ק:ѣ_ag/_פJaï`9 [@T9OdD67,1qma\;R"ѻo RdZ4Q&>4oȘ_C x.|I= p Oԝa7¶K37kM^|;`14Y-:ŒL#@4$Q\5^OrVcYތ(|u}SCF@ImXJznK6ݾ)'AD~){Og+pI]*݅ꭊآr/x>B㘲>Gx4IN:pkY=sn~,j[fFr 鯪 Dxe Ħ΋vkzT W/լlB届fUb"w/~n_# #QNc H3K{+<0#G һ=FiNQ7;t!^ ~9eηAK^=]8> eA83.³صrL )M#sq_~ʸWOS%ucI8**]H?i;.rWCS{T]_qsr]7z9 >:1[LZ,-#zѴMѺNAYmֹ\Lw] z]/}()T-?u]HV8, D|Ec0'zϣ5 FPL{6$؇uy6ngrR5`tuskaօ_wV&G_W|u7(̓{Dq(+3 I/3ezJ&V_q cU1sָxʢpɟu !C=RW藆ף!4_~l;7 /ujS]qbKMJ]r'>ISHuVW _1V@ J=䠠)`"Rgcr](i/;^VѬMyo w`$¤|])ezD?#Q61SnĬ#30(=iT'aV44)P~tGaC՝NL}쐺b<;W$5NcF1aCojCT\Ii:/o#L|vv9 4A+vrRӇatqK$P ZQM74bTi=;@m{ß#tcY  aۖd7{_5ю-^3F 9&[xIk:hu%R.4lx1lbkZ%uxf}~ ,jg~^PNR7rz ZÊ >Uf*ROi牽kChy wmAIfڤMި|Rkz$GmRv<,8W[Wc_||3"p|0ByB 70ktDaVp0q;xn~-4n?U@C)K:tѼtF; Sd1DLƸ-ldyHF0`J̭4!j < @=ws>4&/-ǫ1B%#EoQ;],XV$p̛i$TܵR۷!͈ :4ʢ Ry#Htgק6[E9d[=a\N9UoSZK8bЅ4X漯͐%/+POc[R q~l]:>=\IC _45OZ"^踆,4Tp;Mx#Ga «b)%Lt?hͩa;*bxIM1"ﷵhuQw5ήGִ$ӊLhn>`_jzo=n*oKo:;;Qم!G}Sy+ѭ7'ׯ%3Јj-o/[0m]999B!Q ){\مT!coA/&F8aGn}8^₋@1br;tگ5L${,*iOs '=TyJvż } Ӡt:u|g?aZʿUrB#Z95+9ӍO%WQ^/3 ԾVQ;Rti ș5nWg@8tn?@ׁTR `|۝@y&1՘7OۦMmnMlPVrEuY# r2c hiӟr#~7.>y&as"CHS<-R՗Ԧroc:Mw[{<=b_e)E|z}D;v*^2W}CV}:16F5!UqIHŲݽ}vDo,}B5:{yޟ5ÂO9y홆}[nI{N+_TNH$=B{|̿J}Klw0yhV},w+՛|̩c]+*[JnRdEv>G jU {Giy`жR^Akn_(u^-AGs*a0Ft1{t3LJ"1XQtֽ4x](A_V~'-=|u%7<>`sW`ğ"? ./|݇HW=9iG]IhE&G).iDrJĀ⒫$\$h?@Cn?>Wz>|qA5,hA{;_AcikIӠ`1:'ygA_Hʩ͠ 1KҕXIkjƪ荙5_sСLѯ(j#TB݌Ntze3nX7"l{5iX7f_~* ˞#*;*/\9/Fg~٩J5AP{E(:`|wNȁ2dކѨاWI"k2$.6+ߐ3|%A$KO:]MXe4b:BOya}Np"7S"K^5LSKe)BʿDD7YdG6/W^8 H%<bAc% FIQ.`O/8=YھGNXU;&\w9!*)UZ$?Ȅ0a='P 9!X u.kdZ$UZv`JI}58R '+XSeQ~W*54 a^I(o.zUWaBZ]_$#2܃>88?)65j# m,Z8X#$ۊUv3ɆE6Hn(αPyWjPY5EpRջq}PMoAEaP_K [$^;02c)I&~żrs9kӫõ>c޸cykh8k&t=ExP:WG^BS+vbHZ#`%n Q="ʑ5Y~`mKf"I ډd4Quneoh̴dA])ۄNJAF# 5z\3Vv^MaH,.y,_ OTx 蠔&[^pTlmy qhlw0kD_q*`i҂=X\_x(ԽGҼ;Jκ  Ė3T/ VRMaMUKDs0q PVqV9t:iVIBCuO ~D33T^E ciNH|H|ſr qE!w`%9￁R%(MF7T΍F3)Ῥ6y*/GC^-ImdыfWVKQ nWV*Z6q!A2F@WS=::ܫZ%`#W^IJ?;7wW G ˫0,pOLxB}V*lm4n:oF{W/~dۆE#_p7a&Pst}a۰XoE107lv_}c[Xpe !(u5plF64 h$lPb\_2"c>nEbnTR\ѧw_M^s,TM>23""'6^>:}baIR"?ʓN tc{M:T nzPNe%s*TV> AIW^i}g3ѵi0n.
X}6\J'AȎ:fO7-*7{Xi*HUꎱph TƢ RRD?1;a"36\im Jo--\.i=69-g/ܑzE)4p*R=׋eo|boH>i<^|c,y]Xid3y<@uR#Г nHJCFаҶ qd^ӊf]f{EA>yFMb  {ͼPWDI{Kۥя"\5D~-BN[h3'!3hyYHVY8 x~~첿=u֫>h4,똃/`c79|xO%6uu]A ouP Xo01|hJHPt/CßJBdmeOY]{١H௠FY]ŒTݯyձw}:Te;Kle4<3̊SLE1C{MQD.+lװÇ '(^zcRJW춱 P 2]tAl@<^zƋ ل(6dNB씟՝_7& *v=djDe{j]o߱hNeZy8(}'bG{4QAgXk#FΣ-GaԘ,rM;>hR*̲;Kc~xJrtck?x!>`֝+]a dPAcZ |H!ee ^! o$=ǯ4`qSiSV)*~w|6ʀ1"F6<(ȵcβ(hsWv CmC;2ށd*"n{>a/=+iQB~> .&RYX1]l[ U+,R} *&0P&'X:|E7CSit7JaʕЧ8VP% Yop)mB{Z}}[\" z ;rvʛmyen ~Ga+l8ӽ CB+Wt͟R 2)1CɃkې`,d{Z=0=|kPB@3/ W7"$G^g5t<%9p&e _R߉ 0qS;h$ '!P IG׍ 5UK%yUu8?F5qa>Hf801?]űr6rҘmܜ$o7H{i3.3>: O0 ``Q $e>M -h?k7ńEg7O8}~pіZ@* 0TEJDE(IF&ؗ+Gp"j=!Y~SkG^J(*ǵAټou,usDbppnP9QC}>'@rK/Q ]q߫u[*,]KeK^)<άu*R٣oHB#P"=E -MVԺf <|Vw&j]#^F S~cmDsgc46]4S#psoGd]~yTb ka8WJ>c>:2UObxZCpT쭦{KUIvc4#%%x^g6젵,BꇸO~Db MʨW(|67\Q$@r @G+&G V2bW,fAhOlfUr #Z,E>&'./On`A/^AOH!#G_|_B噄'`M y8 2 }r, 7>YF'2jϭ "pg՘% ?}nBhz2yL{6w@T*puoD.3_QosaVEÝH2(=!/V @:ռkk5K JySw$gzlf/y;n[ mI c[at|&C D6yC8yaJ-ʗHMvNbwムJ7i9}ݷe6yKy"T7{6I-d"sy#yΩ/d´ `/D!G8xzG#ۜ:W'4.}~Z= RŸ#:~4gX M` p!#Ɨj8 dNƧVX'%}s5pCۅ:SG 2 o1\=5sKHS/. PCc{ev{RBW;6I2G~mlh>s OxXE%T=zp}"L$ ,{_` X _kϒo ;׹go؈g&"?1<[!Zt@[#f%&z|~w(?ʡ BYk7d]P `E`Y1,$eu/&%#yI}'&Q`JZnRRƯBtP 4slcmxNm/i#|wT`L8>iLfe@ëY }aaW6 =N.Ւa-Zl}ـ~dG902[7dIH`MnYQ*(=/r.Ք,G`l:6fYRb[nl0 ҇{9x[QF,}K>`6o4L5҉"#[#:@e͚u,Woua+ AF]íR¬4Bv(*x2s;>wϼ"_ lu^3.Wvc;#9- _?,b ƣUލ[=oΆ--oD~0p\~͓{^ 0a.K/dyD m,Ӻ:1͖ W滸P#cLq z]>\ro.-tRz9S6pQ/q'Ji硪Y̤GcC#֩ḹݾ {V^9̿ܐe)FHzas Ԕ,:ozXT}|ЁTZ1kC-$ۮɿ?}/V3}%8u7J.}]DKэ >]Ik_Q_x3ɩ ѷ>|O\l rN-(1/iOZrh80J~4][5otAI3īϱR6Oףj(;),ai` ]\Pbva4&$!UV1"9E9`55zhYvajw3JK>g'UWzpMn&v4+c8!D\jU0yO,:Վsé$gt:k޿߰_b%_[vxc#TU,pNq`K-FUOp.U/pZ W3/zS'8dze^{SH.vF=X?#M Waς3;2DfXiuW` Q\4퀡P{¡5URV_2`"r፳ mj#/hy5aE2)!m:T&zT85YÿS:˹̜ j*pb|ը,[P%tt O0|<88MCLiM O79$Yr.Y7GGM[߬h6,Ho0K99X8_]m?.U;:>x;X4xG+Ud QԏQb#uvw$Km$.z{;!kYIrT[ۻpI}^vqك>Ѹ9+&0|F<8Vn+WB)M/|hc@f$_$ pXގ`AN¿K^s}7LqM +}lD> ߧaKȫW5;E$A=+- hSh¥>&Nk6ٔA@k= 铹ߗ G CL Dӏp81qZEu٢|I=1QDkaT'o q/d W$6F6=1h~(D3"!KEX8˝4p;AAv:eT syQb0۰*k}?Gu]TpaƼ])1ʶ.8zbRȍ^Vf7o.ˤ恳h'-i^vҎ4k2{ wXy5ݞ_-F]a52@TŞ(=z/K6<о?ù u~V1e܋&J[p~En^)^8%8ϊ26a ܭ3\"kcLn Rn##S57(gtZq˪ dM y(SMD((4>$CXa"y6+V/fW8z~Qx Εvm8l:)Z )c:lX}ͨ XfX8p%PM5 g#-: m2tMAjR_;5J xhѕ!kcO~!#y=tN79oa ԯȮ |xa&mn m3Rg6丢v,>Z6cz~Tm@ yZ0 Qd/XN-h@s~]:A8>Fz)*GTXq/_ X,At0ROvc ?~[vSjV Bq{oVT8G3[$ly*o5a飁"f ^ErP_p1dVY:B/WE54BOv,4ʾUnO'|s 1{azŮ =ؔb):oreM)`ߋ 5ҽ_sn&CTL-^Ji4__v'Xa=Aƍ8jWx*e _|]C6P%k[fѹO,:W:U̅TR"EwSM4,5#S].yh*m! ߔ/W =nE+6|7&/VFd.0\GzL<($,Roa9䶶lĪ_d"uꞚRQ8ZLrr ǚl»#R x$q8ޛj4#bBeYSK,*r°l,9HX7C㭏>`;#C{B93w St˙ *3*ь^X:ð;d\D1}ȏ݈#(J?3x 2];WkKyu!ߘryjc˂{UuyI5-չT`E-`>SfWW(&jCsi6V7r'$w0!|F]#ҮT'KZzn>&#$q.7ݵMwRbv Wxtw(S4Cty>qƭわ{0KD__U+mox9ɷƶv`&|!_4!~8gB?%smPS晟_Aȃt}:>oE^zC1يౠ_T? Ry"J>  ~-{rpO<>>W򬞤Tv&DGሆnli\XTf`_y*<ev-%zkI^|vWZ)r4t4} Uc8푗jH};m{| Wk^' +$zkBB-E: V~*"gd:5L0дyF)O;l9sҦl* ~K@ث 7b< YQX-qYVrVR>ܞ[毋6GEN|5ĕ e\։>4O@>0O6 c3] (zW.+]֚}lp㰲}go>T@x}{I@*ggp50tM}R!o Ӭg"GloCT4̀W&X,ҋǠ2vBs,oWUVg\!};-0)t(RDLnlà]"oǖ`dO\ 3^m"lѲȝ); yջ2y>b{' ]nM- GcyӔs (T?E0 K)n*<3YD`p/1hSM)Cb~J8udc-kQz"wΞJ1-h4RƐv(K@ Mz枋H#h{˪Lcwx\dw'(8zGdg+J;AԎv>yhvW38V6a/ "IdaUz5g$󃇯!l]w+=?):38(7u):Wiٳ(vPvϔ.orr-{{a "Q<B{5W*ǁ>?˒wG^h+_yp88H]]\ kE:}1FKpl %tգ'6J%d]/_H;SgBS]M9aXoQߦgCCsIѧעRA}%ppYCJZP Z"BٽLz2P,pR1ӵ̛hD2ͣ!M*DᗉdЋ'w 94ďuGm1[SVM9Pގm˚^b@ւylOG4vD}|{ ;MN (yWuYx&'3Xj953 l*ؤ QmAQbJF@].txjE $Z+H(=w=]o>񼚉E)7 A^= Ά/)p./W(ub8'Y-?aY6G0^0cyk D0CN"nxmaOAʈZ}•?um(.\/,yRVڮ:g^i> A}qk':9 9z}7o{dO=\Q⠎[hNhzqO*ݘ?*vp+4W #2B[5v$!/X~13V6cv58ÍkկǕ)GyO fp>OǢ魌vN[z\)i37$1A(cyj1hB"lP7Y߶rԖђEC5)&bQ?14f"Z \IJ@g|\~oc8(k( Z'y'VgZSom|E+ʙ>3O"u#+n%5yX[[M 3Bl}¨T045`VpL?m+1 rxY^A`=q84C0k߽MD"0DwBMk.iJ*ж+)7p@E1&r] ݠ-ALRtԃ<)i! \)2X_l i*gOIO=<\a,#Hr~R\DLߣӄWxۄwlx_i w|>>Şw2oK\T,ܧ܇h̶^9{hĪY' iNnmCfѣ(X: +GBzOvŴ'פgntb]oÀYnv7Sz[(Bf3!`N2cyuˌc֓8L6^ 4.nS*mVYۯB~=Ck߇>GPGp!fd#pkTvSzo,+ŧ}\yno-ӓ 8{u˙it˄[r*6$#;wDfW -"`]+TC5'Wc@\1Aw Z,g)u G/M%t $ )䱂L5n"-Vz3 ss`*RΤi#B@h^WsMŲ:0}Y^&? Ėj ء><#lKg{k'gˆLr .AQ*G&&hTu6Sܥo Sjz!/$Ob2Z_^Χ++]햫u)7Й9 !k º-(-=g.ڦSҝXQ#]63s rF+ȡH8(-DqL,hj72HY,w?kF8qܙo, &bw)մNw_賥:/vzX̩lb!¨^9Jk#A?+kKһc+(w=2Cf)x|7a08E?7OZ>:>֋d[%i,֕{1MLqL~NA:\Y@i Yp͝1u˧P 3Ѯ;(mZ.֊~ICx"NV*쁃ɍ.6'<@oA1 .޴KWb(ѹƞwtQvhʫ> wӳfl6ckF.4JWc5qGcC1jJh9J9%񈖈  Z{8v!F F7MuJЌq}+m KTx?S~WpbpTg6:NU;G=.wtƍJByX_GDV@ƣ0$"y0Og<8R4d|e1p4s!.ˎ3ά~M5n Mm& &`ڝGkAAO7UFsUҭ\+HEOgNMoړo 7an@P ۨi>%O`~ޣGy n֬1'LsZ!:hcvZhaEߍak*h:tgu55)gM)ɠ|qxI{{Wl +mʵ'wЬsn󲬵ϟZ.) J$N稢s}_X?A2ra^~V{T~X^ ~<2v@yi0/LPmZfU*^"3J􋸚%kJ2c|#Iɕi;'Gi*Qr!0p-`*H RslB{i%zpf,պR5̲U1< (``罚7U/iԓ핎}{~oԾfh_`azi:Fr^)rT"`T:_T[җ,T=3Ui**DA_`̑Q! <ΝgX=M^M`BMDN9EvHpqЋ)9!śrBP7ݑFϋL3{Nnx#PS ȍp%}o9LyJ߃ͅi7&%8 B^\Pl/tN+uqre45*7}{Cf8r=݉rWl /X Zp>J< N8/g*7/A е'Z3;4m׶uU]O~n:;֬2"t?ϻo~)74[Û[ ʔ imOc[Cjj-ڔE'fOi_`KUAA'p7ޓBF΍pKhTvÂ(Q6jmxUKްTBHdD ]!IǚNqZ3at{[i8Džba aR\o=b/|Pmj9^)ߗTb8*p1Ow_c9쓘fu$>r@r *Cͪ.vM %uL牰oϢ7'.imqRpR9ɜű}FG󀖟C+p%Frt*r!2fwq4MU (<6U3fo ]SslZΜ6/R][_O8"v~*\ X<콽ٻ0K4b]Fi 8F=SjNBFQn8YG :><U^Ne3!z TqhŧǩDϧ,C,xMV ̚p^rcVD||Ocھ{2CE}vtcC@|P߰*@XE)wGW0{~3NK=ΛCvY龐ݬhyQP[TM#IBۙ`0Cϻ=Rb}|>|rgz#+ '`g }a4o-Zrz.> H8omLKlΕ2W34t6̉s_C~KX¢0jj&:pn:ATQK~'!fRfOdAT9-k6lFBܤRlPو9QK^L8^b9WKne"~ULxȗ|ONX{ ;uYzaJ Z+^6X O>D ru,8i% #:?t̊MTW4"8c- w8j +pge,SO-}=r 7Z~V#ȌK$(@}eo+מQ^EgEs@2aUz 4i9l*_^wQ&"$½ T ե @}v_.eҽd7>JG/f%c_Jl)Zzvd hgD]1GqG zdec/Q"S[:b&$3~8"Md2v u7cx]7H'XgPgɢ<>Ke[J \ֲ["6H 7> }L`,F?;r%_!7ss+5]RCw YLSڃN+Tr Ry<;#al5wn~"Ʀ$"Ǵ!9f+3jNW0GKh1;Z?JNu9( !}rdB][rMqLZ`AeEY'6o\4oEl,~ؗڻّ.JO3/Ep<\OqZ(Jw<$9Sq=4MˈĚ".ʵ|xĨ %  ca8N,ru$s<)2fڙ6~uKAR~}ngg=vYl6,ez;Bz@@!Vƻ* Swc6`'eYd+fe݀?L2IB}k^`":|J"K uV i]K-V C4]I(~[$ٖ„$}AL=vg>9F0Kʳl*ÓوKao7@Qn-k8GBF9Y)_a/vUo8c4*n^h"Ur}o<|h:M+\h.8gB|ItcwFV&NYNCyjg!3tH2K\94C]gY)f|l4hˬ>w:׆oyT_Yj)^;R~8@p#z^ ;Q;J yQ ׉jnLlV: 9#Vُ_~^ᱤF (nŠ=?B{Q5xTâ9!*iSjPb1i]9[%`J(oVN'QS\i_gFAnӪeoU -՚rkEg9%2n|iR6tDlUVj7D ߿fu;1d/tO8AzF{p&UAjY 5rM0?vU瑌pyk,3x-ߦ|NofFe5fPt_  ˔5gI$Bj~ǷnX氵Kj&"r ]yARߙ23ں+jC9t8Re>/"gNG.QeN62`nȌ F#Sd #A1hQ/5%=-(45#M.7g7ιɆe!-=lr,;a/_L9DN)jY:4LjN-hVZ]= |JÇ=yu-},o%k _ V&IAp9>"<5u>УEb\ 4v028ϝW@ Ym:,dI-G]XD[ 2EZMJ*bN;ZY3둓.!$5iR@=eF?h:ނ4 O>OZ2Ԡ&nd+ \|Kaa?2WYԆiZ k„M Wf\X8pRPѠS%O w[OVx? k0QZ?56@9:rP@,[r@-g24M,cTC_/;x8i%e>CT/hƟs^#]Z,UϠm9P9gC4ޒFb60+ڿźk1nNPܶz"BG 0_t3HNpP \Пy*:U~IMk4 V^N nhhpԟ0TTϻb*~ReRk~s'u-yr|[Iy<"^ ׿OW#A+hYefZgba8zy<ȡCw(}Rp^v >g!*߉!LUSo^ar>[G֪i 2w|ƒ-f+WR,^ykB<he!t>R{̭;E P!`V>sOIbLUiꐡe*j%\`X%W*Y ,dLڑ3`̹jk薾Ш%I_P{w>B9K=X:--e)aR;Aqw~iAB> OUݢYM@SD-̘Pj&I᫂$4KN]/j钕:XBIVmK!_y+~Qwc /1dN8n&]nA;DI4FH:, c4{w16bGl m dz%p-F<0hXk}bEyRSHؠgֳYO)O}kH7ύё|bXfUjc)G+څV/6j~ɏXR G;&tO&6PJs tcN4ԬA,76sIN@_.n3qK=NXNb<z~2xЭJ@_Pf#N\XǼ0f"2-;T 2Tu-].Y*]i~'Ձ F3 1ORۿ|ĺ acqX0$xs;C+{yh7}_ jnAvi ف|\lcj]`6]iY_kZr@R| fGg}9Q Q׮D((΋v (uXhO3jA_ꈀ&})FE_vX08wAVu/F ¬8V wI`w}b@oa!7ewM 6>T[/|3S{ ߫ؤqT}263ڤ#ÌbF,=R2T5Y藉~?X1uFAͼ@s`Y[M;cJ(蛈qkY;If$Sl?J oN kR_ʮUyE҈uмh# ([𱄇 /\&ӤdPUs+rw)P;A=O 9_l2QZ'l?F=K|g%'ѲzL%>Xbyv&: ~}/O1VGjάa(%.áEr+[!Om6JȋW8mWUE鳨|3pA *j#qr\'C,b-~@up#K[Ik]'r/_Q'g#cÏ#~_֓T%˔X`֣LhL5]_1dT ^RT0nF6v7e[w:. }&pWӰ*+Tgz"܊U~V?ST,$!v?k)zNK"o~E!q}!7iªVF88rL$U2}gY1k?ik 'snR|eӇ{h V]cdg)6WNserlWlL> XAk}Li{ ?Afu%azUY^  c<`|fx*XA;x{[ОS :0)-f}W E&DZJ `@oɿ$>}?6'Fa>ЅE"Fd7_Ǻ'rqJm ~=o3u=aΠyb= 5OQ6.ۀ<1Tte܍R+'\.W!ozoҪ|针TGBwr\AM@nyD)]]#A#Δ#Sebc~.PL x% tS-&eW^V)'O':u$'o&`n, =쌇>c[͝]FOb̧? oՋ y{w|A1C? $cc5A܈Ym3{|1PH`@uj{z EO&UR(1,-vFw"2WƩ3cGuk+ܳԖ/P+6jHn"+)θCbKh:ixRqik( /Yj96>kFkޘF"'Hc[v,~}_AG)c1+,=23N-xd6.ophC~CC:3(&|tFcAQ6'];J伺BQx|lӯw=W_wI=&ڌ )4_F `KTxKxЦn%gv$_Tʠ)#?&@hZX ad^2$)_W:a+M}4a]`dqUFg z<$x=Ң 9(UM*2<ތ~VkL٠bv˖S+9G3t@?Oܿۥ&b6јIkHN>:êHW;l #!v= D)X]XZTyocHV d!׸hk鐓3U#,Gbɡ5bv[9?~OuKvQWw[J}TrO VY,Oepշj>SCJ%g|٨ٖ٧WZhL=z@prXa|נS5 QGAݕ~TueE')ge?!?M<"r ͗$vtxw ň_ Ĩ:>KǓ- ,;0diZn$ ;b{:eTTU\s JiK^@Up۶~,O]!D^Uo2lzu慫MRxy̽$BoK3 {PK+[޵Q߬E z)Wo؈M7ϻi=#C\s;׷2]}4#dwa„J#jh$}ܠ]m F(=H .{%'O05?10Q;0@TZV?6(7*$zp P`u/;صA xgƂ^] 23NFM~a}ҥTJ%Yſ)!ȗ.؆ 5Zxϥ8T*ǁs{e ž^Iq>"@i;cNg{xĒYt9bzO8"KV 蒓j#ǃƢp4*A߅4j505ű?/=ܵ8J]'3P79X8Eev3dP.7DW٭հcgBMxuWP_,7VOh,&V=y Doz'hyH#pwgZWCZu%W9DZod| z"!@K!!ϼ GT$S>Fbw ʇ ZR|=:@A͘bI jir,ۻ0ÃȆte|!rQ"qS- ryT\ۧkُ3 M푈hf7?WͿm`\[Qԫp?JofVz}D:ɜ9{}7n:.\z4gv)y` Rwo$ X`C쀁&:\fT_=qeV?&<&Cn4 @J cֻbe:B}:ru$^f ItTBY>2pwָBETՍߴ)9aڼ13"ҙob}rCQ(W oG5z9J$oHvCA%ڍhL?'29b%apI e0r18F<ϓaldNUP /y_ d=)w=xgE p?!lSj5jfW_<4$}(,5OKC7rҝmH1 c.}]L8آc|Prۮ,)Vhl0 ݐkǗ+@GNVr 7! a ./2DcߖI >/bG%ͫ62[p.&I-?"xz;Wo塺REO#2!YSCzlPr/!¹f2|wXakEF;~ǡU~q󠥠@j"òI|[xWF!|5d'cE:s:ՃiA4c _Gcr LU^a(r}r?Ǯ@ߪ UEm4!?ΎܻXB?2s9O.z8 7ol:{|"<np~ѽp*ϙiҷ؃:v4ޯͯ= _E3.C#xZ},#yiõ~XsKFcLK#~w#q9[7(2AA5A1 SWh%oԵMTJ5@cvFֶg^k;dtaqlc_pN+@yO;>n#WǏ*r#ϗ4;Hzoub }A5F`z0F>!VpWx%6G,~i@E<;+ [G0e=[; 7c2~( U #*+vQ%nW dw~{e8 !%-w}w)S@EaNυ%'{0X2gC?=}K9OX8 4#XNYYU[.)|tAJf/'t/obq3田#=e!LJ-+_09o9UDa-b:f Ior `NWY>uT kCyU)1J^ ϸGk$Vk 4|$SUa|}8n,VkP#kw ΢$'UJc# F^X8/C16A||+ gЀ 9=}걠4&郯 '!U}wU&Nov0cB`JO}T8[B׈S{ƆՔQ{4&WIcxuomheaY0?/ Yoڴ^yc m=. 9'gEzv>׾;J%oljX5Xhtmօ ڑ‚DD=MgkVEYpR u՗EpIdHś,<9ةVzfEsgI+(Ҁh&58zSaDdn_(pȩE[ %Ŵ)H>$LR)@ jo-; B^3yo@*#Z>)Xᱱ{A#f6vuuC/*f.2^mhFQ[HޭRkٱgԇ>cWKI YʩT9nx=lݘQ*]ƪpA3 _yֿ$) 8p#^O{W+}e㳽>%"gwj:οuÐh! rU$iVpi"8Q)-WD^go աgXBrs7'n]V@9Ʉ򻽢^(g "{Xb&\$Z#+D #?/2vych}s.I07w]^9}_Ɒ}_'EٯG VDg<(; i%+^_zV͉G~q7>]*^>V\ݱÃUb(y%9~(Kv `IN硎\|ŧn!v~CѥEHwPgmS9LeDBv*֫H!YK1FW(spROA1T7}`k73򈻜OrqGnoH^|{%KD}fz T=>|cmt?Usȶ_]~N2~,Y(7"i$1gr -kֈԳh nz/<%.4 Ѩ`Ek $eI\nʷ IX8I=z<#0גcǹ;l-yȜn_90׫{y0aO Z ?2/~ 2>Ĝ-&љyX`xx FМ#A3n1MO:NE!630[ؒ1Ե@\/9,}!Nc|v[z!:MP[^Hw 75/0A')*ua}^+^2AͣaPN%o DžW8Q᝵,(nz3)V(ߙEv2gB5f ^ھȚ!3QE)~ :woCq=dx`>SFW:*!E|-=T1߷I Z8QԝJRYRԻ %GQwdÂ@ DHup2 R^\c:3_M#x]m()ϔ?d^\8]&I#;I+WbZ~``iME SL8{|̓Z<)In|2UːiVMpļ3W8d.,b߂~g? YT1>f׉{3}*o:k(PNWoV&ˢ|㞕nCrҪz83 V_旮of'S! v{cS9Dp4ʚjt:_.wj "Cz댏1A@0)\ӈs,qiY1LK6S` o.28#lFwuTʌut;L%)̐J[4l6q">np REQ$A; ^l^Л_ϿMOOwnM*6NK8$gt@m[)[OXقuU6V߹h1}Uw "m. e|̝$c˾Z[beђLwJeQ⍜RMtBT r0%0ѳzWHx{C%`JR mk3rl, ݱǛxIt pun14r\:ץ: CNw!08$u|L"J(t]S̑2 TVéZiJ0l[ nQ'z6Ƃz*k?XK/[ziPa$4r/#Ӿ 4^,=n^XJVMmfIf8>3ֆۨR#A`"g>2l7 %;.w*-EC4E$p.̄`ϛ`V"c; ZZ+cWs1j[_nl`N~/ɇz u:qPRxJk%t]1+}_g)eL$UZ}%.d+{MMp }påS#-ܡ6).}o19Ei|-ӧ'@ћk"a)Uⷤer_JELܚ[YU-x daM);BP*QȗOKRۺ'_[&YeTkN/o]|h@hp=(,%g|4J"A^]"?f̀/8JDJaQƺBEjYf? Mcqo'~wFaF |tM'Wa'KH9!k2/QH'f0r 4l!pΠN:pQdE[TGo{tuH+k?4('夽Wazm ;k>ROL:F KxQjBhi& [s|͒ag%l {ΝnkɄ$oP9:C'r1>jP@ nʿG_{՝:~~:/g TAUkw19g^BH"_g ccս.JfCavb۩[ƫIf὜#NZY'`?J7,5!b-$&7kfyeN֙9[RNGR9yLbr\~7(+Ya7;-N iݾPU<;Q?8mX洬,c{9F[N۔U~3mfƽ>sLa1jmYtzi[zNj\̡Xʍr]X蝖BaՃ\W5옴ӈe&i6oK<=A;I3=l;yL'hZitvxQΎ*o^<3A̩lǛF6}\}j61OzMy!''ٮq2U! {?Y{׵)V r d7cz^MeֱHS{ S26Xn{MjsV*׃9a:+JMåcׂt.6tӣRZjZb 7,z}\ }mD6Tޕ cKol=JLljG|:^2V␡_NZvu)i?R#w}hf˧j/W73ZЍm.SmEڲ&־xj8i̻ʡRM,;5o&s*Z k`.A3ꩂS5*bf:a!+b$ l5y{=6x9*2jT3,˽Yu3WF.)vu?*BR-ռڶʲ$ .7d X]6wČSx֥ܹ%OyOj19VPW0 N}}i]'cض*1fVx1(M>܉Yg&ҳԌޚ(`XI1g21ʨG75b7鏬b^,9Ḵp~ד;h $YS`6ffفVlZa֬XOF`#J̠^py cRh]+a_/ʼ>lx :ge\LmZ)fdr7ڬEϫNtj7R 4;iW>N!l:ӘcW)]jnV?WY6c Y=t+Mh,^DZ˕ns'V-VX솜)c^eG/nN-QiPtZlWzVƋV[L82sհbg3m)QcXC>/WF];՝`Ãᄚ`:lUN\ʜ*nڛRI]jpx{Ǜ歰<:Wb)dfx*)׬/V7v2(]g9pw;<CW!ԞqwX(Jl~d^]n紽h^~ү6ps6]>`iyT56֒ Q˟%2m&oZXjv:2m#g0pJN@4J*0s-GswV 3}dpɅ"S+Sg9O'6t0-ɩBadps=֫=YzMEt^Xǚ`5NJU+.T@=Kµ]am_,]\t+a>hQ5HsWԇñCwUݾrk8LSONj(M?Gh)weRXqp&+˩*N< s+礌^(bp5ӱ%u1k3 UlW&n?4slm`Z6FT) l8X{Q'LXlX7MUk+B:54|[>-{2(7Z;zWk5ךtKCZI|2P--ׂy7lhdz mm-w=CҢw^ǩxqlڪ*YmmSgza2:z4ViS1;MXx$t`ܖC4;LE 4wÚ!bade3zwִ ;zWRz\#ю3.FZWL[j V^eNMou<6[0du_6JY3&@/7qӻL}sfFrwj/PFjzhVP2]{j xUc 3fKni]uɛݍCiP>])b 9(9Dg}l lQ]2N#Uf=-~7F8 3km8pqy䒠ճƦ\l}yM&٨fg.zaP=OPi< VRVA- Cq"~4hQܶSmN2\~gQvں6bOx9m&RTζs,ǹiWdevαe'GjV`9 >NVOK6vM=Ŝ^>&iViy\joL4 d@JۥٸW)00^rhf#/S7ͺ-i,n>q0b/~TK bfLn|ft*F59ӣ Z NĬ٣"%gؚγbF-/iuZa8!l%&\N&ّlEkN]gZR9Xv;[[_hjH9"զ#=W.]^%+^F?hvQů6קE%ȚSe<<7y0 +ˍ| I׭We{}7zK?]}wTpN|nNy,i10_LR)9Y if{a6uKW]NirpT+VT+{vVZ궘jIQ~4B~F4+[DnrJ+Q>'ǁzS0 ;=O{5 {lا-ePiWvQy꾍kTSe䭴r>0vg]V\B++ҁS.;$`MviudsYqGKpڬ>/Wv=mg !4nh|5 LY֪=8u|?gQ j찲 X]V}eBt^i~1W[-Ά6n,2vljRnsLh;]6;Il+Ψxe `vF+3uv;>Yk~|qBE #\ϝf_ˣ0pz/&Ue2wU\򶽕E)KF!w];xtmr0-%UmU\Hq{[MmEtqլm]Yh m ڥ\Ƿg 6^g>sSVZk uS4rq^b̈cA8 TH՝j1IUˆa<*=[)iah>ZVSvsi[nυ^i{r]wuY k?wqz*xNzUn8:tj[KFo4'3,DlU`,u;Wr)>olNFV:kq@R/웇 bfX\ZtveXJnQ2)V+KSo^~77 ˞/߿iўKWT[rk9_]߱o[ז -OP³fop>U{l]pm~1>@M{p(NP&*GxW׶cu[ [/%kAۚ<3 \[?).mwO?ܕq{d<X<$Q5d.XGB/EθFMqq.!^R3p2gLmyFA,!T@,O!TS>!YDO^n #MyrhkRߌ;ǟy?C"B_ӇRT}4ڇ?4ʏK~! s"?ω~;Qٟ/HC~D$-?C_D~~4/"_o_?8V~HS-N|~n/e "۬cO>\[b6Qsw6@h?'ǐ>I޽_Ɵ?<ֽȒ!d>"E6Oa헞}69nVp=HV|0zvf{lClCx?gg?0 \=Cp23s4s_#`2 O5K׀m\|mhŸ4,>A%ô?2?U0g_9ϳ!X+!X ϳ!X;!X >φ`WfC0 ϳ!LeC>g<?˿9="rjjg!2M8e_j T q?A\f:3(ATg M%Hzx%0z\;E#:zAKm'̬}pL/ACY`:# M [~ .nf4ͬZng%k?:x? jj+[92-{_O0#u wR>΀ &@񾠡0W-.^lIxVftFߛiJ՛]Hө[T'W:Ktwt!VOEӌuב/mۅ(+x ,Qu}kB q}E?oPb =? wzTܚk{4-1e D.~{맙mo{y:ȗ o%O2*HO\ WԄ- AUL%N5 N $@eJRxC%p\~@2Ey%(HPE>(.S e"`\E ' ٱ/ߴU^(H\D¿wjJ8 ;޳n!o+W~PI0z$\H0\~g) rI8=Id2~Tkf͇) R\9XBC]bF0Ĝ)_`"~?ʰ@rxc@ IQ_Ŝ$ğ6щ7'/viS5pR`Xb %%tq*Ք8MhPo$0rJ|K !-F1vjj C-5r@ D"#0BP8 i2Ca(1`ңဂv0g\W'UOZ.l<#FdF)v8Q8P`$hKo'7$a )FIaks|RXa8L.e (=Cϼ'Ǘ@X :MBuR^Х{ol);9[@hUa:̑LD+nAL"@Mdޭgb@rΩ?,[_8TPxoF'3XmR52%pi J%2ĺOT@G\ťg"*? #O8HU};džE+Us35꽇/]'BP\~'_ٛ8cy%o ` h!8U h 7 i 0X*ߞMYU~ZB n =ߦd~&/!~aB# l1L` %4>Xz1vC5w,]"1/'NMO- S>#qsǟ' U{nXc@a|Sb@5{uC BXoJ)E2PUJKN}zƚl_OO3s+H@ `{|0q]ţG%1>G\_S7óndC鯟JC8'KP "@<N]sVnOo^{6B f*dƟ{Z)qI"z݁|ߺ/*Do|LGZƟƨc>ָ|#yǯX8q7ٚ jC c FnնizJX ۙ?[מ ]ϞێB_J3TΣ$)پFA7gQۓP1pT|jOpDBwC(ЌóUں})3 7dW1~B/4?yMT'oœ ,0OPp 2Ng?][ٍ7V`Lx6߼{;}zO ݃?Poj~ioEeύ^@ dr כC#Ɩ/%;^d'7`Q;X[|"8+Xo+oWfamL,0*U`׮ޚSwݸSV{&~6ş2 L1AyY^Ҕ q"<Ì( SG(D$mE,tSt($b@!5]GL(̬iT.@OQE)!Rq D`WPMuɗʈLr!G54JC j]&+h BoLP"\GU^)[&ʢDv4'e ɮC߅Cl̑VN/5'5tJ ]0 x ʏ֠un"CULvZLlV2 N~s+ v^Oog#ޏqn/פlFAQ&]@ruB#Llerʣz In\@.LŜB~Y$ `f$*#L~lR£أfV#v?4GeA0(P0*MN' A5Ur5E:c@]}T6*KaDĴ|O !+BBE48zԸL$*CEQ+Ѡt=tSH})8s)R A¯hX#r..~RWҢ8YrUL,P (WYQw',s lDDU. "*C.TWXTK@ (_fGs-jTn~ QBUHzDDU&B@iEnaAN"otEIe4+4r=`?pTC&w+ 1w]08NmE\#ojD/2)it[_kDύ?U\G/K<60y5" h8@4]:cߖ9)PS^`#xHR#F$ҽ頉O%b$qq2GuU? #THpbE.jytq48R`/}GEsJ u%~zY$=D-0*`7/r_u brJ,!L:quUfzA#Q*PRzu9sL~_u(ܠN1PG#i@de!0AL P6jKJ8>000 FV.,ko&W{r#=>Y8y54? HgF)o EPzQD vq *x>@*mxpw +xiƉt9jThrXqz'EVI<7Xdckz1w:Qb} r"GR &r.W!4BZ! H@=HXK/ 1i u"kU4\BG$'=H2/q(ݑ2?GX}(C "EHd2 Ɖa]TgA.@hܯ*)aTJ,Kȸ],|>b,`Az_ꑣɮF$(P#QQ$LHuQ́7=qҼ\]&|<.XoD [Hoј^n-a* +?LTlU/_-ML2 r;X%^QZ ?ϻ@tpENJ\K/ ˓G܃c\E(A;`  TF>-DTbNx , <иh/[HIIr#\:Ƞ3.1j&ĉP Mm" , QCn)Cy(qJ9cG`K=d,LT`EӺ7pIrqu "ʠ}qOO2E 2*{7fI5GLavH$q)kuN{N/\kPP/Ձ5\.b.ׁP){3>uG1o"pR h@!C.7i2V9*(jMR %Q%a%"zD:2ژmq B3%X!B<%؝}9I$`AtA$ @. AEdG`P"Gz#b(z+QR}Ic8&X/cvKG//oC{]sg_гmҵ׵4ɵzǧ0<sJ/Oؐy}Mog:q9Bd~X|b[c׶=ze PN&\Go UC~xw;qs1ce qkKrZ..U{ArOfjo7xr)cKlvVK j;[ޮWXfqŠ.0%\<,/9&[}LJLJ__Hxr/Aaۛj,sjy~dn׏lud<}ju|z _-k !>䌾\I ϝ_ߣwԳXEet-.WW_1\ups[--#϶7>/D,j^?}Ϸz[K=ѧDr-Һu^*dkL2a=B0]%_򽆵ayhˋ:buaxzyϋ BO mGhv>^=?/AT_~ Wήw/!; /PhVjژ7wgVh2_enWf_Zu Y#:b:Kc)ae85Nr`"Τ8n3i'0p& nx/IA4j'%Ly$ĝթ_=XhŜtR:RZLEN <4{d:P9KC1p[x8M  naIirc3shp!\4 <LK9v!c2<7>F99dpff^1]35wTWe̤7ĽӔ`_lA10Ef6*qξYZhP 2`B*|^$8J*[ٞe!)t.4A] 9pCW}` @n\sX4siO)(aDJFSd}*8=d -ÇM{`c5LG=_Wu^'$h:!IQ{mAf8fmRMw; wZ3&>o]Zc;vr 9N(ؓo!Ƒ)D@s۪0.[R>fӺIqr?Ѫ@ ] cJF*I]$HNg݃>jĢQ(~-6\7[ۆ)ZtMȤ͡[,9׀P@'J OI$T|!@Ϲb 806jsnbL͋/כLS>rE3FNP(DzdM]æIN%$| Fը遪GUpj e,Pj |*cY* A%~~zuɢxyuvJElX#}+nFNWELWPL[㾇 >D,'p4Eb5TExvn_DFTJT 賑*MQ i:zPKŇisJ6ӤM:5b9 MI :A<]AD0"Y3c P.w\B7HK)GB?c]WbNX5$lgY 8[*"!>kM0x,9GA `~{\\|tCK@SI";$( WpvG"#DF;!24T8]8Hu:R3N͜i`#h'A2D0rLC8GȊ(GBn;c|,B_ v)99/%ȩT^i2~.Mssɂ/ $dɾia{,XF"@pdPM IMKYj;FR/?Ť_ZBea5j,"iJ0ɬ-(&==edyP=m8 ڛ!f yRz0@DŽFU*wY>Sӱ@Ǟ'iq?8Bp{q@eAZcIZcYIZ4Nb3ӱ@Ǟ}N iьiLšTNyTEyw"-ڱ,$-)%'i,3`R9IE7wQE7rRY<2NEw"-&)#14Ø#bms{?A&Ue;p @0riՌ0s`!w@|A 8K3ZViLRKIx3)HZʃs<!1SQEy+IAB6xc&II=(!B0wS4 *u]Q .WSA{ºeVAΞ9CY! ~ y:kb!a($8:8JY /+(}Ѐ]-m ߥL@?/WW+Cw}U7.CK UsPIp3 >mp:t=uGn# Ɩ77Mӕq\oN!V'O/o_Eu6 mx\oy^^_n~;j,֍(;czg,?n.xO^==V#kmPV_@XC'f%ك&f S:DGC`*{F, F)@WF:衴X'lp bnϺ-*Ulz ]- *Wqa@vD:<p) wB藮k]:O fmSFz<:D!#NЧoL`}o1 <䝀^1ZkFgg99Ggp22_c*Vt sI|݊Q]R5Ⱦx&9=¾x1~xJp: WC٩5Y-QPC|-)y#*"RF$e9gQzX@X(}G S_iTda" BM‹4qtm )[ # ܩ̫F˥62x6 'XYP L.xe"j  Qd5KC|&?9_R 9kWKxSծاX֋__ ?ƬS ;9i:*vEZ;N$(/LO튎#Bǭ]1] ǒQ$F*F 1 9,'BmvsĀzdRN|8zHJxTHF1`5 x PcƠjR MZؤERyҺ0 T][ܴF\Ӟ kaܑ”Sr+˽g\8`G%mMnF|LH );C$_ǖ׀Tum485/0녌Cs $sA)o&}.Εe9  笝\++A[k :W^;h/@CW҅zj9Ŝ !), }EmK~6u!8(abUڦmR I#HhU|Ne=2XF+;R 7L=n81'kuk>zk1x[C^:wN8 OmcP<6%!3\K;\A) 2 JL3ob;[| 6f)@^$^$ÖRYpa`KFNϤPA;!>mXt$Wie ,z$r 2y5)]ֈI`%@N|[\-n-KnM% ǻ9, C? ` ?5s!q>7E\Cg5a)a!uөXȟk5G59ф^E8/0s.uqj^6{qt[EM}hI2>XӕbS(ϟVAA{Dr?hpؽݡ$).1"PXh4l'Y-(>@sF_t>:48\}Dkҙȼ2~.¥#q*JLu9m'9Ж;!=n7v;HH]o%}/?$qjC>5k*WL^ ȧ8`#b ue.VZQq~"]+ܥO?X }FAPIZj.SaR߾\M w4pidy KIczlƖ$( "j_SSr~eG? ׼p,et0׺ǀv869a?ovpsuz2$-vJCFQN 52Igiy:ЈNP:@j"pQ?O iMK>@'i ;џ@jkGc6')-$[4<I~n8@:V< :EJ{rVܾdig{ొq GŲKzAg"{b',Ӌm5[h|)w--[ku- dA>[[K%nA*z|Q3:32V\gm}gkM0-Xv82{#!4fZMQpiTO{ ʾ,4{$>/X-H]_)thia쀐{Lk'kȘ3i"2`!"2ft5D꧴H}/귆>aL4X{ S,biuph_ !,)!ACE< =fB73| ũ Di4kmVr{ AL譸Px+.'~+Wd ,Sf AUed +ʥ r\jMg/e6dYi{R5@{[$U,%Hjf.*\),&HgnHǪQ1 *Q/ڹ erbV+G)+r_V૪?b^"V\*]$@{4ZJI#.J,|ZNicJE=۫t_]~YE}}TϥKH|} _w>i%WWeԖ+`UJȸz湢v_KfW⋒xFe;Uku Rgd5V"Pp*m ~᫭_hGp>y1Ʒ6: j/?᫯*4qThJ`N|ߢ~mRk,a8.}ۄ5 ;aoڸ<dQ vRdA  +.ƺXD 9،nӠ@"+UZޭ}s@? VL[Am MIख़~Մ5ҵuJGDf "6^eҵZrkx}g>Y c4J5 5ʳ۞{Rߝ_.?܇o[yqufAoAayd_R) G"S7Ywgy񷛛节~d :!۫_/&V!{,wwa{*Y9pܷ9pl@Wۛ5uw=.-ѵ/W׀f&FG5ew`WqK.@6yXwNjeumy <Т|Bqkpow7Wudcݹd/zw+ݶi[}\]mjZo'ܽ |ĥjmfm˕h{Ԯr힙~]ݯޯ5VqٟBz춡.vFoŠۛUњ7,:!Պn:jO$RˇØ-רT-;~}lDկfw岤g[𖴻3811^[T n,: σ̥dF̀S+S3s(wx2 Pn-7~fɎ/41Rh!x# g^ςn-B!+:DD(pA7)f#2g- EšŨxkĈ0T4YK!TE`eEe XQ[( Vĩq9;^Ro5ӷ=<58 WxaEj{sMz:SAÈ x5,33ﴶo̺0; ɔA8?Zp7?n+JʯA w2DqmȇnЅVj^֤xPD3*M% m.)EKQΔ5032{j*gq/`'*jNI1; R(6t.p4wLY񐲍μX;p'( 夨XUV vM(rDЦ6s`?m-29iRD`Az%\RÌfg Z90JЗ7;[V2vsbFxd*XБpb[e;&yI^rz^u]dzFpI d񡋷8:CalOS&\q[yh fp2:c1>Փ'\(Tw@f$a j06r*JOD70ڂkpzk[Q5 ItƒN[X =h4:of _m7LGiƔM?|q \/^J1zz + S f>|ӽ y"<,wc,mxy.vČDFpH,,a"g$f7-(bi,[bvrB3ehc@,HVtePMŘArS9β ʬ8Sٶ#?Q摟X"9(Y5)/gDM!Z<` f>ppQ2G!2嘊1W\)@!2Ӆ(L<)P%}H$7<ǂ*&KK&L֐lu9:TzBtfI S%4c®@*l^  ]ٶ(@Ґ(I!q^'{YB &jJ`En:-ՏVň > J%qZ&qHV{$el/#㭆6ׄ&'DH:pfr\tJ fgRC|_n`60zBPFʵg=0+i 9l!ªƳC!ص;:-IEP "}{HP?G!}F:$=񣍕p"K'i|6g)%976 2񁒇^ںP~ۉ/[)|wߜn r~Z#>38lK'2 LYmf# B= Pgg߲oB -'JMci,b<6鏎ƉB("KmGѠ~sN\ҧvA"?Hsu+`RHcQ)JILvQF (r(%2.=ϣ&hLo4# YycMTӐQ-jt"@YVkf}:gyQ&|Gqfݔocsigenserver-2.16.0/local/var/www/ocsigenstuff/scalable/application-x-cd-image.svgz000066400000000000000000000266101357715257700306100ustar00rootroot00000000000000}Yoɵ{T&c_ۆ 6̛Q"KlIAR-|_D.K-\$zi<8[st}U|w"qRmnζ7ߝ/Iu9__mo6ߝlOou]pY?lΫ?VvSLJ۷?~l.[`8}S|OTUvoߞ}wVpw n6כSӓP_9^_ooS͛_ٛ:1SNQ|TKUǕz S3m::@spwyfpۿYL7vGKr߮6b.WWOJbßoN6ߣķ,ׅOX-Pk*%oԲZۮlހ>7@ Q>_mrl{{gMmlʓtGcK_~a#U[bVRfhK=fu\l6 0ffBrkgwgv-跧cڏvW3|w Ʒ?m@Z8,E hݟI_7R[܃^wr~k&)ca%7Ǎ@, }Bw- gtUFYd~?+~;Q vAZ&h'K&`|s$D\]U?wN,PI5FP 2R pêPpԀTlud{C>2 t.Q(J BBO} @nJJ,t\J/X\#sfhZJsq lAu^wD1.A ,Ma(9(Ճ;SjV@Syi4=q. sPjs6VBSv<5'%(II6Bm$i~$I>(VO rݛm}1sбҡm@U$AƷJp~|p"T$, j``P_юk)m#(?}_j,Or'BƖz}a>9]1-xcMm\2d~͓[xi ZI Xʀq ]b K mtP6 _u%qjHkXٹ&V]v\K=³օg+ M mmQ.F+e~)ښj\VZ<:Gum]{Ī'M U]tfT~]teȺfFcО^RU@'>bОK8Fd9/,u+Cd RA.R?o@W51Kp +ZT鈳/W)ꃑ(j{LNvC8]w[g;N1yi6#e X=ӋA&ŽB?7^8O1 W#Bp``V"9r/ ҈ƻ(W w^0(%hŜ6˅eE āR~>`U5PFc?byna F>2#-d.f04uD}8K8",aΝxDG' eJcQ<0NO!nLjXP[;5hZ6P@:=7:0c4e/d=#է)OS/oxIPAKaӔ`4Y%\GY򢊣I3>k(i^;c:8M*B锥HLFSfC 8}I{a?imt~ > T}?{4*&zB0Gaic J1-#Χb"Kxx>b `äJnRDJwuV1m>n J:K# G>"iTT\KPJ|fi3)eG]CS JU4yhc˂r01!uVW&ҫZ;2{Kүaכ.7'ݺ_&):Ż{ҿѫ/QF{#v:G|ś&S#Mmif5ϷgxY!Yww,p:eW~,?}Lv{yևz|W{ &̎];JW[ھ[_gWzwty}|E;GWwwyxXw'>Z]^\p|juc ofPҊ'>/6 T}TUo-ܔaTO6#@ދv>r2XORzґ(lD|5e.БJ+XRI_Je^H+PqomG%Vr-(WiyM~/4f=< wd!Uw=_F󳷔DߧKxt}~п>?{w.^ y~v}J~#),TG/6ߧ6n0z?aߞvӐ.&djn˟6WWJ5X Rt;$Z'vCb>Wg|CJAˇw4OKVI&h4 @ѕ€ S{3$=fTKCDbhF_T+z1HJ ýORs!d,j+VLSr`5EȖ)<d4mALcæ[-I]]M$Dd ϑ`xt^mL^g;?$}hDZ겜zԳRV}x|se<K#kZ3Yo磡> `srdF'f} *Ia}y+kC!ROs5_LΩI0 J=orZI`g6B1*!kU}ɾxߍL4`QA6RU2**l\:_àoqq?$N޺ WRcUS!;Πn a 0U0417Я- :t4*f\Lh#}mI ׎Z[VxRnD,臠\pt`&Ɠ% Qai&#ƾDnI2DM.&:p_ͽVzU>vΆXU@#W^ 'd$1o@ 9g.љJ$R]V_g!TIܨk%cG_Щ[on2sC Y^8ʫ]cLD(iRe9*ˇ^ܘW~x_ _ ;s.5Kl#/bk]H>&;K%ԃDw!QH Y(- ljEaDQM+@ XݢS$GbP1cv/EICtEqreЄQ( oS/4=d>[a+Ku/d$Jg5xTf5~j+m!_O 'FpW1~A ]@$l ˄"i dۇ`Lt]yz3J#?m_m?m iccxcfhԮa#7((dM &(IBy:q32ynAXsH }S|9m 葬ɿa IT)p1 f ҆!q)챀-Fsm8p%pYuљz^$͜A0@.DX h)%h)r8FY2=.dA%z<,:֓i1wi]:Vޘm5wpX$)YU ý&|m`= 6l`F>&L7DLXD8$c;c-5U~5: 8 *drK3(c,]㽊i9B@)>DXj'/ ^kןo XQ]~bȫv}v$4c`TKh5BTERQ`Dwkuwē(,`E0La0KO-5ϧc ZE߃F `B 6Fb§bhQLPV*' rקc+NG;S$>1|1/{G˃gْU$-( ,x~K }cY1H"{$m f]8yHp'|l_lo臱|}Gmlb!q%L>\=3 ^b A!! B)ueBwq%QUs^\Qz տ& >j_T_> Y&)Sgh #pvmKJ*%2Cn(=j9NZAڡ@rTRUOBTwc*jvZ LFVь6RVºPwzCFiG#pɔ1Lz.`(RON>ʖiſCZk<[k"=*bZ9h{0@y 3zbm׭9Fu}BE~jn62LOjXU"%++Y2#6MNU!"$7DhVqچs))| :o`xJL!h ,ڋv}0nd#0dtc쌀`ԑm KU'2Wzdnqn3CbPto>xk/ѭM|oItVM`&إS.yHGpJxAHIBެT,̢bpnO{1*Zm0KVJ j I Z #*!ު9-2L99L[C*U+vb];V1wʻWz;'ׯNSDK5- *]`/9^<݀hvn}G)Cp#S?DRF+@ i&l: ~A|Jg߅ 57웘kQA>U08"62P@5F]TlLQK^:tr"jVDd}[ */%οBlAU2G\IfJHGJ@1q? _Д{,ç_QJVD-B} X%/2 h#cg/|8L=2ʨ[xOqqgҴt),7-*F7HF[=c4w?3oaBQ΄6J59n>av졮c.D#B:X{H׉&_1gΕohKK.QOph|`ȃNnvu8gZ׭/+es*ҭtE:4AJA[YTRY*ߥ%svKd5g G@yfQ :f?}9&ڄ4L`C:kʃ@Y;zCi+7n!,mkMFVWIQ#Ls 8Pdʀkx`RJoZ;65F(7$1X1Zq"'8J|y,f@0 2MGAdĦa/xdhx<$=Dz5jwII~H8A 9`1tfu "U2R<% qECIk=gQ.#0ؙ%J a~"3`y1UĮ /h? to;x 0 1-ȼ+H6d/Ê{s> =k(:9ɠDž+4ou xmb0?A4%SM xt73p:G D0*gP-^Q}8Oɔ|.a )v9fc$SDŽL,wDn71IʣaKt YiJI"S?1+0 [<;ĀM $q[[ʤ4 ДL5w+yY܈"$#G@BT>@5!PDeS"95P̐aә6 fLT4t9+gPK2Zi;x>+]v-L>o}xx(ao{y6<#"`II7+D?U(.Iyq 2@7\Y ھ`cf[Z}ݻ׷>\>ɽs7o/]>~zi|xwqsh^};ǫX7) ~\mŻ uN>o){TS>o_6: n.tѠo\PFT+Ǝ|aG/n96k[|AܫpŶ|BWorGow„jfтXu^[e&٠M9B2hgDZ2Gp:ynњqu=5֞o/Vqn_\׋ǟZ2OoEԴ)*tF*O>nnozw/?o4uW+[iml29JJ*9rfUAبl,*y|JU&c&56=/F$T6*Ĥ+=q>Ue7NƬ09E셸6Z8NV)5F'ej6ѱx )^ deFK|vi>6֯`_j2lY{SPlA1)}uN)}LPpZ2H6l|e$ݎ6HBKQvxҐ6JϊY4GB!rsWC7 8EȿQ c y\<*_MRiboaMH>r1;*/)//G1%ٕqOdbL&\y+Hd NHvSOq-@"Abk\UΛ@e~uʭS3dK$(\7՟ǃcCuXd_ 4;{*[Fj[t4J X`#ks|̽EldC0p^x,QsNRPgR Кs)h/̰ꪧ^.: o웛#XCW$7PQ{Mhv*:30>[gsjtq=MTN<'qTG(MID6Blq$]񸭞#pv nCU5@NvXũ"+gpߓp[xP i*qSp<ǎ%7ص9_zh08hV&-&[3 =~QJE: S頦zAs718?9"4 Fbme&bb3{M3a !2BLzmДZ,l֘ MѬE(奞Z]Z~[ w#G-d3߀Q1L #K S=xD=ߘm夡ib-bhby ei~ {,R΅7ںBD mc:bfE*ZC>e} ol<6j ͡vSR >[qh-ugo;;{><јA]]^8,."ȗs %JYhnFn~= ueP>(wsSM`iJ޾ʑ_~[sW؊D3aj' -|S(ѻ~4 :˰x9O(Mzf_S yxaf .-9C2d,ѻMQ,@R*Pګf)󸢦8Kf~ Y33mfw5^@vf}O@|[kt3H^kc!?ºU뎤hn7>`8<[0ƩysP ufW}1pxYzM퀼fH~ k&D:fžwߩQHnbwL3?7̄T\H8>Wn֯v]:vʺRCQ*:w&VZ9 ofVznF3й 3K>A~Gڣ[@ didK e.8&M4 DT whŨ,{;=ssH{{vG1A=>A;So7VեjW=z|Ko 4$'m8gg6nF̏;3B\?҆ZхؤbWٷAJ7yD]0~uGoyM^qy=,QU3;? xK0?q }̮=7mPf$ɾys 'ژWqΙ8Ɋ֖+sL:.뀒<=ǽf#6c؟4h-7D{i>p{dt\6WY%@jכc}{⩑}~Riq9+s }/+TsRR/F^5U-I5SI;Uĥ'eE[`]jPK-\헶Z䗔6Ҟze(q|+hKr*-/V/.-fv ١n=/v-d @͜J$^ʩAʞ΢Z@[C{`Ӧ |*}OzĻU`F ߳|z*~G)ncWR>ƞF߳,iAT-`I O,T֜Wٳ#09Z'0ssԇUߟ='a\wyF}%kX\#{Œܜ}q0XPf(wXN3f>-qIx9;uNƖ)fߜmf,Sd 5<԰!"yٍ9EƍxICt<QDe60bȼO20fd>/j?Uq.(j$vb4#ԅIU3o6 -%GcM5[>p:)FT]EhNƼjˆiƳZ%~.>`/eVKFON1v.RS61%Z Άav󉣑8Y;FjH.w H9v]ٰAXF{zO=;6Ov?bW7Xʹ37 *&XsLCOzgEl/_*$gH.V\^y9&3X7dӒrLyNP+gJQ2S1oӜZ9dJL旖' [!HAO4\ ͅ>"Gg ig|9x4F j@y9F a{xf$~FRǭLN求f1g44ԣ06y $P)ē<5M0kdz $a6C4:A5:f79"Ǎu̺5wԍb膧FɵyپW3PC}ӷQU?^c& K+Ks)_ *h'qQiWcDD<6 $IMN+7قo2>+&dZAq^9Ph~먼d$()%l2*^c:ꦎbG0S2->{'n9_'|zk_[c|GZ{CSUCK웱1,A2藲:bʖ8q\ں:`LuT0}'.Ǫ^V7bvNgT<ͪt͚1ꔴ*@c}A9 ;k,jeI~7I꣔h0/Ǫ^V!P"J!>v8Z,@g!)؍Gv'k&7.M$KsGNxzL$ƺr!R"t") b;)V#O:BQcYc-vv8|%$n&ؔ=͓;p=7掀Iד_Jap4qgxIe!uO:S%}8\'~*wȹS??53ϒgqicKNOrpT5y '|F\㋥ڣi1X~CGdj.yȗEi|Gf}3<а8qnz"s9 .Ko\l]97#s D^e9}=a'>80>t|eSe@?6=@P|@[޺fkv:z0hzsA[%:&YVWOjzpAk>r5bzҁ$Yû8Ţkm4 ÀZSkla~Knq󛝟љ_.?yW;ʿJ/>fӜ},UWgf8o9zWsÔXrÚ`T+,ѯYoab9{KN%)bJd}6oɹV~7)huexy(Y%ћl|ae{degEAG# xF VF< KE5 MFA&' z{n9^TZ`n8!/E ](PP}P}jWQ&,.QO\s|I6vQiwM?\r|rYrn/O}~Q1s H|PMx1mN`$C9/_Lۙ`mH"0,0ypXxe3cf\տk*!S5qx ۺi^*{ؾ0XQϴYݧ:0T'x@(`I !vZ׀)#/h7s(ݘ½u8+ KYCGPS<9"86ipK)K$XV{Vloo{N!gMmhQz/~wJR'6ˁF"D ޭ-RiUi?6Њ(&hRK[.Jz!Y+L3Z{WT٘- Q qm6.AFJwB*m Zfc!)!4{@f86l6 Bxuk)^#pXc[@~e<՞56A!ERCDcb2aYX9w{<vZ$#Jy dXE2q 2{Gr}W P(JӋqz{LyD,ar8̦=al7h׻RXd- ƣyGgq9ƞ"Tz3@,.S,0cyG@Q= Y.}/#x|1?JCۣ>:;Ny=0 Xg:y8f<%ccC޸CdS \8YTƸf2b⻞0}QQaA3>4Gˌ'}Fqڸ1=g=cWe7181)$O=qV <eyy} :D{n{ߵ~}ۭ0%LXAlOA ~$lhׇomW?tqu˷/ݽ#A0֞ rgIޛ.?jUʢ{VŸ?MIsW/o9͏ Aysz\b6푂McYvk|n'Lmh?>^ܽb}qpw.>^u{5҂tQfo?w~\m }'>_j ? Ho?zO#?5vv#Vw#_WuݦY~~~}PEsGN)縮y!{TWko;zczp]J&js*or>s06byo˦Ki˦Oӆ >4n- RnxquxѱݤBͫJ^^w` +\-jwuﰉ^>||w0ѽW/_p˿>^ A׏7Kc30n߼lAa 7*y{ssړmͬ_t[3;p/ BȾ^5;6b,F.h7POh>1LNK[AbrNr)K]9ଔ8"V^E֌ -IŨ+Al*:fP3Cb3%H &dNQTl TԯL"^s0+['xP缲Tb^4J>86tJKuuMI0[1t?%YvƉ`*D+}yx5i&Z7& 3oq )o2',f2+vYLUǙt4_Wà 0s?XH7 濐6eAhLOkGנqEX@l|i|1q5"%s5o6%Bg,P2H ~K)IPop\J- ~*J$x%F2jU,Y4qp3L4HqWt b.Hq U¯ч-ҩ]WdVBݥKOq!0Ie)sJ{Nu. +nv{LG;E~ɀ2~]1F(;ށbH̆tƯ.vkYLR' DYy:IN^ar`BʖNڲe7~MãTiɖXe\*9ސnLϮL~culDU(Ffc |L8K1L7`䥕| bhU^SPhC @a*ABщjZ " ^%="R`k(^DB&F" su?We%1F0L$!\^Q~*P&v/o]Qu,:\3e!5In ĦD ? ytN IMQ/6Sh)"6g[N78|PޞvJƫ G 1)fG/S02*n?LrJ*EYBUNk0C'Gg~A̲*Kp$VfLs8w%tzئ Y)5x:T Cq^.H%dr QNyFs+_֭^XuYYzD4TD]*@yˉKC9 jsJ,cW"@+A!֏IEOÈgD.M$Ӱ[H0о&< CMLn-toB swQ}!lQ䄆T\k!:LAP s4蔥PWt '{mMSZ6g&i@ )XRo AvQa2i,p#T1+q $S@Bw&lcQ=8+F\Ng贎^NB&I''4 ,ϺG tiůC3+xir$2.$l4MP*5vVBe#S^ĕy%OYh' ܤX=1&ƖT,ideӤ4K%CcF6Qu8Ϊ R\ &,}BEXZU8!}À &Y#I Q6 "$%!4UF$!jm='rN1d<vHiJLJKD)C0ec`OP^& !gYa71Jcr< 8*FɌaM2"~,1[;}h;fS-iw7 u` Mk@s5L>2 E qP3 򨅕W;QkH,F݌&-4FB qf`W1\jWrnICM agp 0zZy)0 ܥ[j340m B&&;Lˡ['p-1 ,%&8,P:0mG.ہO6}T&#)4`L2yĎsSپ^jKN4_ ӱs_/w׷>6]cnW)]᧋OѶsbGHq''Z#=B h t#/4H͛ qTS+g L&R45S/HHO~$+/=wۂ_W(*zC,,4`FZy Oi)ӸaVѦ%xZVʳM\C4%]T 5%9\/Apls1<U}?Ga$3(aڮdI1̸׉68pBh0uRV՚ U`xpK{"|FZO!CGN JI˗־<|9ʡ{q >.Mq`t4?YnCO0і׵d U1Қmbm+q0R.:8e@Ml%,)ӔsཀྵђTKd,4DD'rƕl *@ kéj`%CB:mNۥB4}1Juhd.҉٧ȋJ\b Y/b3='DY<,6^oZEZ4*ڶA_.)Y 6 gЈhkadGxx|V^&Ebp=4d3 ^8:AYG]nZt]fkp|9{!khI8_ռ`СpY浴X\jD6n:ұaJ ]09 3A;ڣj&F6cwc6ʊ-c QŌ)vhD0SȖ,  .͆i r:Ai3r1Jɕfhk=e*K:Xa Éf}4 [[-aψaoh0!}@SxnߴpF3C35}4Szq&J֊YKSvCo4Jk 5 ~5ՎE2. tr٠ӡ谝Uv6-K{vvPTsi lw'sXx`ZJ#f'YҦjoU|5BOZ}X(}Dy>=H.ː:Xd3L#pJ7|c{m, VO^ċ"VVvohzɉm=;]_]UѦjUmJ#⪺#u?J4ʻ+_G&N|űTd hJ6U1lXyCF]{ ~U2rŠ *Lyqĥb ^lb DP$Py'#I  Y xW?0h>X7dk`/(ړ\(^Bh"33T m;z|z{w:T$ٽD$f L|7?`/1Upd?rDŃQuO6 ޛJej#>>F'Fe61Oz_ F,O =)o cjZxfMɞ̶3don.K>Dt,v+j g,3ǽx:l XUU3r^ Ůu'GC~=*uq-+ F+s0C9)5 =r 8(8jm_3IeU}#'&K KYvߤi2U~Ќ&0S_7i}ቮ=596k2 G`1CEǸ&L|1=5]פ.\<8/h3~p='i=Wt`EY=Wivpo:I^&>/_}KSs=vAָmn\ٙ~ɖ4n}8͇_a3}b1V2׎H~9j.8zwݎz=Ǹn}jwݖf캵c#nON6vwEd~)>溍`u{Lx;oz}qr_K9rí룈3EJL˷y6–A1TEg,llvs=\ Ӯ}`nzQhKJ$PFF]vBJ וj+߫ m9I'V8P}ԮK ABε/5/m5KU{Ds[[;{ew\cS(ϗ3 u̅Lp˷R-&Z1@EEԶx^ : r&>6W; AyH!;⚤yrm7* GիsTww vtG$, CP]{AًNw 5Jמ5CYu;NZ;2P{hoeA*׸ٴߪ8V1@={y>V׿n_VȌѿu)gLR})יd_i7GڪOJ_EſɓlŻO|z{/ :W<}]5Tn^^G1ʮr ӭ_Ku$QD )0x(#CPM2Dt .o8NQU5$& K$7 Up%ZJoh$ MeEe(0[O.CsyAI_:&Sr=`H:ÄUgދ1'!I,Vb^JI̤It*y2sJlgAe\K7kIGg*,Nf#H̭:p$Xnx󥎤)k=)XH(K4sT}+g(Ie&dN%-XDQoSFX}g x` 5 ΦT|#s뢋xTޕ%@S$y@q;%:n).Y}"CXĠoJЦK$ ȨSL:︬e夯$)Yt Y;VU,+Z6!ê0ǒrl=7,]$)ĩI+J 9R͒Ȉ/0#vtsX{!CFi0#fP&tX_(#sIE"Y{$*\UL=-$(uٺ!25LQf$k 7`1LYl$jdB@x%3X^!謓XR@ZP̃%l-W ZJN0"hfm`L$D]h;$f@u'Oopyls54FJLΆҸ%x){Oد¹AS$B]f:,fA+7^0d*D.0JE) ]LM 96ቢ N9yhF@BҡkfY%)yA& }5̙ l)/1M0Fa hcjtfc  W0r".\!풊DJ"5/s$oܪ}^ I$NYuW̬DTy;\p2'G FC{H?`N%VUz,K̿1!UU6ibrF0L&M ekG/L$X A6Il9aJ:mJB VtF!6۷Q~lLi%({ͪE14 ׂ6<}R5KƒY /8jC }0:-sot޸^y%SJ (O($[S?0ʌPH8( ig14 ^3uhި\A ^ne@`f5EF)/ِ4 `W* f)$Զ-xVL,ɂ c+)N (LCX# j/wIm֒ۋ#(SB?0civ+LZFĐN| 34c!Sb&ˢ+OƮmPJܚ'ɓ"cb];{^4P,3Si!m}-V(L[:KP9lbOI$dkKOȀD L@,͓/T2K1L0 R;7Kkà0Is+-譪M :r$=UHET3+^/M&(7R4eiFסk`s5TͨL%{&9e6뎂NK8^Ƣ*^<,ܷ,;so$5 [[i98}$wVIN{о-"t Fu!<%qvd2I~SpU4XR򵄯r3n߉ *S~Q` xB,mT`ZUˊ]SXbj$c3Z^ e/)z'my _΀$!H!GP>} N4Rw/L+ocsigenserver-2.16.0/local/var/www/ocsigenstuff/scalable/application-x-mswinurl.svgz000066400000000000000000006032741357715257700310310ustar00rootroot00000000000000ks$Ǒ-}~E-E TGǓڎd#3]۱y1 h"_~GVet6B\"*Ǐozu{w}_bwo?/vw^_yw/|?~x{uqzwywvW^}~ps/w~N}w?|ѿ՛W^闘W7o޼7fvogj[k\x¹q~OWs~v?~{R~o^~{u{}wo/޼Q!WO\w>:k~_Kz'WL{/鋩L?UѺk]PvyIw[w7s#z|q<|7wW*nv7Wv͛ۯ~%;Qa+= { \>PţxV|K?vꛫo\L+i!JIӖGvyuO߿:pn7W:ś/~;\UW^*#@KmǗm?Ugw]}t^S%_ᷪo53;T֖Rt%VO9y|/j)Q6c>JA}.ٹzԻ((Dž^%4/yN' ?겘Q/ջ}Hn`vE/D\.K!<}/}5FB18<ݟ(Ƞ=tB~^NYgK4#W˧zqAL93vOf»fwo;}{7nzq6?Nyj'Zͻ˝;f8;Jlgy]k)>!5!w53\ݝo$=IHwt*rY,w՟}ާs{Sg>\B8/C3kuB~Gjew^ꬖz8W'gn% GDVޕ}хߵʦ u\d^gNޫ iL^tGY=ؼo)c{ڛԑC5Z+wEj>_z7;CO_|ZJs'՟/>]_7nz O-.:9>bvf.W2 Һb(ҍBTd>5 3*>>0aS#0򩑎 N-GFZ4#=<ڏz Dm9#qTT=d/v?8=L?R>~#r>k'&ͅѥg bz5veb5kՒͥ/7KXGN 5.{M,5PԹ%5Wj.wjT4٫R]j[VvUWyWꃻkmߢSP\#0&' NT P Izj?0WD?)ޫXrLޑN*Dd BSTқ/>"'^}LG[ϽSL:lNVJT뮩Em ݹעF0!hnv؝{9!;p}n1 ݉Ҟ8 |.*) ~%=#XjRaP+! ZRϱ^^dN\{{B&:/?$<Gy 3"6nWWSNKS[\31()(GU ~SA5[XWWlf|_N~5 O:F,KYmO.slNT77.=sj;3R!>dњk?^.bL΂5WtĪz:i!<ؗbO?sf\MZV[TNQfRw <ǖU'a]J]~R<.pBT:I{WMuo~?.]i ۟H?iY7ʽc}qϢgH7Eyԧ)4?M9eb!Ev9gl%: }߼?#%%ZCkH>mDhC[ ܹ=ںJtz?ɔٷ`ƙȕk&6wi]M+Su}VY:NFT|vF'S%c~"H˟+g'tj cyhAj+3 ODAi0< 8OQ x8ኾ)L[sv.>!X>'?# 8gh_˳0:}^4?4W%E#anhZX4A_O5].'Đx34 ԶLi=ɧ%
    3m1S^gy 6U-rJM>-ҥiɧQئy×-d46myQ&RFzl/K&_FH(lS"A&_a¶6ElSmjðMa[c[670)f n=)%lSM6M1SM/aBm a)lBXm aɮ0{ /[Fe]MU6LA㙂,ᙂ 38 $bB\380/ګ5uqM=kfioa[W:yɣ`< b")NaluX.:uS:-֩+bQN&Szy-,nM1Sq}$A>&S6LůY,aϸ)f*Gu%yeh.?5><죸z*ɥ[mI~\=¾JC碇?{o^3w->{\ r1F3b"+bQNmN\EX'u0X'nuҊX'-:y:i[W:yɣ`2L.Hڔ*e=n,V {Jf[3d[czap}3r^"m҆ɩKb˩SqY SWO̩[{u/;@[ 7N$s}${!tD`cy5ҹȖ5l 7:uuQ:J:LBFWO\'''ERFWO\'''ny[b⺨?q?q?q+CuuQ:J:LVL+'Qa-fZ?q]ԟҟӟm1ӊu' {:Od">T՜i^JVϼ(ce5]Z]`1l06nuڊX-:mӆ:u[:-:L/ZFGnu[44?i~S7:(彞.:/[F0P ja&j2)fBYdT8y2?6L-S⒜ɩki-fJԵ$nYO̩[{/4^I5H#">seF={}I gNſ%?{oaI٪΍uF:m[W:y)`29QYEXu XaK+bQNV-du"F:mC*[CŹ\ Ycd薘8~vgw~Cz#o \ r1rFɩoAdIn1r_תԒU`޻lD9G}CH?}*)vBb)Bq%ѪsLݏ2. 2B"ԉNaNZu(P' u¶P'u"G:y:yESA2 )lRݒCzۇF>2-fԹ 6FJ )uՅm1S[-NZRW#Nod:3yZ|TZճƈVKh$YBc} Gŕ}H-H{ +ħwY-ͨygb2F&Fmu@E XLJalu|XxxȦXzX"G:q:qEa8 IM!V>s->g}<-fʫzk׳z#-f*$ΗAr|&.nz9u.ɩu:_V]rE<r.ɏ[1>{{§3<ւ>^f]YGI׬kmN[EP uMNpA@: uҦP'NKN@:iSzP'%P'1QaѼ)=dCAl 2 lʛb m q>0ۇ)RҒI i-fJԅ$.ARB;Xtma_N%sh()-*|!3 .[G[[E:eES!2 ) -Y_uXԯ:ү:үaK+"QNmΊ’CE=72桲);$nCl?!lb&Yεd RH8d릘IzudI$Nd)f\K2d z#zjJNܩ$?r. Qӡwob<{WK<{_(\]TRTnuX'.:i:m[V:iI`< ib"ɋNaNNYu(X {mI]o%{H0ڶ{HڒC=tIsa}~^GGrl4 3ϾgAo/o{Wk:s輩J<>4KED5D~~BJGդ/ќŠݧD7W|>k_<{$wz͛wvݿ?_|xOo>nSS_yc)-tιz,k_ݼ~ջWz}?.ÕB}}u;}.;\~{.^|6yo?sJCA:cxƇV{Ynoq›I~}Eﮮw7{|.ѻׯ>;w8>~tU:C:z>?zw.ޟ7/=u}o/~~{G}#~]9Ѵ`nw̖I>;u7W'r羻Ѓ3@ݼ{c?r7{P8gGڎz^Bjxx]%/1z33/i,06%sd.T}}էG&Sʗ^_8jH~}W>˯>Vp]s罾 "zuooߨM8|pr2GgoU>^,}wW}VDk|տ_y/dCsUѷ›TWo7xso!]-N7(e4+}&}/7;)_OwkNE̹.wA]p>|;;ʙrv!ܾL<ct%f%XA>]D=FzJH՟|C=vEg՟*.}jګUI$g":a2 DL6NԯNTz9ƈqTO(1uЋ,U wqv:V뭔d?轞Qv&Zc9{jzqj KW% Ү#C{TPc |Y_3ZM|yL'r9MopՆ=>8脅վ_ua_M>}4P&Dzk!fJPŹC>ʥ:y$2qJKZPy,fȂ_#qeuf?YxĚz唪!= SvZT..]qboVE4Uo"B.Or] |Z#e_u.\=UbXRG'C#rB1Vx, f_Owe. &ȉdm]yQT|ђ&mgw&A !9O^ w\"G*|}&$&g*-|ۧ顾2~$_c)xb,(MwfyWϜ^H5S3 tݨ_d*e{@F* Tt^~_IVKˑ#̾յeVH~#iY2S,?녦G VwtDK˹_6>{5qG~4ϯy6Y pn\N0Mf[nzPW]Uv;@9U;j!!LP`ފ"<տSo. ]N5$T< **.'E쮰g~FWxA !6'<}C=WΧ̵t~<0!UUC73俒;Ug4.NWS%R/:$ékŪM+*[C_huRj( z&q:@}_05%I=y}4*EN%4ub^0uB*㑬 &땿+p%Ǟ,?tIhs@qϿ!̫FRwV]\Nh] M 'T$4 N-z?RԂG*X!أ`բ@Gm<KN?|3PG; "_=SX"A ⃶`^F`a>CU;##4Ϗqo(OܧfUs%!,]aart}G-@*Eי(F肺.6 ]a݇*] C: RpHa ۯ^GY@QA@զZ2xQk:aԘ:ux*竟)zRQU+@j}ֹth!wupӋ äWˉA81nhJ=ؠ>~Q8NEN!*ةToTSgTkQEA+**ʖ)W*~:\Z?2ru?N :>=ۄB\(YݫP<NdbM?.Utb>W7޽*jG?^Aܞf ت^޳Zf꫅O,1և0u A/uRcMIN:V:)K }>eE6S **:nXh:V$yU!wJz8UW3+Id9rw"<(oz&:|~.TXr.M][=8]ŪKUվClmyL=1= R6z۪wױ>Hu@W1R^RJ,a@ugp_,;, xP"$c=7T+HXWݠMJ&i+ѝ8oZ;дfF9vUuZ2U[-j٦>QE~=uJu]4&Tq*>TC&,-WqE?=YVy};BTX"A*0L1$ 8a8b0'diRUq1䌮RH"Z^@TRc4#`-Fճ6痾H6q4W쎵J0[eT'W(^vjA=L>=׋TߎĮu=8X85|uxH/Y>{@'QԦU+=ϫ6{paq껀5-\8]*6 K`l{ubI58}PZ9UW+Z st43sW,'t1iDYİ)փdt!g44MJ7a}>XC6BW_l`A% Pc=AY3&6PTV8;8YRJ0v Dj{8U:]m\R. w؛&Hh&|y=;U} rnp˜6Fe3v:Um RL IIY N?tlDZ<- K9%5F)ȫR!cK0pku8v ᧰:ՑuhdiYU&M6 9$钋JUrI;9s(Vb}DN.yu+Fۨ*Nˉ-劲FC#[NI %ǪvRt.ptj8AO#$LjtC 8=GW 9_*M)T"kP!XUGq ~ €p.:vY9{c (RLE4NFR_Uohcp.Sm,yq*-(\<]I$44T^~1D #R!L~afJ!$Ve !lz~|qa\}318A%7*h Fċ,б|qz..6@KHlH"# źG| ָ"$]"! cUs( #ʌU) !ћP^Xl.7 C a*{N^ d_b+2DȨCRB@~F 1_D #~3By. @0"&J3%z=Bå0އVaˌKwKaS}4Ue Ƃ  ʪ98DyARwq$Rtm4,.⍠3bQ6$0BQ*2u1V,Vc p]$*T2",h3 ~GSL4hI%a4d|`MogaAM[@(Yu7P=g p t2G<1k3SC.u loFw` f8@4+ӈ(hai EFȤ,#": qA3K&s+6P7a-DҼ ? 7!,T>C >%s;tjn'r g &g,_<|+:RH -?!muHC am&a#~ ٜ9DÓBW 2^\TDR`p.Bb&v.ltcNҍ5Q`/Xuee;W:eYP>Öu&eXflp10^ u+pY" uVdHʈʬTGZ5`.4s(gc˽JZR2]~0H`UGbazhPizwA&z2*V`Bui K` dȓzICVK0J@[tub]ѢKHZ0ɖa^r`YxC"/X4ӐER%sbUhn6& hY` B'+BxR) [sJCɸb!9!49bW,؈ sPѫ@,J23!F)QGdd`oTjС0ã$.JXNaEhPQgN8@Ƅ roDrRn ЖH>2b}7ddĒsTz_eeBzC ecΐLEݝ1v>wQ,kREkRgvM"8w`~꩞kHU}mC{OOu =8B  BG:{M7Е |6bi$6g ߫a0 /R ӹ(t`B Cզ+JHteB176""!“5M TH\@hqFpa"[qȔE]Cg ЁUT.D*bh-]Nhæk HL9Ba(S2:, )f|dZI!SO(E=L\/jǮL[ԔT#̗dQc\3X!i7c]6#Ss ^2!9ĀvyS vd030Ř32]!Ǹ!]yG@37)2b@.  f6,HFY˓S pH̫ fIWёӲ(,i+XEGȟ}tgܽqۇqD*0sc3k'*1Ƈl1f V: u1CH!'8Wd "i yղ<-Ǚai$L,J4ǂu$l@= 3EDQ fҳŘkZy#ㇹBo\ sUs| Kg ByLwAٰc]=[60)f& 3_ #I1#l}^`e8)|7Yx`Mq%0h&Wr]Bl8ZU q4I8Waldv3BhνVE]x&BM/X_]>|#kG }Ám84~7 z$};%}}vy"}+@Ȁoq 48;k3 (>F6:ߋCH⊡'w^~ީ^{AמwaTf> [U:ыǟY% |`v^T?l` ,V813c'¬P<#[>$.`'P|6X}>{p !3 id4I ٜ<Y'V#\qO[}\^gHQ,fk,[cA\C"Tj0 c,Lv5c7?H7&ch *LxE!0n`pVE:-7ۺDV/'JH R W`-503O3 ezu w\eWbYs6z0 *eD !Tqh<w[9AỄ̙6OFOEJi@mDذ@Z"}h|#2 =7ICyyKIb '0V\dk,Ւe@9*R*a<ˆ[^gCwDlI1Is dlJ{^-2y!#lWӿC{k F-ؼm z}Ή;7wRæ]fl@O̾ Pןf^"ͺXâ_!=&7oǰޣT3Ez1fcBƵYiD:fGltITq if틷P{]-<]J͛p!uS2M2eGNă׃,MACYbxP8xIxHe?2a&B٭d)ҳAˮk'󨠛 6RKte (L)vȕl7+!UE5;UQ֜b*A\f6 9[']!n"R96QU$ɨc HF[5xlLt I`*,`(@\,s .]KF,&)"ĉϯQSL %`Lh J B^9>6DÍob;PcBR1h,@JyxۘɁq{kS0zXpM پR献zb ǀ }HiOx$G`Ot6n ~\d-$ kmvמxr[ôXW1¥նaW1ƅ\v 38a)Y4n΃Vb;C0V7h[_,qԕ#6H{ nԗAh&tgm`Mp"nHԐULT F5j&# t%~gl2r= zɟL:wXڼWJL|L- de֜'l Yl12PZif mbCbH2ўU2(xmpd2J6Y.nOfoEauId-.)2 3Ⱥc"Mz)2[z{PdJZnPFN]{&4 "࡞ρGVvz=I&"EoS`3B.D ~^P -[9n9ECϒ8Np^R_F;3BΊ !{ qdD9DSSyDp#To~lyGX {FI sD%\Ǎ6Qc>٨#0ɍN/z|Zd6.q6%g'XOu2ebɘٸHI|0an105! `>5 MaԳaNm>,dT{0uiu>/:??atUl`URcφIwTgrB"d |W:-nI'襹gX(1"9bE8lluU dW 9@q;6*XzqONu.''cSj/X&cnG l|JG ջ|98o|Z6 cl7q{rO&.;p7q½qʩ;oEdr09t 'Gqǽ{Ø{oԽ77hq_t cfwl;yp={o}jaʓƊp1 COD.ƹaNqqx}`.̋VϠ 8bqԵ,k27aqT`,wU8یc\?]34+MrG4 5:9Uc#;gRYhVs;ś7ٛxX4I@ǸobiJo3hw0?)8C\fm };EOu߫t?Nrt\B$?|җS/h ozeGGXz)#(jV;4/u{Ǟ s ^Ѓz P1 >ίVQwO)9%bk)fIc a|a&a0b<⣿jMYTVhx|-4xod:0֯yF%BSm92_E#> ܕ@>(ݍ)M/;_,jl9YEhVcGN23;'[{hs6eEe3MJN6ד;'X`rf;r3l NS?SUg?9Do&N50J*ýF/-Sb~h J~fp,J4-(Y1Ywcd%r>! DzMD Nw}^h?S81AЭ @R |֩MDT\ThB~FRPJT/nty/vHvkYA7s5Qc&'9Մ˘1Wf{0s5Q]:cf+\f jŋz=C*vĪ&Ws5I%\M]9jr:t7|qj˔4S:iAw$ʜG4UJJ#2b:^)Zg=T&6sSjBMĈj4J<{u$RdpPga,8 L{y{,75Q(B75|y0mcĸ mcX6Aog,~Zb4Hb ;J-TPD.F+=. m1)#Mz}(MfmXk}XBcb[D=$w1 Cጨ+OY\QJ^ 3٬+I8^bVl6BlS^Etsd֮g!@T@hm̤Rb4wB CC3T#loLlWW@`YfC&~ӎ];vAvܱ0}/7Ni}nIM;v9ٱ &Dlw6+-vزRbc &iHNYEhUM]iラ46"8nNӦ]b" $ lڴ+YϦM @H-*=smndE2BW6v^ݑPV0`9LDq{Vi}> &g+.V?թ> hǮ,+'gu@X#Y UgIV #{@rlcw& `@PNET#U3+7~*/9*ԞIu tr,Ѕ1 tj@W륬]Z܂C}Jz fI\ c*+l{fJղ]d˽JFtX:qºMM[`(υA\-C/%Sz04OG=`92XIQ%[cЅ P Q5Kd]\P xC/XƠ]4S{C,^Бa R+&M0#a4Ea~|b:c煬d< _W{ yo}MR36*p+ /H굴KҶgXw V[%jEFLpuN @FKՀepP߱ ҍB #:V6xd6's[+uy]o(:wPߗ&!d*C/` K/H1ĩ ؔpZ_%G:SI*,[<ҼMxT71|<# 5 "Y7s2n5Ⲣ;-xk xM OZvr!#hpbN+3N4{aC%b=螗ES"ƶЩSdHh{ZlPZAت7:P޽Tqݩ ZWea0$*-]MySO,S;YP2jA:x˺(c[.gkW-l%ۛzĜפfcW0lJǖaDv Ụ̋@`={/2 nި,#b,NZ562&gܳ3ʹN;tICŻgH%ć=ϭ7ˋҡ^0^%~|*:ΏSAC;FnS9v d {WAq!XQšbara`[]#] AsƉd}(kHO]'Ԗޓd3Wˬ#/6.mqW?N,xQʹB;VMj6$-u.`%ջܢ :ΘnYSs-N* <-4-tP{[x'\-n|)6dz6й F2vZ"^ .s{[dSm$܂6e{|^`/PeJ{15J겱WeCc/ֳCc/a㬱z}~h<ԛkhК36qM^6zs3̛όYYkUmUgSk/$ݝƗ3pً̅hI{-F {!&ƍvle11nKsYg/ŬJuUђ@g/Ŭ{!,&ً6Y3@Ͻ`~>tFDC޽*uDȅvMk Ȳ&S9ZACSB&}#m az'"2YP\)CE^JZ22#L]1F:z:Zv`9t|D2$7{]䎹hcf;䥳h:1la3J57܂ӪTP-5vͲpTkv-"Ęz̸ݼw,}N6c^zܝ^|Qu {d!cAPM/cí5尲.}DV{"dC_7:b-+ؾ0!:&Md8)6֊8q ck}JEwn]# KvRbhD[3!dBV?v@ˡC</¹4Z;;$hN tb;䤳1gJMEUKD-l¸ 䲡/0:d+.Cӂ{< N(V>Xd:2>uT2z\ilc t1q0kJd]X 4R 1UZ lKˈbg !AW* w6r'7`Oi4Iȹ:F9ˉ[F?CdSi]xbԓbO :Yo9COFG5`dUD3UhݠuNsCP3'XnWcO8Vpώ_ ]6mCQ@xĜ -RXrWOl>?RhGPӏҍB;n$x#y]Lc,zD \>o#Le)-X%_%er,4e鰄BE pԥ5taX=CkƲXo'5;E?JBj(zDY,:sU \,޺.Q*!d E4OX62{0|\QdԪjnJ3]b]d,$Md=1K! ܓ]5dlS!kh26.t5U.tW)%ăbdlS "ڃԆt<k:;D+Oƚ>58kuX?yn.ҁY¦q=kNfZ"6uMwhF76uPM]Âc/,+qwɽKaa$b1(vbbԫIcaPڞFe *ŸPٌ?3%5;Ryx_C$5U,_oȽƥҊG >PQ+N1t/)xxR$W>z.85DCS<=,_zIcAn{K:r}-Vj#CG^$ãCaWi M5WޔT?y{adAz 4Ǫ%C}UAC^I|h^]cϩ4zռ"o_V^:k}Jd{5XCz>3R< =rꡜI +[;-HVP5 (wRѶSnp:XsC}P !3 (R9Ђ̀2h (zqdBOf@)Tt2;3KSN ft5%OԀVحT6 s)g(4 뗨ӉfȊ~P5 364WW p,>WN޲_]c_9qr#toosˏhG )!2)$m=RHP#D_-G >)n(`mG QB<5< $yGjS;ƒtI-!$izdh30e qћ\% Q UD$C)d,AGmʴtt<*=ܠR(l$uZ~dgpU h8t)PF&I:]HEN|bx %O.*$N,'8m`<ª+b >ZcnA5]#_7HUͧeG LL[d|}+դ+%XHW0DÆW`-wO)ɷ$U)HW =t%C1W>iuBBpJ=:h a˺~`[)MU^wNÔL@ /bg'kN|$+ޓh֚؎izOڽSq$7z5;aSj{C;{'xrOf0BXv,$|~F,Ԃ q|A$&D8 >! ydDz,=U3˷Q >2YςOuH{ Mo׿Aז{K= 0aj*P Q6L!8ո:j@H{DK1 5}ab\2Ƶ6nʥ SBތj"jdt:4PgUo1n&cpi`Q'l[OY,@f/jӲng W=H5Ӓ 9Ȝ{e$* "4*r}X2W朽y~(Sr@y7'`I>J:V-Ѭ` G沀M?L ^UaDfZj_Y V(KR0K8X3% ]9W14S۠I)y#kXݔMKy71y8'x(HB1+!;0:D%4HJ5^K}'1w!lUӇ06:ܛGU0HN9?A~!!e[wdvJXVa`b#٨e87=.HҠBb6tni߻&6[ļ~Msb< $!p`ސ7=QOufYЄn S0ēFOMНJ,1jުfz5%ns%ZHM>$dEcsUaa&DW 6իdp|4RBb;&I Mb&SS"aT0&U'bX-|ɾ,\J4]#@` >XW%B.zT׏ކ~a>ȁB"$\i,LzeXs uY4N"+5ۖGהN^/E} @s6dl#<8WxkW%M'kAӒD7W8\Cze bM_aJ"ZwVz RV9Q8*fqoeN27HĚu8Y2Mp"yWɆ7Ex,uuZU}SZ.>xzhWP, EE2' >{yD<(i^_.*J+‰F P(FU/gvh.G͘%;穑QK_EMͬ(4:TVTI<%%]'P;- %ZJ |T JC輞Z |.4H |G)GU12%S&Cd)Eɔ1(2uʦmIiLԲSdf6:5?S\3r%rMJm& }r'b2QSD|a9n 6Eu!0Ejd7mSDYo"j};"Zyq&Sj^h-l 1d'[f~kD-dhksm(EzE,me"rOh$|u_!Rl$.=^nemX+lDh Œ+2O@[|mjlْYY49ibgo#x=,!+FAza\7B%5B%B |*B 8 yH{1sVn9&scL̰s̜d9f.ƺ6sl1srs3ƒ~9SY;2"g3D%&1=rsfΖ1]2}3gu mr5(:+KfDYYjُ̇8 Y ]4ebU `W Gg2nΚe&&ܜ\bLB̾vt4EzFdz 0K0=%4bLD XNY MQXˋx%sPF(4:i_4namc2}*6{BveI|8C&@.ڞ @BX%wRZrHJbĊŬZY"#S%bكjiJbF!&˷HFyuWlarؕξK YyTE秀\[8g/FX:5",|e(1q+% _$.>xк10+Zf]'{o)9ߒO!SPҽPBVӋogIm!_}LH( Sa.uu"%61ҵ^w붉(p2nclTKr%}zs`%:!6zmԻCna HA'>CI.Qn`ˌ!N[kT{9F#,e1@5%|>aQuu8z3tPÚ3%rTm2}o}Y$f yihIHhԚ\g3*h$UBjsGȦ{AwdSB%M:8,|Harcf[9/- 3Us2PɣTɃ6ZQBIe}a?vނg 6pM b*AմJl,ka!"t_VxZ-Q˖7-6*Hl tWusizVFweu/:dY%Щ!S^y"7"JVr8EhAҩ[*݇yg^8wv撳)S9Ʌ~,WEbegZ4eםVރOsTEL\R*! IIŻ[Ȁc )$FdD 7;D HճI,*913EpEޢX8sǠR1|-[5cI0$mC[uޜ{+=K g^t^RB_c%иD0b#D9^ܗ%o3X^&%?_ 6#amr?&Ic$=E|.\/m$-KAo`AB{U BFzݦɸ~d4$ZjQnNvRXxb4.CKCݢ?gr IdIL:Jy9ֿ'OIuUA?CjQ'9iuJR'_pDs,٘`׭8]v7f&8ZHW}rJpg^,U&BȮ.]DgMe]4চ ;/,&YTkyxxW/+[-uTB,ttX뜹Xm/1rd׵{7惞5N%$Kt8$kϫWxunHfx@g׶ԷM),jՋiכ˚3C'Wk>O,g&Dj`,Y19~&/cR׶(JS`/_jgst~-ygp,vG'-T=]uџ0+=`BFs=EZڗT "0=) GO #\>)Z%A)w$'^IiѵMM*3'OjRJMUCÓQbDA['XO 9~d[!]ڽ3 ݺ (p(^qf$b eQU eE #K ѦGyx])5D)JK8T-YڂZ'X&7 ΞcHͶ P6g42RNlH۲BPb~׀(%쬄kH9BYD^تlI%LY(1cfm&0تhBHE,$R9b.n> U ڎBɃ͸ECBWMA-/ك7x44%dҒͧR,6&^`LNy>n{3ci Kcw ٣=b0XNBP+)9!<= =Ցa KKeCd6%sK雙\)KzAh9J0,GK|\R(G?gnMw6LUڂp x)ROF ҤdɇuH@G|5ݝz4{G1]FaoDImٽďF65ya9&O1=%vv1%ܢ~a PJdtG+h]r Xvg'H? 3 H'&(#@kGI H%J|&+M ,^! B~P?a+\Yt'!O$ P2)Bխ' ʠz}Nt>KDJܡ6of~z=bz=Q] ߝp?y5pOBk[/~xH剭W_nZMɞ3'NƯŹ⾯_K>1ksBZ^<ĉo4nOcg;;L kzfC'&Mxz !K-R~wxv8$tv'-/8T}[=9 !ͣZ_Ko7Szݾ5˷k {^pz,(#J;{m7WU%o 7q?K)tKv,oפi^xŨ=פiΔ79u?q3up{8<eW\dIq¾^V0|kɯkt{o{ԡ^q)H㽟W+~ν/ V^-8pzǠ;Du{M3 {AuLb{CVXc|$#lcW>q~]09|^q1ʈXo8/]H=/O5}3BHPqtƉDq8U~i'b>q:c9ǐa}\L琜׫aArxìXU^mE}_Qk~bKy#'׿WKTNB;IKt~x F}^ihZjhę?_ѓR+_';|5Ppn}z̔\/a3b)NkS' _mz4Nyp0S%iG-)7A"Sr&jSى[EAB9\qPF 8?x֡ X-|4KPGwk,z$K0",JuGvGފ%Q`lx[GDHQ,~4R,'K>F<%Gmi41PY < '-0c9&WQ$0xCGLRG4O3zqz/ų:3)Nj@?W68!r#tI3HĀR{) z4WXԾԝm]-[#ۻc|T_ k=;FWr0[)q(hhv_#3v_)Ͷd];=^׺b]!g$ٞo?|foUl{r|Jmw<njv?޳Vg7䇯v1c^fO;{]q{1+4 EȮoO gx{?oS~XMWhj}(EdL:|}]HT(~Etj叄3ʩw<0˺"ǩ$Oq&Aqj7}VޕTD)|a:8ԥݦc.]oV'QfK]3v%ss뺪8EYʩ˥RO.U|zISK&&,dP^'_j-m.4-KۄCv6+^{PgQRJ+gTZ}@IgTkE*mgLZ>{bIjIg/TZzzI<8TBRi9NI0>r:V#KZi9ҤzJ{E|MisEzKus03FK%jϏdtO?-%{;a,;N3a]0tQqoMPnFcqEl9{YML-<&*ri+BH;i{%sbqaq"ϽGK,B[d4zאm1BrZ]Po}|2ֳ^Gɱ#r= yVK\ײ=/e.9QbCh^!64L9??h'+gKSAz܄t@]om͠Nn'Iڔ]($m4|Ilk$Yr$ 'IZ2i|YIj%cc.P9 cnו?zu%zSo'j5-XPѩBdчo};f=z4;z4.20գf;zԪ$~M?/Bz( taǘn[)PM8VWW 嫗aSzwWAX^X+N01^zԈX:J8둵ǐYqg@Ʊ# ȴ]S'%[^G Zy W1q% %jw0]Q}`I+ 7%M5EL^|޽rywASA/+u' 늪߇]P}A| K7L{^3OTqkkZjmZ]~%}Iuk&#NZV&V,]x ~h`>œ}w)f|ܮ{35)tNrk$rRzY^/=pYZBޗ ^9+^>X/쵛 Nڍ"f6}ojHHrF^;M3ҍ^;% Knw6C$ZVj]nW%M=v{$UݒV"Hb+#777K{puKi"9lᶻ'yKB w5.pj;QzyDީ?SW"y'e"LPMm@aujj/سIvY+1UY'^N8A31vCHbM]!aXο<6qeݷkxΗVx4AS%F/7rz/c? b﹗{; #BSQsCSU:s&:5_8 =W`*{1mcW_j?0,hJ`VU|IoŘBB.vY _s+NxYR1+ 0Erl1gK0\ȘUx:LRH]n6ZF?!nyk(C;pV-_|,-[]?f>UNaȂ~09 dGϜ)*򠖃x:5&% Ȫh`o ɦUo`܅օ-Vt HjK)smZHb[Jq/1dڽSbncxh1qB[8Pk dl M)y :1]'N7Ԓ,ہIUnCV!_>Q2ЭKBןU*oMOżnCD/4!pL;SQy\f5Q%Zܑ2;*Z8D`շKq$շT OF-+WoT~uZMyu/%^Jov+X4Ⱦi@kW^h´:ՐZDZVdn-6HWdAQRՈP3rAp6%=sISͤV5&5ŌOcv:W# HA?UF8IKs@re]JͨᰶԝB!K~ ҈Vz% _452dj2=rMOr[$/%R$vuHqқ%tB3sc9YX.{!&&ܼU0^f4h%Fe(!Ԫm FncPsREMf`F8DOycLxR9=fS{^"$=FdE*%tkx~V }K[h)F%NUz'԰@N\[&22f)'dK cqUzG&33*Vַ(M\3yf\U3SNEUYLy:)0g/D$lRU/ 56{uUƣý!4cw*ôj%0 A%Tmͭnh@PT턴6M=¨UP/j, ?jK֊ >F :`6Lyv&.r7`rgFI$wx!b"^-XwZ1eP"T(Fe>wq 8%f_,U~nz.ڣA"V8/ᵄm$fzbYOvo+9pIR1@P1ifK zF^.gݑ. Jbs^nb*ܦ뮷ʗv9`sGvo —U-5Cro@b0,NW]Jȝ)6`ZkPx(JU2z"fnK޵0$Q*SNbNؠ4<FZP78xs&"u EkX횂Oy{uX?w<~w⇽R|)g'I&{Q&XxmJ|δܻu CRRAIՇ*OH0xcy!ko_^Ei<ƹxݧp|o>q{vǙ}Ʃu|3EN~?Dqr UiDl܍ܸm:E|zuu E(5(pܒA zTsdb)Ϛ^7=o~Ȇj43i%!.EM؃W_DwNdd~ݭ6]smLb!Ypv֮c9r$l™P:@&\.&tG@'>1FĽkD{$(GN0:Fgcٟp=0 &D8./{Iǵ.T԰3CVF4nJיQDc1Bʱ{B`M-:FկAI՗}eO &iP Q5e1UvKdK$ٱF4X5}+}Jo%k E$>zT҄$[_n-3N#jsH( a51nHBC~YLOFD4-@VK6pڥPK9BYCP&EVR,!ޣR#P!aBWK.[G/E=<-p0XSbz'Ia)RBJ% v֣79mr WH#W"-¬eGp),LR骊U:76KK$懥 0rqҪ ,5VNRdIq޶rfe"3.D+pC0͏KDj GA+!K.+XAAєTUZ(y_ҜmsR(+nŘ\f_1 ÷!FX)|BFL Oo{6B5D"8< _rԖθaѧ#1paEs2ڪڰ>Oā#h&fқcibAXdPGPhHۈ"ok(FJoFc0VSU݆(SSdc(ɫ,}&({E'=ϟ[XΝAP$bH8)̱cIh+:=+)宿=#妸4WF~??x33$ޠQ# 1!H/ON鄓a'V4Rwñ!rrSmgVZq߼ԣ]Rܤh'V<?b%ŒK>K"5<uoKVY+:-[PϺwIn ev&`v[,$+bOCTBՒ&ȨRml>q[𸖣G:_u.XRJ -X6?v^^2zLYСL"FSqX hӤ5ub@|$ieR+T CZR_~ t;^wXiR/8}Lc8i3Cf9KӜ3GhI2.' %G= n%-Ix͝0 mNR S1GL] T}N7 㷝d$rI$g)_UīBy-<WqG^W&/mKa3Cp|*Gd!rc>qF>^r[j})ddt)** cp GD]} l@, gI=)И1-uN̅RM H<*uU\l0vQ~2 b|X`$&*S΀󱙻^!"r! hx8Yn'YHn-LR+g 2&/%cўw]t w*eqj؅X39>*K''&e0)tbZb1hA'$8,fž[h K S((s)F8?dV2gRj^OccF#[7#ro$',ֺbo O)VZ_T?faaJL_io,ApMq] tpȌIG8A$5 B?&h)!f$F0dV\u$NlԳY+34(9ԖX"j% 5R/ m4)%.% N0Z 9+}:KCʅZ $c]yP%S;w}}5_b>G 27J3Lqn3}QԢL9iტTm$a.RX9N9?]G/D)Ѧzfaս=[%'폤GHq.'<=:,(? ]]7hbR`*ó< Gv.:vѻwIelg&TV,5<\XQ OOyhuUN͊=Ej{tB2 h6n%ǵK^q7yZ!ȒE} Sfb~M`"18HRLCx@n&quM)!E,FU1=&-XYԭ8og.ѻ| ݈^޶q[0jAP=1l?UTM,;q:#q͉vaÈǾd m=a .t,ޝEH4d:_ϩ39>d hj>_ЕKU/1%/+Y;?Y%KSY BʳTXJ%@rAg9dzdYY)s;Tz'Mwp3=w`r5]eZg:fޙ'*پm|{ A`AAPi8倐fBA0f![0iA? }cAf#4} Y:0$d @:,ga?p H M4u M(4{ ~0H s6)jhX:KD=p0He1BZޠǽq2ea+;,Жƽ4* NTp-;uحbly=P;[HF ϓB$dWUpFc*E3|Jz2~͍Z[,|M{>m=h؍KZ6acv uAf86^aN?I஭Zހ?l:.KazX;3WxXGCTXP. щ4ONvK,mDRu`};iY8ĂY^e!_lmbN̰Spew˝wbD`7=JX+LԄ,]O6ᲆWz)A߱M KdTѴIY$>n^ tsLYPkA%˴l+6j l2B_M:߁u#cYEv wh|I~~ʍڈ+Ax|0b+z.)0k\qĂ;X-pC زEfJ2 uV1}}^q__KgSl +g0;϶߽hgc l\(>x훾e<~}L3=?c M{LZ 1/{@:CNP*! BTcY8g ohmOj89uDhsgĹҙq*̒8ӚgDX_{o\irLdYxlK dq(%{JrgKzBj>&g*ptsJѢKB";ߘ _H4ayz~1w &`f2ob] WQ@O|O2$&$yl; 4=t~t֫848!TDN%VϑNb&r/W<Ρ>m6bF%y:^^Q|񹖷zbs$ !U{ׯe4x=|+4 @?>ȒkQֈLjQET`{w lP*!.Dڥ^#nٚ&R-@"RauyY+}8.zل߬XMN4k# SVe[]e BY~ެxM7l]ԨIaXܝЗ@o5Z˱I:~ĜJi^MrC5&Ij5Sb# vLbP.Ujx\[WcDcGW)ONgO4kkXvPÕJ2K$F3<*BY_:V+Txq5"?dZChhZ.˗\0570ɢ q%9F^AY^L3 K}\-H#æRYYYmS5'qEf荥[G*N.7'޵ܔW=?c3swܵq#VO)syx}YOL;cFbrJڞ3hO0%ڝǤѤciMtV _'~. Ĩ%ܽ$}Ilb8G29I^#.nHQ<6DWtLg5a;2";ra8mM/ȑx41=!K,"DiyxaBCK*A7:5TH5 r<-ȊT n 6~QNb?4IY"$S!.r0]&alI+gkH1dbY;.}Ŵ(HL$*\B9kc9)/\\<r0,pܶX/@Erj5Sei*Yq=mV2CO5)] U)U]c/s9UdKdzoUoqU }'uTHss(|!Ο5/)'=3zS)V-%Ŭ&X$j7j;xpv^k޿ 0)Xœ!an24JCQ6E`dvL2L~T8KAgÿ)u#YL%=v/&t[wq5пhYh,_ <4|+Qs¢sB퍣#<*HU!1UAib(Hj?x{o *Mu2"P» ~aeq H"Yدr( bخbH1h7̮r6U.o)/Xڸy">/yƆ;}CctF9k:G,W9G 1AB.g>5^\ɖynEY{'xJ Z}W\RK=g4qGxi3Gx}?2ALx-s1\FgBL9"d>^N'BJy@@UFh6FGY,: reRF HqC,u2:bEV+bQ=h&mW`2mXqJF(&T6sK? m ]HA I(8/mh,TiƑ8KE(vuA5SA㵩"MWQIK+WT&'FAE} 7[q]Nozzn~:iO{}q挻OG |0hOW\Z.Id@{H$;gDA(OeB{XrI> ElS"V鴉hQN7dYF[OOo@ltIOPl]l?Ct\;Q"7'-UxnXh*ҐgI!tъ;8 rsK9fBI&S6E.mp$sV! qLE'DG}g*8 = #(5ecӞTqalUnhKa#/=n.dL%țw[l42܅>HMrP;*~ǠɅ<w\H+T;F!?m,PkTp7+ 6iν#`Ej3KKyb1 Cj Saۚ$0mLfqI0Ix@FcDXҥ6gzҘ4/Jv'MJYL \l7j44]ZV,`!ol/_ڨ?{G1Jg[TĪUYfU*R;g^dMR~qTyjԛeU]J%ثJMcE1I{UP=?:P f'܊Q'`Qdɸ z3f[>hĩ}# 5#3#عB?SJ '3:d؇3|FC ʹ'G,3X)]1Ovz2FWͣ--/|{:f焞i'e5]u8N95w:fZ{TnLeLOYk0dbNPd9܎J|s tR5NYl)9锽[qH{zHW}ĸ&x59eqFoV71 wϾX_fd.Oq0XU-4n$MTiӪyRNty4a?( QAyh %}Ɔ4)LʼnT*6xo%8tR.}rnMjGj/sZQ̆c/*onOQyv\ⶮ ֻt+ܻ~:k\ZU2XϟEo \vnHx.K"G%|c%4%}}ZƩ 0nש_M6p"Ԩ׻cFF94|@vVhXgd<鬲Nɔ^*taHݔΡOV srz6hu{ k]qؙrII۰GrLCB'g'DdUeUTS"UĽpvdՓ^ߡ7^_藓:tZьr53_n>T!ą!{%4\F"7TQB{!b+"r*ŔBYQ1 Ѕȫ߀*HGFv/И̮eTabT/zjU~ZzpzuDDjPt$-r@8P_qO;R@ V@!HAU@ߤ@" 0Α1)zd@Z@ +Hd@XT9Hjlt) ;s$48 %c@XF9y4r I941X뢵p(eAwPO|ƦM>eAx~]xy- R"dߥ(%!,iş$hW$AF[ ݞ6)/#U?3兑V u= VGo ði0GR7;9(oDZ̑4{{夺=hu3VwBPD;n K$獖#+@9p;%/<R/@)2%3F-X,@E R_pI-%8 у0~fxJOYMuA6zQ{` # Rǁ@P*1&cftEФYSj$JED%`*UpLQ}%T=ЁSa_Wq* }AaDG!6Xlģ>VH7WjIG.qy"|f凰A*LR]*kWu^A TKRű_Kr.zZ!yEpZ$EՒs#O^jK -Q`%X,bsrOPڽݓmYwѿ¾K#w,- *U^z,r:|ç 4t Ќü9}Zd]&%-Ex OoV|zRJM\>5{C6P!˜/}偙R]PF. %@ OPqmdlDPc95tl;zMC(zl_ʜ'Ic4?ih4{BkOE5Ojq*ڛEPuP5jSX)AWH_[F[5/@ r甄 >4Q FQQ%ge7R`jz2$wIDwcǂzq>ro o8xBܗb,w .jC(~V{UYnSbZ|O,GT U/ukm5.]c/ eԗjco 7ʝ FxQnў E_[TlKbd!"UCio;ҘM?KorҳB3VR`K]m[!ee%u]"TjLa4b<3)Hf//)taٝBWQ6blۿyb'x#S6II, ?#L-•/*.P|Edn ҈/Tc*#)}6|uI %UQ{qwRG~0+3ENwHCEw*PfrCQт2YM8lǷk i16ФW%rluÝSIm\s\qt!)xk~q_p6c,8s|Ly mC<mϼ7^E?%ce bec䀕RF kIWu֞U=⍔zj q폝)gwӉ$*ǑͿ3bj2꾣7/Eh CX)w(R\5^g+޾`=5<#AYhPJbl2 !.M㈹n?$14T[B6=U2P(pM)^6 ?y|E<@ EfO/q#Xm8A5M_MwcHܴ(QHbSEm 6F |ĄyTxXjAfueF(C}x15R,b zD-J)LY-1K89ƗjtΠE}MMUZ#% ( !=,h:4˘XPƴΣ?@UrkDaa(TNx7x^Kc''@0O5i/Q>֐gg/eYrŗH@3&J#4d8D&~zSLy2lw]|;;g \ۍG Wյ x ӠZH&WP@ZʿRH@38AuB â;7"L1 3By7)vl=D e}.Y` (?DWE")k~[_Hzvk6nj! =8C+I0Ȉ&iJ޺MS)a@k얬wy8Sz7Gz/[;|2Ȩaꭻ*7KjC֣ϰY3z+1SXoʩ@5%jÄSRTfsoHr{:ʼn^֛rW/VǂgO I|2Sc)\ ^ `XhťYb GUį1b?M 0+x XY N+P|gs+.1bn-ЂZ!PfZk^fȧ[T!q.u 2܄%%TtYς%]xGrBHP) 6oæh!Tͦ]qx/E7$|Nd%ť "E:e&RD;/%T NO9BO05 ػ _P5w%\b蒔y`QP,TgGEgFYŔ % '1?@ih?OؾHaIDؾK4OOI܋0}M2r#tR'tߤs:Iڔ_=}>IӴ*RT<[%SV2Fx樘EHŔ~'?Cco 9F3?s$)ƕ(|y#ùQf8J"GRߎܩ`WwWۣK2 PqgݟN>"P];q(4&) B3ƑT 9*NڙRֽhEOX|mf:Q|x4k9\yxͭNώwX%X 8W}eZ,->X5"ppη7^?Ƒ&. ǎ zÌR CS vDT'Kx~V+ {~g%wXkN91\ɻbev@oɼ5)Q8{R?W&'65$9  #Ft:j|٥B:Yha'pDPejaAx^wJR "XbU}VN*Yp;iTH{yHʗ')IjT!<6a[8tE WO \zVl{ _g)lua}ShUbJI [K!J6˓VާC3]Z#?'I9]w-EfQKUz *8薤ڍ~vHtd ;EI N*`Q;j`C!r"lRXN 'E-ӱA,2!;0=bq,CG= V6)Ėx/HKUEM1Yvڒ]\`&pD B[e1=hCDhL C&C K L^C]́[SC&,3&SA,"uM}>[@Kc$rK u1P(YMqpm G"0HR%T@1IEi# s7 *:R.⺡ (M6z0`ԥJvt(M\׉b6\ [RRq4$ddc#ֆe{/ i' Oyn?LNh(ucl@xtsUQ3aY.?5@ &!nyxp%=XgkjeiiEΝZƊg!ԋf8zj7eƹg YPpv?]_@#cM50yX4Ev%!/X1\NǙT|ީa 7 ԛV4N#hARxySNԘ |ĀÉ+]7Y;U)ŵmZ|[sηgG=©ne횒{8Aic~̑Ϯ_4]S`3xtXbgkxTC=hæ֥^n6KDQL "eu#wmQl`e} rtvcH MT\ p%ZׅL%ݲ47Y5ԇ CփIV7KR% ¨ 0iC߷CARVAU NZqӺ^!TaX-,)zC"yuceVJGF!͡#tVxy+}J2U}s80Xfvv13C4sBkLvǧ8a*݀Bm3=Qׇ70&>GT~_ڻBTLK+ڧp_-1blC{IJ_D1jQ`եQ6lhhc}슦FF5F%ObS n( xYU^i UUlX_M*(Z %&ϫ ODaCabžtD[IDXl…xtyN&N 39x _#$з۶!Sp0* PkjصBpŪ] %3"`69lh3btՈ "_~/ oh UYD'gU8e*+#%P?\=PYП̖Ce|Vh+3 ʊWrHI"%ʈ`d[Ab 銒0&X_۱2sbʪZLØQ aܿBe(NJHnE.2u3%5 PƩC8,F!t9$T--e`~Eo8؇ZQGk":ZfsS=ڞ*A*֮K8s:%D4d/jR?JP7+סD3%܋zA4IR-rr_:]{.[@#0,)Xw!QI 后5\}q>^c=N\n?uI5aj/_qX̾dR-9!{oH}DDIo[sZ@ܻ*7?ޒҢ&[aY 4[ #9$ lV@f"' vjKJI;5gJ6 ZsH%[\ŸCjO=?$?#e]z& eӜ?%W)jy?G?6F5ځƟ!S;I ~g{Xy\juP2w)7^_2,e$ܽ')wPNL+݃ePXE,w4%&P|Z9{<%"Pz&Q9+DsIWJj RH00|%R&TI`SSEuIQ-4-d˞Wz6Lh¹rKj+9PGZB|Q2Ȋ'tQ6hʘeƭ /w$uK)_ɂ_`PWSru_wԑGR:*Xp+Ma9I"2D[@Zn4@RiP}n'uH]5_ҫKNJC[yС{K|ҮAU Gz]XsB +n_|=E^ q[P ~.DR'Xrε+U-\i\8:+a[ x=b3,Yl;pD)jEi%0m9?(;hYN.\Mv4ÐKvKX%ODG`HK5)tƄ& x08~;+R ZcB^ܗ0N%/9WkDTTg1Z-CW.O46s8C2+9Y")cb}P!<"މsHVW!,о)"$'sWG_(('ܗI\n7fOqiKZalq[ w*l^0DYw IJeWJ$Z"_*|H>k.|G˽J*pK8Ӆ7 1]tUC?EI}Lul; lIc Wd,lݐG7Z>H{@)/㷫ap>|z#;^; :`s 7T3{D&Ock ?CKQG=v$}eu&HklGl[up)~w@w(G=X-TJ ȂUN3hq^TO6M@h,UKhLO,b9Z(NkP"92Ur10łH]e(ݯNpf5,[JfF`Nf_)JNΎt+ǚE G9ؓPG鑧0LpR"[E 8w1'O4 X"Inj@ֱ4&f(Rd̶oEwmRڪ)/ddCli'XjRMDa?DB;&!*(8HcO=L@lᚚϣԉ]ocD?Nh7(+s&U|;sD?ƴwGj\brCq I DZ[r+'Dܺ܏)"Y܌BuGwYcli O[\Pz-d)8("hL0g$b޿( !ٔb8LÐnE0R\ !/z!n׋]8t8ĿJf⾧&x/Z!Dz (B!z׋O8IA)لt/2hmY:_$BpgPt%*o!f$BF!$1+2 ˋ=zU2 E2(cC]M)P71ܚFY!5ư io);kh.Y`1$}wI,z^=յ n.I-n嶃x',A/r>d쏛WCw` a#MS98[a}܅`!hOQJZvR%W]DgHYFmPµツk2u.ǡ'\ B@R3z+P*ݸ4G S)_@(X * fhPB Yj΂$_1=}1 mj"=)@mj)b#sN [cx qMtW!t}ފ5ԯ)_+(=Tߣt-3TWr'JTTGiȢGŋ,M nSVֻh (XC'?Y%k.CܨX#3OEj_r5lpb0O5e8j ̴PM o|Xo "=;iKhFSG3Ž\hFB3+Ҍ8nyffKf ͈hhFpVShFd9Ҍ(9ޣQ!>-i)(%ad HFp-aHFpdX 8$# ^G2"xiFЌF/I4bI!|D#V Ќ5#b۵7^_6tJ?TnI;*ŲbI;)NG ݧAA<NcZk4V'N;9vNnI;']SS;i$EvLnI%֝[CwRNI֝ᔄddZw2[vΈe'ܲFꑝ3bXtrNGt.H9>}6׊-_z[d$m 5I@OenS!ktrW`tKv[jר,rl5߉Є1KX܊Ɠ)!gEG$ZM`l=zbccvK"Nm(&H[PX{ޢ˘Zp5$V#VV {ZB,.j NpH aL٭BHb >ROPB bXd]TEXP!$BS !,$ߋU;qW=,m~f÷|`s:'̰?+ջ)ٟVemи[CSuadˎ.\ӎ,NŽ4N.$ᦛE 18eB&E|M\$ ғւnwd|)i!7Us)qSZ:*pJ[Nڗ/7zVS*8p$LK -tuߔ~ɾ)n7%CBuE|iɴԨɴ ɍfH.wј6do$}D\fKs*QeWD{Ӑt #="ƌ艀j3 )4槸ť J𿀣k2,+ӧMM(-B$IJ5$=wc} %,SJ B:U,^"ye/8"Q2{q6 c< *x2Ct? idơG{C䶐zz?P>*P½\D4L{Pqk$Y}HU}B,:[þ"q9NITt$^I4dB bWṋvDgLc*;unh$U9-  ;W'*JMRf#w֓WzޛB75N(h?ŦTE 2+ju7Dh+tʋh nQRi4Dn~*PC6鄊Qzjߘo}PU>6*KG+T*_G/GH$#RZU#V1]6hܺxX.k!H ֲ']k!jO0\GVXXx\ȷ2P(K3lA.R 5:{a[w} Fd>R.Hc `t:PL"Joeָ, [s}F#$it˪f"DaYq?˒-K^bR=$t?0L#}vxK ,"Am'Wɂ3m+1(xA"_IAd"(ւ}Ao[oÕƵzzWqwP%;&YYNam .A$ BNM S=0Rp/9)" Aosvd l8NNwSEFsRo}+pJ"ͺumӧE,'ft;TE"sJU.wSb cQKA.f7uQd8E0]JxYP_ɡ9Y؄1j4 W~xAQ]CKer4H]N.&pVm>|H6 Za#+ͤ(G0`:C8{йEɇJ*!v#+J/`وne Mfd8YJI99J^s*_o%jVeE.ifE,vU (E[m9}YU[o\d!逮FUIHm`" ?-Rzjʉ|A\9Y>{s?hpStV[|rӣ ºN"U&z)V{H+)>$DUF5;E96 )I$Ud@90])җݛ@X07.5{F.Evwrb JJRf㕕 &j(`UᄔG7MkB ɗȿj's(h+'\D rvEK̑\"E$GvhD$bЀtB{캠h=@w9;:KTP/m0d98nDq`6IoƗ o~ a9xkHI>+zfb7)|ۭJv?Jp݀#QCi(;gc DmROJY@N2U!{2"nU|3H)T Q(ȿޭ]+E@SN^8*(ދ "אّ{*K)Gք Pʢ - #[KŌfi#AQʙYJԸ@x-HMH5=P'dʼnv<=n3T#BR .&ZyӜ$fԕK2J/_XrvߎSjN"qW&ũbV+7B  i~k)8VLsKk#ˋ&U"ќyQMHd#`=+gs'7JGrN(.'spsQGr5weJ鍗9*ɇuvشKJ7{Єメ|lj6hu$N4;9Ң~i 'KpHEsddžȎQ3],KG %Tn,C'VE+PPg\'ht Uq)hK p%pV3…V:Q< 7t׉'"jprfAEQji)=6Hl֭EGPJ!u$ZT gB#kj(ke@H<ѦOE=*V"h,}-U>!ӕ!df?Xl*z`_е.MRD@CS*rI=%E @rl)#kPU`D7 r,x#7Beh6KQHR KLmpd ' ^/]dIaFî=7TM@UV0dS;diۂ@;%@Xֺ>toQ/Է6! N*N;☖}˹S]ŮV6pOYM йj\qF,?uJh0q)nDPbgIA.۩+*ܬl5NqFBRLI3,ّҺD9+MYG 8Q>DlJ BB`*(J?_]DUmVUR-Nu0jUj)V6-\xD7#)(Dכ%}R<ٞVW-B~H}K⟎lgA**Q,2*Kd?>J'MTPۢJ5XҊʗ5(99jNO%f-vy t5!x:$*\P'n7QY@rȿڷ>h~o$gِmzB]uwOސdx**/~b::&Ӈ&* .9O+v/\7U{))n&ݿ"KȾk{Md_jYf2Z[?Nc8w{k=Xi4)a!+,:eaA¤Qԡ9f$ OwEݮQw02aPR+3t:N6B$\yPdS%g b܊X]8EyxsI)LgQ!t .mQSAI21B䁐., ^G`]] G^%\. 1۔#wV7gU =eu2sDj?i(iRإ:Q^[u岄BZb20l(VGS rBF$) 2lvP[waRS:q>"q>ј>rӍtwgİs0'Syέc.ܴt|>IÿlXqQ/>T&ng v#Gf>'uDf}U1Tх=`HqŽtjuybLXK OC0EEzhRCO4 GXeEƹ׻X+j/yD\HUdgfހIu>UoCWJg6$7Dt!)S4v_tT1d2lq#.364&-Cmo#КˏMT-6|o/2.0FYנX< 6x@k"TTi5N/fOD^ţ'D*EDms40#m!ݱjci!D.Lqs!z-dʗjdU1xw$blGqnITw 2㡹 cUG[jj(UL] To2/U^'HOOjEnTDQ z-S &zd.f1&J$R^0N4Q-klgBɓH~,;"?In<"~.J+E|(hFBk[5'ջθz-wۤ9b2=}yHS DpuƠ 0* ?njH\bYVM,b>WwAAAvR ƙ7$ŎA ΈB鐖qpu3c~IOOw?wO? o~_yz:Ou??Xͧ+ڇ_w#۸vӶ~?oPd`>ol;g=c"?'6RWǹZ-QP^g_z~:2ۿ'_e=~/O?~He?۔_mtP$}O+eK_dI7!\و~s!wJZ4?u Dl0%>O/7*S߼x%Pi}~? uoRGkGj/~in;ٺic~'c~c+?ms}+O??ߝ^?O%cZ/Vp|gZe/k t82PؼFs4yk^CSq~ f%胈e?m7Z =Lqn_ac/2Ci0~PY˟<>¿_ޫ?^O~ڏX=_uÃ!1A(^}~>ڞчH+#v>ä F-@0@Nf+%z sbACʺ^=')ƷeJ\v ߑol W PĽ jlzIaq&vs#ZEpÖ24$.u85!RuJY>C](e>Ԍu&~@#~c&J_G_2} d_ُߤP٥;ͣM@DGE(|yPhb}.-]ZOBC &^h~z0AՅ@[`$_ZD8凴As\* \N>qx=Hx@DȧJ'|맱@cx{*_^1wVl.({#Lz=w}E@1t0 ٛW樕pӇʖ{GI`zWvvVɐY P,jq&T{+DX5@|}ldF Dgq$Ėjmkysi\^ޓ,TJ|dJ*z~C%XkGa]m8H\}/MѩF7ہ5TkƇ8%cޠC"@Fu7{]{Kr.ʴPIoʤRW%a ?č_%=0C :Q |ƞ$}ͤq$ؓkƴF~pX܆A$Y*[)&מ3RE=9EQթ%zCkmABL%=Ց? 혲PyULi/0t ְ%XmmP{- u:RoDS'{1Pq 2FӦW|mPxtF_ϾBQAr$۶>^%弟~xGN틬 pD)U}>p_tX$qtڐҬ懗)zLQ,w;,Toّ NG`~,I2h&u:of,yYsڰw4̸0%¾I7'"p].פf-ͪ}U/O&.o֊jk}5=5rTL’TBжڥQ̮܀}JᲑڮI&S۷m'<0: r.ivpeƊ+4=:"$}ڇJJN<:(% Mܺ߰)Ȳu:[g]Ҹٯj9&?R<~:5IMX"~2d\t&>9ύ3(e/+jL~K[R%b/~,uĬ ?o/$y *dm糆uSLeZ, qP̴z_;íx#bim;Z{a,{h]s;zE]nP;{oO gO 9T((:UG-P|6UW_yP\Ù"ާwȣ61"a(in{:ًp;ZPgu9/m|dzH|}?R=,7SWS;Nzbr}>%0l)C;rh_>dp`2 ɴ?l>}D{!d#t)|DwP@p@IޓH ')_1JPGvgo u }|lͲ~%9Z<i&^dSQ Υ?:x8qZu A6 8`1U!ypݻCs]?ԗ~=) P>("?YPm.Q背7Nm?`]^ 6P >d /(KpJ5<̗ے3$o/(aFg֖[z 7:5vyV~#f zS;)tLrlw M:?T8r] cX{ ;N<;&* dC\lO>LȤ W vSVS.o&Nӄɒ%!F jϯz#)C0'.^e0 Oui rf| $X2 3fc!\{cUiʔi$B/c:8Q B!2{w[~OSLۤPf> 1 FmD`O,]bKm?sQ7@zj2H5s1 Sq%'G Ռvb|W2has6^L(Zp{ȯJn$akC%]0!4˅`E P\}&G"g3{#.K2M=SX(gh?j*i.0>OA)HLʷѧ^g@ W^1,L(J}8,@PI~ Ul&WD7:Gfp6 Si4~acѾelnn^]Kl;\=)eշL+:eTP. ~}pm",A̱M:J(WŒ]N?؛w'|?WhnD:"ٽ|O*Ï; oIn_%bi>6iVJq =fj1U/-yIK@CF!\S90 x.\}e b ^JxErcsQ/@ݡmCQ![Ϗ;(7&x(D0VqnCyډ$2}v|CqP xu8Aײt(WFڦj=L\rCxUJ8%pg(َ߶ nW3'?~jA8k~dkZb|Zsij*wxTӭ[m}>_VFC@iTmʂ%)/m M8²)nj +mUٖwʓ owӒhyΞC7k9md_o aqXxe}c`Lo Ђ4wiaYiTjxtΜ,s#ZO+x#${=;'zzGkJ u_ 6C+/hZOjR׃rv޸B *=a(X/94ۖPnVx7ݢyS^6HOGBW  Up Odԍ=AMᠽ},Kx,Gi<.ͪ"UC A0"Zɠ2H"{C3*Km0\n,ّ=;G++SrVaQ&+KN43>܉BN-Rao_:2ߟAtHt9鴽3A6a~ȏ cR2Ʊ; xհ3W|rRu7$\4wrv6T'uQZQTiZC^dl"ֺ^^)>ct=~XGFӘp/$%@ar\ڏ͓X]liGSL{/%3Ss;RGMnhMᄽK7X;xÄURb-k!׉'#Y [RʍoÑo)2(ېhn_s{a Gm0'#hPS&cGQ!TkE (>%m.V N4_IײBvc*  ng&QMbk?4N ڨ*(<Өy̑|:Ŷ 3#&kIa#jgyLf!iTycƪxA#6֘Nv)w 퐖gdrF i?=or; ޘt~?IHY4 o U(nZe}3h4SESoL/݈Snf!\l,Yke m׻S{ƜԒъ)YBj٥[ˑ~+K`d^?}t>*qHk^@V]v;+8 <֫7Q=Pe\HǚLcSP)NӸ,^giSa 'g)6 ) `y B=uim5]st! g Nm=OTADTq}Ē S\*BMrM7A|"?mS5]^-k=AkF2}Z=o*eM?Pg9I_'n%ux^{yЏ93i}2hP ccA^[ט,1j??ٮl'^'A]:G")%e8E#;&H Ad~\k97G')J%"9bp>/(]Jh&:Q|^{-ιW؁eF,D/AEۂ \᭬!{dWD1ϧJ$,@6'oָUe>Ζ3ł ž3r[񺒏p'ssEhfKD, 5]z9|q^=Ty}kD Xk9*cq]b O67b 6Ks"1EjWĬ^USƾqimJ͆: pPV'op7f LxuyC!T2v?x~ 5z)E߄s[$%UPd{Q{"TL!}sje'-2+J8)xIm#sei-!SFWYu *UV3,QMI>ϼ:"Ni)8Zٸ$ <,QLb~ _JTiTl4꼰}!yQUIc{oZÝ̬P((!=W2МN>/|A sȈk|4tDleY*RUy117*.>2ˠ8p .wC.]# ?\ pav˲^%en@h<-*Ѣܶm<=։2/b'fՍ+K%1D? eN{D84凗!Ƅ˞mGp'SXNΑlZUsOd "ҦTY1"QqƏ;]3XɿވXc8٩onu?rӥ]`GZYN4蝕}yr/3> / L.q;MTNW:YV~tM-lqIU_6Cu <2+YmVA%vZrPگ7ހJB) .n.N ,؂*!6$a@}C{^\s{@<ul(,vwg92 &s6/qTsɚ,g^tH@ zy 2- ;yдв kQ}Ԭ6i*B &3>׉) 2*tE6n/)?WʘPg>c%"%2~dM^Z<{׃s<K~iSI2`GW3L >~8_ ۞PN,Vx0f@ <+zz?Ǫ4"BWlfBK$iTobV]JF>n#ע- ؈8ĉ<"XU|Mp~ uIpUz>’(I V6&{.PTTg1Am%p Q}cT㲫Hy,ЇWQ6҈֕;"lx`PפMu\ daOƂՇ wFMW_f|58KX:Q`=^(tӜ8乲Z3#A>yQ$Fа~цzLG)ry_߯ͺqe6rzs KUa'&/PlPzQ)pKWPWУVtJKhB=fwDDEtF6\eAg^jҹ7fmlzPzSqzqWѯM8<" h4X{@9O "gv @Dnd% 5K>qx}]G!`T!փ{dK=D A-9Ys;B.\; \c?)~ DicBʷ+sN>"6r-E*(Z"o݃7'-,=w%1b9N6GjzGF̡5uFɹ3W j5ckDv ڿbȵX+y">sU7tG ;}y^7<`ݖ:I> e R>8 )w;Ƞvg䀒Us$vk&w c׵mc2+V[n l:7 }!,z|12Rz0L,dCگUR׈\014F( R@ϓMCA, e5"68,I(R~WdteKUv&ʽ74e=;NSpG,? #e0;%< 1"v?rc*L*+4 T^ l=UwA\QA꿶1+$AaVq!UҙUOZ#s )mWXLTWP"N A4y F]U)aF djմDe<0M(`Y0Iފ,YdduGuE"9 fb^EM(^ܐ4crd9|X\w.55zplj7!zVDN0)oab"bl.jR_D.x29kePc}q8pg]}>D=!5]>wÙA7iih1ZTWqV+\(ԿsN)A#[Aswńg V i-CɈhM}^nUʶ?rL說hM36}T*"<4YլG9v@oR;mP/.G*<8Рwq(adzܣc 58,QH_ xם5ǩd(EM GLʼnH sͱ_-% 1 IZgqtD'tΉX`+D7e:3?@4uDnb:O* tbḿOq訿~ vP~+8/CĴC CR&۪ O ¤I?ZQƟ/ԏ~9l#iET[0NdmL/n/BXjJC2S!U!w1?`&݅>W#;Ԉٿ$7jC0V lѢV7$T PC>pjŖ|A 9e;X(6(񉽤Lr%;b(X4^ D G>L"S4+HҬV,o_f½E&ZI56F'k9pSJh.o k$>(7E;S?@9О`4WWnSXC/6~$2h^ x+E<\N)I(XPﮗr0T ejQ5,MÔwlpQw̸#}c ~{|<\ 5vX3;/,ЕC ΃*eHEFܪ'=m̢"F~YQGP+ uܮMz6Qe(UEv1Y:b燽}]δ%h}4+әTQ8Z1 .5kSP㉊Mv8@rv@C X­sl툈8r뭕SIQ6*ޙCj^3"oLPkUx>YےjL+ 9gDۀ-)~ ]aIQ"vYjCA(Hd BI5 2+u6IMbA0#{iy *,' XW|;tR/o@' u$ѳ٭YFAA"TeQSad,6HbA@k+li@wyavo!&*T'ݹj.T\9!9teNUQXu $גKgdDb@+w\) j^1;rӪ ,/9Z# wxyv_cjG+@3N܁?6 1HP6SYꐃf'<΃/m줾}%J:o2:M(\.Qg/H%/cG!G (V,Z'@:-JUQGH~Y)kV QWx4*nGsca;t$;IDL!Zln<ϏjӾ\kU0=pC.(rCn|2LoU;‡ $EV{DND: jd3;FVY:SG=DUU\\CB/f;3CXRnBW>@$Ԧ7@ESk>*Ѥ\KwSARsp^?;YY2.ݝrf]%QKDҔ Y_7 M";> hPUicj D8|lRwSv<XjAQejSvyuNT)4 LJfw“Bf/ӂvh!i5]c^eHs͉vՀcGU1"ƙ h~qԥi'Xq=UEOe7 M8=i˭VSԟK"X@B^Bc%+.(M%H:_uM^2MMj\4ۡ0DW`beo;R ^1.sw96X*t&i+; H ;w;(REgSꊽy@,cO߂`+7Z(nca2P!4/J6z#]zڶ\\$T@)3j㠇!q2Ю[JeL=:|4PYj2B@_X }NҦ*Jx5AlivMj<'1TM7s.ˑOu1U9j8$0wռ.wϠy=B@p6ס;dÌ'9c%Q᮵T ŁՒigf'-u+o[r,@g g 7UˉJr\@ჴ9>ֶgrF[yڎ47)ul4VH=V qP/`H YaPjn4QxVWBgk'99;@efq>),EK`e҇}.HHjQߊ420b0ͪEj&x"V?=Bkc>bջ؎g &] x!Ң/׺YĮAmYYKM4͑_Y3!ϯI6UF}2T;bc$3+Z6b)^R eYшo%+;!RGeJ >&HÒf4"PJ]G_ԻR5X]-ZëmݪCz"p3|(?E٧2z.BCqO'\ |LiҜt$GLp-[+"}"7N+H6VzWgY;VolWv 9C&Ae"bPyHZީZ^6$V!zYrwc J}ItH4 J3&WNrGqyU~y3Tb*H9>38 |]SV-< 1ӘQˆH|X,2(R cɐ+ X?3uKP8}sK:GOFVruBb}u[+:jҥt쎡l2Bd0= zYW k8*l*7b] escq-c)~PFVXʥGB,R CRobq^ᯗ@1B\XhS] (#!R.)v()_SMlC8 =@^wnt58SqN21OfT ?GeXXZШVxIbc%l(JwS ,w6D6*[bp;lqڸhaO_bCXoSRQD&Aq]$+^Cq%>J؏lH2 b*mqNaXf#kAfTKW2TğD9Dj$N:ZE&URoNoPK!tnr媿\}u in\,7W5EV"2n~vS)R6,ng`~..kz^Jٛ XaUw5?O~_L;~Sxï^{@% ˹AO?/۟o|7G\x?7/o??/Q˟~|m*2W&5"=v|Pq- )tg@1AmT m dUXuD_odSjT)iZ:`v'k@eLHSvȍ޲,[,+?]Rq,P< bRP7ISkHhc@bS=^r yAdҚD#)&pR2CØ! /'X?HqRaG{.8W>kRVr: 5"-9C@ѻ}C ֥u#{IdB8JΆf%l=;Dd>uam伇2 Ld]_kG]mF N*۪c~lhhCI,) xvz6\Ea9,X(́,jL禲1Nx5ۍӕ̜tmp*MUu,aqy óEo`5/?* sri\M3[]ΰ95!P)𱷍9,B٣񧽦BKPe8}+syeΟC;֙OeIZ൤,\a2S: XNvl07 = ~DH]$!kmAIs(M }ߤ2I$7Iy\Ow $iJ[ 5RAwohXWP*Lu6˱ U[Vl\bj;ZCAJvWVLF!Nqi\6,0lw{V)oTPZi<9`}hҰ-̇GJؠp1="a*ÂQ-GCT[Ix;;Ǐ+6 UgFV݅,Rƈk! Tfjvi*6Ì 0p %;a( JGR0m#VĪꍔ൞^ ')܀dz-l |b5R:QnÑ S  ZH)@Mn_7Viem -1bUZS69X*&ԂNw&!c'>~6oŨ܆Z*aG_PULOA$Z R:52`Jq4Q`"Ӌ*d"ص0ܱlvEq/ |jʃ۫ 9u Ԥ - z֚!Ezncp:$@zG=,Aq~ɜW!A(HjSGS[Klt=e2Uk!!y] ).g䢒NPR+ݎA5:CȟZ eauRp6j1.\襰QmǐUh7ФqKczՅmG^rWhD{.][NF1xe 7A2ͤf|A+m;BWhp+:I墫XXfz ˸"?v (toiJkr˵ cBQr%PD" vD@դ<6"cL@ Fŀ {| JW(+%("c $*\5Nn!mG'ysҕֶ$bbQ[v៸A+[2'n6CvEyn|Y,I9$bU,1n$Z 0rtK$C#y[ _#E|(HD(:rq퇝m:%=QшgaIJ?v [ ٘"W347YaHJjN7cJXS [GEuL8R-EU)YM@r!euMȎ@]it沤X"<6Ym8/ эo-P/+\xdآ $׀ļAɩHz,Kش_QqCKAzNe:,$ж YڑU& Ac)wuy`Ҵ kWGA%҅v^8|>Χbg2s- x ;%ҨH +zׄB?t{`/,L[V'b\QVesDƮ+6t +]%2 #ްIPl@7Zsq'B=. zPh12#-:'7jDRYf,ai.فuDCٵi:Ce"6*K`F_@lk:٣V^Rc;ݖ},t\ ls?+H q9GǻTئ'ղ|u{n]INJ[W3V{,CEt Vkʣ2y)(wtfnMݵHN&զ&6'K6C"> ZaT^*5濼;ߙ9J-vq>a/{gJ %벫Q(n CnI|~tb˶r zF:⋓\CV^Dz2ԽDZT-+bX JzcArJ#(j*\ WtIRLn[M[j((.>^c)=ź%.Ėx8xTaﰍ覙Ӏh ̊MXqWɯ*dw<?w;5:W 頯niȡ-.*qn$!Y\ޔbﺩ_O[%7Q^TP%W{u;])X|Z&iM JzGp-M g AQ C W]P?ܵL t&(Jv锈G?ܰFǘ@WLIܻ  1-~͢jDs)%Xy${NA>}KB19-;i2ƨu5#Zq~Ru-H^&>=X3P+tZ\hAyO(v/b%*6"0@!Mhuկ36gл)޾tpٍ+DvzLh@gg+sD,:N vנ<)3862$6~_K?oDphHáˤo¶vjE"*ܦ}_VeLZYTm*S r_ٛ@oىhiqH2tb9*RNU+mrxV;ԷDxk(SpSte37n0mW]ҥVV^FDJuc +uX *H5iEn}t.jVKV`ePE~E,.NB.EG,kPH|ʣOX`<:5"rJ ~Rv]F#2B{ޖ8%H<Wַ\z} 0ޥ̚yG @!yQdJj%A 7RZԞ݋ \qV7{Lh K22F; #8E\!`O4HLlHFMGIz/n7ݧ"[[ 26 wC 5+w5HXS3h=86Qz'r#あژή$wneS Z39҂DEǾYZXt$QEriEF꯴ǒf|8+ V⒙?1 Eržf-̀r´7v|>WQ,>e5魌;a c1 :y8yРcI:$L|WnpUEu:-;%+8w a+| XK[/VD,&4֔/T`"2R@t!Jp5ۣz彏s'KANn }MPa sU@ >/he*.soHɦqx3Z=Ş1D4x:YyuSY`gCO^;3qr2 ;awheў%Z:?pZ.<eh\nj šZniPSw9T~,8{saqPR[`;Ą=MEUHl[} 9 W( mZ73G~ft}^#qeIIgv?~1yIZpngk,%H H !زXInē}^6`9dP9WRU T(*& =EZ >#؅q)^]/fBJI*_ ۫; GA;}-yr:-jveQlZrm ^ >^M; /lbQ3T]ۼ[E*3$VPT J! ʃ;D=#a ^9L/ID]V9|+TXvuaF z5u*KDajF`0Ku7d;RGCH5tUQ-ʇ'%-Ը7CGuQJ!ED#6m*8qMXBL Yɪ&%ꣾMc[bg(4+!MnP>^X@5xl*3 ԟoa 7r"=M*}('YUf|l !4{s*]S 8v0Xb>,eG5 ^D+ "w>diU9Uۢt(jeݝq!+FDLH3NYn-u,VX19W5ʌDM1H7E)kD-YZRhyb: DX=sY92򷀍]j-SuqԖ@t"ʭU+ q{Lxs<40Z"\51KnWx?Gz#bWP yXc63Wg^zؽ&tZQq;*j!_AH&R#Z 1HU2UuWjO\IzJ 'sT>⮩&Y,;c tYH36:ѲwPNQfB[ՔYT.!%|MC\E$wSb g ʬFۛSTcN9"4R~0˵2G8öM+_\^HAE^;d;(U& ⷊNL)Yn6b3BH?}5ʇj] Ɂ͐Qx UWX"|P v mT7ePd鸟vC3k9 Q#LJм_-Fgej؉ Sv( K% hlj=e++872 UXSR@vQ0'FTg֋#ErG5>n^P]_^qS6!XSy0ܙ",{Г&KB(g ".zslZO;ڄo>-+tƒAO( 3ԅw9CݾL'%Ä!C ×4|@1:r%bs \;ؤ.f.Ժޑgco7ъ .VЮ+m  m0VlG+Ӓk.r rhTd# vɸqnwJQg|rFCdůn ;ĥAMGV5Gp۲xl7x.{:U;r<(B;*6i`h&WJ(t#`ӯ^p%|k\+e ?mڇ r?YCoB|mS踨Ӓ/ޓg6Ծ}D"L`DJ o !iF|=ޛ(Q9Qb`mTKy8yxs%B{8(#snZkgq"f659-w_ު=B+ΌeՈ&olކzty;i/RؘlB` 饍vTnXr8K]N`"[tۛ#4k2hʒf}04$ |.g\ mh# $!Ғ`N>X|ZR{4CU+U^jVcAM/c']O'gG#=5ɺO>SV>1QIXȅ3 ϨL--Vڮ3  -QS_DḩtRo %֞ ?&،[rTνeQ&/2^ڍI6\VVɦÚi %ZkM #'v5 c-a 5-o@j+>SH$X@3ːU̍_ݔ{z}ml WtiU{;ŧhƨ恅ẑWzk'6rgNCWΡ&ݭކdԽ՝3A2 CTCC􊮄$9~K3% c"̙ 5c" VEi6[8>Dܛ!nB_g)Ez>ɷVhah-࿾Xؗ2 Qo]9!!G 1f;vdt ~ ϲH{ti7Y0c%Wb 4uRD>7mA6l@~l:bPwt!ץz0)L s} ϕfTZ%kL*8t@!Rޚu2EQ, J[VRo"eͽ3!<~Ky_!(!%4 |92jFJ.  J Sg$ H>̖{TϚtco˥BQ+\2Db`Ү"cK56;AݘED[Mlxu)l 4.rS 8~UDk )CO~&+H~cO&%..HTliJ99w؂{,1 Z )k"p:.҆ͅ`P} O| .lyTu΃*G(?<)򭦶>ʃI<`!'jҎZ :uov/4qIWJd1Tѐ" /sJ9]$ݸ* m@q.N>9B&eDSeZ0\1YV1oEv $̼դâb+RqR$`j>NJ&F2^עsC\STÇڲ*at;uؑ i,R*g{(m% We7X=hܻc GB^ġ2De@V`蠗ӽa ՝ZD^k@UW"t^Z[P&&w1TgWb> kxpR)62LE@b4p 9Q f؋Ui7eӨab kiP$>^3RCh($, :ǵ]-"3dD\CuS `k5a;*pdUX[cW[5 އoau.VPUҽ*#1V7@QW9/.DBRwa(#XYarz: g+"%cHU |hlnSa[Ah~h= Z: fG.%>(MH$,sbQa, &@R~C*˸jX{NE]Y0sj[^|PF[BNKd|ojp`ٍta@@\ryRٴ!|ukۏZ4$c߼K\m@ml(ȥ"ڙ {?J AWաNb. GDPs8vȣ(Wx–KU٘)3][!S0&p,iENIxkb[4Xbg$U)Rԥ4\uhÒoYn4>_\ g>IKL@PWtmшћ219F(t}k ,U])AwD1,;Eʵ6WGaQ=L #?}е6SVpXbXzyVYOS 峆Zʗ IV[izХi-s1(` [ey8zȪJe+kۢ6,bej%Qyq@0cYVx[,Qr=0i3Z Jsu@#e\sc @9o"X+VK/f>_ *QeQAV/+T_bBPbT 9eN ĵ}ͫu-6j{ǎ-},Wof\v/~jJ"N%l"tP3H6u&I:j%i\ J##S e@W&c.YEی5;?_Hsǿ)p9 x\ jT~?3r %E̞vߟdV*,@]9b<2 @nahV b 8$[CD0֓,.[mz˜s Fi*f6qaȪ8b$VJoŠטS%EH 7LƬ|4)s2Cʲ^E׌XSf; `s* wЀaSńXnL]11 FVξv3^?ر6k?;$q'E,wՁJkF1A?h`p t%\Vƌ6z^PP}t #o4) J\ZgdNk#i/˙\! ,E:dܪYVtZj4zpԈ4Nڿq꫑>36hijffޛ>*@Yn5c=r)G K㐸{VЦbPNBQt^ R A8JawIu.>rJS0 OJ}$%=b0RⓉxpF~C m;蹩!w=tT7+8V-P:,\k9q;c~4jx$ A$y%TȘIcWR|mۆ7`Y-6 kJ BgI4TwjtrZ 69n%:yyŚAcx*]U10.D[FʊX6tX8F0 ^[y~;Ӭ3csj`XzM`Yf/208<1Y\pu T\öK|eo/@vZ Ttc-Ӓka ɑArA0O7`9{1\C!::PW>4? n@L0^iO 4N`*6Mp-yDrHI2!7-=flZe+s.E/WPZreU-* VDf8Y:-xD;=AQohU9@L|ǬQp81wcZHe)*2GdʇDPv6&` -$o-h8__NE./ M 0{⾱"8'rV`k3KATWN.[@eW65 E'ڢFl9:3 |Ku5`/&0GMKWS\KҼ `T.?'ŮLL4Dzi ?a nD1R0#+8/U7mu_؈`̻}=_l@!<6PVskOBxoT|DhIJ=֓\F&]'dW>gP$Ut]z[D3 /t/V9Ԧ|!KVi42 t⟈F"t)Yu4ud~)T 15T98V h]XF@d𛐵i=N gigiKҘ;e9C$ 8#f]5({,-2Ĕſ~&bg&s+~@ST;A~ V4C[TJ1-CB=Dg"?&$X>nnE8NBP7dw:&Fe AMk޵Z'Z޸H}{I\S3=8#ω{*ګw9S$7B1Rq9>|aAR8ޖrU"Җm,MAce@9AbݙX*E.4e6^%Z6} N[UV-s>ؼ֯>mdjFF"5Hܪ1͗4ʓpD5WwTКԵmճ,(I%rJR3{ 쇉"dSC>MY_k*'&o}m +e`:SU,[v!v e2 shFu#P͜TUQPe A8=u|(W@l~ª4$6 !5#j)b˾n[b)==r#I7Has8FN;RH@~+jF/TeT *&fne*H˦i@nt/FoZz<{YP4̧n04O-E,Ag{]AD1xuo+K_$qq]-BvORuCA"#e2ع(/;Cj-QfxP+%YA֋I C4_ CғS BgUfjϝ#1-m$IQ H6sXZ @/jH#1@`@p5שN6S ʡ(S.Nԣ3rAYڇ`,s*qQ 7~Me~2r2&x3 y/BߥEf<>rT%0p}-L_j@w[azMsۭU{thq# 6Ec S DIxڏ2K@[\; ~4fr.UH׋쓶c&?)"r374]MCNVVZg  s5R{7#9+1b/176o NH'f aT#%OXC4a]yd+`t\לٵPFtX¥[*x0a޴r(N _C"8f>Tiʊ3J#ȾuH"O(„ BY;;fJsZנVwZn%q6PDQ@ !f֦݋`A`YXlvKIТ*HX7?Gr;nGdCy@ofDçyƏ@dAxb4!Iv3wm/֞lR;TY@+`=aRb;\ 1iP\zk!DdXmg/{W@Hڸimf~-53 : dg+1Rr 9=2CAR1Э}> 7+`XoΛo%w-B{ >3Y`cÙC |#n*l"旤OMuzmD VU'"=v;U& $b2a\V:md.e>G&V3v5Le]R5~PJuIvU0O&̈gϑHFiYS"EjԪ|)!<\nTK] b@?('mrTx20naW>]U)o8k|ɋ?D )5N8lanmroGcƭh|:1K7Yq9؟#P*jiS@pTK]'_1X+8R*4x&.lIe 3GkodJ_it"0d?[7lשb[sعw%\ BuZxadžIDn!IȈ/ڏǩkZQU;ˇEΔzI˜zK*s` \<`O+I:qJ@o6:_1o$XGG{3sUikMz);ym]r{=U䌭g-O/5ɛM6>O'1u<;•9%r8nWŊ]O/cZ1=tQ&B`N3&M[8!~$?)4(F]I'xNˋH.TqsS[^.ʌEpC@  i n"6y4f7UUvfbԅMI$p9o-B[MkkG%u{qOUg/CY,|V[l܄WPQ%T3 NN5)С,(u\U ȩ;$Vf–PcSp){l` nWur6@^X.AEfc Zke|P#î`H6;TLjXyd\-^֏x}Sֆ8n_Qkۉ>*iutU}Ek9''-W`57 K4ĎRoQTL;E(WzvEMnx@ƽ bm5n ?ac PXT~|s J{c n.v͖4Rt`4z].frlq(zjNBosh Z6>F;b!GQD$7 @/ۻ9c&,ߡ#zwߴ܍YY{Iz$b \LI0XRnk8d%-aneJ`i M)66L*rqK 7JsxBsT7ReO0fwjf)N#]掿%/m#E<ͣ*%$P̌ ㊑"FmFNi*f""8*Dn$uf?"%fZazք&Rt`Z^sm넝;hƻR&k }G}hs{Bx2ځqXq<(ٚq<gW,ψæ5,@2JDSb#@Ms҄rQ8)&kuM ]KE9;D`a4VYVNuկ vBK/R͓z*')H:1AX ʁMF=KLHa!mBkMG_]n H2gTQy&+yWѫK6Oβ !9Q}`iL?S vui%;UQ(jV(M,T͢|=R̓ y$U D>jfC븮l~W Z)Qr SV+{E g mKM "uDs!G#rWVLiqXtZʛRrZ#i'jӃF1m<l82ԓfL폎5yA9UUa{]ٮ+;_qzqq7aUʤ@QJ}}0m/*TFjf3u7A,wtS{A `+;8ӓ2⨲Fh/#kE>,]HX L^4 ?K#O$>^W'Kr ݤ8x#vT4܆CDh"!浂ˮ@ ~oDDbIy eRO\[(}pzC$"YIUV1 yΠZcYd$|Li; !o/phɬA`hO  >ڈ+_"86ˊ$x=搘*ZY }(Izjbui+aDFk }vriD&]:&ꤎ/O@=/ጋ@#ek1KE)PEW0,c9Dh#/˳4,Q0n63'դ&ŧ({-1B{9jb˧~4D(CDNw: Z0!ݮrBOS AZبRY4 f Җ=2Y~} +NU(cRYoя9l()^izgR>t™oݯZ D>.W5o5JT|P#@Ir ޫ[`V+s@#JŜs1pO+n[L>)Nɜk*햆MQK瓛 oqS[[+ǫqCG2NfǪ.w i愪oN=.2ԛ߱]n"jR\eyovm_t!3hk_Dy޴CX

    a}5u7ʔ /̛R іQ* ESRR^yqV3r WQJwf}f]I &}vV2HuP-nvP4S==_sY3w/\UK< ݈@G)X95HVE@nV.)tH7) rxF:P}w_(:K"mpWխhedE@ CVH5olt`=bS`de}G;F0aU4ȧ &}(ĸW)K\wְMsh+]`pդ\ qv(^`?6Օr[=~t_:ew4tTeyZYH$Dr׫U{[Sk. L ?t6*%eU= OU,U7w8·?"Gŵ^եMb/ªds(y"m*TjE7kZ <&48$Mt/G5_e#,NX:TD M4ӄ5-\x'(Ԑ9y^D'wz UrFfҶk(LBtGx)Icʣ'ݵE5o75m?FA6d{ *{n5E4¶ {LM$D67$8Ӱ>0BYUU+GM96d5-wGXnY% Q)}'x#ޅğ> -c@l#u %*~Ka0vH&?ŅuBAh.YiVcQRVXǻBdqSmiD[#7YDg?'Y̩ZgujSwPփ8&[f_"HnM!g}ni׶,Z-MЀrZG5Θ}zH~6":MU#*S=T$yw}ޟ}+(zD(lM@5=݊MB$Tnfs>]z_A;0z`+nElhI i) j"J-*xdJw*OQ4ӽWhNi&a#xj\[>;hsR6~ zLEmTZyS̠>kdKؖB_v%KX}M QqS+B I2s[e|hF__d^1pI+&^aUVk̭ݕynr$f?%Dވcq>wєWr" \,Y\2=nkF9u"d5& ,Ϳ?~_#os˟~??7/?~o_M~ž{EXކc9ڞTHKjG3@ԍᡦIc+~m9OV/S߄|EJߍz5cy8s&K_/85ff *CicuWtg-QEaT\FgsF@PD Uh {tW0Y>uwo!&=nخ)4JE X~q܌*ZsIt3痾1W5JWO R]ˉr屍zR k豍-ȡ0dѢLqQ8԰IIgKթ3Kُ\{o^U>Ǟc ZdskςJK f!V5KƲ3Q~K^9U$.C(Kt$~g{(7Z#^5,?Fۙ\2'TX;es]ӻ(U>͒e) Wbӽ8s7biRr"ss$=j6gqtg {muZkruVAK;(:!MkNOiÚl$W)2~v}cuubI4G<Ү6wwn,v|/i K5)߿ZGІb"Pk12ze6Cʤ>C(1μ-Rr+!,d%+98,F (-]ċڷV͠e"sqO쿩9m^g AqXƄ(Hu ˲'I;eNQ <LJ7Cרα8C.>Z#HcR Ʌ} ϊs,l)[)~NO>ߐkqRR3&PY:YlJt U][<ǢkRc`G?'#]'B Rm4R)6bgK7y[L!2J&~+PN7f4uk-@I[5ZBtz>muc׫t,MlP7J_?1;vlZ@"^ac/o ۑl&Atd󫶩y(Jb3ҡvYטiY (T`~\J"LG@g ~ eckDDġE7!e) WX#Trl:\AoYշ?[01hC< 5T+Uls츷K)jٽ6|$kLjZV-i&s&%0]]Xa_>…=3n0"ۤӏ=B,6ot?"P9)eӿXvLr;T7Т`豏 .U!:3@W nƐgXB,E[Tݐ'8-ZG0e17tt7c!勡}\$Ċ^N)dU<+xuMf;_ݜ!ژIE/LHUU/[a4F"qFѱ˷ r*kI9 *ax0PW\l!hDbB~4Q@|lHKTJ1? 6 >zSw18s*k Tpp'ʝb*x4*i{eBFD !2:p=# RT%q;g g$FJj}}k %[.B1D+M̑ QLPfMz'0I̶- 0Y#ap Xa"Wmt&ꃛlʲBS6WBR@Vcu29Wh'- y~UanԈE,_dK[v6|*hVT0F@տVuX9SSd &_FԞH2lo'ïb!E!.&ϕ&'LxAöFBNxul8VLJ9>JCj*2ѮKveO[u|ݨXF\?·|C!n-:? tQ`GZ/fo+E!9UB*sWE|5C#a"u(*=j.#c4>#AɎ@^Ŷc!ምh@}*,i,,>{Funi"$VIli5 F`%2jק1M sbWR+yZ|&xZ%G#If&.S.=R>9:4}D86KjfywCUkW#z)I.ES?+|Um><;9cy8үnGTt"=H˩Lx]d1aʨr8 Rh*C]t)*/B"\Qf Z[kO 5@i_lR8}eyP9# .3@0p pMT؈BF0% x}oXAQvl0RhA`W3eH3™* YWռw0c|Ѥ+g^uYykBEqPgaW&wb]lfp6bKCpPmjZ(XԛT=1OZ]G~.فE7  Vziq~NJ֮@^;>Z.>IPcneLQPfE6:snns4I8~ zMc6ãI7g fݙrY#nz[v6(rjU 4'Txvz>Dx帬I RRb֝L$)8% Bq!TG!;2J &BrUN%c( sq`|c_3s_sކT$]<3U9cGV{ d0*կldZTTkզ+Qpț/ҍeq joiLb @bR4e0ar=Ғ<[2~嶥d# 5.46hj]^T=j̀yrm9r0 gsSDhH0EH'yd!ƸQupa3H/X(,>c[Rfwni~L[đl~?QRt aH)zr-9Iʌgt/vQǯ I^zr(r`܌lF]{䴟6H:Īv#O.Ak.5M[jA-p %˧Dr!/1ir4ʐx{YL PR[a >ղո3^7 <"zK6ʾm?Yɵ"f--X'~cY5b"@>gIL?R$9ȣ󀈼gYL]d&nHҼ^ҏwfL2r8db41nDU/$RK:i"6Hr(:Cxk"la.WRk]kqUu>j;2,X~ &aG<ޅmBj5CI8`ܠVi:ObtR;0uK&NwIg-ɨA@lKPh*i(?2}{M`;t%jSQ-;--F#.d^{2ZovHn*\KF/xvB 3۬qAǕͅk:ZEUo, ë d  5|zRLj;QS ALtkfH IԢZ.yZgX9*v=W0SiJQS#j'F)]jb Z﫹RpYF{G9SGh.1~؅^33.ٸ5P`^2(>lq$WKcbk_+="iLz{ CPX Hj4SЁy΄n "ԢȨrI,]pc<{QMrS$ [e5{^Z885bXմI@P<t caXEQ9-SՌaulp3UfR:f).,CJMu}5IGP{_68e;iĊi/] I庡 jE423'r "^A!cu>I U{CU, ]$-?w8Hz,.0Va3ϒ2pꎨT+I?bP8<$%84!֋8{tRel_r;c7qYyANK,OM5|F97Cl c#͎_0j\Nbxx]U'Dؙ <;e^_jz DFw\wR{ m rGNϊH}Y{y#U>} "S>@# d (#}CZ2SӠ {_N.p dI{G}zV*dg,d,t-2|ZT,qnai UZ(5XEw}fzD=q۫$8fftG#@ڽHL(v_*RVX#ZN+šu/{R |VAS,ZQ{S1u5нT7O9@tb"bŇ.jthMrgOo4=ӽX25f v@h&\Xijn V#@ް?>Va[Ce;@/dqGYǧ&P]_Lo@[di'FB=tŁ] 7@%PcB% ʺpճ 3#u溝 }Ty:Z3s&r/]E@ E* quT5)OᬥcéIg҄vwkfm/B]KAoYe"Y+JjTL"-諫]3ڸ\\v}s`T6yEuYUz*(#ј(y2z3W2򧀎$]- h,+#D+yTQC18({^ME!NXDbKk`:jjEشD~$ >tP5NWW#srb47[X+6v\Ԥzș'ȿ2IC)ōxQ![HȲl5{/x77z!8:L*_2'ޘu;_Z|\pbcQL$Nv(Vr-g/taXªwlDewuU@]9(p2QqA'HD(r+p0=1)O;)%dU.! G~jo*:tbdP7q6̄QK;N=IT6Rj 0v%j+޶wVkKWnT83bpc,|O a&kUR@)YtEP`H3/owQ4׆E7ĉ'K3b$"H\vR!;} e]N޾βr RhS/"-I,{=AEaNYoy O K1]"'bXH(Pw Wk%o<ۢ1bJ P؈6'(_E},c&+ӆQ-EH2^`48A},>}蕔is/_s6{z{ˑƗ>Y_xq-;At)72 q9m'1M=ƚb`uP"ܮvG™y8uxsن9\48]P;NE=f^gW5At~!YEfMu9},Qj a7z6 AG%*w?XDڔZژjJ!( G|%]id;|_Dj9.=s^vd{J( S.MKh,]!3؏h7bblо1fް#=/_Hf25̔p](lom:p v<;JR0aPYyHg8Jj,KU[aϐ!0@RGGq `qdvRK 3c676;JG989"/u{x(ֹ3lNRQFC$b]H7Fw,•na܋n9h╔óOY=e(m] ']qhj+c42'UTzBqs& W1+|.p=5B(ZZPֺlBצ!.,nI^:;ڳZRGV4_\[^LHgfO98Ub:#Gu*zcn u"qv_e(tνؓ`a?vdWD H}4kKp4zU ö|t >UvCn[ fPp< ?S$h3Rٛn,WoQ;4l2L9:s5#6ܘ[kVކ2ڹLjQHNc$ #x[6m$%j Žv* ^WRG|sBU^ڐ_&!XfQn; %!0X)wzyx2G;Zcgq:W$0maB",ZXG:[5G@U)6g(EIJw'nYr LB=0fѶuD-kBtc1m)Y<[觸h&6Q ƈɔ.C̠abKIy{UYíS+ qྺ٨,A*\J)P}~i}hkE4wQPK40 dcrw2Y fuU R/MjkxjDwZ$.$*Jnm 9u4WX ĥ0.paO O&7<4aYM6/gV/s fZaIJc'B1uqDh/~E .mԅ 1P~@5Х B.6l$[#eB%%92U`@&YõOoH]I&آmDXt=)+޶'<{tzDh zt JPD>>AxXS: s1a7*A=yscn`l6-"lmEHpM >TfUGu ƗUBBWiQm)]^!I (|4\+eTU]JaklH\\K"G]{ȾLXyDc$X نWHll%AAذQ[d-mvЁjlյ(3Ԃq] OUUfyahz3ݼ|2 o[-NJ}+8es/6'iŹR]q2XbR*s0{#y+^hJQK"g:)E>O3]PmlnE3Vj^K{%Pzq66z.Z"ckHw׌hbҖ ۚd4{R~vݽgjx&Xs0>OKNڄM">ҴnZp U^fZ`E5c%uHJXԦ:Iv7:o Nhh),Fa4qw-bRJ7$ۦK}_qy mM&[LK؊?d) 3rb`(K 2P3kWv O%i9HmV>k"MD)]IR} >M XP4arSͦ!\QePX窬&m峥n/\@Fn-5=-Ru:T iCf ~h17iV `N#!J^gH M/1@qej\n;0Z[~Ω=>6]̈5a*FR J^5d=3m.lnd2x!Z9^ݓH<'as,- }V߀4]lWdѫ[Ky MU4>k%#[]ՈfGovg5 i:vַgspRW;&1|[I8.)6szeUs)R$`̊%G6E#e oQDn6 3b=hY jV̪ yT@7H'̩~޾x[[7VKL# !ZYzg:ݺcgXPDШ1eSW)9WB{ >:riѴ eJ>+% )9,y@sĥ^& @˩D'!ta͝}6,!DmZD+Aҧ }x>$Z C" oꚨ}8rVDQsC  <7-9̱[u_n=hՑ7R C;G3qvZ- 6|ߴe a$XJR.mzQE:mTW@"ci5`㰠UN*ηO-<I]SEH 6Lh}AфSD.KJG/tZZJ6Xn:i&/dغ=_5s Fm#"e+Cz3L* h eSCu\`M+zs/f)AyZwtl(tqB2||4i~:uO"1-ABH$ cYm&4pmr7 rfLNŭiT Vb]l)q\fOXքLaUR6L.I-q#ܒTEt͸ț=al/D\.4BwEV% Ahp(Æ\mU*< :ԩyI@VeU"5^'E'a C'2V !I8$(-$5cUv-_]|ݝq1bkJ٨<"َS6/}<{9wve(h^<6ꅖ]dHkZf(Z}=Zk;]( YӥZ#ǎ w%˦jVnj>2r3@22TV|YQb?Sw8/lGIDrf XM ZcGg#gnA pss$S.:.42O\mۍ鼹׀y б nHWDh q0;rl{*O!7|bQe05Yv?H W15OU)[Ye.>Ex$KH+e`^\ҼPӎ{xDJhz[e:O̹8]5Ԃ ~xJiJa*ɂ>\wǼm{301Vϸ;U>hphcz10 Y2-H;u[*кWY%#Oh[FJ, nofK;(W2bk5pVN+0UW=XĤdLn+c.yXiBDD˨t1FM|z:b+]!-ax'ڱNNsif,7*r3OU]-2({0kqսmJ |msӝr".+Ҧoe5)ܶC:{v02I>Sqw%,s ;ne@x"1Q!Æ 9 ٰ̪#غhO]BRc+gXbeRrJ(-$}PaUEiUo0r5P!$KJgMSѥПZrTc G*#q1E*%놼J_]G.v'*YF|ԊP"j͇+y62g.&Zr1%E~sր9{w{L}* /u?юGnےNb}$=WOv?ug6`/@D=}"۩]9 #a$84UinyX dOrlr^ׯ͸H |N/Vc3 SiyfĄp8?FɥhきG1%I O.u)dc _K1H",%7*;( mb<T4vl- @ϥ-PS΢aDNJ0MYL4D!l)DGgDv+ s:,婱W銪] 6Mt!09Ҭ܄? T4Dyg%sHk5>R!pe*zgN˫!G`p]cM s3@z+BodRˈ@"7L R+O?{LrS?TXQ[ ZMz զ6b  iR42zuïKH1VXսWJ+)j*l)”*_ȡ3 d+1\]@$aܚk!=eNјzb ŬU)iL~gCx=kgv䥬&u -etsW;I܊%!&lN@}Fv~cX-cs 'Ӥi$fڴ|+!sO( 㷊{!j(I(eB^CZ<-R"C@YLT4!5ƕ-DسJUHE唒ҦHQ$ru{p#u_tʬ C'(NhE{Np,?Xm'9 pE?Xc"SqAdH&}]Xhk∀EhKyh붷!6,sD.04EhA'vLB YƐh6pkj ݑ-Hsύn%>?;09e9݌%StX풍}Ƴ,Vg QEdu BE`^3q-}92! 0GAKlO7KPD%F$_?*HJ.SoLY A*DzH`nK;j;ZP= SE!iX|4p|<ߴq0kI)[)mZɣSrm,c3%7$&%p%tl,gƂ HpW,zY _^:P6%m*xTp`ax?-vAun윒pԪ|0n=ޫˇv/ Rmܫk+%IqIq!_՝T'v9ytZněr.Zt x̆7ah=OU~׋6D}+5 *$B PflM(:l"8wI7.b0t Nw[c}wp8 lgDzt7ĜƇ5M_&$3m1E{( 'fK}HEʸI2$\SɺLCx-랦# pEUsbY=oÕMAPYŻ;0fHh>2RR*xRm""*2s0pʋ &}krC(\OP=n?p}}/oq9źym`6f ^}G@5D8q&Yn~@9lfTtr([+ ~=a2b^Bb_9 $Y:XƦM9R D@4ޔ U5-8b:mԷ ZΖkvWd,ކ*gTܩv i7f`y΁: !v [:hɴK ߈źQA>z2H{ !=v8s3>(_48{8#襹o7uN?sja\Hꈨ:eT2yY܎ yFmȤjBLjY>x4R5pPR| HJU0q40"{F"Qe5]B)^> yԨrtcŮUOq͸ِRr,3DYPLvrz{䷃ͤ<C6ۅYITH#vd<ڋ>uol/)6gKrN. Yb{G%+FBՉFt6%[ÒG䔵&kg7\K,#ꚘS]NN#f鳥R,9*UNI-2 Z-f޲Ç[/!gzt<%)J/B⒊@yToJ&"DBGfП"ۉ+ZWwa2]6GZF[tҦ︵fHT&x iֻڧ('`xp9!$i( ŝ܍`S ?:#͍kԵmzW:)xtJr^RviPz%YH]nM3ϯ`:FZ 17Ǿ? }\7j UUy0802k}iBڤ!t4GEOW|˕AVS-I<.{Hs A 1@Yy| $[ 0HN))T'lBŎ i NYRe;Ц:K#uU)F06r􂏻m; nWXE2VdBt-'IVZjo#<u*,+ IAdP>_ֺ)?s P%ij=XWk'Yx" q-/LݑDfNF|U*=,'I+ Eb 6ubYN:b(gh v O ;wei\/c$@LvvН&OGW7áXBuWmmR`};L`.XIp8-#;n%Arḗ"k1ዶ~Q[l A=[d(VAqEEYE)~ ),'0p 8.S&gDRDH=)c|ޛ5c>:;:PoZ>H@GЊVqQK'X=C13u+VAeHx#޶fUJS m49KCn?+HT(* Н0/廪>wrLY+alO]mOC?w9@hBŷܣ_DU -O }Rf:_O!JK̲v眬MpT6ĝ4D5>)OhrmwWF Y\}]S0hJʬ).tpVܽAU*?0X Bjc!f:| c{ j̹?ܵ[!zY_.Is2N]g]^h, n#wH`NY^c""G5&kZ[Wf` ?7"1<2 ^.~{0E[[}vk#J2k &HR!sYDžACV=- iݏыVpFЎ@qiClb<ꤶA2mάSH qy Ԭn=#050ZT2'D9XV([`t#2ljcoCWTaJ Ȉwm뀠FǤ:GA>&ldnL /ME <05TD:y)8 \oo]Kj+PP2$!xǗIZd9;>%|~sϷvyӌ,+g֒Wh)  L+cMyǪ5&o3f-eNfs^cv.*xJdL+OQ4~*;3`lP BfT_B,?RKP~6MiQl+&WJ >6B>}L 9hM(3!¥$ə蹎֭ENňԯӶG7\4`x%_Z#ۯ,L^7^qWp˯WjčEk'n7@ظԎY);1kŜ#ZL^*#f_7c܂Sbdy1 '2s~jbh~1Q\~L)B5~VQ_XAx~LxX_(F`_qFwG4bqałvxf'__X(L\xv|LÉvǸk@Xex1Ãoocܿ*&Zvq^LH Y1Q[}qwo#~x, \ܱuO 7ر%QKP҅#TGqu qD!X!5P8<,-x[Q_qIi UD(]d[ȧXɆC+1:*2| Ĝq~@Bre rtw"STh6<[\c Ecum춨8 c+FP>p+c9;q!*uc7-PG n !@/M3:TB"h/xQz c tЧbC Erk`ÍX5 Ev< Jgo'<^%Ux F~֊h6VD{Xq?q*gyL8! gEqPYlq֢qzpik&]tXx)aXo5ux';-!5FLlG|YL8 *1s7j9tFCsH6Lz4bۍiMXi+:ԗwq?>1y`%y IGu/>Ӝ:iĒ mrA2c tBrMCnWnHI493dY$'vXiž^ר Vсl]/^zwiU8鱒C' GGyː{~Ec񽪨&{lV# ?*!bFF@)~oqE?@ P'EV&QR'LG D-ࠤ!Qؾư+Ti2a"_ݲF!ǁ\kR7v$8Rkڧ& ZTQ nr?JHnBU}oL'*IMjLh"']Lkݿ`˽[ߑMOf;r){#UeoB6xhB5ϓb).Wr ُ۬6;XLL(A*ƾO). %r09ΕRB3ML0C6Wͫe$/( ץaoBؿqms0=v`RlZr٬SH8Eف `>OWKI/Kt*啓d;:V.#]H:T*/ePWc ݇L'ھzv;tZu,u: Y P:mD**PvYk;Q]ߧ0zYוBE]*E0gR$p;9kTVʪnGщ%_Hep2nfRѵޢM'cBc:U'Ά Ў|8t&ط-6P*6_Sm׉(SXs!1щ&/ݿ $ꩃ{I!8WyߑKOݭ`u=&KAqƖ~6D"2;&aB!:J;.AB^lD g&Ln7>Sʢc OȌyP9Jvhd3Y}Ch[ 92mV?89G~XAMy7֋ݫҪk@׺l#9hI]csg,%#L@*. +s!V`|uP0 !Ph)*' U[ADXJv , TZANp  ;=m K8B$UV[@fq%D=..,TBWC=+lZA0"ZPqLo@P0b5 aOZ@xuK=: H k, $P|7GȪ\3i >H( Eo|G>ٱv7sPGT8J_8E @Ʃ :L-7LQgLm@RPUdGS `&_WQ{z$#R2HUvڱy %-a['փgeh"؀A 3B ̫ZhZ\nP** Phx,@ ,P) dRug[P iiP7Y`p,Խ ,T ?EV(]fPO"PRvB>! 1T14h}hg14i}+bhkP-`C7M@!FO͈gK4uJNuAG:d߹wdk"H^{mR7TE:|Ҁ`e9ѯOh` Dk 4rA S>]ħI΅K>#gf@<(JPO1&9`_};  ցVV Sg]6 0A+IC+D{pNiItQt (#g\^1 H;1?'EL"8tNu"{EoF{^ ^/"e1ݛ:{u `G3Xuy+թJU Ȋ*{>X;Z8 VeKĀT5j+$T-#ަ&OO7hē+Mdw@ZQ^;ٔg@vL)!93Ĕ A!ԥ!` miߕSkGCm @T9WX& :'*gZJ_ɰ!!I$U`f' ɊHU DEh̫ m-uGYd_*' әlnFK%m̪90 f#5K`6Tfµϊ{4}%NK-$*"*. `),bRZ;Z݋ntj!cRN Z7(}VC#q26z?従z8rhR_@  q_>TSa(Rn`r 0YVAfqyUa#SIBn\Ob$wnLeK^"I}ɪBC퍔Tl'`&'b {8uANȊbul+ : k$%Qr՝wNbP8H\{Q%#䗣& Tߓ`Eèk \fK6P nw"G!.7>E'C9U(9nةMx.FhW5[a1=r 1uﰖ_ƺ`KPN֍]5-nб""GOV؍DCVV <-B|@O1WW;h5|hf=x^"ؚnffޟRp;{,E[I D\S=&P-:*ghlm%!ېPՅlOіmVd`{@_̈AA^5yAۀL?cA^(e`1C(1ڭPD>[v`l & Bb.@0.%ʂ, `26{t2STRQ |) W4CRME_~1ȷ*pM6Rhcg*rcgʮ\<+ȲQ*k/czcPWsUQ%l&ۮATHi*-@ICI7^?KK쟴 ;gBh4w.q~bϔ@g)4?T. ^|ƽ2/{βMRڸ4&+V< ( 8)$SY RrޘIUs?ʑE'~5P6t{ހi;;?xE<}21TUBL,^άd`9B=bմ Hs=If.F wLψ.E1p[ ˋ`ֺ+XOg@q<̺ Lyw8,G q DS܉Wd?/WN@[;fw Tk  ? V`1rމS,xoWBI;}IXDX;_[;<}27Wݒ;V 6TeMK&lגha)YM 0,Y{LJYb!:o?^~3"3ujξzgBH cGW9p.9QNzSkDb%iNȓcK_?ߑ®Vh*|M(܂;x+p2-&Pjk0Jk^2`iU[{ a)Ei0Q$<xuɢOHnEX;:pj76dw*Y 5zK]B~k;3<]㨆ECt"d+$?1:L@P U\@!E]Z75HGAWi#n5"q!R%= S: K#-mf'a)HXx7cK(%HYkH*NYeT,݋ImF+dHY,/-*zrv%O.M%tYƶ'c#g҇sg8iKbW)NYtr0\nHY)K߶Dʲ/AD VKl6$P2@I6Fb0H\o;D?v˖v˽hy4n)BEҳU6 *lyJ?.%&ov(VkiZUk͘oG?*ݥ2ޜBN25"\SNiL6*pzjkGxջBHwpo.Ș"aw2Oa: ([EEzKs~;pQz_Q0X)J (XTaw0@kU߭Q{"U)I*v@W*]f]KQrAݱiMϡܴ$t=RsQ݋4'5#7>ϣyܴR<4M^KhqN"9-M%UK89ͪwr @J_)8ѝ)鹮t-3NdYU:i>̴SУINDrZiġ"ai%Oi9Kr,\$U$|S "9P{E0SMk^2#9-MNcu63bNMZR1٥te]AjZ))iocYr}̂'ļp4 tmVHz|$P) rx7sD:G$к:;{;,hibF"O]M)9l~0 4!(`䕎&kuwXόyâp n=&V&E@8*HzXD=ߖuwdbXB 0]̞,FL3 PI6J<\J#ӮSJ AB-( K4-[gu̅d"7973UsdbqR"ruRd;Y4Z" 7CQ+;sɷf)MCmʆ&D؅ΕFDU߉ 2ѡUq(VCxtQE$PA1uZTZꊁ+^+H%ZhU$veY~aN9;w2!Bh^60URrsrY>?R<h'qAJ ݋a" ǴJ %ЫFQWd'z"7R$9Jm4LZ! r?,eI#]CGk$)^\g52[|2Xw#6؜V+B{IWRvAaHA_T|^)f2UA$\؈*]Z#G8PlT$DdS xwi5S&[rg+f ZI-"TNj_$a%͕i>bKAuMa0i#@\Gjb(P@0D6owur}$"Rt B` ~V7 ^؊ GRqAL,_>Y# ?;}8g;9/>C?5IxJ: 9f- B^UC,q4b|3jvzF-I@j(ĸ %n!"5[wuPoO͓HKHD[ eh'!\yEj`4]DrɰY"RPי"q*d@N7C|Ф.x Wuwu9q:r@׉\s'N!G|z#y9#>r`>IGOar}}6b{i߇N>t}W=߇U]IB"t G9*-߇J:}>t(ַCj}9Cr=ϩCe<r':e:(Hc?'Sg~GQ~v{vX3ps5BmQl^s2)X;Rn=lՙ;N~vTQ&bg)WW9vt::atY;< iPoXcCZS3n by ;'{v)zV @cu4h߈Rb]vg)<xHG{.Jel(Z b%NMh+C&l crԲ'Xj9>EDZC"Xm &m`M},IUK-`JK]j%ZܥɱHD*euSPiR"H2 %_ŭ²WJQa[R*Śg l.J@ P knFH*%P(ʄ(P4:n;Լ~Vw%C`$)TG7j*d&`k*\䉉A;!@zҏOτEGx;ycOFUfj~4qZqxS.# 531G獵5T\MX%=Bi5\#q1hǍ/i36|hGNIMByz㈐Y3DmI>kvsHY,2ZZ\;ɴ<%@avM O$NƅQFRK:zP](CѪfKf}@чbԔ";:&M !~hCŘJPOMvjBd#FC6(BS B"5H%KhPz:4kn->IE{29 e"$]?0'Tkajk/%+M EzD4げz,U1l&|eO &ɼw$Ix q663EvhW13D8f"mg")q 6vY>SU/P7}Zzz4`r=p6He .Z9\(>2UoXtqP6\R>ScZP65.䎬/\MMv wSw$I064h0< HU_qĎ<͵5 "@|Ou0aZᮚ!\Cjijk.!th԰ܬP&RN@D ?͆;VHD~aZb GG0^SK2_u-BhP|=exޖO$%]nT0jDIަDCL"WؒRAU5Z6q, hxAP\!8-p 1̨eY^%hƖin&ìYUA?,άIU @sܧrʠFťQ$0ӑncd4Po{imcKM+ *lqEoi@~luzL$6\srdT˶]a_1LCh[$M"% !K[:͗H& kjҐWg15KGXt!z͸\`udv1 |e\Q hu<Ğ]ϩ*s@kܞڵߋʞUS sAk8 sI<q5m9z9/Z˼=ZCݞ}2MBի6)<nIbelnǩq*ȴ:_Bq@!Pq*eʄvp؛85v;j5Eb;{ lZ9B5+tbMsM55Ja~=61@˾N re7)3; |\(+,`F QeqXwF { N ]. _k*OUwqXK(y]Z. F$$(iw@;W;#a1#nzbyʍppĠsYۆ؊;4ܷf;Mn;j|h.3Vڢِ:c]NFX"Y3@IBBH45fmsvW6R+q-"tK:>fՅVϑ//E@&3܅x[+^cӈDҟW;565Y 8MWtUeLk0ŖpMtuZ'?;g,VHgvpϨA>Q|f(*!(x1~L3Yj)nh>Eo1rdT+&4K) ppk^5^~B^C&I:ӮCH}%,p!ҬWvf" *U>h~Lfn gT BF? |;Ѵx$Lk"Z[MMPx%Ͽ)#zT}EO9Oγ2&sY6Dʗ ~-ݿ "OO=ruMX_ϥο5;=q}G?vVjaEX}IfzчrmU5\ǻT_6ʋ5˕`lTz:%d˖\}O>c_kI*붩)mkXlƦ&u^:ؚaS~Nz o~=;l?ౖömV6 vFnۨk<7xM^P3e)b]ʼm`9 x80Ŏ"d|WsVUu}8%G8Z=::Ĕ&^_4Va=ߑc;-}G1`|ە5Է٤caSز9 SףKdY\!;flٕϠz=زZΏ\_$ێm?*LƖ=" f/6_muښߤ 4*^v.nm3M.]:b evہ}3Q -ؾaGO^l5n;m)Í4<fRk=l%|< >Dk?K&>6 Mm;:y໴GQ^PKvi+Fz;wϔ* 9_v*xMw/~,F(Ӆ})e}Y}ln<5m0݇~E\c ךqI$" 2P(=,|~IݷYQY4uaWlt3פ!"_J!<Ҍ}֭S.V0?Y+ 0.jIǸu3-^3m00̴.JfZ=4G fZLH(Ju٭ ppӊȈ=iMv뇛f{*~p=M+5A2iu5diCY&nRVeSq%Dܴ${o84Դ${M lj#9hnS&\AN1";"քI}7Q'97foN˷ߘn1;~cvZoLO+ߘnӃVn1=mncz5fFH=nQ\:8nc~5Nsi4&aLN+4fmNKvr᧍mOۘn9瘤oAmsPKnQ#0}yR"0bܴu[;`yShFk>vA8r@Ѩ_hqrPKG8WO=Y-t; zZn@SSj@w$@6fJ=4ӓ0dkVtb"aXKCGs n: LGkw f:Laf8lyaf;3mqhttfRZ06@̄ ;;4bӌh@̴;3#F |a8,#LH ( Zð&BZ0!w^idw=ckj$^kjt+M -|̐,f R5̒/iLTo?FŒ2џ?؈ZQ^<|R򇤆>Lρ0F=fS)Z 1Ə욉䚉1OT4OoJ,;6̊(C+*4Ư:5egJj"4Ư*aGRW9~qI7-5-1~[ 1~CmFcGc `S?C!ZdTmS `53A~#Pp_Q {}l-$KbbUz =mk5Nsdjxou'<.xi_;NAxhx_MA7w,5dc_)Ȕ~i~<:l(**r4ϯzd!lA~Y6`gg+B,;!1~C6Ndy1~ӏYi{9~%k$T-&qJ}+J|)anh^hGU7)1єSOmP&?6/aB }w31$9o[;ŀXDb6˶ v$X)X)E-@܉hՖDcZoM3̫ Ucy{ʯEPu[bmZ1,sZ,β1Ų9d#mAdlgL#KlsL"+lcٸWD6wd#xP2)G%hR&DLD0ħPIAęx Dd Y"&"o"A""<< 4p@RE05OR<(b'fV`,|YxGjdʟ68 KxYxq$~Q|"T8N) N7ʝ,9fueCģy?)pVeE;<ɡvH i1"7}hֱycYY6ʚuL Ĩy4Ve@vV׈a͒v%k ;N0b ;Îy-ueMQ{띐QΨ}~H#@dMFIwbDjc2+muvUorj 5xMg:x:|ud̡Q`. ؓ:a 0:esHFR~}'+tguǝ<ܳ۝{{qlN=I'rO:')t'tJ5:q$t|^'tJ58r:nwRQwNI=xY'Gy[yܣܓ{qrO:.w^Iey'ssO:.wbIN=5踎Nf޾#3O l}i12vđ(:|xdlD3c9r<2<#cK99FƎ8rJr9##cK9-Fƶ_GFƒj0䊙gN;3c)12և ygƞC8sbfl(cpl3gxhl;g09WgΌ%ΜSck:&f3+͇N+:xj38rR qydfNP#$މʨ^Co,TLcqUe8:R9xTP9sƥZ1FXuTʁp!b%/QɚFr™Cn3=Tu/&G >2pd/ p[G',]ci-E hl|"tY `Oڌ0b:[cf`bH=0p.+32p]}ptԬPF&ƙUߦϷVW8.c@dUGP}w@myRL$%LU'E|y6+Կx/W-pe#8 F*SuEBf \ )1";%W~EJ]亚ίXv.-gܻbPVҔUYZ;epBzuI(K<:mȴ&|,0 Bt8@e2rZFT('o wI!iM"+C%?МtIP_M4Lȷ\;U7D7F*(`$ED9~CA^Κqbgr3Ԗ)_R3Ja+DZ+FS]5#P㞯WU=Fz@h=ŹnֆhIh#Q,F3֫h!MuSqAQ,b>FڿLCH8)p$Sgγ@ 0,$#2tW\Y~T)1yyWZ% AIxwIde-zX&"^Ed6Wއ0eꩻ*rO$H4;wH"hz}/w)11SXA8ip} _6/S˿ _NqZE$owd壙_1X.iLK"uI-$ORC@Hi9ScenKs9l #fI_\액5#"@3&Ps#`ysI[⇓ Z:$X"b((z=b(qͬ j%xP')d 2z׺F1ϸ-'`qcv~NCN)w壹_)}fT~3]B )g$@ HC-Ŧs @z 6 =K(LH3C\T` 2|@>)|&+.a. 2YǠj<[W-hkVӧ 4%e!1誵_P׸%kA9"X+4mϠ~sn A&9#&4ߑ^ˎKo!R U`3I:-ƂFKge"iO*,\wQ9Ꜭ$: Y Ӧc=X-ph'UI`lpS>ŕTQDY@&Ҏdeýق3ҩ*F \%`jwa @"±C\Ǜ&e V,zT0EY*O D3u<0B D3;ܿ|4׏R@~tPH7Y8q3ftW/u<p{Gb./=p uYK* ᮀ 6$)=yS* ,k DQ`IWRO2Qch$R!9Es v=r 4'E8*d'"sGo">0W'b?tk=fwPחANm %˽0Y`#o$ Y LDU@ 4H?W 56={o @ X*78p`7_|6a9rkJj^VF`Lj/D}-IupQ );%^ l ;$T]WS[H;#U)jI5auS~\;ɬ 4@f{jR〸F-Ě@$e(TF:)'?QXvT;Ad-{ JGR]ܪ^=HMŤ R>7M0 bn1ј܉K>W|ii|'Ԣ ˒auUm:],*2&A\hY9O59,2~JYg[aHվ'9$|4ntu.|2.ʹfew.^s;ߑK!?,rBZ'%*Ϧ)dEH-^#y4$fv PZ#~քw"=pӓPo+BjѨaV.b<U ,M8MAz߱ 흂7F'%FzL`F< P3iWV̈JkjVD ]+8;|C1VI=VދxcL  f`j% eh%:d5M{Xw$}sXTJ ]|TcZul΃ԝ1HC  0CQ6ȤwKQ!a7V@L윬Q=NayKS;/Yq5ZG_'kKNMiddgӼKb߭HGaՊՓ3s@]nc1YJ=X"밵$"IɦAwP!G?Ad/􂂓\`E!KJ45vDDVtMaEQQU$*<>')U5@lN5Tހה J,TѵME21 Ed6ahf{K:]8r.#IK253zD P 8M>ڱpaS,Dz{[CA`~IZE3Ʀd7;@ɼ#WT:ukK=SuB; ީWGX{L[H7YPY1.o;hc(ofn<'EiW=x(zΣzlvZTQDQPbvjEeZ4F\f1ʒAYk@ "Yab2@# :Ȩq2;5LbBb?) dWR=5mt΀;W UQ   EsX;;4Eky t쪊T 7YYE!Je8h6X#oKPZ Ґ` rJ4&^*H#N,*[GaVD6]hQZdyGiҦʙ#JZGQ7&5Ҁ"@+_3?;H3B@܈$wM1:.sȐG"/mk8JDGGyK,NS7Jk :2tx8qxxPz \./3^ml5~ )ApiE+,VE`[UKHz-79ڐi1"0x3;Ys K^(@"JfLL+_B NVP0 Y4O9Y’j\7"wt4%ƦkW:VJiZG0ϫt7rBc|Xw91 ֺrn]9n%tXA;H ιnLOqQ_N8~DѸOZQ3KrͿեb܇`ܧF^,qr0TX KE^x]'dyJFFÕbZ}(;J44L0:ԁ.$~@Dz@z7 `TDeƁ Uǝ@!f`@U[ oJ7yh2_骞x˟fag(R;VkeJ AKR H=w>|AN{N^=WD" tY*՜G\(ԓ`OQy\.ƼX-jTӕ$Uz)4;H)'pF;EYwV;( g/I%,N!}JI_(P=ռ;j}2b|jõҤ!=3L4IXn vqŒ &EFW<K>*%?DǼjhX\mS?$y 6ߥ&4ONg(]UQ3ib:*z0e:HAۃ IP!UfnѪ_UG]5smռuDDJ\ʂLTe-~&Z|tr}=sFF;րxY@Յk}Yx˺5q^5n J{ZrOe0z*^/kh+6+&Px3L \w @ o ղ퍍ysTdRkW4 p);kSӉ$a~TD‚\Ť1(TYnvj/#tlgb?Pٜ5w}uǓ@t?".ȚV%7:ikUV-"NpU$YIN5z{`1Y%-[ndm-f ; ƢK4nZe/n ;pEl  b]wd)Q!+lᩌy鋁~k|y%PPg'{QS+;Fܝ_<"6(y$/2iTWGv޾ߖPjCAs^\=øv(Ix)۹%e \ zpw}t'`_}ں͵ߜIsz7' €u\lpWIy(sg_]I^$\.'_+mss 1jagGL)0z,XzQE |s'Ɯ [k@CVx u.2;mF7g!sW7i%i$~|ko\n3 v.u׿Yz b+9EMzݳjTvһYN@(O8q`b'WObjo.ӱȂ %3v9n;*9B[S N}ņ^3'=CS6%ED8<\{zE򓺹_ >]45@C]Y˽܁7i~ZMݬ~sۢ#,OڟmToL*-##+HӳE(KnBǡߛǻ5>|,*&PZx%\vWDr2[1/A9:CҡCBLeR̵؊d2* dSg@nl t i {F&I4AE^?to ;: #3DPrM13MA=&g-LH@'@WA=QF~Rx1CsKA5:4C]p6Y%5{^*? 9 &"yR{< bܷGZ=~#ʹ1W&hgs5 EE{ȎYpGT2;&a%wo]?=6)R;4X&߫zn=F$'-zOƼMc,ާބrQO̽SOLou/HUGS (As'Uax1H>-L3칺we(R=CjNӏ*7}Nk2-^0PiMA(uMR z&&Յ(LI9h+P pҁN$)Ƽ_q*_ѤU5OU؄HކD/dC5 A*TԜ:ٔp;vQOV})mSH:~q0Z'䴗E~ W]ߑI.k i vLIhܴ8۫%r#KuSyj'w Hj`RG#@9"΀.zRt&X<JtضLux#9Q pjBb \Sa2kAQ\x"QW%Uj( JHjRCO$ l|x6QL%m'DIt|w)~wdm7͉HO*>y=:_Q^/#漏g]r;^G8Q_G0$Yd'&ZdD[!(P1eӈcQ9ae'*Z)"!ΈG a^.:h 5pN>$' z33 <(EXTPR M~M2aM"͆_;}6;RQVe:LZջLa>e׀* Px*[rm.BDsr6 Y5n:481Mu֛.B#ll~֛RknK/ :6Ҿ}g:z;x).ZNc {(19 Mٸ1Rմ- n;v @i~+x4ߑMOfej9frg"D24iAMlnӈc47z'7R+rFix6wZ&PUGeN9ErYn8٭puC.#n9N׺dWC *-L16fF,ɍFyy3-Bڹ{OuMPW݁goe#.LG6R}ݻLRaC!cA ]Hj?L@@W#fcA;~ V>УF3KgZHXNG$#ER~?x  āuY(?S*XKC]F4@ M`m2B^}MШ I4!j(SYʠ2g>ygMdյ2F4"=F$x) Ѓ7TPNfIheELB%:(Mڬ;|TP( ۏֶ>̙4<#0 ߾Pݿ#5SpigtB PR V Dց3-Av{~G\?H.93hii8IRX2dΌQ~i+*5S;25@favf3){(Q!Df p Ho(l LU@9 8 0esٷ9tg$+TJg.,吞!-}g3RK!>S%>Cj6Aτ rl@ɳ }spmC1}ӂ!pW|@٢֐l)&|Lt's<>`U%Ϙ]q>jQ ]~}*a2W땘4OHO&~kQRRWzK(肱5_9H)C6h'K=#Dä{$T"ZOx&,;*NbjP^OEDc(~S ݮڹ~t0Y|\~`j,$#Pr:_HSҏhS1ߧ`]hZ/k9׺K +J`@kQjdXU#->Et { b&Ky#k}Qqv9ZS[s]ׇQ`M s`0CR=(SV\HHtO6^nޜn|GU]?JwB`wAɐ?IYUͷB<{#.b}Zd!6Re]X<WQ`^q#Id8HIW?\YT[+rudQD/E^}uxm.U4WĄ@ AiZT^KJzE 6E eSHaӲ- <, *(O9E"ˤȠa"j M(Qnt!:ţtJZ|E![ę|j'ka͑Ub]D'Y@nU\lU^qdb9w7T=}7TAS|Q#4D#Kc>kAKSU^$ig(a2O<>.bERVCAV ECFQb`Ҍ , r`Vy'l Ҕ*r @Ģ2̣\E.5YRkQeFѿLNҕSdFelWf۬'.BDjN6iS6KY0&DH: |WIO;鱲djLs K֗dl07%^1K*n8D>%9$f~ec)g9[题nL .?~6sþ 5oow/O jP.֩!a=aLRXG(YĺRX8d)d~U`ْ/QAYuO"Nzi힙 Q^*԰YZ20;]9%,PzVĽh7#w#\QzU)/$q8w;kVBkgs/Z5SAZ&R6w)Fsile9UNZ*/leS{uj2!I, laj>ueե:d3$yg jn★qJTIīGQNz$-mJ95T*:e jwLAyC4(&V <1(J+y[ F֕Δ؏Vޚa|ƑUz4 %#eɴ!u/ADT Aޏ悔Gk_%_ cՐGɷc rLC)fUq桯KyŹǠ&~Z<kh(`CȺvdu7,%l6jG}Fi٧y7s&E7#?QW͂`uZ+I.SC5I\@lz^ d%_b?¡/^]<&qIbC6DsŴ3=*q׏o('Ko6sDlqH6)?x0ep?tX8>b){3_&GpJW3Yt8MT}BvI֊|Is+R#H,H[w20 d >-2u"G^{!1>Š f#TQQ[r$J!:U21:"+X~" ́{OFa)遵3o!C?ۻct s܈c?OɸĔ1 /5V&Gy%*o9?P.);'ڏFs+|vt5'3?n&9ŗrwt5cN0URuBziIJAWau^Zi GVx Ry\:>â[:i{P׼ˡΛ]:' ɰ.עλE]ZI\@8}¤·1gJxaR7|.۵g;}}l+m0qB+V=8kQ>e#&Yiͬf`?bnvI=%V]tdؕ_"#+0#E,JyQ''qI&Rqb7/8?&4)P=:4%HYEUΫI,UZdozrsL d/!=Qx>F%wNr`q/!^+;g>f#s»_u{G7f*>-cm;7Ϯc#vwġxj;H/sDzrcayO+ShLP?*Wh"[xOG3`&CԔpgxf|KNp:v0إZaVa͉fÚ'[v7O)}?LPed7k!{<3G%›~֘Ü~>^Ÿ~]qyb!\W?2ftٵz=¶+|k5:}qU6SrYQ+OtSX)ft9P ytO+,*5.ұ8Vx-G<*L \g Th hiA4`A; @XR&w*АA-G VT ډ%SH'LNgw%i$䠰pTF=~vw. _55`r/ 9U$;u/Aʔett}3lHQ#.J5ի;KV'&|%owwwɚ(G@.`aڂBu HߡX\o?1imtY ;m+]|bh3.s%bL5f{7ݗSZbE{KB>mݥw 8!(]!"ǁ$NeN43*Nҍ~ &)49%pVFq ueH0G8KLi5H6r8*zap7/|mksAGh!u ~A'ɶDI&mԫb(US.J{'>ZA%"$*~%M ㉉VaR"ޕND{͂D=wHC~MgOd Lаwy}&r4LYG2O;RH%p7yXΗ$0Jh(&A!K0nـhPy婟 N^ \TYTm󶟝dK)pCBr*"0ڜyHE "bt| G8i2ͽ[}{Pg ZY(mȰ$heS:6&+ǎB3vÐ1U!7VQ%*fJ1Bn=]д_9{[HP=Jt~Vwf'm>!G2vzFˏ*)W3TO Ilb&N+/wJkVv.hͯ3Fk).n(,~H%)j:5 ' ΋hh e[Yi^HMe(nX.gB9)a*na,8ٚn)xy;I$,,̞(x{(ԙNBC?B֯ƒ fIM:c4(m%-Z~㱂_w cq=* 6| GŒF\z9 0઱.R⩱sm`.\Ⱦ d3a CcJuZfHC6wpE5#Hdm,P@՝ʏ*Q{Y֊l]5 10 3$eIyvSM@p4Yi7a2 NWr&]B&}YMiHjc5ﱾ^c9_ SN!"*gErQ Kib>.VSIH1Ŧ-(f%3S?(A%Ś&{C.Q]|kXh+vYIdfSge ")'f.p?&H!6NX[B2a5iw#bx&S1SRPh<ԣ.DVqf ̒}љRJZsEM1ڙ;"ɫ%l3T]"O%FWE>"g>~R&;_E*】A+9npO9r4ֱzT\d01 r 53Ǎu$ PNԲ%wVadWY+]|\)ծ~?vq+o].a/ /5$F| bg!5X銌y+Du)e ut̴Iu߮ȡֽuHBK[]5m?ĖU=k#NnjG'Dq:@BtGP ]N {l{7Oq4Mtp/_Qį?, Ā0k$w7e3rG3D fH 9g؍\[;r8{3Y+ B$?tND.Tx@YB8^|'`'Xshv՝rH+$,:$s\IT,Rs(I8'~QD+R%|GMI^лd%0AEC -0sL,6znO|E :xX ȔC9FBuHi6fSj')C.QfVLr"WQsѕ%"]}dFkmB#/DCF}ҍrC>bhݚC{KHKHyKwa|qyǽm׶)5ǽidzLqouǽa+qouǵ_wxfgiLk`qmvǽUǛwIL6 c5I;&{t{HA"̖{zܻcyzxshz\ۣ3=16< kgM4'a{ȤOs|d=85bxG{SdHҕp qVp1z:+,&_g98zI8S 9gSʍ8 5X fd΂'%z(%fQ`^P\qBmE i-JXU]Ԏw\U1U"m2Ϊ՝QҝwR>>}?l"u~u: ڕ`HӼɿ7;G?Z849fPYD(^8҂6x!;8+&Ic&]3-X:!YmYޯ}fMdIU;̦4]a󬒷$M'%k@C;z<ܴ^k8dLcwX|e`+_u\W'8hUnw!+*˰= ]([Hko!뇬߿@w;*?-!C])bh:nQHśEW@mRv6-F{&ǾU(8)>5jfq;@EzTfmq=nU@/b*dZG1厣 \qD1Z:"H9$m谅7/t,bḳQAhGɛb D;2H%Fw(GM6:%&}@Z*A~:rd 9o쵹ǃ7e嗫r,k_0PN >Sx䣖̖TƸA{˱{C&i+a6?:63w:l^COAFon/G'zL+V˧ 2vC3]m?R4BdcFuD:vd vK &DRã' s^^?2adExliĀ[obY7 Wf\p:c,*y8?14m^%Z91V{Odb5>c_tՁGc:A x3nv'sbr?^r'/Omֱg\!Ǣ^6?da_ԉ[p,[b1Jʽ+kvǩ>{& 柩A56ԍ,0v0/!n|GMX>\!F;b5PIO  N*XQjG;DmmyYy"؇-Վ2]BAku!.eIS}41pO?pq1t)oT>$! @! 9\Xj}Bci /%OY" (xJƒh-rg ,!Rͺpǣ謂T~|GYX> : +]j~ojkF#WKy5Susw'%4q2n5x`%D*XZcm%|2;{rck= {{=XY!@pՆ$DdxUUr\1l2Շ|pq< ,2EC`BEM}S_דּVQOttU)l.oJAaL w+S?x"ʠ*ġƸ,o~ėWfs%.L!%4Eꩆr5jR_Qω(YV(1u%jf:HsS&O..*vir_VlAҝyabbA!xNTʓ2KQ~얀ķH-:B6P/%zX7Bϧ٤y?k"Z~} e嗸$YxOtɗģגk+(80T{g)3 tμb S `~fx{ kϞ5orP۲Ca-|nk$\SEVju K5Tt{;7Kt1i$Q3WzƦ {IUS@pBf0P'-,\H9щ`J١:.T)W)}de?d^™XCgE ;16,5j'(7Aru)e9I~*}ܟ{ͫQ7:[bM ȄBȟ߁4A;m Q߰ a[ۄLqQ7ZEf݅'YU85q-hu,W5 gE6y%QrVl{ﲈgw*,GS 4{ Rl}+&ImCQOv q)<1 ce"%}o{h`z{#DqąøY)ۻ; Q_?:?i|"P$jF6tOǏħ+6X?aEPww;[/O P}]LyLYrTƥ3'@T.I[2-  :@Q4) 0 ˌ}MQvH2~֡cؑVA22kw*Pߦrw]L ڍuI 1z\Q3jdWfn|GY?J&^<ٳ$ɚ%тU$ihm,|ϭ %fn,nt5ϖrCY%ˋfjDhP ʽ8n e nꃥԙy$6MJII7ieÊvpaBNb yGjե lK,vE'hVT.;'jD/ & llqΘY̳Dҽ;;jSF瀒[DeU„ǃ#˔ Y\+jcp] -RW ۵bfY C]G]3swv٩/D`TMdm0qyKMCD% !8g3}Peʼ&EGáh\4R3obՓx5eC>u~:r,>6EsI r=#ݮ}ħ]iJפEtQkbK,bgp@8>Oj^o)b1h tjŪ΢S 5,r. fĖ|wKB ہqUY|L/96*UK }YAOf4$8˽@SȬƹE-wKNݓmh+,1t%:UEl6*/G >(;k&{2w*\(oό& AOa]?G3IuS.a&RYLϾ(,suY:a.۴QO!<; [ZFإUHMEeܲ f`rTfA6y90Č󝯵XDDytb&*ͨDߋ!)7;#4W\o'ᇻqI4A0@>g8Ɲ&aЈ(9f2&e]%9*b< ;Zn3 z>H~ *15J{M5$"W5MkHDPR)jQiOHE)f|!$556bamXxG _ǻ}@(1]gi"{#^J@>Oj'XE'0GzX<_a7(QG'^j4{DSĞ,'s5{l93(#vfPY_u>q(lסsiϽziXN>Y@w3cY:Eތ08ovLp/^EθJ8fj8G+.78.w;;yY*p/&^ne.| 2P O&5\ -e墢y*/HKFMKfWKqU(]zGY74D,!pw-TLU=nT3XÔOg4$t FMe oǹ+]+J}܊/Aŏ'}.9 buxkښ5˪ipm2o{|Gm?ZѬ2]>?T`vݭ'[C, 9iKϼ4; wG hz]V!d[?q`85O$8Mv²Xޞ4X #Zu~Ys4Mچq5-چ^kEV[$hi 2#jm|saz<_~}$=xo}*p~VPXnLF wC 1N|^}R|;*JLٟO>9vaȩ/&. ͤCo9@ӫYܖjۡ8~E,ӯMuQ$OIw!pG~"4 &xI=嵉WQ)5X6΁d{VζQT4 S}8ĒΑ[.LƂu !`_9jyY>h)L CzD xNZ2W1FiZT69.X;qPH[RJx[PQF 73Pwf+#Yλ{Olg蕹ug$oTNف$h)I4ľ)9|Ee2Az[@򠜹FTYڠH+*-4 cw(5`?+<ו "ӏd ,,KDR2d7貜#$).Kbe7$W4N7b;:T~ >[ RΔ`$ok[ZZHyC R_̹z }-}&cdP$Ú%ݼ\ Xn0>Z+NDGA5 Y8A+ c<4*iČL+IQJ2HkƇY>W[h΄NS|Σ[|NK,L# \mTZ -wJ㣅/EIeŻFq38ٴ{G)6h)RVM^У:+猈ڭ!3Pi)j!c7ǔ sE-19xi-0/"B bs)SE; |S%Ѫ6b"^,Ny\㰗\G68.,9\}ag#(r[\7E䯥FeR`'!saЊa}ߙc;ja_KugDx=CpBNvGװ{`7pwoQ5=l$z?$axEG"wsyKB#C${tOBK5 [Fu΀E6#|/~s7"FO8CO0[V7/ P/ S#˭ftcu%Ϸ'G9?Y}rIqTD-Չ) 8 iZvt,MAՉ %T@j=nA*y@Dd`Լ'Q4ڄPg"f5u;4R ӳI I/;X~6ݡ /vQ'0= 0.] Y&O]ѝhll}ҋ>紖h v髒_=;0QbONNXd "_5(њ7Zv 5AnzwA3W ^p+0} !NOlHH5>;Š, 9pW] )Pr;p 0-w3w3 5$$natK|G8?Z>2=Loy SPb:ݲc=tIt̑X"3IAF=`4^|NلĘ-Ύ}Q^*ɠA=wBK\%EXPUM{[A!~HvBLq%Ѿ+5rB!m@V~o6wõs Jc'N+]+v3Th #F F52*215F6Jj ?* #ï.0[iSm3U| ¿y'k𳊋\[4٥E3~0|\X߻A%v,}vE OW}vIQ W}wAьۻFeIM*оQpϏJ4dH Ǎ)$xAaTc$dގ\ILzLkp&ľCSRkr i|y%k3`8HPD-\4D:pbZIVT7j*z 2 ) Y!UXd}\Ef{ao߈ Qܫb hqe( Nhz[r-f;HZwC~)erf6P=t V>ͥ2!$!6{Axx6._K\yoΦY̔_ƽ5([=h^lF9:lFFyhbxX8de6{@ Z=-ql_&+eeJvU&%axI>N% :>G 5L2tDj$n.p R Uަ#=8d)ED4aWu*"ꐮik?ǐ7CH* LDI+&N eR9jGeG])Պ,D7V9g;YLi(qArUUUsw;;94Ǝ6O]^кl1(m.s{0[y V@, XiYʮ'[,˗'ZLOPߠȏbG ˾]K63؆dTj a!6*~1BH!bPYU\d/I͍(fרr>cd=9"VtWQڵ%=X0+`p/ &H.r,ٖБ(R0@J8ໂ`ϊ/[Wyn|>{WUKJ7 mLJi yIvWi= oeK5=U?5 ?#inHVB+!. Ϊ( ъRC 3MEeMfR.S>fŐ5U_h@;t;WwMEG&'3%#`U9l^il)sR@%Ș%V0*9T9L;S_ Om"Fzuq_{t[~C?"xeCUfQzL,a\맻& P)n8F< 6)5¢G뒞0Q(&8fY B`86sDn ]Q}HRdKaRӦn.fŦ==@ES/Sx| !H;DgC[Tw-rjW[%~(ZV3ʡ(8i4+fSD4z$`q!/F zp0RFi=窼O!tV?j!.EId_qo7T{|q!@D(qriwꇱS0@=hʹrMFEkxf./%wEn"C5ZO:cs['P|TR'sA Vbo#9dQZ-.<_5Ec@sMw |_ OCdyLf$?II ^F$@h$QD2Zè{ݓ9PV#;S)9UOSAz镧Q8nE)%SqE+\7@$zXXrQ_zo:O|xM3,x#v,j՜Dگ._6=Z2 On(xOŨ*.F +KNOx"] Ԁmj1/hT{:?Y|WvB Qe_ Jٶx:s2.ΤZ2H10DM2MA Y-j6CS>@K^?#01AlD`3~05..+Qn#kV˨ -̎M _~5?Df$DU:) vfBT'a?f(9 0՘P/HNiESE(ӺQd?46I#,4Eُ>d;D .MZKI)`[5hOw#Rp SQƵ_&ԏLTܡXJT Xh!/HgUzXj@~_"1{+OcW{tۏ*|" \Ru.Mr{=Eu&?>D"|9jo8nWE,FFVily?-95ﶫ=}GD~hVо81aޢ}?ee~C4KiӿHw1(]!n0W-FȔ[;?[τ=K7--wM|@i kƴ8_UIԠU"_[gNO;K.]+y2`ZZ!n!B?,4f\:5KW)sw?Fo̩6zs'7kѾa̿ =!n͠:P8LT3ϠFʀD1m-Rl -"y[j<V/"?O@J&Œ|ߓH|RHU0r-*i3 豂{鱾ڏ9V`oks"䕻IJV_~2H؂4mѣm!VQtpl:F5G~9a^-:8ۂ 1;RC-ݳd4a38uU&g8m(GI' pܘ.x[TIvxq\]D 7j `|-~h6Q+Qw]\WjgNQS>tY^ڪ]Wc̕M 5s<UC% 7#3 hH첑4N $8qp(#-O>联w}ѿQ iogĔջ(dvv쥾/< ߶tw!/e\~lt#Gn[ן%4ԕA/CrMKpylLs9|w9C`UnV|D2'o 4)Ρ`@؟y;ۗI1)w BTi{'|Ga?(OևU8MW\>lOb[;[evw}Kk!r.Z+eԢќF_Fulֲm㮬 _o w1.aFw bRt6߈ٟڴ}l=L {>F !S`̨=>Q<3l4`.LF3 U}ÿY;"+ ɢY5QTudJF, VW+248+c3 1Py%s?MW2WD ? ѼU)o@dSDˋ~&4tGVc!ѓ)2l%QCeY~aDs(8yeSȐȟנcj()⨢Vh|G-\>ߙy !1fDR{ʙ0l) M֨oOHh\8UbUcI!SCgF[&S#ܴg^%׼ (͂TL`X,yɧAXJI[ wvD,ua>~Mp(krV#qh hdX&U'&Rwf)>Rfh^+[qs=zwT壕>yc"|101P*1p,1S.;֓ciXP,35b%fA}Br^=撺WbleFm:>%o#:l~6YLN؁-RlPk'b[c8jb p8΀W"X!59`Rtu 2b)J\g>-8*:jLR88;w%b-n]򹑧 ym-:7i3.sn~ HE+F\SV+RQTNB"8 N"#"ƭ\Љ0/ `-'wZs^$&a<é,8ӵŢTnyr0`=nZdU8̙YYѬYu(5)!mAȤR+R~wwa^ӣk޸M(ϊc䢨\vTƚ|sϣp!Jbzb:K{MTwu.T}3%3- EtѠ69f\wRO$*̻:3E!88"=Z1<ឥ7Da$Q?SDrW}Uy?Tc&! Jc]rE]vSWZS/ߩ-2fU!c*M sr3T>#S%A^zWP# ];W f;ݍbEa;j!Eve EB\VJ=;EK%9]!}ӽ ?ӠVaTj$UUD;uB^Efɞ2wqaE!TC@gOSv$Ow;Y 8Z,{5>~͔ya V| Β`Pc e3]aAg&;: qn%[(Ϊ٬8biwω1wkC^wޝ.fI_ a⦔{9#^=y#+=L[u( G帓z5b޺7z|a.Lbi:_D~0U6R[ [/E*L+VA|$wjFf^Yt)Z3[Nl2[/Ot2]/HE[^6M>qiN*ތ6"m"r<Y\~6wT܃d>%ΰ =L,9P"v?0HovN[T=۹襰dB+'Il*ӎ>N݀UE@wvzs: }S]+wpGZA\IQ5W0fLX/5 -E6MrYkYp<ZBǑYKj,VIL"hr喸at8!,WXԻwV/Ҽb %j5l=ؐ/D &⸬#PJXbF rt2$G@&8b8d}&(@.\aO\UݹaQ#/Ls^,=&aNu DAi5%!fE zB/!;J.l*B@ '{insyY5D HjJRtG>l%vu|ZVY<#Ra D!KK+MMa*&.?C{cO# oˇgtlr$d^Gk6 \]#ȌxUՀz?X07uv )zXV۲Vmy3Bnةѓ ۇć sJx\v_/Ɋ5q9gzIGĹ:^;jQɵ/UXj w -22жJBƐp|# !jFjpg=RANeJhFm=U.*_;;4, [SPMIˉi<b~K! 5.|rv*DxQȧwo*u|uY#aF7 F ]vD2&%]$rAes@p0V?}}G}>AԨd7=D $Eu=GH "yΖXE>E2a0+Jh;Y*@TIwyEFfe)rxac6[8;Y/L^Yi7nR n_|G?cXQ=Wz]4:=8@-^X" 3/҈٥# 88EUɰ%9Gl&s Iӧ>E 5~tˏFBS/fBvG;oߙ弘0:~W]NY@iPSIG|FՌBy 0KHTW8xֳH_{qlh~Jh!`RɬTN+Gswdӣ9 \OsEO7KR=aj29#~ϷlmtRsxW5?p˪EB)h3I7[.VZc+~v݁3RʙIHiKG4с&m KtP0@!"SEJ+H ٜs5^!l݆2{|Ά$إ>|桰h*WWyh6C#ϡ W̸ˋSϦ<*P$FL[C>%{pKEI^:=E #rCRPRC|#ċ+hq3UTsm5uq鍄_D=Y+Sc7 U"oj渨œH(]];J O˝y>OID7Skߢ~c03@~JgSG,|KzFu7l |vn7ѽ357;\Ut^]a|rBC21i`CG# @b#$fuQYħoq ke[&A쾧ir ޥ͢ >_n -1Db49Hd75;si\mkzZVH)af;ʙRg`.[98`䮓;; p*pw8+9d &:yƖʗ#ߴ3iPI]M`nNl[Л-A?w; Z,@@̙|O@ǩP%k)B#Xz1({RK[&P |85 / )XI10< [>Rz4Pc:8[,~295_!~iF67iЁ:(G5H[IcPG le#*Em*arWxBϢ++ ewe!%fq:DXE}h&njp[ S]C+B9=j.=_1)wmNؒ(4GTmF"r3ݓB 5}1ܵN__!7ݤL9x$" ɗ~`y4Cuhl{~V,p'd4eU!` Bq:ziH3ȠR,mOf55ӧ_PRGi)TL9"īE|$eTg,_\&jB:C9\Л(2 \*Bq`ZeuxXbU~^r(^˶~AW-eMs1j8~JO~wLT3O[EsoY|꺞y_pn-w} n>]$+eq)Vps}.x]yi{R;~Vݽ;yӭt 5=M/1H{HR0K&8ڡ# 0S9ݕ\ w'fG}h%\;;8OkL1z]Lp:bj!ygTx.gCJD1Wa 9-t1TՈZ9U+no_UC "maC./Oa[aˀYѣ Kp!ۆxU͖nPtfKbj8PTjwI ]`4HS~iyH}o]5?˵=f9Fd%LlxbˋHpFyCy&U(Y{TʦXlC'f VX-ҏ)aFLUsb9I[j q2 RE늉'R?r S?nO8GE3Q]74,zYGB.zKOA|hp!]"Qs]Ubj7=y L#]e+%|^AW{yݽ&x%4;%ߓWB{3@_7y&f#d;0Q sX"'7|)SP!dMxŰRITp}¸@l_jtA|mp2ySX\7Pʴ j:1EuK|ӢyH?gW(j7k=YLLJiఏ Lb.>f##2Be@װcRUDjKAS^!ejmrOBa4fx{<\s,i,z:zx\DN(92Sc,v؛v@3|yzmO]*6aX13Ab:+*;H㴡&gmub[HM3rs+zfv{6$%21 #- #kVB&pmT7|D#'·u5:Vfޯ|}Ku%K1yXZQ^@L.C\tx/?:!^_}Zx6gk_}OO>]k|x!Lޞ x:,/QHJ6CH>4ih0 i62+ 9֑k%O>vf Ӟ^ͣӤfp%hB(C G!a:Ո;`@d"V?qDf)I'3PiOeW^D[.ʹuГĺYrC8{ҽ[a@Sz4 2<߉ppcPPלT3(Y&dePRDw8C<<Udʋ~]._r Cү1VB4lX"6*ʽozo@ XYcֵT+?bs(@DbM#RYA}q0ٜJyݥɋ,: KQfI1^cɽ(v L_P OU{ʽ7IʉE6%t^~W;F2,ymXR~@je2OW|ޅpI-Y 'AT C}X˟\cyH߆3.|)S$EJ,yu0tyeht[AL Vd4,cɶsg>3LEp ,#Xm ?R'9M$>x ܘ"{q0Ƕ~2!$ Rf&^5*3>I_qс^<۝t%.LpeG.,%5AoTK+^WO_}di~~Va@# =kG$Q8hBMB6z6=BBƫֳyhjy~ب&Z^nYr$.wjYdޅͶJ!ftJ ܱHsO3a r6mq |>{+g}j luY2G((EH6T{&H&(ljXQ C52#Ʋq!556+1kMe< ȟ^3do3PgvJ4Nsr1&2{aAhg6^E^3a4@I C s6y1Gta5dt UЄ j ҷV֚$fNhЉ'gv e{B3q*xqgGn>2(Sۉ}ltw.[s;={_7'VW_qp{u߯6??/j^|Ww?\_7.ޞ]޼[x_N޾ᕗ7wo6?F{]x|y{R{cķO6 twӳoʳ^q7on3GSryyx{[ οv_ oղ6:nԶnuKN<`)W']t_]n~ڼ\p۳KϾpsg^=g?|{~qwW뫟~rs~jK]wqon~gx–O!g]~w?yg]|O7gLM~hV^9}t_~\cW^i#vN|;|7gޱ۟/tlzÎݡ߻w8"BͱWG_>?YkMƃ?۳o؏>;KǞ3?cO;=;&pn٩=ǓoD3xd>cn&9Oޜ;뿮O.oLۓ~gr[;wsNf[^mue~=nKVOm*.swaSGsf>nx>N{'dkbmC}w޷GϻN34!ѕS2ۄmx)m-%'.et?rYtF~\lO˛aܱϫzq~,[ϘgF˫7ҽv 9]^]]oNgނ_ەa6[Y:ٞ&lk6sXm6!bz,!w-uU6>mǡr,v/Ƴ\vۺy[֗g{{_mֶSk1!-:BtqJMۚZro.*mkMmܒ MWIͯteֶ{+_4kִm̫/}&+/f`_4ek(>2+~wW׽ss{}l\msy۳ebl/л n:Ö? Kcw ۳=.~>Vr)]r.; Xf7z6yN-m.RHҺ-ٲV~ E(5ˡ6ʡNvg6m{ȱQ0}>jQ?QIS;ߣFRNǍy{|H܄zBztydս}1m\LCmt5{ToBkX7wq:tbm6rM];]4Ӡ֬͆r3סߊi$CZڻ5ܕU۞ćiWݥu&Q,e:-Eo˭iP{0"~3e7>ءzXe=`R2ܑ؇f7nVL PF1m7 URK;9s̓ID{ qM(4b6-g%z̶Mn/1LN=-!t{^1=b3[͞{+[(ɬK_>۞?MْZhغ3O9}r5;{7/n̥۞v]ҞUUwn}Ž# (gڸhj 3fKB䛄 vąX~v+Lf/6OW*IWnKm6c\' ɇرwc·7y!w/+&GY\̠~Yjkz=`ͥ[3kI/gG|I3>Ĵp~Gz?war?.R#bU!F۫i!}K=v̧i#_<8,w,qש;=qoCQ}x5{>] [󰂯딶;hQU͑Jt8<9kbf7*Ѐ9db9 Քb+`ﲞ?!nS Ssʸ̵g += Vd. &_! )v FY)l#}tfCkE&odCV0bX_CwTS l?r>qIzE]fj );.[v7. {'Wz}؍0{kޗ9P}OA2DkІ{,huh.m{蓍ݏ y\'l#.C@!AOz Q?F7ǴJDaャc v GψQd?~FĈG%P#Z6L C>U]jݍ@|OӽG)?a ap*GOq|zdcCG!JLQOQQT2Eq=ee'y;`ԎَAߞaSF8v>Sݧ5>`YQHL@.w˻{?0?K{B QeNȹ&r6i< 3!:M7[_RI6ϰͳ {(O :{6Die0$1_x0Mb)Y'Ϯ`,뿀 93Y>8R˯v['{ |g{Q_6x4}05u4S5oϽ6SE24n[9r !msJ?hewͧ{M,>`8/47H1sџw4R>d7G󗠄~5~eEXկ̟O }R >p0]w%tܞ߰]C-T{ﺉ[W\w^F;*[xqr{nuT| s$gl| q |$'\HS=ɌpUa#)yQ|.Ä2a8Zѕbx`x_gdؙM1j| /QA;G)޳ Qpޡx8V(5}!J|!J,F9ܔV(5EzYJR(52R﷐zƇ0\>U FJ'8*P)5yYJO27xɔSꍢLï[Zͦ_6UY9W|?ct3Nï~,*~>χhJ%'/g4?2g:Ukp8倫#_pgUA[q9ΥYUWlɟ~R?`SRm<=.e[j%8 ۣeӷ͡uUZ?3?9~HG*[^&3@R#Ckϥ?k{mb-Ľ#Q_"G?iul56eO~ {4?{#z[9DBz69< D݉П$բ`߭(o 9EgGs_LmqXn3`X9hH[}2̬_͊0Ya0}i |fXOg3zH=!9cf?zz:jo G?e?n9}}kr'MjY$4n~H"ɛM|{=X#c YE{ kk<v҇ sִ?j{֞iFzVG=+&>ju3 K;-7:̈́?@da!;us豦˷R`.N9ԏ8|{oׯ.M \ lﲿM/Mקg6?Gzc.~~W뾽ͺ@/~%\)ص~{o*-tw#cy֯ᰗ{}`΃bw v>hU~򝂝/1;7,?g>te{ɷvg˦ڪ{ѽiOAM>"5볿wP{ A["%(pXԷ=l4R8 X+4ͿJ3B|e=R|Ҭ}Gu)5;xR4{ =拪2TUg8:3Rr]z|z[l]mv-Qowe,Qo1ovToWovOSu Ο +mɊyƊG//Qq?28dQ)U_l>߆bo)/)XlzXnl$jG9+7~1GCхZg$ǀ&sX=aS3L3l<1ra ^gD0]SB3<)$U5VlгL8yVl|Fk(U(n浧b:yS* >hPw7_NT9hrӜ3m;;lW e M 2'ܴ!MSU?UCު]uVX{|nmL=Fs5n1\.J諰GnBB,X=_LP Ɔ6sqMfbzu ا|c3k̄H3+ʐˬK͆ϝ ]/2(nbV i1Lce^&hj昣} qj8h@J)%ohgcH! ݑb؛_4\\V]vr ? vdOL't޹}pI0 vVAA޺ܜ+@V勇w?AqWQБ\\C7gcxw_ܞ\4-_98*_]\on]~u=ڹ#r.~ߞ=;߯.v+ ;ήrOC;@?0cpA5Ϝ+L~o}'/W|G{>r?iޝ^b[3LKETb af2B m?8 24YSNԗY:S  T%jV L3LP1Ig, v5]doE_ :$7!!Zd6ك7q9@Zn `sf&p[` N9knPGd&=n 5H0 ; 5W"&A0ҾJ?*# CG$W/Gq;._ .z(?Bez yzLsu_k`˞}U ;/+>E`];ζK 8+J3D;#p暞mLʘZ\AȐeȖ"pek~ly[M]*]3L) Cۈ_w6VSn{ٻWsUfmYۧȪ{4O{Nq";GL1:gg9. ^~En4|#7pf03^{>R]lH'baPxoV}ݫփ;p74[{@>?^ܞm9}{~ONi:'o.?q:p!5ȠDxۏbkl{ۄv~L;fo/mTx붭`:zF[pﻏafk$ddj,mXFgB;LW|!morLdRFgRƌ5l7hsW[ TT% q{ǂ6&Y>.nW @_fUN_qW ږQNZE!zO?k~3v3&KpΟg#!WƽV{&~{#>u7Z⇵O٦vD+ѣ?laP (Gfp PUL[ˑexe[C>ĞTH{ h.fό݈w_:>W+M)#⋍3n}[#fGICDMzX4}Ť鿶j ƊLxHgx lBf{] Pgʻ#ЛcB;vN2 9g[j: rvHc3qE`'n@]-C![hGodGlњelWnЖMu!b@R&Vsfm"9挝-ղ׆MɾB9SRL(EB,]dS/m蘉ݲb(]CƢH/Rn4+Cy;J~-qHj/ila!ee[>U>>Ȁ)\̥S܊vvpLdBx+RB-I'BKRDK@`+&F+CR@(u u ]4Vm혡as+z̙LxAwוK5)DE/'cliQ; s%~sFa5C|iTY.P#v)JuP 1U(:(TM;P& ʽZHV|u 8aCydt\mX͞x >-P"Di2'DVШlCu 0,֙0/C@xl}<|s|)f="Ps5cte&}8GkӾ9vy)3IkY'nKK(x.-Nrrv̠[?W0+H9>QFR8ags:; wRҖ0 +8׼ԮHHR?kB[ 0X4 ۨRWUbS<Ԥ޻5 ɕ`[02r[jaX0!-" {lTK@iqaSa=X+%cֻm!#YFeSyYaYom3A](R x(MH+8έm-Cd R#]nzՋKqsrpr ـQ~phEZri]tabBɇ# 1%RFCVz[4h8 "H4l+؆KkGl-fTI|@ L\G5E++|VJw''" n""#U=.}0ՀwaM)@O/&ڽi4i2P6 3cIjjZ(n# +B\BA2Xg#A%Jʬ4yểUnGfE"d*4ۨ͡6ˆbಖNF(;2N-Ai f*gN JQ'h`bU 9Z%O:H28062"|0CD qp) m&w 7U &puGT&oW1:Yeۭ Wn1!CO0fa }Ds2\W" 6堦$VLuDEU`'"9@?-@r4D)2а,$GMbJJQ6 f|*cv' +UH*jd !}UXSxQ*tA_^CA rC%}tg=Gw@x6\PÁ\bۗ]GD6B.UUb[zĵ5g]Ō$4֌'Џrfʑ^T=tK zxhep \S$R`#k{y$)*aJ}$B@m yPEG(-W8c8kuJVȤk+<[t. $?z@:мR &&Q m呠rTthR0zbm#nX>5V399k|+HKNW)aV%"אbrXȢM0`4>˄EȎG7I>D>= o7C10H\qQZf2ؑvqP 0Z9׀'ɷb}ҲDm <4Ƃހ9&̤SőlSBIɔ䜛qż8([xG@TDaUQ+GEQ4+ z0)lG0)0sʯ!P2C HEFNH 8 טFD5JC=!*+K\V w8;<{zshG6+Wmͱ5ů\gG\ `%eBQ!+s[ H 820bsuzb8=PH֓ElR#: p7q6Q2ܮBZʵK!sۤ-ZxwR36֦$EFm$"0@w*7a+4E8~FCEy(oqʊr< {kxH^.c«л^#CeQp;)9/,kSPnF2sb3݃Hlk[8- Vf (eO^+BGdh!4# `iZY 𯉃]69a -8397g{DδnI\~8mgF#tE+FQ:qQ E;HR3اS<49nsIn%h`1VT6L1#\HUz3-~xZRܥD׎y!HSa +p{obHz*`"- *V$IܲCf$,(0 t \Qd m@R &W\1 $ЅŬ寰IƯ3yسZtNrTF=^F4)5L@g#!+ih!Z .p<3M}s5,;Bc;U\桮DDC(L֩$ce AESw!is@btDž[PSt vB/5ie@. $ d}nfL hA%V$<U%8Tv&a0g"TdAD3)?C9xr0vq!ptKb0AuYGN{'QnӬ` {< vDvҼ ʐyoҶRsM k_% 04pДL3!L =xJ]IB.^6ov#h“hMգ@1N 8B`BYEQo !4Ct-kg"qiW &<8ʩEq Og ps?7X,0|nxGh)j10LpEE$a15'!w5Yo+tW"¬F$GS92~\6K|f eh$HDN -De`kqES~V 4b:-mgeK*ERtmC|l ۟7s~lN*}O{/Lct|( qCEWO)[{JjǂZ1ŊMmzhmD  %ԇE]ôD/iJ Ff5WHO> {Ԃʙ#mxBkd 5{Cm6JP'5\ahF'&;l j~ek %@z\ܷYޛ0@PG*7'@Dq81-W' 6k^DX;U{40L &OBBSa%('3).1#݇ML4qFUT>.cBuM̝ʠe9OqciDp{L t)7VrNQTFhEqUJx613JR$, ӀIٻBK[Aqxj rhVЂPR5 I $ ǙvYý9<-J@c[E[!'\xM,c`a,Rhu ,˙V :#&\Ӭ>}ASQĦ Y?F rPa Ck+ɼ ڇAbgF;J?0H*@*DLvZ! (hj&cǵ0A^Fb䂓́$˰g/$+reȕw=cQBEZwWNQ>9zi*/tޝOGN˙SbH[Qۼ|$B|ii!Rb. cYET:&%LJ,` Lm[wÂ*(gIHl 9v} Ci-I͒p-v/t 2B0!:h^e?&agMn>ȯevV`'~#rt}:zB9.L93 ;3ۙ9N`vCo$k"PcfØ1 ,(6&lIVJ$w`pW Bm ՕE>vM:-BlU[08l9(9De!)dIT8fLA'`&${X1 |DLT*|ؓ2^Lݗ8RKcoDaWE0@ %m|}$jRV䁢##Tf  { ¬B۬BARyclu؊2%@{2die?Z7OvU4IM iU# 9JǨۣ0$x:'EL}W3}lt]"[ ̶!el b oiY~(U{;iP!ҪXC# 3U:fJ1Y^^Aod8ȱNyD+ۥ]b(7N19ي e1hr1bk:$W0 X9UΔ$n4ꃳ[yf'`4fu%2F`3ɬ.vL ]qIa415fNq&`! { iА SkR1y̤sW&B=Dq1OP&D/fiXcH Flx-vҤlKBaP]ɩWcKBAM@\ G& X2D5,Y{"P[:5Z#<;DjR93PUL2"x)?G GH=ʗE"EiK Th"؀+yAT+ w偿D:(h%NX.4'72ETPi,†$dr3j&pAX>7 +sKʺl\P_! x YLBNN^U\?w+0؄4 ŔsCW8*,0fFZgY:tAq:*r^q̌fpuhhfyYCq~A %u8A. 7Je0zf4@2R06uz*Б\"nPzHaI  < &m6R|V9wZk5 $-C%j0'v˱Y"ׇQ i'.86öޑ6J]cR/&{2F 5í'&q`DBYeWu i?K[1y$+J& Z_rJ&<+4 G*6wuQb>2 GDҍfN}.Q; gPoBPaےY@8aӸ LWI/ !ƊjrOI%̕_<8RͣfgՉj+ю} ØT0xzfXQdB q\P'K4,Qd%6Fzzy@<`4 \[sѠM=ŔMi'=y,LT@B W'C4}HȜ_uINof9(ߔE#(Ԫ D/== I<~Aa % @I2N\ IcV C"`&Hkm0Q/^1D# [])h)Eb4(ړ{.v20s72oetm<^[qtD6 xVBJ(͂*{SPePN;dZVɫ4cc4 cp$ԋR2X#|b̳i`1 E 9 _f0X݁M51yK!u6VwНUh$D4^pY02D;>8XKUtR860đyf^6n{wήf/>FP a@;JJ7$Id'}TU"H,]fuޭ+E$XCNxW3˾نP"- fƢyq%P]D|P&FO0^, JVOOUˁ8#p!*d8iXGL--vЌIG n͐=z&riD=X0Ҕʪ#J 0>XUM` 6&@0)fdTCd9!&eBTp7 lfWOr~iۦ3 lEz`ifLV ı%\'Dch0$2%aFQ~e,dӏB@bD J抭UdfnI˜l#z8CN;WYމ@vE$b)L& jP3$"j)(厥'˨o96xkDh8 aM3%A8S"~J@,|ADxcHbD~q }ˀ'E<%,\"}X21dSɦGȉ)&V^%{bt<\~W2x$sWC.N6}ثA,!zY8i$M`*{.X.>| 3#VLh+˓ Ĥ,'/7U=3fLM&fds*g6n #B=9 +e-{A %*!"* 3AnWƮej>VkKܬ>6&2" yJXs3ÿdX(f,]!T{$!aOgdG: $Y {xsHѱƆΞWďZ7Ҡe">As*wF9tb嬼# "Wub-w_k7g A $)4"丄.S0ĻЇtӠ>R !aW; "@._8YE}zxb;AzyP9ZfǓS#V`ID+ 3q]0r8Kq@ !RqT^4h$ ֈ`&2%H0mǘ.fz9Rf(1FöI0 iحʗ=e{T4~\U;{@CҤ6R)|@ V_qM-G¬<(~*ڙ#$$lx.ޓLěu3! 1:?2]z~݃(Fz)!W Qu G{if(QP=a b04͹-yX%&kXfvCѳP@ x0319iM(j@[:!,0Q0]$?(e UVdV3NI}ź"NeA6}[*-QJ.BE0TU/ c }싻\kyPxAUiO+TzCY[ :]N;psȲ2d W(dWNT0uTIjyTT\Z4E L F|XHivE0ȸU%i;Ƈ|LM/7E ,YGO'UKab Ů%2 "ON5m%qcxZmJwy"YgwU^*:m,- 2RĽ`]Q4+ `qγ A脕D3,u)C/*#_2KpdErR`W'"F9`DmQmTӪ?Ĩ"~JFWt-,F"nU"zWZ燢)j'D``{e XntSo$=Aު-oUD_Ý飡_e%#(| FI:R\;Լ›9(H04_DqH;'ӃT 'Lz'2ӧi6N$nS+ Ł%x\G(&f;x05 Rv N ӱe H,4 'Ѽ{R":4yV[@wl:c̯6_y tzᓃ,!F$>&XW{ U Tj6^kL6EJzGpЗlnӏE2f2LZ*"ڋĀrh`+ *RirkGmfíF!^s3Jy0C9ĕuDq;;UA 䴧R0uUONGJvd6rdc hb>l~?mpΊ6ԅ% \H+ @k1(r٩ƌeKo~RwVʷbc.QEEl7I>DZyIr6pn0lg&8pGJPFӕ*wx@FiÚ AM`1;Y$:vif>>@r[Z%Ab*ʘ["aA" xGxjU5s$JuRbىnY4 .Q58BG]6GN5v=FedVI:PT<eR%\r`){) N5^8C(\FkoZPߠbjlW%lg.(vZ Mfju/ܸ;S˕ _rT`6)DdN"TVTTU*HB0& R8̾f,Xl EB @A\y=V=THj_f AVWwhJ E |e!2xA4KN7 iY SѤ}x/F8aV,6m|t5m ߼j0xAf;.=x՘%q?Hh0tƼl3{tDlۚ~9(E % )Tw$ o0Dӳ#_ݱqA\D{F5^IH@5hq I5-ɺY)Z;(slncIڑHZv$4(F@=qZ0\+f\#K.&1AûQ3Iq ʅsf79 N.RaASgMW ]sxц&_n5d-"YDF4]*r ɇ'FlϻpX-걔 /͢)'`>'TVfGHj ]DZY&!JI*i!O>paޘY\98h"ɢ(-H BSQ7YRȤ0RABP4-yĀl`iwNOm|Z]hɳ4e͒ *}Ri dS.*\H;&;d8v uU"Z5,dv7#'NW8FQjdJKþg<)u0t18` c6@('~mXg_T-B37j lpR2sd6p3Z ]0itF7:$awggT_. ݓ?~[ݝe_&, <, -S&9Ae UĖbNϠ y3A bz=R} #^ݷP=dJCei q%.WuJJ  MߛaOy9.~$KxHHj]$Mu x@6i/iFm SO(tÖT)P ][3QD+*mz6j<-Ěx(Gr!5u%PL$ ucQMڊʪYދ%9E*փSrI:  o}4ߙNɛAut5{sfl]O U<].zt)[5K?uf\%ݵ%X &йXA֝mHIG(LrURq͇.a]UVuGl𓌁FY倳Ӫi2rf֣1^fwQxr3t\0\ 2 rڑa0؟.+q / dCb66g՛a!lҿL?7>NWݡA-'֫ 25tKa=isdC(9Wui,pѯ2(Ufˊfsʳ=) ʣ#)\5Xy:q0o 6꫿ -2}A]&, ^ڈ N(zɈ,XDtJX|Q&pB^9 *KF/c6ЧBΦ.5¡"{A?}\*G<R J#K[()//0) {4MH(}F0|t0W@@5ij"yh j6X5 8Ծ9NQH@2@d?;:=vEM{SyGs R 6PAJ_GÀ"BP9 `ڮ*O3WQ78su`eds ]‰CׇGʉNj46(aBs=@ cj>1TFqvL!P[t#j8B(G>SyT`e<0K/>h5G2XSTϞQv܂M4,.⣠Z++Kх>8n^ _DP[9 *#J٩ktlMS]܀YEs*.”Ӓ=b7bQ5G5+ _A QUP|p\%Z40(3M$Ls&vq(_EՋ|Bpf[w4dB/3rMB ETJ:(MDH?Bټ"bV(Y<(+6cG7J D䑖FvDj5*$#K,BV/hohf5r$x`IW\Ba'nFiyqRGD"&(0=uO\fQ؃F!JF8J j!B!m ȢR-.s;ר1woG?iMQܠ3,678Ԓioer}QruZ!-!|Q{++UJjE όb,S# 3>[(/luőڗ+f]yz>h2AVmG$ ,^a LLؽ4G; „Lqz y:XbٟƝxBu o9{e/.a~nbm,RPND#slWZD0C"x,!q 5 7\cdBRRF_ Ӭ6Iʒqfiomd1j38EK+\*ϛ7d`̫y@S[ǑJOtPۑ:,TiЋ:Ey*'clBi`*XaiGkJl$txfšڢS` >Uyx?qf?u݌_2'B2J-nDy^ "-f!`@;Ep.;!0i'^pHR+n#F)&1i}qnk&։DY] :uRZ&4x9p?q`ոyhn(!.+w7v;2I xw%~FӋT<=Xn-H)j}e,]7lF]?4Syy:o+2 vPa#谴_'g瘨IDoİ|VwBƽ`PJMvtV̊DQbrP#WAIDa:nʡ<~bvn46zXT7kd(!Uh0ӏܖA]cj# uUdl$=-^:v }3 r/2uZca tĂQ̲THF{ EIOQqE2݅!!8M@F\PU Yn1 anD!Vuڸ,g # wKq3!crStm3( 0d5!570#FF_5)}P_;9g!U}?iFpoE%fKF?9i4 vҚ zt |@R\IZX'@"CJ!g嗬VI[}h &C)%$̎JA 0dʋ{(ԳS~^8NkB!Ek@\HC\.-7cbu^[E6xQT(6ʀ-žⶅ"XO$;j*'?ڹ~تPl8U5q y*JDY[Ǘ (}#W!:H`-֡l‘ yv˓ҍ ?E7lj8e{6 `P1 1*QyFyJ7nBbWsGr,ޑ Y2|VmWW< Ceo@zZTj@&k UtIGƨC-1!$f=`HW_wHjD8: ҈a͐qō<X y% Bb\2$B>+gDv%V6`Tc`rvy$! {ׂɐd(HEn; VÌs8;_NJ'tGkC9X(y@ۆrV1&X7LG+¹3)6ن]~ɗg-]*#F`ye]4J~]^"(U^.@/1㗪9Aٳjdmp Fp5MS_faeZY ġݵ`6aU"P3r!jV򉍠$$27/B/,WkP7:uwhl|w^\|{uWwD8ۤ?]:NN5L؄]AE?jjcF&AtqK5"O= k]]W,}tslkR(<E$ ?g=JX?d /Bđt,%%rAi ğ_~TAw1ZHGAA P I`H&563$hU caeM6~m5 ytV]0m??srpjd:"SI .e6K ?-g%Lk%4A+ iaDXU'K߯l;f$nt_MIB4:mb_BH.}cX7*E-."L5AI8M IB0HvrRҏ%?cAH1y鮴uGo |ه^hĊN*e1Y&^¹z^7C܃OgdՐJLe3d?ݚʜL05-/B&  Hޗ=Gf $PAEw\<1U$H%3VCgwCzkEj_r8/Q ʙ0; %ںj5B,Ypl *B lΝl>I<qO ynRmF¨H]aM>6d7B3;=4璬8UR" Ҩ4WsMO(}50]E0AX*LݰB.3t:NnAcO/~g'z}e13oi8gMʤ; Ck'҆<~I_8@8bٜU@mG-uKI5s(Jk_Z ! TNZȬ 7x; W< Ah1gI!q/#:j={C'.!ĩ0,>˒ܬ,9SB+GLB&(^DYpuU21%h4٠Ey5򛪰Za#GR쁃QҘz!YW]>p^.Bee,+AjڤZ Dm -viY#Py4Y]q(Wd%qu')M*4bbKO>?*ul4N_201Z">*a1zh㰋]H?ed7ㅴ1ߛ&#uF`jCF2,8Uk&Q(PU^F] UHd6d~\ N9V޷=..a(צ0LEnԙ%S iDFEr0r+%wI{7C; ;@ٰ#pIU/ȤnK2@HmΫa/#A`جfفq&}lEh(0KGqqcO̱^XMɚGaʠF)"&}0/ѻ:wƢHј@2wsFOBS #y *0@!o)U0!|4L qTΣh#{Gّdɮs_%D$uzC@@S=}[̽De Cp77{`ڰg.D 2/eA#ǹ3J-)W޾eyl†6!~ F:M3G$PE9;[6*e&_Pヂ :wb:y,7-wXdfÛtT)gGѶjo6$) M,VM&3e, bmNzm„¬V#ԯM׈ nǍaqvx!1,<=+fbݨyٽ 6I$`Q|eͲJ=Bm 2ĚM6|8>XB{35 .#&_}$"wϳI=b \#{p:5"\*0=ࣛ9BCC]Od٠>#OD# k6"nBo]<)]-kv"JL ΧQF/aWFI)Ěxvu w$k`+}Y zqY &Lo lYe[ :ѭ`7Z >--L H 81~6!9; [ɏR GTyY,a2܅p0 %"AR+ hVh<)"1>v\%c5[:ĔG!d0s~uxg D:bpO=,%ns^ʃD;?|s_ xSIv%;y2W=Y-9\FbMr>¨r}gSDBi xPdE텍5s|ӡW4:T:öf&JiFax9`Oi8ܔ5NJWBs?}3K3ں3/tȘ¾@ũ"Z/; VpCGmLzmbź趍́<ܔ޳ҐpƄȬ˓&Of6J>L7_NXtKGOhbw~cɒs~?-䉇o)̀d> ks$#4M|6I7S*J5LM[fG4ÁE IԭxjJ/rH3q(O*GYͣ!dؠ `ӃKCf"r\_ hI\RKaS¸zp_;v6MohG}QD__^.iG m=طᡪDG~\(:g$!Q9 a!o(D `֕5EftrZ*_1$lCX 0` 4C3;$:nϥ43h#SlĄ˃4ی ;;kSꫫ]ADkdM`%d; 13gv(ҙp|̤$ "ka{;Q;5 xpCɉ;瞗>{.p~C X氥CV^vݚŴgNխ5UpgN5܋ E@qcR`AQhXE/.̺͓54kEDsņ \\8 nM?R[1ݿpjU\b0 ݠq1p/j2eg2y> u9}Afp? sK8'rHuhEQ5"Jhwva-"(g!`SVRujXcS]p=LTGٽ;Z˄ ԌyJ*+>Jυm_`whɄ>0"7ٽl"j# {_y)Zy=lx$!rG IfQnhj!>!Ge1!%jCBfRF TONũ{}?!%>6Z$Yn<.#pUn;?]1R%+M"XEw2ː<0doTyP*A9|=}lZ-Dkxf5E~H6ICgukD7 j~s`yL!yrڤW&6l!G|8+룄=yyxZ8cͿ +_G#ﳠΓX 'r6gPၿ.*AO=#m64IǓilu[FN<3:)IY_ʌ)3M5-WT+ÏWs@A3r $11kW:>/Ozc7V&sgD`ᯣE^R,acF鴦rtkt08)[@ *[0嘪N;Je:-_?-VNldIIkbI,nam;kiW#_K9Ʊʈc[Lttx[c3ߝ#5?UǠlF(|fGQh42`?v+8JfghXF.0vt1&Rbb#i{*QZnjho?{Kئ^VdGu^`2WHYQ i )i0N M&&4 LGL4Oo$)J]4ry?G_?>&20%AiH+ # 2Y=ョ' \jA(Ao[4q^N:44iѼA<3DX2gBu4[Ug+)*ae=\Zhd6 Fְwz#wt{< M3{DrًȨDtp&(Ŗ)l`ϰEgctds ͭQNv wV67t5|!o3gOigW 1)5J3u>CԿt@kN\{L3q䃝΁ v6Q!JRsG7K>BM$b(~n GƞOKKQQoam,I$k]bؐ#l  vخ'kƠM#L uylu v|l3"cy_t.d1nFQa.Vt G|z>[ m췼=kߧyc~wYyOA'VM74|`Dpا{Fgal"7\QQ=Y;RDkSG 06<)?jxppU2+ .(zfzvPv!9Pc55[رrQ?*A^Ƥ,3(UdËf.^fYHzS_Lmld&7gO`kg*/g#^KW욉n^67IA ǵn"#|N.i@`uٖ 2` }Y6mZ!w&kJ%*Vu,W ھGip!OFtnPB&8Pkv8Uv$ a)nMi2cԏ(!e]#6æ_QՐY0`Ҁ!jSe#r)U2~v3c3q+r+pG0O9i@,v#H_Ңخx;9 XImNyxD.ct`)m;E1|6ߕhye\dx{SJI?Vxc7*+9`[: OՊz&4u8պNRM* aM}Ql5(p #s0ut_l=+ Eݢ<+.=Bv 3Wq=\:MAd6]c{a:l:wsق(e~!L]2 eZ:]ya L] NCbJxVv {6uml{(+DJv0wT-y rXiӭ'qiq:G/c^pTߝ-lt&:artzpEx6tyS)id# j;r^7(.OiaV3^pAS8en<6_vab]Oܶbmߡu+B3ۙ=JOS ,N ٽ/jl2S}fYL-1C6U̾X)X$˞:T/ tve;su08ŀ4E:\d0U9x̹<;Ȓq6z녳QLAam [YNXCo1ym"oZg'㤇)&Oyk&*l5F$ /f仲BN fE,FXyz c[Pf6k,(<wmfC!hj?6=Ek/Q]oNqׅ;h"L~և1C iI9bߛ Z7&9d6OmsV5d6@o%}Txas]=-黻]}" {7͟+{.én1v2GBұԪQkde9Xr;INqMzՐ,^ǰ[^u6Db^4[䥁$-Ol ;p_!e$ "I´Wcpˡ0h\sqG%~ [4=&Ơ/l(jS:s͂WD_/y}~ܹW.X*" ]qfƳ? EgA˞3ncnÂr אP=1 @ǟ@F[I^hq4|VJ}Ӫ&tbB,ʧLn$U΢%?UH- &p\QVn}LLf*ojZHjզ{V:\u ȫ0lD041{de{\vFBB]NŤ[ Uv}&>$Lm %˂}#*BZ[u%@j0)h_۝E:M5>{F&yZP$ql"-T QA̢S@wG{¶ p$8?ݦ'|G${0^M46Z4 l 1ٿ7){Y4j#_#K߂]Ln xH۝B;rNһ,QuUPlZmd ^Pq߅^[Ln H]D8L7Fωǰh=Hdz-4ǒ|WtzڴL1^Ж.se{De>?Y~6'!%>fjK jyodd+k)-+iSЄj١SWu=%NֱiCs]By&g`wl)9='͕ " N.@>pJuS?_fv\xwGFfFbuD:ɹA#t} Ӄ[9}mNi#hw#+g-??|,Cƶw8B,"", 2&IQقcD6{(G"g'Q;Y*[O5a9U Og7k±>'88O;G}IaP$BZ[|> {̀=k.R*&EZhBSx{K\Qs|9]YtጢΆ\~\Qpy%Z"Bxw W-Q~2A  R.Nwq 4rƟz_Eˍlg ̐m5$,J^uA hNpx5'_ ڳs|ļVV7\?\MW<ћy c>s%ЦEν]-wq}򅹅 jqlV sh\!7vmq!V(0*MhEzC :i\|G[eBLpm88aek$jјc^ fiQ/3s`ka>zzR ߙ>1P|s7/-߇ ӛ"mSx 4g:Ӣ^8juZb0 ؒV6 u.Ѡ.u@es]tΓv1f]ESr[{O biDDt@2FL,Q;W PIWyCv\%Bѭ[">Z;wԧA^g8nclBRSA5@M7RÞQ(uaop9Rn(FX#wk9A%y7h6In+[h Y[<;G˜6MO Eσ62{)k_i.8',zcEC@FlG0DuXV=~=!CI6c.!RzK-Ht <(l$O.!w Ť'E6Qq D+&T1'Ӭ݂Nc=iPѹr؁UN p^o0ūBg[˓h@2#z6ј9/;EJZ's|S%M6s-i^ /.OcIoVKQ+IzڒM,F-)wOtw˓13O텟&(: b`w4МmM]8CơR@s-<|W^`;V֔pN p6Sgmb Ă`Th+ae9GUQE|-sf0IAa!p ᰈ6.{Q1b̥D$ܘSճ3 ۨ:U:C3!+6ƊnD+9V4.߃qi)2J-D|^$Z3meU5td) nGP@D 88(I_2w?E!$1ϧDlsfT$s]:JDcaZg;")ufD$sʗR^@~2uZ\Ë}A$l#$GW̌g_L+?Ɉ, 7^VbǑCs %^y櫢Z/<֌ygv5!g/, T["P#a8LGNCNբFˀa2 i:vB>ٿ/' ',5{)q"B'qו 7aK-<ƨn*X/ Uo(4xYq'X$̸#wWZ[a;DѣI4v#@u#R{0Y.Gl$:"<y *jJ- y\La:'7Ŀ!kHz=ȷ}H<?g RwRF(6w2PH9iR ceP [*Wf?8mfJіq~pώ [}5g2v0TR7H*n t咘f~ef9JKFҤ(1ZLEcm;1k|fwӜb:'ZC5O>$KS69J$zs{ -Ijg]n 3nsoUكkmi~tsE7mv+0ש2y# 3=BĕNn7`yFOD=F8WMVV ˠ圷ØMpk1@X徴[yNeMގUS >M> ~ AqN4aTUi8o]589|%΂ќDg똏;i ߒTNPX ۵m%3OډZ ۫-XY fʁ% bY!S%అ]tBˌH8eQaQ]TL %`r2mʌM Ű&U| 'UV|KfHOGhu2ATȽ,ɰ k2Z&] &ǖ Lu; v?+:>E7_p޸*tíg[8%ֻ&:.*vbqZTE>^ 󋱌{cH9x0qb]fS}Z}d#ekwԩ6?x, 7j_%WIܣ)l5 xKW%] ?ߝL,jO(]b۬IN ޞ4<"<K5-J;ٴ9}{Aw?ITK rއa u#^osf3v;)~ϧWO~܋G,(B>m)RXY^"=[+Zt*QE%b o.'b.^]ky x/ %"LxUA.[_'j]k72W7^wSN"m[y <^4ym%{`BEcۭYѼrRc1A:kd hjcwp"FѪxS׹םC]&HG_P696ڣ;_bWDm9"nt H潑L갈 ?s[v@=*=ՌRIbC\Hk4Q,QZLF8_ Y+6.#Q6\^/SL]6c5fORf"rz! |l[6|Ϡ01Ӕjf9Jܽql/0+կ Řɇp9 Iue={uy ⢞$tԾo;#QܭɎ[ƴ**kؽTx|Z$kUʰ+5>YL3;}c{͸3xAc5'_׊J7l?,dSM3uP2'&J$@d3`0'DEDи ~g_}~Ryqi>08xPOKf3 ]3S߉$AM MT]{Hx3Hk0V(asia/c*ED!Ա<44Vq.ؖE5,ҩ~vgB#w(Ea\B W2|+\A4 psBA'{MF)Nўt7NVS+1V3G PX#pk0Ú;%?I%-W2)X[[Ήq!Q"S0m- 4 tct?%I(Չ [d>Cr#j@56~Iڮ6x̿H=WcE+E+0-VPRA]>A1svL";kg,|ے3lks4 "$ ӗZSsC|V~I@TVknKr:oe4g~lA ACe쯏/C}>(,>@" MiRfo hs \'K[9 :f$ +/GJ<1fF|֘d'}FlHӲfRmECюy+9{^b|u/1_~CvENg2n0SG"* z<m$,yʝFsl+WN Ti:H糵丗caa*`ɃKʠE3W hSaOgRnlNVh&f˲tX=?9\Un ӕxts#-39i&ij@NG7.CHgO$Pϙ;UDTx- Mg2ӞepkSHH%>ޝBhUQF< $5fq5zG(Ҡ-udB`6as7)*U1\Vn眧6y15pN[@vе64UN5AO[H]P#Z0 3$5:󍡵[ς:uzB]dRquqp8] D$EBZkRJY$WMtHsm#ݯ_DžR u>4V^}ums^wOg0k:TU ZkfИ25ym)W/— baJ15|:DP|0f| bѐ]ۢH9gr o7/6߮ۈ$vCk@~ot2QIM*,;?F)B8av1E+Mjozw]tb-~ ؞!,{ hǴ}ڝgTV#QÞ*xq]RC|Ĺ-`S-bK^huv%IgZÑN9 7%x='pc!}#{5"Z $)LzdǼfN{8ņi-fpt,B $]lkg)R)C|C$KAd:/nho}~rدUmey(s۱[U%N6+84AxĶXq~-QDO(8֋J%APZzm;_sabg\g+w RW] Ir# .@NG p#ԨLmk} xݕ_uƾ +4֐~}1lC66ڰC/D[W9il[$k/]{LgCQ[ z61R B͎^~UZ`uA13ǒ& w>yԫngZ q6)qldk ts=Ӗ` 6f6)#kޯlyp~uf{>ryLdʹR@1-;͋ 1,+;Ta^&7cY:0ZJSL0 p!7w%`XgQee}A>&w$n&O&&G ΋#nZ A7o#& h$w# ΅3-f$2<؜$ e͵cp2Լ=Dݣ[b$[fmB P g;9n+S,8b nr9GhobN3m!>Ԧ)Ar#U!A)ci`Yv0.CSOZ82<m-,("oxB^O-&ӷML(&xPr8j 20s ОPg$Ioxh0w 6 FUy<^#Ҭ/eBtN!Qr%[ nM,PDFA4{h |+J =]G!́初3s3yo?~q{\V1ՕyF̔i5$ĦQᔼ`Byy~]Yŕ~}O~Ƶ_Cr5[$&bSM`m?A2vqYun>#7fg4\z">mmg?,C֦V$%K*Mh0UP!չWXAnɼ&af"jw}\lnjAWӭ%4XO fIӫ#A=F;ph40S#I :SbO\Q{jI_IQ↕ E eTG%&0;ޒiVIݍ؈+y}YoCCxpU4KsX j5{` <{209n* GkxZ}2G$^dmZG_JALvV_-3(gkO Uv8A ;{ 4YL6/fGAre}ЛN!LHqf$57/Niεf.ЈmbA]hڻYip.{r[.htLk[Z۸)wӫ)<@8mnᕳ3קfPb nޙC\N<¬k 'I*~wOq ˂H]C(q`0f mOmPnMb$$_b*D̒Vs';Yb_$~Y#J3TN),虤!S*)x8 Oe\ l+[q8t _ v=Pm( ;kqX=R?|ytcevcB )6g_K)0`U+ t$- +/ĀX[_ŋN}!m-KcS"14̑cIr8296s>c'5fNI@ &hWxQg`{"r3i-A7B}7R{/ku]ZVlKr6ڮ4鴩[1ml EcDZkiX4y4(mڢ;δzF-)E/W-ssmУ;:ҥbLOǨGAAS̨2sҼ:"E<|nh# %0ČfZ>w.+ޤJ~ Ѳ!Z! ͞ڕvK6SL wMrɪ/.$#,^}SWEÚ(eXPJ464u qdO)_af{V.e#̈́B?ܰB$Sk[}2A\A}fް^cUE! g#i'dE(m=VF?&w{?ړQq-Ou_g皠5:{x҈xU_mqJ\p[h}OUUׇLuyեYWBR6ɿwUfFuZU6SE,{/Je!&Jvn PF ;h,Nm$tARFgD2 ?>.ʰZ*4l63S)@D!mcelV- }SDAomy[:$Hx)6akfiB7FO>%aNO>ii*ЦXP "̽|xgΛCv [xS퐃z~jqV$^v[k>p$҂( oFXتB"i.^,Bp<>@qmy-Ӻ1Ol1*VB5@ 68kN}bD6U,6.V,odķڶ'}} JSf[wUWP ll!ą#QnZD(eY+5N<͒ q,LA'V:#·OJΏ~:Vʹg<8K(),ެ_Q?6j=W}ev}oJ3ƙ9@\n c_k\Zo<՚LDIuwŌ5hT*LTE*FT9-fe=c'6K'u&> sJ|0_BOwNhFEҙEEa _M%MJ D,B 9ѧ/wK ہ=嫔nlH1#nf|'>r;?_ =)n֮F؊ .隱mȚ:]iXbƗC,jῢ6!F`n3(Jz S!abO*"%层Wz9f"KXY ;w_& yfU tU}Yxt;x|rI˳p?~,(>Mt*:Gzq=C,=͕ "@e{+"=3qf2 6`|pѿ'PU؝Y ԵbOj(ŵ 7qbh#;|&Pncub{c r2v&'ql(9:P)mS-y686#9P; f f@yGCS3o-"AЄzU9ڨJ}g5E(=la^&}iEhtϠy 'ˑ2 GZ<f ' N2VCTTn/ 53{61ϫΔ+jל~=r;!mWt}#/* mF^#| )kzE3'`Pee0v$&԰C}C !+rr%vf^Oחy^* fi^wS?Թ;줍^Mq{Cq83+d[{>pYV=z(ӣH]܏_L?}zvi5F`$[tZ0%26,XFyz}=j9 ( T 9E\C#o489=8 ;1%{rd+3%1\^%鸮iog֊-fVX5uTW&W"ܯsk /X/L]ꡕH8Vk6g(q)/E5YB.%hsÛޗzH *aȆIgn5K3ď9;[\qs'  Wd'S53kk˓t0Lݞq>b|RBJHύ^ML?)K'j0oT8 Q坋];T,I70uG/&xTRKmhdk9Q2]MGS%v9.ǣW2xnα16_U^s;,Eox2e: _ROV}rMoJ NJj[5 TJMމLFyb#H3A5|P=c>[HBf&dN~^oQt._hRSl1ى_y((I, [kk>'7SaC. CZf.}%wE@YY[r#!^='>Qmu xpo+az%? 5>$hoW:n[[p͟} L SBq<³ik~8\| _#n[ۇ$] Q'1m=7wI ҃U܎=^⦣ڞpAwJ!=|- B:=eS)_Jp*pQSbY)w%&"i.-*վ4ϤyшwQ=<$:=q5#\خBǚbL +F[~\fb*y{j7+h4V*}#P-WpvB0ŢR@z ˭5)!̹#3AHϷm&0$y0f1x |q Pƶ$ti3l1af nξ.JSԳL rp0;;/ig ~٧ȅ a_W}9iA6GnR|ĐQv1J#m@w{Ɏm=w=-bY@h7-5W`V(춛|P3>֚ulK"B_8KNrk* +T vaJZ'}/ׇ6 #)snc6bEl\tngь*d/t*QdQu:DGXB;y}6{"ـB`rE78'plHY 'ӄ=-X$_BzP ;?c,#أQOQ!E]91Lym wmd3Ow&N sϊ2BX_J_ AjO,qA\l(!UQ?mсqxwxO-X^v ǾyNAl՝ɶQ۩zmH@V8 i?GV"ٻ/-2/nu0@xrĜf k(䓢e|@rإ6G9\xmm#-fo>m=Wg1^u+)rȊabvjLֵmP%&[eDh e#c0漰-iLm0y "X9>&>spLԤ&] te~CmsTׇٴ(A5)'$ .{p|C=e_XaEkٗks )U:H¶ Q]eN57_sy4NE-OŹ}ڵ`3!uha» 㵚 QS 6b0ܔz)ނ_QnɮOKV'׀pRoH9Ҍ&'ip̷py3-c"cԦRwbo.7UƊ=)1B$(vQ 6y[D/DaA$JyC٘Zٜ.^f&s55ɽl {EWP2Kn}UHI7ScФJ1mҝk&E G3!U"BNv%JH{[Xij890jR}S`2o=tqϫP!O8/'CQ L7]IN#_~[ŽÞ3}k !1 UǴᳯ>>>LH8i?7>}}{\Y*&`uGjڈڜd\ctli@T;Nޱp!ƶW,dTzvTqLglPQUl#iVQg|m5>_mvpط۹DR=c׉Þ|{ӀL: [~XG6kGjl}tϮ`%w/TՐ5{*tf TΝӃ]' -@<԰P)%/N~@pGTxҸXs? (#I@uBisLK=iX׊l}^/}2Ko RRчfD]IJeHDޟFEMzLE;GkR Y""Uۡ rH?!XxS.;ܐ3SCKtf¸ ()^ꔖ]3-[`OYlaGD=P4}V({ ˔%LYO !@ə? K~mx jֳQ6`oM 9.ŵ4EXZ-_À̬~a¤#9`݋M|}`M=fFсhڳNKvS k&8n夙&]Ix`o윴mZ7t,j"р|gJ?ȶp-fO VMiRZÖ譔e' zBݖ]8tO> J v|,04^CO;].d Q3#q=_h4?K+d.|+u-e>j[&rIqҘDA7i&; {3IYc\Z1و 4FG7OĔYWP ? zΓ})i:1Wkmj6"ZjǃkYW卉I:d%&jI2>0y , AzT?mP!!jL:>:foo =M d˄SP{0 ~B qHm?|ض J23#] ))T9gU]|7z8ӎߟD8GaaKvga4 &b#R+Qauu^,’'?6=/#h!c?JmM4&nv/rOai{ %/g ed3EPopcw2k`r8sc:.;|}ZKBL< =ԼDRAC۹'U&NQiBYZOn~urP^TٚyFcB(a٢9VqUb#_=2k;\ڟ62m$ -)Ǫ|6 #ÛO/WIIǭ gn;0r(įNˀp@vP 5-Tealp4>x۱mL߄`j!3R-^8|ig4$ۈ@%44q-&{Y#mD`vT%'=ȥ/4>+g}z1#b|pˏI-Շ}& bZ a k D܃f$₫yBE JWS8ç\5 *3AWxd[{&h._ONziq/Jz#g%q@:f;j h8:Q!\3' =%+@M}`I=fkp3:mɾِ]wU$:~iDz+.Q24sAn铉-uiL/y4RNtRxAx$r@Iȏ瀒id!I-2l>BaEq0+%*9x#{z"|!C[p|5ēr5;"Z+{gktȘ&j]X-6ۺ.x'mW'4DfLQa+ p{{1M>SW!kE.}ro{OE2TmsL$Z)b!d}>=ʥ(2?T%`ohPPNG@\•OP+i,[ 4GBk&l#]%Yxr_djg "V޺?)u4fi.MES۲u5a l1(EnT@ 5`"2O"R{ @TQQnp>uYT)ʽ;23+j.YK:91pZ=2Ʈ5Щ .,kaRgc#Ym|{OX6qG+{]$l7,46O7|0?lTds`Gw+TtMIHXjz^W穐[2\l)&;`ea{hވy ~f:ZDZ6 MT.dmPAH yֻA=J8a'τ_h>2-=2g"$YY?޾O?C|;fm~aװ%S./+!S1pj?`b|چ^3ޞ$Bh'm9(9߷76ؠ^^IJlKZ9:Ft7Gso$*Usd#E|XW4[3v ;f- אFNfd!^QmTڛl~ە uu t a nv W`dhG8:b6/hrgM4@H[64>ZHƾ*2$4c1L ;4O˜hc"J|}v2 l?2w)q6 B(ip_'ΆIxᖢsX[I ,Lt$Xg~]4v:;=l$,6n(-kۨw{vjf>[ sx똑j1%F+~<ݰv7QҚGyt;JXG2Bf0?5mk˽2_Հ8LfZA_%&s$ذ6c͘7tӥX8GyЁ05nח`BaVg $s\}zm5yXz\%#(zbhI?!ou9#Hk0xO@= %OӒ'=xa|8~7+ M9.Ks&)ӕtۮrX3k ;,4f4nG{:=;(۞D&>5]EEd넗y.r`85Žb,e D IgJ%@9.v,Qػ4)#K92.Z3 +'HO,kCYbN٣Q3lx#Rď |:*QH$sߨvsUwNt{2|d;SL I y o;~:pE$h7? [iL$|~.H#A#,\AmH΄9]jZNxw$=d>Z|wV s Cu8\*:G9I"V-ß.T-y29 9ݐ&_--Zҡ܆b_ 3R WZ` 9IWMFU{LI"Yϔvά6WQ yVr~tj&poN:M>r M>~u KGf$πÎC#Qe2@IQUbh)nً鋫$Щ(́F3[L7'Y{ġIOA{%{&eH4|Qk'R5 eH:|A)yHix6Uq.j߱fojN7&b1EfXoKlE:NP y]o/ !SǙ >DMAo:Lt\ uvZgr6i xEBT&qt:*L%1JjS\#ݶ5u=O3||:Q7-2 ! 7 ӤƦ'sSmDD&U;GWJ:L\{"u]送Dz(zz\N#6kuI[K-H+Rsn*vwY>coLZ'3"[f:$4/{>VvzDdx8YM$ⳤ36ָ5/b , BVH,7fڰO-"N" 0L2~S_O 8Hn',$٘:mm.0=2R&`53TĤC"KJؿ7O%KJtRfbBNpC`-߯>҆/#K%bFf܅05/DYI|dbiX_ VGyS-uBpfW蹞ӞJdkfcLzlñ SS74w=eɔ_䳄!ě=uћ (Dn?}4\|*OA5aLPiv{aH=7%~D-φu*Ij ՛ʰͻu;':Zf$NpΤ/n]6h,O3i!FƋ**:6OF6u4;n1ālR%\`+)?ۤ2 [\O*oiMlab -CbhVP*4]X_1 ׾qr οR#w#X`׶R]f~)ihJ,AqFQͥ;KFTʱbySř$#@K72bFlb38г.v!`ﱳM˟OOYM_V{-5<>ϯ#Ok.:wbjs}XMC@YoT 󆋊 V՜!+4$i|븖$I'CIGl-eן"^u6 j6b6P /hbȆ 9lY Ci2"?n .== J@s^~Qzd=Rx<Eo7ŵbɠ%Rc"TfyU4N| X[ȶײx梭 Vz׻uۡY qvhi.|z@tR4PNYgyh]yځeB¦y fU8D3вk6 L{9q"'1 !S6Bf| B#d,v[n|9ֵض{][|mj̋%U_ؔjԵ9(;sE;D|nGA&nLr Ű S1V5 WH)"m}GN s=wc*Ns+jtXmPEY0ĥF&"ʥvY, n \CjܾsY\bJ#3yOM]]w75w;i+hxc]`vfU;מsPlz^88LxsY.S=xm7/Pݟ2FwOeJ8{4ܨG]lLIZeXj3^m{آIif=M?vV ,@,Gk|*e]>0`I D m9GvԲ!:zdUk໽NiKd@ד/޻@>kh]/1W)BNk닳Uvw< 8xi-4(&+;Dl0g .ѢMdiPN33!T"0S ߾A4 hFe6~ R6N+WA܉]v؝ (2V{ g|ިPz#WDܑ =mD -d'{OkajS%R`O!Q &ᄺB݄@ =g>>xM WUK{c8)QK:fGp6`h7s LIAVYė`Klc^ Ze;X*+((!\\R3;׮_KnOZ.d e{{yǜ=;Q0 19r# +m |\kΧ T/i<2ϤI =jdtvhU'jD_Q鷶x\+(yqұH:6fx. :Y+[@hC= 7ߵx:^.S:6f E0ڱ%LR bu$$%~;>GuY5ˈ&"f)]x!zQ"X|1.:9)!w83]]k;~ݫr~!PJ Y';)mNRY^=_+Pbh(N"sG3laR"G䆱;bsvg`c}mtN-2!%+"@daRBO_{)A*+ʓ\m̔VKn@&8*QO * WoaT.d[ h-}[=cT ?O\jc &XƃOC~[}Y9%'SD4SJЍaV V5rfiMmx#'Nm(.0L=?$![].ax8D u{|ssh2&TحyJe\m D6x-tiq5+:o]י8R%V/D6 PH(>yo  DY]V~w IߴpP|%fĀ͈-M9ods'yAg!1W`D p y)+ XD?JLpX_OS>ᑩ;<0+2A`47 Dvk8 R K!* H[EE-}93.2]"B%Ђ1y TM0·)]z+}Xj6їpq sAϪ8(@p3s !vހIZGFe4lqbz~-n䂉az; rXDDXx-d4܇-l f|%Gj]$3fLsn "=#7WG~ |^-{,HC1储:\ڎ^Q*;s7}qI D$Kt]Q*r$[+:H]E"?Rm@/˹w*2py Im44$G(m)aP5Nvk3,?an- U3:s-.MA(qK<g;|ZF/ NJHD5jQ *n8TpƦO[5&.mmb:mM[L7gKݮlA3ΈNе\M<6eq5K'8ČG TI)П =s8u3GLɤ"ppl}'|e K5!ڑ8ĊK_⩏K$(qM5Ar!v =-o!VZeeల]oŧvQfY\M8l@׺9͎3CXP[ k:S@kĩ̈́G/6irC3uJ)rW1ߋ&qníM Oʑ~T\Il%,Nt@X*t1~{ 5!Qns<ۓ"L,  n-̤ΨL#6ٙVu?[Z ]EhX'ɫ R 89bm\-)Z<e]b7%#G(n5NzNf6 !%tJ67HdYm;GOR?&JoQia8^o@(Q%;D i0X`)GDBxu:9 ;g۪1|y;Q3ȐR'ǭWpճ(p$ћm,ɝN[ȃcGFJH71Oby3:ǚޏdڑjQ-#MFA_o 7/諂c Jgnp9;˥aר?}3~¦?b[x̿k?zקx_S?]T|'AS2OZn)=qSb*~; 6ŬɶA۲@ښv1aCqpvk1 Cn21iRx DmwER '["/l,u3PuzTEѹP8;^P1h {B"+je iTܙOp KZx>_8/-9ڍ2F`oz+[;T]`׉`g>^\C/!jlm:(`Xq&LGh4BUjkNb`Ճ9 |܃onHA\<ߒ[{Ȭ|ە X㷆QrA9$9Nu]rPV0bL%+%b]ճѲc[De, uy0(yNt&h R*/;QI2ң$:qbmTfkԨyT@;~"jTpyҨPjTNhu%G٪ 2|<̊"~$bքrKH>l勌 ͋tB݃)ye+USg|n)dy ghe#GHlδa;9cDʀ&0 tIm *u] >F0ѤHJ`$ON5t.b&#dg0S.%rx]~ھ-4ӫw d8ȑ0H,&o !fĆ 9yY+*5ERsmnSjװ~qpub%иwځ\Z>3Q*+T c}{O_Q2mVãݘP%`vq2ouy4$%=NUI܍Ym>SN2RL;Z#yLv1xF=+0*>ޖX#:CHc)rwtO¬Ԯxkޣ\b4c*uck:Z]cY k;ˡwL?06]@æ e}$=Pciw [QZeи.o&Yjy vx>|vx&vHMI'$GL6*Ҳ:^_I'+~bSVJkvk|/`9L k$ hs K}ZjusB Ar|B8l<? <_[cyB3خ1٦L0 Of /~Czc104ӊ2FXF^z積) AbނQhPWD&댚a'߿ J CӘj<Kֆܳ$'F+%NpZѰy#GqӻlCEAHVC7R\pl8,0f(=(|4n 29 '5Dž[ʕ.Mn&ٲ FNC.VPlǟo)G~H΂*3W/Ɋ訫%9@ʼ,tORGYQ3ԝ =7YU%#qɥ;90IMJspܮZ-q.ɹ"- o!x2ltCyU+#8.-ޢkgѼtcUiMiJV!EZyj7[9X`nC {f.^& X$hC%;w#ԃXnhxW 7C%8Q*hq)I-tՈ.UF+'q9Fm00Gx;[MUDƳnN+Z}E7SA18?d<^zL {0%gl nRe W5_< T2p5:sodNn+T#0,Jfu'3x&_kOx '@u֯3^TcH|$`y"x,@e_po[7$vcYY}cd:TCkwv&ia6E/kG~h L>>AA}>@l8ߪ#kt 'gh9'E"Aju7>d㴨wDrE=cQÀTzx1:ԇJq;$/O0VpZ:Q15 (3G5-lY$pTSqyʴ céTb 4sqPV*lzxnYRuϞ^@:5ZH#ްBͤxOS%'5CBIّ"imI=F[\8"ņzoKhgS3EtI(){޲0` nߍ5xaspvUwK4Fݩ5˟KTk;"FC?dIښ2|XCK}/ԏ[Id(t^אG>"#2A˳r$bBIP!{WrT6{h`v!A\"6w| J($װ__Ǐ[:z]w/yR=+a~e$8:hw0|RtAnm~x%(ى^]j0-1ىYһӼD{]ָQ\kn;%苳뇣WV`"XJOe ( DAIë0Sv/vs7JW|>Mߎ[̖6x_ , Hi*+l=\:ʧB1n>=^<wWO@ߎک.]j+2D-"D-9+&tJ\aLyc'U YmQʼnNwIӂ~g 춴g " =^K`NSUʃ4 FF%=,5Tp gNU27(.,XJСaԌ>8|l6~'Hgeң=@JPsO\Rp"Çt YLV j?,&HUsSST Y*.U3F(ObCt * gcOSMa{D^N^e|:63T=ϒNJGiq XJ:m"}r(<\.[ݵB:mpF=S nq\U5MLtn1yPt<ٟjȃR8(-Ӣ#df-f3RBG?)= 751ceb¨d'S6,4$CQb{;B?VfotV$P &]$(4}[ ;mGJ&`Jl?xz |&ʡ46_K 5}[ȏ>Fc6(g"VuSj , kp3H?A|x+_կި,-8_Dz:p.=M1bc]VOH"iZL^w[p[]+4az!6t:8\ݺ@bzqs "F=Ű@=n[XzXm}t:L=c%p&4`aw6m!t"~8/92#lb=Ok]RXvsxE?~n7 ,z~_ܧ^ں;Ox&ޙ.8d8*bRlmAӾ1 ꔆ0!+mVQMJT1ާ#"NFg l"):]s ϏFODiHV Ld UoE'YOm"b[KBhS;JJODvfys3=YT[s$ݏƛ^g歬^*ַwv{#BԾZZ#0M81`xlD#GLjחToҤw}f:VU1_ &ONZ2'Uݜ#4kE.ORd}B8$;MaI2&v8BOw%lcĩeK\)kC' Q❐Ca 5)qbAG dM `OqH=5LpDAiRk#2q(al$Z\".#i%-^/߉knc:,HF5dwї.PW=˯.6z![Ǻ٤P9׸٦5R&u/Τ\#/d"6ܶZo.e"@qX&hUcV]a7rumg@1kR9dAټYaV"(vU4-"Ι3Vh@9__m#~zCtօhO#sɁL+/R#Gz n eϮf{,>cBlOTJp_ͱhHvz>qH@}vu{ZgՒգbj3d>)WV&Nsrưr XlW_ff'!(bv1lI a\mm|K(,AsoP넍3;& KM8}s/J3=%qzEQb7ڋTE <ʚWi:H@DRhR*sc# U)y/8GWl+ N.> >jaTqX&(/j,agP6B`0Om KdGFN)<;dT m5^g>EFbl&eZ H/ !'PIm9w V 9{ {b?i%W-X,bVkwDLrnytEd0$2z&'Cmvcib5v^)*U!D NB KrFT?0"5pKHa}y9LEpjp"S> 4a>|7M|x7|,sOՋ)bbL5z ̸("*JjJоemlK 6vqD`dL,B+ lm.S H ,#(?N|Wi{ШS y84"T"(Ψ_v$[$E<"㊒+ʕ>7˽]u7Si{f(pm%JB_i漄Tn pFw*1oM|]ɦ;墷eJ[Y]xX3p" ۵ƴHN܇s^yyl|jQLi/EmytmA%QBaHj֜Icf\h(F8yR H#_ślĂL7A f5qOS!r=yk+UxJ!t)pBz"*gNp~l"~݉aJ}l§ ߭\aTMo`xzU/FR=BNmĪ\[TeOg5>aTfNE#[ 'gE<$/MpRڷ㎏vnk?߬Tj_iaA2Ljˣ5ڛh L+-PĵΦB%#c901.T;U {VLKa9.8&د)\Ztk@i(,p6jf˸ Tiwzc2SVH{J{j8lEu#d&da[*?SpޯWRI)Q+%3i0(7 a0(zJw"$jn)*䨪rDYuH9ޥIrdA. &he(0Adu<[]#6EˡYfj5:!y# oϔv׉,oЎTF2 #D 9[Pe3٘ ,! gi,bZ d؟l)-Pw vFS~ ڃ3f ۶86^<9kOH2u|<]C|Ϊ>;rgK7[LpMKɁ ;mŢh6^͚o@=aV~ّ)8n>G1: }{n {>xbMC;L~jMDӽ05{8zϧND_|P lE).c=q>`"͵QG|By T 5^};S ?kk~hpu~ˀkZ55m8]+*92,ʤĂ*mEi #jo-a6&T+:eKXd,qF{$GΞW q\X8f yeE(eD91!׻Z1`n9MT0R3?r?a]$P-vJNޛ.ddcfNB=h5OT҄|:H0ڣhyVzSdF݆ױ6Ϝ^(ick<}[dj卾5oo"i a;aD'?3Cߌdbc3I49iV=/(2!σp09gZǣe4YHNah壀Adx/b@F4{{kfh[@u.eyEؙ5g{oh9Cesf&u^Ta~~hۿ!cVQ6$fMdx\<rלDfalg B>ҁf0P{mf#dx?_oǃ/H"wq0Bxo*SqG_/ʸ"}o<_?/?%X yy?^8j~?n_P(\Zį\Z [^{WJ3/\_n~2"VI&m?6Dw: W+駗rLPRP 95elGTZp87ϣHeAGxp)26%1 PI2*'C! A>5!}2y<ʢk1Y MomJVм%SnTGȴg7B c|]?-?Dq$}|#?/׿??C$ſOx׬b}+{0C_OSkek slkIVazc$tUES`F؀)ED9wo#H$ҳ!ɴV{sjC{фಛߵifpEE>gghse7gXkw1WA$s錴Ƿ`gL{!Znx d߫D |_fdonVk^::"Y%u*t;?,v :<ݻN"7tE<_:_Oy s"[aP/bB&vpCvV*N5g9~!'QY|zLsXo2#c\6s3>JpGDuzu)[(zI^n.a$P,Oȑ^K1dW%x:?6 Jlmdἦ =ֺ/Vbk'&6S+ؤ<"#W5QL~`[7"'µSb`$mfrW4)LG vjf`.o槝?w DN7/O0V,Aܲ`nE5[lH)iQvG:03WʻL0~NdǬ>oV,2@޽6ѻc3(k)`SgyD<ֱe˦UCe#}YKǁ!1y':|gdC9x}+-,TBŴ E%_7OOw[$mWENxRE`)s5꼒m}th ;v&:h & t=ۀE-Rtx ꣞%6,2_)(%Or&=qǦd¢,!< &c^jHzlf9B0S!(Ү а?@K)ٰ Hr="oquUqV[ HC[ (w@"؅dQ`p/2:#L%;U`H cNΦMa;w|xEUq]:o h=Fݮ3(f(#M+x 8vLќZ6E;[eBd;g/]hq$++ăWjq S.(sPiaΣv'(|`b!+71!1.+X"N)<b($Rx;)h3EќVK3p*BiSփ7럧\$כ]Mj3T&< t, 4sNJӂO(- ߥH*6P48fd,:u5= VK 堩59*!]af[=Ck;6ƱGF*Lq<% DVU|\Ⱥ[Oˊ{O?O_J}1k}8Scc1et؞>m^)uMsvA݅wek:MuBvں;d9^r#2q.M#Jęzf+Um(Țڿeۺ\vWu=ґ2 F `W{ ,.qG!I"Z‚8W{"6t屩h p:ݩv zz_@ՃcKV)j%G3Jsx搨jc,ϞBN9hԭ) clq]iUS)g =R&iyX$*y[ AEMy,%r5\28B!b5H!%UИxm[jBv%mM!fh8;¯&X((.Er^>jY(+J6B.co%9% >Kl|R1aIVe2SIұxךNB 'iJ5[pT-p?$uIտKt-"I6F|8B핞x Ё /h/鷏)< ߙIc_d99Oo(n'Rדi τL Ss ҌȤj_R[GDW v& 36YƯR)o8Fk|6t G"O. &5p#>.c=fF8O*mWt ȱ۷XTyG Ok4dq2DڦG+ε15 L%JC)}C)ӛoq:TЪǐK^F#3n%MJN) #=(?*[!883 OYWݡaei{g;YU~!OwCa5|оۦns ]ug^we ў}+|o^1 >_sxm>s#Evf7pL:zWj⻇KDLJaONPNE˱|%~U[$l#e8C·00[K-֩&c r/Ȑ)憰¨{u:Zjb=MpDbӔ<5 {s񐌼))EFՕ\^g[y֊rk&Ԯ0j /}\&_@0&S`VV8U=J?4 񨎳˸V~c(<+rS'QjC~Ow6~imYLY=J؀9[K6 ;VB5)z&Z閬Ѯc&FIfɣ;v9hP+ Լ"Q,)"r"b\D}/SJ+kˤZ֫ac;ӳ$J0`[36%WeGH@"r$o~Zu]",SJʮ.iۆI^]h=o}E2Uw4Ák=0ms\sC29JfZy=ӁԵf4y0c*X [.G 78 HPN fIx-EštjF}Ws,qN^t엻)daR.~_#ڣ D %pSe+Yζls=ά.*yi?mmT[tP'tAm=O=xf nܙh&ApHC-bbf6<)&Cdv'̀8 Vt@9vǃ|{1{83NnPTgKyݬ*.9wI۪tl48װdͺBv:G$u~3wr=hphAM[giۡcarՏ-.v-p4f 9eUxt3x-0GƵٿ1r8LT]~P|*\k~&q*zX, Hz<b.i?6͆PXE|*T:n-4X06^\e[0wƇc-$B ֨snz̨y@d=9+.&UOX(uyi @nrl|蟡t#hmF䦮%z0Y{AIgX:( V!p`)AQP!zlzG'' Z9EKA Sv⓪9I1Y Nk_K6,^Vf{ѵን8rNvY)XA7.#"fmܝ$3ǔq(-:nUm o2oJ99:`)+1D=#i"WDT=NZWzN865dvƒ 7@(:)39f,vaIR A[n 3WY#&x:k̙—cD2| 豁Lb2CҚ  Q=Ϟ\atpFj%X[Vz3&uD}+;L%r$w> GWPs3#\ 原צ4ӈ`Bk{^'5uAfP8.m5c6`)7mDZjAOlVOf畉Y 14FA5tL'=I0;$ wxi{F.QApo5:=NW5uGTzմW}Qb4R׌=M;rPDˈ .L VZ;KchY"$W" j%aN!UE mq@(#>uӟNuGDoOJj2X+cP[I!6+)*C:ae8UhbԐb'm2ec^<\hj`GN4+ y -+7RE ~2|hDԶJZt^ߍCXOK11s_p3 کQtl1Ll-*C0[ad̸& )k`3d[ɋtW| T;%iql3|M[$| 9ӣu Ҝb4zVmA~}\8A2jzdSz.`B>٩7W#ʽl6tnI`}v(kaSN/t,ìmPBT)EIw~(cDcN,xt`}b8r"͑2G_;Fm򙹟tۈ8x&UH3lmTYĬemOk98apoAGS,b8Ǖc5 $϶!:4i+xm6CpPS'mx:N Xv5w7z,jzV82- 4Q-Ti\:B&w&{" `J9@t@ˈ3 i46ПpfĨ8֣p".+QVࠄ֘x79ȹ=_0/ҁbk'cr e8r֚ GQcz1 *AKS#vO- 3j&8U!wҩЍFȢ<;Ӯ ޺qR~a ) OjTÆx}f{̈JD_8di1t T ]fxL솉 ޢ bW4aV,'Jc5n;n!K{ns?Ld?sqdlu$(nM|jj:?n|mf]aۉE7wok>,v@U%aKp$_vMaK?N[H-p39*fxZqQA[l&4n4fbTcs ' 5\pm+Z2S=|lo[RYs=5*퀇v _rANWG|psdìlix#8UNJ\fathbcDtseyJj\}KK6aCjfIBR2͒Nf^z8|QyiT@r'ii4-Oa9'چѨw,Inx8\vUMFoDڈI#x; 3Fm~1 hʼn|(p]p~[h/!ezPjo.˺*4TNZTe<7.pۂm[^YQ67ϱ4RޓĿ c}kh#q^nf=vgXG5iSLT!.;]%SMtdfQ{P^޺l{^uE\#k54G̕Ê7yy ZLd$KxC[-k6W|-&9W'=f.@gt蚌N$ى:ʜ'rr#7gRoJ }n_L"VdhXLdY6"A a(&ss.O P Fqv=BNr"s)vc|N[o2#kЎvY RBB5cIƧH}%9~,udz %f28Fӯ P7[$o3={>/+,^aA5eC, h'ǧ+hgusfIy8ޝȡ$%\ױƏ| Y6ӑ9oPh;AH;6?#ʫUQ<2<%qq("àJ[S OB&(촃Y8&'}&Px_15WRzL qebYd(:Ld(68W[N-5(@4\ hw 3VfD9P ]=v4NXNvFq]uLt8F>ѯHΥdWƔX5xFֺDH$c%?q!ݚd4[&)O|eu9d|ͥ0.w͸X&#bi`8y|=~ݷK?챪k| fZ7x]ܺe^Y$L?~wWŊ1O7'\j9-Y8^(" Ms3ka7N*39X fWaNPsR}E2ފ3d:CUd%0 ]`>DΖMEւ{LN^:C+N],/2QQgd>i!sM6{SgR\Dl{^<=*\=|նm ҧ9m3M[5.Г&c:K[x-kM#Ʉ("rd߰Dx_R@CbY*\MŅBYmFuͪ@>,;gB72ȊFf>r,Ѓ%ZAt%K63ɠ=k50òph},kۙ*n+}Z^-6HO8-OГ\. AuO Ũ`>}mgE-%U.Xzٯ͝ӳZE`$ToHb$Lc٣_$H,Tꜜ=(L톹s}3S1o} [(d~f,`-߇l<WTᅿ~w{, #CX77M CL Pf\oJ+^"ORaLW,ӒOCD޸!K^Fb;fk bsk5 Z!D%٣Ƚ{=; KA]cեɶФ,{}'SGuw(3t`/KfHiBuXwQ >l绝7K\p?P&|B{1O^Kd-ǭ cs̕myU9 ) 5b>4x<ŋ.]^/~?\_O䩌Pߛ*ֽa/I]C"EnY|frkZзg",n(3!T/hd& ]$f3(y9~ߤ5>~k6e/:럱uCaݐ'[qzp #W>45!ண*)Ukrn[v4q,U<7W(B_ekdjtrP]~[I\g?o>ZT.-vCuf[-V|í&IkdZ.BYCa>|dg*kDj[.*hG:9 9%ͽ4vAsFj,;K{=d`=|S'w^Ԓ.K}a<~~]qFKw(ka?IeQl*3(پ9߯/]"J"Ή*O3QϘЈ&١v^Yז,puS)S#Sw=>GЧ6 ~e=tzk)F> q5;_jJ/t*!>Z^Tp#-E+u] fp*7c5iF$w^-Z-?݆2%dwS\U!E{v25[^Qft}5]s}|LCf) j1NOnWi>yDRIjsݡ7֪Nq|% sãwL!c}JmH[wu,pD0W#f5A7O^gĜ« @}xt5y> f]akd;cm-+xZ0LOWr&q! Vu7=9˧T;tGxWgxxN#$Š 4B r<}mWNfۮtfne"5U5nO/O#͈/o~^c~}_eg!Bd7xZvaaG-'Pޖ6h)4*2 'D=Ș -|@7\6#H QW!4KrbGk)Y=2IGfh2>?&MJ2f* :r$jLL$:wV~O_]*Yk`rp#1^~vLQ-oAm_c3>_= \T/W ¼2unvɟL"߼ljR?q6sDX1cT9sLNsjZ"b=фćXB~0X_,r f/wR+ ,yJa2׶faV-U8+MN$7Ul {\"F㷾l`{OVv}dr(,,v0긅.JtZd0j _3pݱ,fv9GܻHeWvb^H̱"D)YFeAz9ZXdY&e--\^{9D9CYc*} r 6k-"$t\DI_qOvs6,C|E%u+%^ms:L8K0#JetL.ngIlP 1xW8E=~w ,09δ8^XH2'Fs`UƢOJEdȔ\<aC/IolFO6 GcUg:Z< E}OJ-cU16W63ɲ۲P9{6!uXv_1$Վw &ZrڳJ"I21P=7""AL.ջwFi`R|by>zdz}?[W>e9.-J5.\&? i$޳b JϮV*sF@4\w1,qyv+ .|̌YWcUk{;~?ߎ7\#/9cz'C]whi9w+GjΜs"\s8ITSh탦PWjK* JNV⮛w+iS/SqZ=aպDw໭׸Uetˬ#CVƼBlu%mn~ĸdxouUzG|F:n<0bE{9&UB}aNEG%Mgګ g} p ۫y~X-y4WԤnґVJ5Fe"C;og7whiqѬ f>fˬ#+dϑ[ CyL՟|MĿf½N9)xb:ln{{er_Gk@L]"Tg!2U\VGeqdj4s@+:!)miµ뙤@y<+Յ4an҄wkKھU| g{x2[ȢBO0q|ז< Jv 1鿜woKu.Mi.̎& r0I  '4MIIX)zIT^ <vO֮d_8u!1#m8ˊ~ o^7Ypab鹚}k/ BU>!laekg1ȂRC-8$IlRSyM7#jIfp9>u<߽N}r+ t韹t2h;H| cء5@kh*~p,~§ݽ tОη3eGS.BO @g$Loaf9ݖ`LM aMUHwUy|(fe -"<&° J]uInk|sH.}V/:Ml%qI9/ѻta=-19-<7"wIQ&WkFA Ato\$,/- Ep$}%i,Sm/uZ RhfMSnDJCy*Mu(wV|6Lˇ_K}EYGK eYTiGSQmV$?kY6wUy:ܳJ->#u([`-ֿ񵏕+p[H˵14['Oy lP @znf|NEugDP2y|Xy?x漍ƥ[l/LW T3xv2CVGPYc0`AT>쉼R8Ju.N3,ᴨy+q\VX7Z5Ԩ4O|B>RaqtD|j%U=rj緘/<}S6|UZB2@3uX36KMaAtN,:q˄NJpS~ꛫ y i { 5!Y{Qh@k`a_I%r !/PR0\2#bQK;>jW[MO}lvb *tXe׀6em%p`nM+ˢéC&VKʲ &)o &;+&Fz>(e -Q;ygjflo>5j h0)sͪGH#͗0vve(>RʷܘԎhO l &~i,s"۳=zlL򒲵vȭ#-#)Cp$u*@(wnp WO*~'{R47+VC ?>jQs)e5>$QFqʸ]fǸ.BUQj!E􌚄F5LZph$= I<:q5pI9TpwIW`HagIYpSq: 50Q6ԟ>vUO5u7)6jcM>"=BCcoS,cdl'$㤢4UnGg!Uc0ϷV^^t|Y  aualq0HU65mAPFRO}&w)|/?9_??qg% ͙hM$$yH=θ!Ӕ/TIjLӁԭ<cU7Jq5b&NB^%*Ymu$hѐ*K@7[ (bem}Ԉ`w}X= vwҳ<]o*`YINR7ٹ?g@=aOZ9`6\06QL>RS}Bpfbj^4 ӵKo6=fBH:#8? uR?"^b Jѿ-)ߌuzusc 92[تn 5ʑ7́ۙDU]7LKby7?Y8ŧXΉt5}Io4NK2Jrn 0i@؊/({TV*ԣ fNSsvZ<؜EMylyR4qXȳj}nٓo?m|x7du≯'+I@+ UHx+YGTɓA^+cUp2*p+ʺz&[D/q@< C^[p,Fqv~ k0/i=l6wfYCr%5l`*ؐTwATM3*hӐFto]dHi3Wty|'ˆ}?~_>g+׿?_/{˓w?GT~?GϗﭣMߠ|{ Kٵs:0Ԕjե 3j@ vΪ[]GGh6#nM!7#[ Ց[d_ja<~T6u 8$8 ~ڠOG{ r%Qhte>4Y8~y$3G0-!ќ̀B뼺mM\Gpu]wESzN0_C Y9n2a¢iuazJ5WyaYm` ل!6 أV .T^"l2/LYmMP:aWDWX愜[jA~Obi[$T+CǪZ4l$/t$h7U0zWP tرj{.i-aiI*VS=3.F_XcU &ȰGGD?55=Zqoe|+1LXe\tbW#/d qJf;Tbӑm H۴H)Srt3#WB{[,p{a}t:lF!Ej1, 1˼@=J<ȩTZJ1C"8NiqN^Aϧ!́d1rGC%+1>&΄(9K~{F$#_&6m֔{V% !'ö`ye<.JOP%Cg[Dk]g{AYYlDBbVt%%yeۙl2>jߖ[F.9 U!kWqʸg DcKk[}f”Y^wvg9QBM[o)>K25|,MﵩOkvCžrTK(\x,";OT7̑:;@TBTJ0`,M =%l$I'VGO^-yy>e p#FíI[idhk94"O\ZUh+;KDj_tLƝS/Ʌﭫvw)d M,ޝMS1o"LҝԌ*S LlT(!:nJJ3o0&3xdR>ri5gAG\!Q8+dzkQ/w-[ Rz~?NL#{WqƏZ1dt LpCXD|bks "ڙR?E| eqWl#%e?9HIk7>jGH9tdS )~>%;L5 f7dO}([Ů| =A]o#S{%D17_Хuv1aUS&4Zo*_bi[_[e5uƩ]Ž;f֠d[{RVsq7GdD<{ kdSTb*2:Ul3lDZ Z B>c1M`3nLz mD<__no/5%]s$\ȴbdp:^kVpz1#&)T mftwwʉݽitԕ}!wU,^c Aay2H jӝ64-;;}J[<'4{"/=k/w o>sM}O_9=#n-+0#jinvY,\_Y3cءqHL|*l>v&ƪ=!M;f'bMS^Ӣpn,-(G@L~5Y@mrXUy+ =U}aU&sK=-UW"s5-9đi᜞xXXl@sX<cl-$S5m߅dz}Y}_G芿J8k|k=̐$1Wр]D^uJPÇcz-b6_f򧞴9%9hזlOXLMް|FܐsKWN|B{~C+JNuw~l^~l!h`?-SPz3k5pKx}$Y]3nӌ35m_ѠѪSnXTpHʬ OkR~a"?[ҕḊ -APFrota zB2q': 2+CNg}1q,jݣ?G?TS+֜'dPKC;9%zAs![-F7G F+uPB0"nÍZ5'QKJw^WFf ?[5BxT a;'JĮ7b{|Un9y݁ne빙o^5)뱈_&4qz,X}DğD:FAPݏlK8 XlvX8{Cj,CAEV"y̵qg |=̟Dz''O!QF{R2{Q4 '542W{tnf,mΈL4P^)ux> PjGZ-8)|@Qί~,g>gSZxF})tZjҷ۝ԇ$AlJ_n0T-A%JI}g ˸*JKf9NyFy|3WpEKxXy3G_2<\dY[7+]pr B89򑝎Uy\Jv^&"WBa7UXV:#4lF+"U_p+-KTp) icwt3>dV[Oz ~ym{ix*ё-$0tIgX}TD2,ͱ>>uV"ұɁjH_Haf}܃mk&p9SQk+$ qrFR1H[i mJ/}O!sኸh"8Y&Hj0fC.?Byu1Gj!֓t 8qq!ߤWӳ]վKj ? z`kT GH땋VeL|6}ŖZZ~+Ɠ4OmCz_9yCzu+bRa,Ӷ[ПIO5"?_ڇS`3۶F5rgԽ *ҔNVDF6vvdVnu[;)sAj"Z T0uCvhd?҈؀[JPc)xhdWh]38 Q@4"b:^D`Ygt,gjsg Qjr@B4|ùVΞ;5_\@ vN0Ms h\HQXE>Jh=~tbL#$G3@lة#c( o9m7Ǹ.qg@Uי&^2cB&k$ݚ-ǃm e@4ǣl_6?}faxLЛL2p&usX Ԭ)VuaՃLv13VȂ Կ=F5B涯yFYxӽ83'"\Jv8q>YɃn-v&Pr95bEՍJϚnuR X_#)FbȀ)!QffszDz";0tևկ,r:-k{6x#2 0L*:y >lEzi hknҏgW3 F.0̺[tdg ]n@fL-#ڇfM7tkxfy{5'?^|[aZ-Hy&] .g=dj1/BK}=r0b_#/_ـ SH:J7^i/ӈ{gN<㍧4S}ҟ Q==n tc-laӳΒ0=nrh]H 3 @3Nu[%nnpQ\O .QWX(ˀ=\ۋhvcC>>aok;e4!ex ֧e/&?k#Kp0{cEymC.{>tsL_π`F:ix^x^Wpg}nh,X1|˄5,1j_廾+K>pC(> )xIk-{__V sNj{qQ3PQ#WhӭUDvt5 n!aDӠy ,:c8A(ځdPJzi9#4xeӤIa1 ^(f$ܞiԢ1vE,6-o֫Ns IF wF|GY*E'b-͌i϶b[j1M6-YPuYE KqYCTN?ϖ|4Wi ~!7W;BN=~dEӐp2fK89Hp<Aڑw pSF hNGq΀*QmHTK١WXw=wS`zCTcBK F4h+KPHܝhbzY[58zB ?эtAhԈX񡛭n=B`_?*qfHFQay.zW'ilz9.RfX?ϼuk.^KXw7o[iS8Krpv Q*^tY)>tHa~#L1̄9ΦkTp Aw0+2;wf'َ$$>ҳmh}PI1RE^0+ xYSCJg#]ABğ9v_RC[|O`grC -Fa hˍm~->ƌQ%Ayc!#`lm5)L&GK,x~kdeC9>_A @$сօ)Ъt*\'Ŭc?=aB$( 2f7g, )b2B'ۖF*6piFۈTAFfj3㓃.#;}JapzW @3`}? s[vOL(RnH?ҴuB; =wX-?͝unrx[p ؔƫW>a>3JW׵ĖWMwQO;H'UCNhy0[p(6#JͤU8?#TM<y>=gѻ2U5;-?34ZH3kypUip֭'D|#{mVqyry4ؔNn ֍@?8.FD5p1 L_p SZe]n, =%vn(Y;ghAKx0ua^C٢+widఀjv:x}_H:0/z=-N~Og.N J'2\hͷXg*E13w~Ex]&`pC^@tV?͋#..G~? }A*Abc9YҾp ,$ރE9ͮ\mJMԷ/ԀTW!W!0Ի#">f,zv+11WȘ .4ex 5iV@H/p<@]=QL0|Gy:h53DZܞ/OV ݒh]ZHv N1x28M]ƊCOBx<=7'XP@OKuZ[3/,* @eph)aQ!:|H]`mY5˼KvL۾ETLnMpI =4&^wMuC>PNO7I#*C ,>m\X= @{5n)Oh#N` 7W\9{CvԻ>Hۤ;qEGSɸP-]U"vFc7u/W'N!V8ra|˦vKx 0NRNv?Zlj]Œ,g7K[*zb!E_ 7~Ė<EZKk (8;fcS=%1-wZ4gZ+BC. .@)W=urSakdm^h.qﶬGML3+5͇D{1i_& Aӭ5qqZNRMCJ.zljΆxR߫!VtZg_캳 ,9iȗצ' l#//@EY7gؿX/I>QCbvemTЏͧ`/[sj1Kd}pϷ,9xKT+0 }W{yJt8^c8(2c4`; 5=C-7N/ހb7t6 x*'z^pܴ3 qZQ~3~DpO_/۟0O~zN#@;=γ_dg]AČ}E Lqh8 "q6&{ÛqK&ݚ%yE/gyq<DE/ ͏[8M4T1[xHCܦFgǸAGc `?Wc4d܅m&ynFblBxkב C%Csq@"]7uoVZ;^z)*ԻsN {Ls hkv> VkSUbF7?D4i쾢L_q1"W ei,W㶲18X]k33w9Ѝ/6uNH(R42EVj>Y0KO/ :_Q[Ւ}")"[%@=g~b_^}xR`j{|OC%Ae1E Y8ɭ|Pv9jsf|lnRTHXz,kMqGtPǏ+ԕnM; Nl1\=NۼZtf@GHZa[fyp@[ΛvT@ӱavt[qٚ&@=X*+rz+NM#fɱ-@p4ʽ}8v]nZ$dW`{WIޣQayuku6Ɵ.h;qDHM]:eS~wx}F }얅>}:*?]oByRՀkX+E8{\S@u^z"|/Q \7e?`46gʈhq%ͲQ>u,f)0-5\zDVue&bbҮd`ԪY9 *wb*d'ش*&h ǘlVp;)34QPF-\ClՄhV~<_G#YAfA[?}vJYc&,T )'As/8c1L\f{^aZGa+UR2x'>6p n9ޛ^h<\ zx#: g5ʝRi%E(:7 SLKo<JC>dSA~~)'4l .$BڱiUz;}կ=f\=WqȐm5%:EAD4Yqxv<{OnjFZk#ZL,:Ϟ[znXA-aȤ3e=p8:B̷vfqu8AjѦnQ6B ǹģ-rɼ兘2]-S N85:1_ n0&2 7 xLv 6Vm^љ'׏Cp/jF}$"-S0`xn_[mvZN %n^跦;u?D4fzV kxxSô=d=}V?O6~#qg<\ϸ.h%cFonǘߘF~8McC8c8"kx=*h̯C (fSX=]0x)i^vtCIzX=a #f=wb`N0§y7r9? K/p8FLjjOE2 Znmh iTWZ- /VC,MAʖ՛'&jdI3nsNGsК3֤*pT )*^.qԥßc@^h)>I X;w#vz4gS`*YBl(p9 q%lf*^ #6L !+S;:[اs~u9S=egx6G NUkѝ.S!k8Vw݈qkh J} Y{(x9|E)kV~SWwߝ*jgO4/(\._vkoex[9Np3C2֮h<6.TNɜrK~t9܃}~r {xqUy0uq3~f\?R(]mWDQ!xOWĔY{Ͳ=iÆ>b)A{L_1tjyp~$3w>Er4;mfȣk~G~07Z|1 .67hD+77UPi~4'?"!-_rs=oY~htxU̝].dGF4сu/u F_޷6,וxJ(T؄`QUy UM%4gdyAV=#w-ɫ„DV8ĄF MP]Lv 0uWDGdFl"J[Ap3[Ua/(*f pMm`o=G,c{o5t#6"dj]3H+R]Jb5C'7F%p.{…* H ,,t:j{0ڊM-i!mq9P)row:,gW sOUF2Z$z>;Y|f(gۻJA*-+ilOlhΰgv-Wp"o쓛iH!mLy ̙ #N[7Q6O>%]ngə;a~ٸmm0ats"s6"ar}nx5Y? Kw>ϧi *[]#BȚ{x"8afHǹB"/|u 3W6ӣp z%uꇣ>9eyZ綏RHqT5tmAx[S{UQ6"Uk$8&Gs :w#ɦ#W=E>n0W `Q稭^ߗ֝KyUS`ԓmjxo¹8CIZuq~d3#M]Gѝ_^bY7z]\*Nױ]N7Sj3 f@(YV*}⥝qZ0:vA4x\ܛ جk,T+)2Չ'fdВXʈTiF us@ͲPX5~ѻ, :0a_U՟-'~?קGƓW+vGN}x(b0E]4|B'$x8QL?c=<7=2 be~J,+IDhu]~STTN@g.!SRB"k }dpyNzWo\|fѣIDEE3;^5x`Σ%ewa0/[?8 8 ["﷤_9Ͷ5(Z~Vts<pxEt[\ qKR43hi4  #A~Fcį! cZ ThRCҔI0sTV ^2UgHL'M2NellDұPڪ9vj[,d7z=Uclֆ-o=8wҸ!wc^Bgu*퉙KM Z>[r0)[l-vǺwfJx!Dp{b~Fy;Lg^?r-^3l-\@nA- !Z:SX4O ۱O_>徢O2uji*Y{;M)^vLxAtk猧8M>2{`Pҡ`eY2Ja?H{p\h퐀~ޮ.P(ZՒg2>bsߛơ.&HW_PDK(W[%(#҆YJ!dTA:GELq +=[}m~: ?m6 +`?4BD; /Q̥H 4ey:_X,." Q;=U,b;ҕF9<}wu (9fNpKT~3pD3Ӹʗ-Dx*5z&E{̔Vj2[=; [ #`y*"r!ٲEﺍB^{aR {tT5UKA ͫ9*JB݃~PRʱ15ӚpDuZVMKwk㺦n85>K .o,1HGfXYT_D~^ȢZ25p25o^[ +4LdL q|i{Cydי྄<],5˰#|LLe9EX/=pQBYd<J,w;b830}g^EcQ[&NkhO{Ydb ]_j6d۶ *ЃWN9;|dܽix2V԰rGX.dƁF 6@ !1iTjf\hč0hiP%eʦS4sӍtk[ ND {Nni_??lPPF<s2UcaFT %p_c6Q\XmWp0(a,ͥN/CQSQ0`ԛV:F}XJ׏Dԋ/s kmKqNb ߙK _(i}/Ce|kaJOޞf`K'@,n @>NYu}:|X*"׎Q%G4(7A8}ybWW㯘y`Aime=`rIPi8yX+5=_W_ήLF2?{$`.l?+ 5#/\!o;XNuZ~P]%۽insGpdV8٘@{K=%E1JP8bt ee{>5\]{zӕyrc!!,f|zNڪx6 FRYSLC *(Og1 2b:ݫ@qNb*$z'(XsY_C@|H,oS eNo/e~|8`nLHss4ۄ y5_F2SɬM~d{mOVaHJ!TQ"ez|K> u0^f|CXc,KJhXndjAs+Gbŧ2Q_Wq5,5Fu^˞[,fhWjaٜf=~Ujwk5&ښ;êpx4 9^?qxH>YߞԬIaGm@&$T]y =rm93朤RPygAͲ.#,~Q5.T'&j"L:M=XpPKYqwDTZ7 Z~nw y KX8-{7'"(\IxLəf"?!nwle F 3vvlN@PȂܡ0s ">SN$s݁^~:Mt*7}Ѭ$~9}I{6:"%N8Իͼ&P԰7tEQFhNj~hr˛6c/^bk[7mɵ|4?ds^h|(N:QJVCs\;Ӛe B"_7>7] 7EC:pj"bξ꿼`PK2?%b.§B-ᯟńTEC*~b]w{/ρapb0GIiz^=>i{?ٞ!^,[hWyFAU:1^Mx`ޟ=ܭA{l)i{ ZSi gZyNOÑ^sR7%H_y=;p'م ц81W0@/ѻPKAޕ8kHcR p< ZOh[؅&.Iٚ/0eHe=䓬_DžڌMEO~>ٿkOV[,/-a۱/! freվ[&nW'5 #Q Ϻw(}4=/Pj^. ۉʪGȖ^^gZk.=tZ5WjjMxNU":S}:"VWCHøapՀ pcLHЩk9΁+ AY>e`Xú6>Ÿ&%B&haV7׶$>EG7|(ʭH^e^[W @7)z㸱hħln1`ᘬpoWR_5Hqxyn a)KI+p.ϵ[UIiw[,S1l'E:<0=ђD-4OccQsTbotfu+jz ?ʠg5f%fZ# A]F#øOEu4m#(z^Ep9~Ya6JvpJfX3S-6UM p~\zՎ隞>ry]\ + ߱/1:ŋ0,'NB^&5|7mp12cMGTQ p`^l a!;v.3Lɟ &{ wl]iFL,6.GœN"'fd2<ذa#Z@O} 9 ytk(8&Ŵlij?BpN5a-+t7aS clC [/~k]}F2hP]N[h]-#"BaY81;r}~aS8K&Z4ӡ@faڌ9xetb"W'1EK8$w*>ܦ)A0b ;.ːZ;4jm,k74vK%g=pohi;ds6Fyû3Ԇi01MnO v"aX&Q~Q_bn׈4kKAaeEn ZBYpF <*(ZjKqp= 5AG9bRtk>SlvQ,FueG0ޖ9:&QBlJ411txSbZY~j][xk#Z$&bKw&b11Xێ`Fq4Yo#70Ag4\jv-{ȕvR7XO du-$cJ &oVJz~$ȪΔǛ~=G$fiZDY9QCWA}vD֝SoCi}a+ڮݍ؈0o%bdhw725q@Ƀ;qyģWf,QzVEwqFwϬ|ʫ sMS-ls0Hq-Nk&Hy+<_R $nog;A\ѾᢦۧH51}a*wn "|Dh68Z22QZY;=T%dW DG@~,bmfΏ`?u[VɵЦj:c[']dYݢẂ޼J.W~֠% DlmhLQ.WV=^c bk$N/W &dz`:'0]T,nF$XZ-ik5XX.7IC^p]TZt VU NP0VQ ]Zx!/DV[ Q,V[JT7k;~8%M)~gcnNxd]fw}YƼԨڧiN sIQsy~ -`h2ޑ@y\idZ{p]ܷ<i4L{GZ\ۍTw3Җ* ?G>㗀4ܔXT[g3~;s{6lq,rVyJ0QbdZZb=r !%zJmp-_뜎B*nǟ43u6 "fom=򫄟n{pJeCO6A3yV} GiSKkۚ~"ݙCH9P_C?_]%[325K:S2 d-`H'v+j׻ԆxYHڜ0N~QٙU^G"sGo^2ݘa(1uu5a9E'g'M,HU/@IQ %>O+ KN}#-i2J[?n#-Hl9T99[aNo! HSMǢȯ`QWᩃDj -d+4B}_aE&*ummq~%9:hks?l `DZki L$ /Ŷcm,G-)Gl.5o.og+ =j9 kJOcy|r6>F=0LZ0yCGy]/hZ }'0?DF^ۇ!NhH zg,fx$|ŊM9)]A5l:OsSWŻ;ͱ4+s:Md0G]?LBn Qiƚ>ܨ±$)#:df5,O^JBIGɊQ=Q->o <>07z,ϽTֵ5Qw4%rLWn9cyIxU ` Z GcPMnp.~>`ٓCNBj-|EWZ<#0uxZ< Fh&&DĆxoq5ECc$i )=/&ஆ!L mLL%-M!ΎgGhK{Ż9(#?]x=رBqfq,HUbMϬߥ)zٗqmg`TzܕjQo@LZ㹒ebNTqV\uС]Q6l@ Gjklq-^}jha9E^cRymh!s͐%)pߖ^ޚaH*_1h3N;S$E`#*b>`ѱqWc "bkۘu‘1Z%Vpی*7`P9ٖOkDRܰx#fvnv`(eKE#3"p^~5w~#ژCxOs 14"2m+ xsf|Ы [vX6EL GMŸQrH$>AJf:HǏta1ܳ)e0h..TRq*0CiϏVB91 5b/x` h]l/7ct9Gr-±iCRFrwxlw3/Eа}*O˶^C^>"j!XulW ,W-pW;0S =r#:lG\ʮ0ɽ3OE_˫^%Q?6 UOʱ檼8J09MĐ~y_?[[6Z}V w9@\f c_k3\ZeݞLL KUwkШTZA}kT-rrjg"yN,mx'apjaAs|GZ7 ӉS CaZ'AXd1 ,*jW{qi&_ $l"ǩkѧ.7ceL`/*G6c~>훧1]nǯZ0:xcB|?v[Sc݉b)qC_vAk SօK=٥*-0=8Kd|9¢9m)-@htjр6Ɲk]i-l] _ngJKO Kl^B%E kX;'4R34P\sFx1Mx80uj;ң~=Gpre]ilq ݪ$ze+&@^e{WDzVqfFKvTc=9g5 I8\7N$\? [*I. 2 P@@̩OQ/q!5Fb+ݓv|%)ڨ6+}\t}5&VCa#fӯ.bg3Y yVW9ωuᄃ7oK}j)>jv%p-p`;4(-$s4-@XZ+ 4 oxBi.PAO'Z{(=@di@pk5NNף'2iԑ7?h׏F aHgnTʸ9c8|=ScGNxS&\ןEoysЪKb*![{ҝ gXr|[kltW{Y~u{~"a-Vԅ|+>M<@X<)1"MP >SUY m$jۯy_s^܉a>%Stky BIZ~ocj k)]Y ԵZ=*\s[9}3^ D] _>S% ,6ãgJJӘZ, B#msV=Mf&Gam"\&@‹'P{y ?"h,O(jx2xN"_[ۇ όӟ oF+۴p }F wB180 (t yp]`>oMܙ<ߘG'22W>  /H0݂Ohǹ]cAu'_ YY)I5ߌ/qVZP2}Ou=/ (bxIdr[<>*Єc܍WsW P.ͣtUΡMǩ˙(6WY !$ۋ,^`a"^w9wfIҭzE>P3!rF?mlBL;b1oZ>M~]͖@168Mj59S]|Chco=G1ǽ1loE$DC40O{Me;Ý.PӰ 2?Q̧-"A\w]丮^}׺a+? 4ac0>k}kgU 0ՠLNؔ4Ϝ{u!n6jǮW{e-۫uݤ/m343fԃHN[-E ad'!**&53{6193ǟR;SO_Qo!,\!m9Wt}#/*V mF^#|)kzE3&`Pee0$&԰@}C !vm'ڍ:y)>cǮ=VFR^#6u]|;#NNԫ)qz1zfYlr6{mz#RF+cw/&ϯy ҾPr=4#Ƀud-:h- ZDF fӋT,gb={WMU]rSYTȵ/ TǕ@F4A>SD?:U3ZR;aė xGfBW!&tN_m "tc uVOÓeig3@ve,*:gff]s%HvQ +emfy;Kz01ï," Y{Zf%c}8?d:?p˧e:xc}L(Qb6C_IzSɽ`%;d*?`=sTnCp|T,_v7UC"m>*EAaFyILh o|-ySVܾs{"hL_BGQ\ְ}spUK*"ĦZpԞ!ǴUD2ID)-+1Fr=3\_O^Yet.x#f4t[nmSÃf:|mCMM. 'i&M"FANDP"j=d$ ^3>Ru.AZ%lر UV-ӸދprM* WN7]8XZ$ACVa_;/-0HƇZPG|'{z~Z<|?_l-xwǣ9碈~B`j w 9"Qk_elZxXVVHvصPEh:9-d` Y5MuT0@R:7A;l&y82xHވLjX+I`pWvEbyG8%S}ji€sv 3cyaSAtF2ACMs]D1/ G"b SY X}1y5!YzOEJWf<r#u+țUxq L֥'Ws ]@9.veA oQZ2nvPUT%xҏZTs'e mz.}WQ Sk;YNk.4@C%[Qan&||PR %Dnv-$ QvqwC8Όo̮q:G0+ ,lK|+>WF&G?%au"zw$0X$݀8y qJ),)]}%t^l4ۻ,>$3:;q5#\خBǚbL +F[\fb*y{j7+h4~_de|N:TT, h轄"#F9rkM fsL>c F=wm'@B`rE7&lHNwR3yױ[HT- 3$g vzˆ )鄞v  h#E՞ Qvm'{g0C(#o)5;ۢdIJ'ɵ̶]Ga'gT>rޫdv 6NdیۨTd=6s$ +MV{m4-X|tTݗv']Nz\_j'LP1nYa  hn{>RYRaF b#\[@E 6{`͖?f~q ut/8㈹\԰DEHCVLE 0em|/ 7ϝ+!Bkf!/=huf3Nc:=o}ۏ[W랣Ck"?Ԅ'>uL8 ӤyLd6-*i!F?q Irj|eu/VXu'{ѵ5I9A{G|FBC*|$Ɔ(["2@ _myPNC-EG]O}u;ۘ  ܝL@2J ķi+ #/-ꋣɮϔ,??3jd>^H$ΜEiF]0R[0_1j);e7{} ws* FaEcl!^{;~ y#@:f#T]3(⥑(W߬6h R2r7^:3[ ?ӥ ٝd:5-~^$ + 8rrGլv[9pk#LA* {F\md6nFJq}p%f:f88N^O_g\W{s3τR\8O+ЈO9-wƺ.bxB gVV]|Eb H0"EB/UȝQb0$." Re{ y^-7VТK0:y* 1FǰK4N`wAf|B;,b W䛯h (ZQ}Dokl4I/>ȽZN%\o/vҁܞF DwL}n(FdRPQ9|1>1'<]CK}q5^<\5dre8 *Xx]Ѳ7mDm`s1dr:4Y XAqWc۫X. aK*EH8V&=Ll6G*̍42?[}яl>BD\B1DaO>=i@&t[~Xk6k^u="6wn׃ f:L߹SWCD쩸-/PevNύ0rF#il[uJGs'g0ΣM E‰ 2Нԯa5ڐo]+Cugbs$ {)|0˴`0-bTb)<.ӋӲM;7)pfD]IJECD3FE4gyz:N3P\)OI3d[ݾn&Tv*ceVf7wY()c@mCΰN[L-5 FwgKzSZvTBl!zHg!io{)˓[^yH./,d~bq'YVLǖǠj=l 6޿R\[HS%u#_7/̺FIO&#^4(n #sm*1 4r\F+֞muX󓺯dx0g^3xv+'T0!N{=I ٦A~BSA$RsL+nQz:i=ʰ)MV˿ٔL-V۲ ԄORCE LWK);@ׄ2ިx;k`mOϤ^z= _f ]_nQr4)?kHg0$ .\|H0i]hiK̪wAX{Cqhz4]/PcmQ0OYZ 8$X_eNˮϳ@i6?څ@TCK!w}q@\>#V0ZI Pact,)b2x"[t |[n=^_r:]3%:R'j5w0BpMT~FS$RK]xpM"몼16)YGD-I2'@&39d!5X V*$/@i@[ǓQ SlM8uت %ڷ|q'ۿmС8W#AIffi`DJw EYߍ^m;JsFfaqFk@.6"%'R(I5",y"т/hb=b8#֔Kc";,ͷXXᰄHlQNq`Ufn#ƘKhwhܷS-GD5or" ! Zm*A'4!C-'7[:9@Fdl8{h#Q! ٰ}T@lypzEUww1?;^{_Y}5AM\ڟ62mJNݦls۴ /LA<>p}A^Hr]٨ S4)^zb;/+I!~u(D@5(ִP{OmǶ3=.U !՟jGDK=s!>F*@x7k1Og|Lbx䂜'nq@ǓYߦnGj=Ǥ?2ՁK^ψlҠ*ex0hpS%Zm}l>&d|1>dx}~ն{6:q˖a8L5tP51Pn:&Z1}2X2P"߰cHv#tԀr,_ LE-CRmHD\2 3V9,2iX;gta~|{Y&5b(-Bf m*\ Ek0f@,9fLaQ=n9ǡcQa0/xr\cݧs`) '] jE<čI7Qo sT#tMYF(܆X:,N ökڜ`iDz+.Q24sARnغ4& 53װKO&p{{1M>S kE.}ro{OE2TmsL$Z)b!ds>=ʥ(2PPp)t(_qw j$094kJa0%Bf0 E`;.jÚ ۈEqV"޶%?+<^cvs.ڎ"3ΤA W]`'a/F)2߰JI/!֐ YMmr֛ˢ΢CWʰgV՘m]tryzC2oI0Nʫ 'yR1~AȶJ ~B3 YlnD yE6vt7IBKۚY.Tv}k| TeNa4s4kN| X`{8^pL&*IviF6 mXjҌ6$A eV^~ jaV:3в Nr<%\mj֫-^ ə6)p3x3`mO>z3IXE$}5]1#׷z@u9vltRȃ‡ 7@-*jME&K^$:}t:&ƷH%eqCrbކMGF;Zib2R90ҥD8~Ů<ys)ă!/CԈʘn f` 7%*Sr e6jY͊m+-lXןq_lytGVd390Pš(:4jq c:'\PL2;ݵa <؉.o@dO, m|h~操eZ:2g"YY;޾_G*lvŃ_qz5lȔ-K@JT ܯ3&Ƌm(\cFiK"}V3›}y` lz,Tpɖh5##νiD֓$~VCfmҎIP=ja]la6}!svkHx#'3/樶zDK V*n6e@dc::l7͆Cixńū}0{4X{ z# 1|hro=x~8!mˤ9B􀠌4e,V$yf`՘qA4̉.,̷8W)AWz}b]`;xKXUrΎU~lXP'on)>ik@ɣKGB|'U_{NoW(3ӓ ߔ gt޶%rmN`ٽUL[g0?c9a HP5{Nu߄~<հv7QҚtw䶎d.ֱ/E|Ri/9+)U)P SAjvkQR'i9]QH" l#*=، ~Nz>]*e=Ռs:8:0#5}G1:PX n8 >gpݳ\;mW>?sGp^fHO&ie7و$&M4e9O9CX;-;BLyXz\%#(zbhI?!ouj-+s NE5&Fg@1` 9z(<͖M`T̜X 4g2]izL̍K/ƚYKAeLvۻ2ce)!?!a u<}LŽb, e D IgJ%@9.v+Qػ4)#GK92.Z3 +'HO,kC(YbN٣Q3,x#Rď |*,Q+8簟$ ͋!U268 M.p3$')(I(g>#DG4t'l:-|%FGQ*p +y'l (Zqw%Hz,#JbR򊼁 kr$%$;'YNj.{{?}Ow?}Sϲǒ69b\BDd^E.oܤJ.l8 SyC P]T*IvF|1e42Q\_L8W|\J&l`Bvfcz~![v փ;;z^~ \G1'ƘD0i}3fFk vjxطFuT-yglnHx/wCݖ׈uh-iPnCxLޔ 3R WZ` |{ⓇM~*K]5$DagJ;́'ά6WQ `Fr~tj&p/NMH>r &&1~̑f>;M/QB*JC N놽Ji4tsuFDQ"4mRD3vB-UPxs'OC~~!k: xH͸ a⅋k:_,8I.۳rҞyc} 7ȎZ5C8f?j[B330quygqAg1fzlñ SS7npzlΟ)g CofE/+L=+>)NS9Yeׄ1AU?n ^mzluo6Kl [0T̕#a 7aK}6j8 Q:>|nON f=`T3vWxQEYTGGM.͎[%4&8M Ql%1ubk-FPA4{&Y0!X_14o`+(o*4]X_1 >qr οR#W#X`זR]f~)ihJ,YdEmƷK"pҴ/V"lN|%aWZ69)A4\+$uqVQ ,㕘b56OA($ZhQ!:ƘV=G0U.*uRXų要YWk^x|t)d57R ZHHHU&. Qܳ{BM&9X??RIV{euw~"\RK •Q\?PPRIHB2]KJ"XX`PljM~]c%]8d3TB03j=E}o 13XVqe#!Z@aNFF=u"}#ƟI.6Yh{D53͈4/N,FtQlGB/J6stغDZ)p@ENL4LhfokbTq9S`:gvCK gsNEeB۞C+Ok.:wbjs0A뛆N)z A9CW icgI;ӢZ $SIU{B0마W fM5Ë9Z+`h,`6Y Ci2"n .== J@s~Q:Gxzx(o%㵽l[Dϸ%Rc"TfyU4N| X2ײyx梭 VunY qvhi.|fU$ i0C>C:e}vuk6}c )U@٨' 0ĉ(hJXϚ2c߿[!cCܢv\İxp:8mC۵wѦƼZBXMκvҕ9y Sq䙐Lu*Nj_FO_ 4# 2Bp?JM'ɣ pVJ;FP6e<0&7>֙OFU?;&h7}E A;Zv=)|~\lM`HK^0q.ONK'z&bT vDp쾴ok'p:j`GXάF zbp(D`% L nt04t틹$i2/ƼBI9!bpw(UGPP( B.z}}-,@] Ȅ}Q .2sD‚|)ȶ5V@8m/i[ދ : g4ngR$e2:YRU5l "/m[<<8bjbnJ,-hz!ng. /.S:6f E0%@@HHJv|ꪳjMD.;l 9Cv E<45bt\}SBɅ+pf:|y^l$W)r~!PJ Y;;)mNRY=_+Pbh(xEv?ŏ0T{SN E(c wXti^T$-VjAȖe -Q" 1{)^lA*#ʓ\m̔VKn@&8*QO * Wo`T.d[yzl-Z娯پq'wuR[?iR̡>¬Ωco"|͉qW]F0+^ُ9&W݀6']6]+q7?}s7w}so~:>M$u>|(49TХogTsJwu$#8\)w3Tda`zz(XjoeGū "Ո+! #vb(*o|oNM Dd[;jm<wrT0}H(0q4Ҳ) M-a NbvvA2}]1\ ,G ѦV+[C):^a0 X(ǐa+va bT֭aMl>lWA` vt6k8R Ka?dNf3a;Pzrv0!v. 8LmRbØ5fڊE LUo39.`ݑ7̵i@pXuj~b5`^Y/$$0["-=lXzzX>֥eV v/pRvKi8:  &0I niۣ-0AKC5= cӇ/>\g.Ý2V V(4E*<,w۝] S߱uiXa9moU_PqfBk+m$F#n2#B>"G;@)=(&YYQݲ}~A2cv4a\9Rs)Q%ښDj=i_Rg!U1Jd:X&aA;[qA2knʺx18T՗LtEIN{M'=.ͱ#G]$hjao"rKSnP[7HQSZVSY\a@3UoQ3e5#0N؊rd )[ y;0y:Mow2!٦24326"hQ{|tG̠mPfw=1m_aM3v bA) ؛rA֎6UEW'P^[ AHsf~-,Caaɯ/x⪟k:.tq=Z,_"'XUNc_o&n2+:lv!`D6|8hN"u}u=@ 40KWd)JŸ{%e `5/͂iA+˃AͳWdv7ZU>x1F?8 8t#8c`p4xlnL&SUխٮq(|vUn"+LQk^ŚPRɿB7eqӕU,ew2FnxKh+q 9e8Dgl"# z頺GaFRXGxt^;ҴøPjI7 2FEPo-'bQ(1M ~\  (u5[Qb[+=t9Gy񈏤hjT2Z}s)N$[7G›>@XxJ 929ҙ%HVns<%Kb2!`/#FIy@jVP-]lSϡX>^gv^W }w.{Ѡv#߭w.DŽ;oy/iW4ҩDN`g4bnG u鄂X .p2oVRhI 6H1)Vb0 u]ϩ4-㨘Yf >y^zJgzmdnU>s1]a_ {.a¿ݜ !6@L2Ei\*q"|Wz4*8_YZFWbvf۠'sϼ&8z=X/< ;$fod9!FճeҎm AT%C }ШYқt.,Hz+c{%NZ43+BYRMTFքJ(*V eCDፒΌRW|u9q>/Z-0rʾEAgɈTP*a;vh&$tfpE0B TUo l4v7kJS?kUl?kX`TXϮsusk94yXt߻jC(\q1'A$x$;TW(Y؊ &j=fK0h1FA-&y"jVoa #6/\~e$.gT+uRSI?NUDⅦT!ͱ *^v!")>1EVS s;!hPr Ґ]:Z'\[y=0zՎ#ȮŃKQ0{8@:U/ #*Pl.Y@fz c0nu`LY-0r}tP32U7^DZ%҃?nz[w;;3K0r ;-xHglDA@*tP\!1 Ba\mTc*ߡ2y"l6"r%*Uk+#l75v*)7{:;,R(w+OYcnBCMN4sQ-ݡIi7Ý[2j Vۑr#{NNE hBP)L+xm3CBS2aXkj@e =8d <@<{FY橔 !ӷ+WìAjӳs3fF|O8qL4?D,EMd#][\PNpCk<\u^X,RK9)FSͫ @ 85Dѕ06\6 YUiڱ9[#I\pRQLG''own*pI.=#HuU}"!Uu|WJjtu Pz _* fJ`HLfp,.X}ޝAINf -@JYp2n1u=i|BX=^RC-XJB\r݆NħҀ"wҞ]MTTuvnL9 ='?`>U sж+E$=):a@NE \an|:F.6n;l6lk!߈ 3?:V{撸JG"YP؎sA} N (4Y/oZQNBb-&pfTɋeF*Н.p~޺m$Ym+W!x&z|^R\ϰs6)61eNEM >}XS<>Ou>@l8UGΜٮw ]Jp`Ӣ3AU #30D6$Ë)>Ř,OC@*AsC`[ײu^SY;nakg0@qS=L06I%Z'd99z`T ͦ7IO\^tde\tZٳtYF#yiv^XT}I|͐P;v$xbA[FsrÃظHV4F3θHaAx:ڙL1w7Eҥ?<ʞ# أ7OTr#.BG<\<ޜv|z 5Dwa0?pm >oo߉е"FC?4>ښ2RuC}?Y%<[eCZGDz`2hya*[# 6dzϾ땀\Ɔ<*؇DDH%H/ɝ'_ZDx) >5x/9E/4B;g[1) ֕02V};Fp>}t[iV[P|mOE00ՋZ\m%&;Ru8#z.Da}dB*i3*I~sd%0ii>8|l6~',{eң=@Psw\ռC P:{݉]ݜ;sռ$%GT*6HL?r& ~<<4^ϲ i7SU^ltKA8)M7Xn'鴉IpR6JձoNјRpŏNAUsDKc-1r?cv,N& .BEb6?$DYQivLWLdʆ_d/!v2CX4 a%Z_m{m?m^Z}L]rɦ?"Y,_aEz"/ȕ2&4Omf-WyR[*]@3\6NfUJm!~Qf8-yE"ɾ:ir T8V-,)M̮8hwkb;Q}F7tZ>Hp`)lGoh*j9f1sm֢AWZwf[G(ߩ=u$7Wd+ FsoBڮ~94|fq?qLgw_!_ƹf/TV3gxI1)&.('-.gv_%.+!؀3! l;7`y̑£ L̞E5{é'>Kn_RA ,z~ܓ/2pm;L 4_ѓ+Ѳcp/({*?3Hr[QSxү$۝(Զ"ɺg[5efzk6In׼]p[6EI툀;/&eLK3 0<Q#AoL\lׇSSҤO]^^[j=>鄫%s"mY1Bz""4'/$K5Tab.>tW“/FZ.#v qa@Bx'PiBMp[B9iG&YSRc GQPZO-ul[T5cPeGI"{G v8Idus<}\+ɔNd8#}8IJKh . ܱ5r1E10$7SA(eNI [Uzi_ ?Π`|OCTƮTX:*k 5`6HXх-r!ҼW鐘O-P\a#o ɯȂOBi*7<52ƌ {X~$PbqH@ v nc=;-\:+!v'0T(KNQKhc dDk#0t\-̵PZl n#8-%%lsvKBgUKr5Rϐ\"4m6,gg+*:Z0uŦ{ef~R!"fWCFİfվ OKt]47vΰtbDŽU r`zRyMT2 ; +]{ꐳGYUZYj:GE?P#1[3HzJ >;*켆Lj†ȁo50*e;,3搰3Jp?PvY.'d^mI KdEFN)<9dT =*^8EFbl&eZ H/NCNX'E")6XM ` | 8eى5xO+jքlkNq `9[Z#bR4wD-"!3i8}?Auܠt~)V80löcd N NŇ݁:{=OY}&Ȇz';_2֫Sbc0qc 7QDT!Ԕn}h&@8l<uX=Y 9YX)\uUW:V/mS H L8uP~fD[f)Nc Ҋ2bSgmD ~!Dd;~؉6Do#(W5kގ8⛩X{f&|(pm%CB_i4Tn{ pFW*cb:MgIT.ކoJÚI=&\kLԋd}m1q9畧>yɝVW*'<&*)eW=پ.U-3}aE.a,!4v;iiΤZTO~|1U.4SyR H#_ś~  3 lC1B$`]o|ҷ 8[ɭT 3Dw~T9S45W&R(ܝ4dijR$ %~mn; =87! D`~w>򶔑TFf-<=֖hlph 3"b)[ 'gE<$ʽLpRڷ'ǁ_o׺+#Y^!wMu=4d-Gc!7>FK kMAK.$F?p`3QVH#MWXEsJu]pLxZ%BS@23~s2(tL~6J-LS…>8ƀQ79?J>6Nk0X%BzC\pK%E?yؽٛ~3g@[-#7t )B&6~Rk+abMU(4 'QMMBQ tU۶a_ [[]#DˡYfj5:!y# oKiwubp~,oЎT6F2 # LPe3٘ ,! gX~ 8:R[$!vmh2L2:H[KaRp|BHy 6pgvRsU;rgK7[&ۛy(b Kl5o#1h} p0c`ΔNÔ9 ;mdh6^͚oݍaV~)8n>jctG/ϭ/O=^),,ZwS14h,#L#->>s=|q`y _6O2Dl7uEN?7ך[})rq\R2xQ;=k~htAה4#iTv}J?#)sb¹2 vC'l}@'0Ow'%,32 w=#gu+\.exj J Q3<@2"m~hxaLx61!׻ZAכ|0C_&DSZJ?-WJ ƬxEꕕ0 iH$6=fo!YF2JИO'ypoU-j^a#/~o?m>q_)wa+Em&3B:e|馮F6@Z\l?SJ(eT:%Իz ܜO߶‰b[YFn7O^Nݼxkg* v^8ݶI/g?e';g'Qid8R6g;[KCfs~1(" RA1([O[Hrߜ&C-oj7gn#FcDGi 6{l-ùX BSd,qNs[{oZr LN/MSv]1(qT, ^`[79FX #m Xfxl p>ҁf3P3nIJ}|Gֈa'Y%_oy=C_Oo +6 @|&WVD3A6_Sw3-$rKp$/`F⥢@iHWXuߪ@֝O+4Y$J. +;,PtJK%jpt ]Y Y#kn[iZe>dQ\J.U6SwjFk>v8\҃OݷXD >8^u/Z}C:#*{b\&Η6#Iҷ-F{"Wmc͗W7_c$ډo v00i["eY_DV3Yu&eb&Sd+ jEel n4KjGcp8l[.B\"?%[mu].n :_+AW[!cK[yu+>Mf"7♖>ܟoZmJ[]-VxDV:$Z]?lQ__>ݶ"ܬ߿o\ȟ^Ǐ燯d; keeZw~_?7||ݷW?~K=;>~i?nW}||]|oq:x??~?߿݇_o?}k]?}㇏K{^k=[!lV?*~{ /Nj;KA*r˲]~텿~o]_O=?;=ﳺtGg5ROOץtk%䕌yӶ'L "G$Ty rPdZpzI6ANN^L]W~o I6nrY62F6LV6<傽M&H6䈟pdiIڑLh[ֈ=Ŭsx%mnҗJt"]UJ/翶&qUhXP"rm6CW$n f.YFb@!bVaH6Uioz1#ӻ3u)*!={]&ʈL4tNߊzLZMuOo=7$n-I)db k,ux`ۆhl"R vdx&F[*%.k@ou;g7Vk($Ӳu͔@znmui6#sv`6,UiL׭i?޺ fY.x}}mw)(_Ndla2 }bg~>i|',a33xʵݾy};?}W2zx]÷oj?|?_O?}i=z/ྨ.6.:گ4?uJ7X8eb "ΣpxI 4YyZ/z?K/.˶8S zmM/?xx/z %޾o?O_,nϋYP[2:QUGr?O(z΋,*5xYmz@7??82% Iocsigenserver-2.16.0/local/var/www/ocsigenstuff/scalable/application-x-rpm.svgz000066400000000000000000000322171357715257700277400ustar00rootroot00000000000000}iodw2xɛT/ja<<ۘf*ARu-]榒yJ^K28AOWw7߽+b>9~݋&X?^^\{q}߿ͷi֧Nj?^vNN>~w'/Mo~~X,^n?]Jj}pWE_~/~^\]\˛T;fo>Zs'ʜӠFsS3~}{(NyXW.AA[{-X|;[śǿ=l|vGSr}z==[ߟNjŻ^`|& H)Ug\Z]I>w~s~ }s|jNVh˸.Õj4O7wۋui㿻g'7>[_7g`wyr}ruq~| 2_^^>s25:}{nO緘W) GooYLa6l\ˏ<|n 6~¿]_<@P__~QwW߽:}oGZRm;=)j˾)|Fa` =~2(+у(ʬgo˧6~Dq#>XV+MFPP 4!Vh]jL\%4vtrN< :>WgxI'l,2]h78:ӞkOϷo]^MKm5mJ =o_@y=?vk]sӇwMrJY~@BkLf),jfUzјUeX97Ư JegVɻ[*-V,ۀ`eRZ~>/JR{]a9˔Ezg;Yy~ǦI+ Ԙ][E# +,'̲qKzWЕ -;څ+NXAZ0~u1ư5x*< C!lXbP˦>4|+ߦb R7|:X?w7XAe<:.h.Qt{sY[?(P^Y/Np7˫$\|! mS [ 5y@ҭgИ :7O*)egpFӘd(iaa$U+i/(h % m^52G=T)]Jj,vAQiLϢscm_bsT[\)+rW7;LX>6dCnk>>;hfJLЉ1drᡮЁP:*ͮڝJU>Ae=.+`p9S/꜕[ )(3tK-4(^/(şʟ[Y kzz+RL$THM?B?+4ty&@F p%˙;) {vgRIJπuq[cµu:ƿQQ01 -؀߽8Kȯ[*,KuŭӬ^Rz4X;wg`yXm7n b܉z$>$4@>FY At=R 4^*E54p:_!dU*rnsp[xǡ ӌLBi+`s mk2p:Э0M:gMJ3 =~CєJE: Sihs!W18sDh<̏6& ߨ(Wgm>i D"MN;MNe6 $O/9HgF`9oJN@g+t%_&kK<ߣ\8/mЭf!UjQh #F9вLJg{>h0o%p07M9ߵ9wM=x8L4;|Y~7; NF|tdr;ԶUg]P 0񁍚ݍtГ3fӝE#Fw\>HXմ̦;e?rN?q{ۮZO->힉:n~،vcܖg݇Uh.W>h8<\[pSc$f3 U4gcpa 0yaٟ~}|3;Dߠ8۳A@w;y *i߿NsЊ26>퍫λ.=wa je]ca(|K@mD|i,3;0h6WTaϔ[#F:[B;϶>=m3̥nES*U<ZqWGr{=݋=s.kBWbJz`o 0~ZUiXhһ{\?}|et8B~d u쮅۷Yz'X\R!?ӆZىX:}^|O BPjJ 5Wh [:Q[! fۺƞ^PdrK&|O!J=}&hFaެo߾y 6ULiц[-nn$qK uf-FnZۻ?fW#})^ڿq{y(P%y(Vh@9(GoG[!(w.B 6 x)՜T+a\|2n=T`;1*@25L#a <֖HNj6ǚPz ބ#uߵ@YՄwo;x~;p׻OmL-/`;ذހCZ|g pH blaÚxQ0m:w^͆qq;9rd>"p_/} 7w/vSi$77C0~ϓ|D.؈;F6B3Cr! fn693ކ\,<}Ɩ)|llS󸋍APC 4Ծvq<> sCڻ0y4[QS;Tcȼ_Pទ{lf8Tܷ)jf`ס=B6wSuj_@Xu44{c[N'wJ.vhf wT<혝7 V|=1)mۍo<Ċë{a9Ggwb>9.A]$fU~` 77=CrIc#A`aX?.%(1JɦVNLJ1v2=ݡǓ22bަ}fJLv1m}rDC о-ϠiƎY 3h{> GY3 < GY3h=G9 >a6O~FR$汊f3l t;u' 0! + $P)ē<5M0Kdz`Iٰq֏X1‹@aBF4Y(v锁nd@cqenz|>ljWZ/fn|a\ۑ58;B9-蟊Z/AI↥Qv\j ì ɾ(봉 |[qGT-< $IMN WBn2VLȴ rʡfS3QyHPR`~aK0GYEeT"t>eg\hMɸ _=O?_?noO߭=%SZܝGA=}3؂_힫Oo>BN~RO&ASc!lr~3Ɨo>CY6^pw A@஁ww$݇>ino.|}=>k޼45t}4nK-5>+xĵV_6.oޜ^iO/ŗL E_ebܭ*~x~^|׺›w_7[mq!> ֛>b-1Aۻǟ:{v ppQAWXD}B*s}-[/9?K'y&#DG/.Kv'u]D'-׻X<}T˛ņ6f+`*_»90٦*K6<]a\T} XѰarh-q JW:~p`mZ&%(SH J*%sf!$F6 GI?qgyFR !6EDTN* IRw漰T M,T(7MZk&j*l I&c~ nJ6&QY % U5i e&9k/;ū۳.zr|*Цr>R9mh /n&ȇ{A`04UnT&o h˘PeDAexRI_.lEKn:mmJ,֍g,I [RJ|/T^edEpIJ|5W =Y}_,v\ L#a9t0:E6LٞT.H•*脶FT]fPd@.]Z.Er̉ OCƊ-CR@7;d) `L8Kbd*7|0@ɏf[*\ڕ_Ҡ3I_ %yQ=3+;)"l2i  ˖Η7v5JKI0,Y%G]zg)02m>vgWKa^.\*՜W lLh`RNA$| d>#F^jsR=|ah#E4 t,V)% h#+5)ͫJiw9Em9Tb`fF^RrhX?&Ƣǻ+DN&Z(3eCL49jg ^ B39c(dHJ3Z+C.lKBK8(|ڄȣdft ,̋ J *,S2*.<52fô: fT9>t` c\4-U,e]k9+gR yn2PX\#@$D\K Ӷ+R֔LI߅>_j,kbz%rpY^z>I:ѽ譎_%meID4›< \}%g0:|r̖!r$!Vxn7BDH~=ԫEC GPF{;cF͑\^Q5u|jrȆٯ<&0 B+bK7,i| ʦb3p\D1dh֋h߀;tcVԡ>.aHπ . !gnC 8x\/0c- og#X"0X[P̀eꠈm='9.58NScgrZ"L4g0NT"#sImj=)BAl6 &^v'19L$[A٧JPNo!CXC%zaF0k<< +і^]`./W-vs~zs A ;sc0KC;Pk$#:"`yYQEZDOx 'lJ`a.DiD毶~* iTT T(/=1-Ja3SexNO R~5- w5YJyV ˾\PjjrXR.DG %Ft5 gl;8KYie&}3yM>mI N ZRy#/n)Ph@tSoo:oriuniGf/]-q63$[Q=F%VЙ+Dx;wM#_ox,סJqNkKblzb \DKR*P<9]CPYsA+ZD3"cbuZ].ZI i0 z_Ҿ&Ah]3V -wqijB:u>F^|UB'l$!w5= ",d^J~SO|D,rF1tڮAn=l$.qm?n0U.#kcRl0n]sb5 /)[$(mKvDQv Ȓ1rۢ[,>[C8mtTX-.q|Wc1ID 0#\qGٔ eɫ]Ecc8r&!EąMp]\;[s31XK#`ev.`fs2v1?zafIqZR˨h6X'S 6tai73GRHi 到4wҖCR4@[y޴P 8ay7^@ƊV̌" yɞj1V#'6&7Uv,z\ƫkX5ÆݎPiNVvz)xF2e#,  D3&iᄭpFVgX3☤LfMg*=*LA0%RA3ܐKS , ( (LPn4Jk2њfH:8]nd6Frt.m Qs5A K#e;%FcSGblʍҠWYXbVީMf}{䪾l`S~sgkb `~횾5Z+'%Sn'T4gz+G/u /t!"vPn.0cMXH#Fÿy2ΰQ[Cר-6;XGhg{u3kBǯ-Z6 lJy4RS#[$03֪wEZ6k~mll9B"xN%:{bnJCPnmQa{sTεWВ4]H3lRՇ P))BsMY2.ٞC [T۵Z[B}Z%Z+w YryR*-_m6n[u_V6ؚ$Z~iTٕo'W~Co%]IX>KHE(W g |"$g(27^ztCB/~]aB7,Y{[8TDe%E/Y,HKA$w&PD,6 ٨m" D)C34: 6Mϳ~3(Ɇ 01@qĆkBqFI7?=xs8`mޟ[jgmbN1vE \B n!ɹb&zȱ;.R4ݺ/]V011?s: '!F dَ< `|n`qO QKbmb_"ew =%(o cG1 %?r5%4bgo;cVWviJDs߆G[4XV~9XgGtA)^$Na%> _ёڏ nk옱8;ca# bTXvʑO_1lB'>glVqQ0)wM+ (d544 wHhr}h2G` MӅ&Ф MqN%_YrwZɿ$m^!u(ky"miTţä҇"lfarsAg ڐmȱ +/{ݲ=~(p+[6v,njn3'+_偛vl.:{OmF !->q΍,t8΅nӠIn3w6wBq^6~* nލ;=j{k|(bLq~fr6(b0َu\s<|wG%W PQl:Xp@-0̀B5®`ɫ!}99/uzCb0&,Wp;+3FpjP!24Aa%4ڇm:Zϣesd[j Aތ܁#7}l- itm1ЯJf(kTH4 x9S0kh!o obb\&Fnzxts#!Aʒ'_ϫy"uaFCseʀ?1c)j]^O.NFee"qp-#k"WI_)4|TPK`}ӫϯtf}'9iZVjJngo/ΤƴӃ5}BkY|ra nG]df02V3 -gStuSטvsU};KUq$[^rHln2&IQDGmgnn7vAR8ŷ-W(V\.BcJ2C|ZW*-x1 NNc4̜t-m#|2 BI8$'k <]BvX9_1`I)TIb3L%*Q G1[W7"caRSCRp,G5Ӌ:e2@a8 BL 5xW5}[S4%5Q[Iǫz80fJar-v8b`ci+NBUJVT7%^Y!(JY\=x3eנMI Xm]yڀUIs2œb ^g_#M1(`vT(t{ܐ(jFQ<-ˮ1L$}WVAͼ yl9(?@\TcfNJ\FĕfYKLD][/$T\$2>ߕ8&,EaK;5xIgS3J%9(p7ܢ7N#t 2жl;tl25AYit"K= 19TdAE ֠$ ?rHX@ ꤸr]"Q<2źnsP$قUkM[y} ̈FS)P,3;| ƍz,(;>h6\m&7S@ɑD`v`A11q{@1[Mtğ\Q?T̉ C sS0VCu`j؊'Pb = lI~ 1"HV YsJEԦ[^Ic7lXS/"j$.kxbxw MJަ`q0zdT#i1/$| 3V|H uc&*a4#)@,U 0|1f(=] ED^L4O Y1܀7#fWӥt)1] t*y;!T6t $#\pGPvuTE-GlK[e$8>KuA;(2kKjɏY`"XbHw-K^-ō(-rШziؼ3$iEPrn)7aPL>;PšPɽJxt&NuEUXC ro/S*"*1`b6: '=Cakn0?a%a6 nU)FJ,pTE\#qݝEf@Yt:">oA|G3O @ƼIaɑHjJ`$O/DYJy9L Ѽr.[O"5sݞg%/ə}KjrhxFJ·tI`ެЖXd+<ߖμ蓷j%bz"I8cVX%E.+qՈ׹znp0@a"d4oRg*'hω(F-yW[+w%J8#0}W i^5=r3Kp/T`#0$OU4 xӊ[$(2[v0PLYF:W`$Ӎ]1b uȥmǷyRcW]XJUoK &ȯ^d݈%BH^hsYٍyvVT9`m5ɍt?(l:swAh5at!<b%&,OǗ2;>xQD[7{CfkÎfφ68xJ:p8(9_vBMˋ;bVocsigenserver-2.16.0/local/var/www/ocsigenstuff/scalable/application-x-tar.svgz000066400000000000000000000253221357715257700277270ustar00rootroot00000000000000}ksDZwY;ݨ"uâ,#v~s P~̀k15Y'3^ ]ÛۏUz?>?\_޼^y?jvuz:_|yޝެɧOꋦ^~8y/Y,{}͋慛RduZ]ߝZ諟Hj}}'o^nP}Wd9(sbLӇj*8QJY_Z.1;#O17[=[Ǜzu~V>?|є\^nNVw'mgpIŇo^& H֔g\6ʈF;_o^\^aCuz[o>cp.C^=jzq*DO~^_Nn.58;Y?|.q'xjuxwuzy z0d7&I>`S>+|WXa2l\w]Dv 6~>4x+ejnO X޼:x=đ9TM{9ECx7_yBm43 .`Pkу(سk6)Gfm+?1Tȃ gh.Š3Um=3ߪ~- A_&Ī̈́*d_I'٠GA=B[ y^涫|厓==M/Vq?;6i~/EhkڔrQ|mE;;6k:xp٬ XjY&S{er Z:Yo^T9 S9{|mP7jm0%Z%o3FRobIRKZVI/}>/TOa=Z|΋XN,2%EB\,c4'+R\T@+I٥Z::XXak2ʡ).E+3Z;څ5ijh[X GۅVCt? u)Q._H-+0`;ێK>kݬ/ p+5^w_%ʫ6#_o'yuV\HG[yfU AiֳihLĬSji;gmZQ4&Y6s+i.IGJ ~KIC(WL=T)9% w5~ `~rӓX798=E8ee\Qj8YULiM6ֱ6>\N3j m 78֊eN$ u-vITijw+U4PN***,rS/꜕[ )(3tK-4(S//ϝcc[e=)i6$*FZ7B6t+ lNYnCe%hJ%@E: Si5C`׀18?#zNdmtyr1|ۻQ6&lQwC@Pxc5j~X^FYz>*Ö:*8KracZ[(D7F#fK[4 OP4W6Ǜ1"ֆӄꬍ&"Hɉ@cC x߉=ј Ԧy@uÓmK&3ZlFwP@SЭpJ8E']ɗ%ZQ.iNGtupHի.)?>Z? #ahŒ`Ì-v4AAa&mHSzhT?o]{o'1fƗ x)߫iq4 NPxY8y9-Ug]P 0DP,pjݑͅuʍp#ε w85vQ@"ovZ=Y!MSv>:vOˀkö8۳^M@w3˼'{ǷňV=m3̥|h)F_+#ڹ}HkvGWbf}7oVեjV=z{^dxmt8B~dsiZ;;p{qg~4ߛ)%!4 j&'N1u|Ԇ~['j0!;zuأ.wCATxs=0=a= U'YOE^7 fl߿?'@m*/8Aц[-mn$Ϗq;@ufFyd?fW $#}P^ڿq{yXQT3P%yXQ T~XmF3}O|A>z8҇:TtZBp߹ %|0*TsRRD[#j)ՄN * IE-X RD F#eͲГ'nPM>񀲯w-R!#W0??ܮ?^cWd(.phSpA&3鏄@2ضX[~ X Ie/:@[kOC{` N?@܆~ffWGul fҎ-^m0ilZ_0gilYo!$e|P|–5?< _at1a-?wr|E^nn^ݦ|V|`:0s8܌yF>OVa#rqFo =L.!ыCjnI oCCNr'gc˃{|llS8PC rj96nLJڻ0ظ Bh|j>QDeOP᎑#|C{F߇tG SqߦS-V_ۤ{O֩9ЁdgoƖwNDW7wTNﻜڡ9M<1;oAf߷?`k3f'~{r΃̔v<}1mX{xu6~?|Ņ8Y;jH) Hh9vYPJV KkNHOM<օIGnTBbؠ.NvUN !X y`aX]LK15Peϊ!LAMɸ~OV\>}ӍnoN?=%S(5ޭoW 5m؂_힫w??An>^_w[ &"LV=;_}5 Dznx{ WOK tew??} ~\mf}qM Njݎzn05Ʋh܎m;j16.bi4kt-lՒJ':$=ALBbO宔_OOO{!ܖ[=N랝N߭?b w^wqE\%A`Th\2y_:=ПIdi>zqNh?^4}/_ >lw+H\\/<]h~x—\S`LB%TY]aLT} TѰaph-Q p.+&er5- @@*E$dDRbΙ$.NRl_Hjh!7%J>p &}JB%|Ȅo/"SIR: ldB$F'ya].w(Y]P!nGoR7iM.ԔlL bJ .t|k5jDњ\8ιTC8H8˖|^]A] w`f ݨ"oh1ǽ ˚i +D \hi׼M@1{Ǣl@^WJIo2LwW0BU! *f$_MUB0o0zb[~,32:qzNFȆ SʥI7+p :¼HZ"EoEVݥⷥ9$@Xd8Թf/&a=6&X+6;%#ҟY" F0|m/_#L,7 dGl\/NiΙ rZET #F#`4LH R2e;Η7ȩB6xJVQY "LLM+3z kMFrɅb.J& 40T)A$<#e:.a{Hπ . !'B 8x \/0c- f#X"0X[P̀geꠈm='958NScgrZ"L4g0NT"#s$6t I z6V䘜 $[ AJPnA8J0ue}> ECC)׭1GXI  v2,3o]4\3#@T#bLj,FYqYm0v2d;qK=q7Nmh0$K?-d\ew4M Ld7[M"RrhrDns}mƖCR4@[yf+`pI+B{ nSBlwV$w<ffɎ 01@qĖkBq Jo~8`kޟd(v2tˬ Āb#\r -g$_C';PqG[ř87ʴ&b8f+pyoi~[jLȣAOx;i7Հ%\FdLb p\rP`Y9vT P xP:#WSO#vfi6%QhnAhڌ_t3DQJ"v**B3>`9ގůH`ӎ nolXg+WLwȧez @QTV5lVqfPw0)wM+S m/dfvM|d;$495}h2g`wEDŽ&BBS{dhχ&cBfLM x@dyy5DVoI[WmAjg{jH{ho3HaRiCM^sm0Ir{9ރSAg Nڐmȱ +/{ݲ;~(p;lv,n'+_N偛f6윽NF#DŽnkOS= >&:3S4 Y~6(>CzMi7t8a?MS][yΧx"wkl6%/;߷?->N>H*mnZ+8 .RPFuCyil_n:=!A~knjX$3pRʤR0|(ӿ0œ7COMz\^O}L/ AGyljlK!(<[pd ̀Yk+cڮDkI- mTh 5cv%Z̋L;?b&+{PߚtM9dtQvCJÆOfwȰn;ǽdF.]zh3/$PӯDo;VeѪEkyvX=1kŞ3ysPdK^ )[?_fP hJiuW(TVV旗jgr#7SϮ/ήn?y?T~{տ_v/xo77ݯiZ?ۜ]ܼno?>ǻ۳RlgForOksݽ⼽+~۫siЗn{<^ۋD4_$cυzT+g MV%xvWλŧ+ޗ_1asw|wn7uc%6cIۻgۻM;/`B/w_^| SN8޼}Fti'6bu5|I;97هWS0nwwۋ ^nW7?|y00l3߷錺}Ӈ6.A|8{~uuyN\\m|{vu&p`'7 WbL3D0Eï`ݞ]\biҕ_Բw|hg-F?5߼}{6]^ܾn]~!@'^dcVvLug7۷۷7˜nu,vTJ3VoVխ/t+{;ߞ9oBX|p7}=GgW}k__n\ CMֻ/!?mo$=}gBJlX Ah}{}6^e>1SҸ~RhㄒٵhBm~Ǿ=Z:J]=]p e|ƫ((⎙{[AYzBo򛠝'7Acw3٠ZD$7X2YV0ZO|8eR>+ur;*~Ȟ뛋qݝ/kmu׳!9?pQf ZJJjcWbmfrUĵUnpFD픥)5kMwDPRRVV;<E *4rm7>HiJlś,'ָ$F8V_J~(,=G V^xCoh,,PULXha^ߠ7I +e7XuWVRD;"e0|6zT}Ү^t/k~`c ~{r)MnKW|vXH(:vog.Ϯ_^}-jD_/]kaͪ 44&ĨՒZ(k`.DH ZVhJM^6x(ih!'H DvQT!1%+FAl~r*":WZ{?`z RuN:Q(cY= [\m,|6',֮6fzyA[7}m7 ʯ.W Z|H PXHA#A k)\Uh@eƋvE?Y)_uRf::^iFK/Р"}2[-}ܫ8U_UVIn' Ti$+fL|]vi6N0ZG ֜9:zgй7dmu&xUnF`oq2CBMZm֒=pĬRQ s^Y8a:!%{,%4֐/hP7i}'Fp\b$hur-=K5-ns$z@GQpGђ^Q㻍X*j]+ C~1nJhH;^(zSMZ)bƐsKj~]9@]ȥOQY> #ME00#0c 2 6<(I1(ɤ iBȉLۿYڟT~|_R,b8Ո/%x{x_C%Z3#N:BobHj7 d` .3Cw4OrK2n =1zoi{O뉉;c)zkl4F[)${^@z,;֖SycDEk ubU: %;s%BI5?{͈˺qgiOKQ8kFUᄟ͈Vɴ/uїie&*yׄ#xpFq ж˷d} 5h }51#|kg4兛#1Ҍoa3FXdz DZ-#\fB&?5hB#R/vqܾJObi}<~Hx  j{CH7yKe4Lz3Mz;"V_QͰO}_豅OYz'R-)ZJQM~.NNY{ - FƁVAg (-y&^0gxQMklyCNΐ=?9,9n~!$}Ī] EĤIY=o^}sV*N mj~km^D:/C={;0aKhI&:4SGx~xr,gѷ3_} ,06G{/`Nl03O {iӺc6?c;qdjKLR7NA|I(Ð~Ds:rŜ~”3&䚳p!8[dztғ7 ՄqAPEftdTGclt40ظ hra.bVFyb[G42tH(`BX 㼸6!8.<<|.h5SkC{L"n׏T]]ʠFI2M5i[hCU7z67RZ/)BDVaOjql !zc:2۷7plSI<ڹEkdI"W%XHKQ tޘhّTRjlwsq~ߓ1HJ=dxHzoDwy"v=Hۅ&dI WJ:w<@`.mI)"qן/h'۷Ҝ•GiaJ!МnEWH c=t8N,9q#R]zNvS;`֡; &/}qyƾ읥d;oӡ8Rb16$K  :'A rӒNOdv|y$Z)i6tOvk@E\mڷko)%lIBcҼ!fUӱjhlXiaˣ&pyf; M"UF'ir߻~g){G1xI׺^ֈx)k#.9:aL8rL0߳}a2#{xxgDa/K#=N# P zʜØm9,no I_|r|f)AB|amD@'xZ,W$^h}FD^V3Py UK6;xWpT"Zx`(rao庸w$qGv׶lfX q6!8XnGD? "_&XJi::gb J.tZ8[o\gظhNSI)j='tH7 f!~}TF,*llWCU *@vTSf6ʊr\ȥ_.3t/L^!𢯃ǕYHMŻe3vu𳙐M{ou.8JEP̂+%Ո\ =@ǁX$^u1 aK90Vf&JC6ڵ 4wy[cFGguHv%iՊ\HԲvI6]UW!f![hiltm5k[v\ȅTIv]ƍv+kvK-{k20nV.je >m!P5. )u\H|sPLX#OPp6%''(8F B 6a3F.Սvy)~R/U r3нqũgmd5q׷uL{9=vŔ:ІlR'$;M|su~|t|_rC,{<];6󋧳lHNV@s@ǖQ@O1X#B$J>bT阜+$7)H%a6?_)*wchUPlV6r: 0M \z Us"a.vMY'FGTa?[U: Wx%(4[<6ʆGغ9x&!0ag&x, !)3~rչ,8r-Y:=Δ1p~B j<քټLEDj 9Kی5__L80W'HRϨj wFf`y=Oαh-,GdJ0RH`#?\F:|s&~b?4雲)'yf{~?JUᔣӑf)3;]/R&DCI wPAi]WlpNt|`4tTS9 _?ǏXWg7wf*ggǏ`;kE '/ “+<%q~kNd>20q##_*Cnt sZ.V<婩 +Q[*'?Fh}2p91O#_Nt*k\Ín =(}TTP=ihGD] @!&PX5zn/Aᕇ&'7yTߍ>A"ac$t'U|whWvܺMvSvZ09zr۩eP@t3fRkx\k]ٝpnGӺ ೸pBjv_ qjJ]ĵ"~Bv3|AO)oHX 0~|GgI(kڽ.U*өfBSeKA3%Uxh"LL咩1%>T/$XToc>Έ9ytt W.zO3}8@q]N]{=;?ϧԙU"j5)3aiMuqy Hf^q ^D/Z˪r\4!e|QpMO\`NO;52zxJڑǽgnOsɇǴ?G?).A|#UvU7vm]\7u.}*Y7w|ym3,^.jLWش; Z[]&èrKA! [bdXtL.ƨ֩P&Hx* ?tFf;38s]Y2ȰqX_@L~T4 ۷o?]t?SQֻgRqqXcġs1F%޶.b{ ^|E -6J2ei!B(.Wapji9a+-tÍv3p}?zvvcߋ뛋[t~~~~w^RGtD"xcVn!, ̈́ AJn謠/(#;y֯&(^z҅UtYV*Z!,Q8a!P%5zH@W tQ:ZS0֕*w> $\][ ëH}xIuI٪+;ů%v?2I=?6?9S.ny.ʿrvqdxon fg^^lGUo|*5Xb+vROUǻջ7gWճ}o߷!j.M?޼ۭ0?vb>}ƛw4v;wRυEBG.gھliu߻EZzywjh^+ѸBjs}Y+JZ\ K6z'?]ʅlNO2֙%zdN)k]ɕm_:xP_"4P >;mZ,o/޾߯ju;@%jd\wqp^"z~@CbZނ>ޞ~o67KC;u!W>fa4*_7dozbRonnVr(Oo>~xիT¢ hs"*ɪMy/7Jϳ4/iV@f#|~6f񂮟Z?5Z9ZtfoqC`;sR4[]F-Z|W^ŀ;0CGb<0ɀ6a²d< R8~OP)VkNܝJMtb6MhD6,x_ut`DXN'Axʱ b~}'ցdUr#w~rKDңb0VPM/` 6H|V" 6ƙtIAj黏JM_bcd#4|iPFkׇr3\ NLhfX &`-Xhʓ~\l5  K:ڸ?nH=gۂO+O/Z ,46D%Y&t,iT~yi f'#U _U y Y_ !;KrSSki'KWBaq!cńL(EI'TĺL#k#'ڌڬh㤇*YW[m5UgD6&4Ы& ebCHJOC8ZHm7߹\ۆ`*Kě[疵T*SIh谖H@@̚2!֌ ZXNX|y<\ }}$܍F@7B7r\#DEVf[لI'T׃w~@Y$<4ball *}B IKrU^5LKi:,prJ:H=$G< ȀrGMQeM. ; ]61> K% VyiC5Qf, ,`V R>[XfkKhI(<]l~ƃ{H ³gޥ!1@LIze.=#Xz<~0/;IMM$J $ʹH8Q~+jS·2Еnxawqw{-z[NzY'CbPnH 0ʝpPE HR\3(i c LUAIZUdg@W 4M:7@LAY*aH]TkkhZ`M 3 hF85DjK]lx+1NJ7ђ4v$N $^BZ!"tcj=INTj &$ r)fY.(nܯ,TJ9R F0k֡"1PI 6eZ{zM;p. ~Me) 3"8Ut*& s ,InNϲaw`, 8; 5!k.87PSt~+ke-{X˸a/Tb43F+=2 ,|J AmZ(dD ֠;@r( ^KŹ^vd`j ta-ұdf,&b:6dT]@w,}JrzP,ހ|B {&9Wǫ[,֋fe|ӱt_xߛ7T/1oWi]]rv{{'@nࣃeG>84ĽjA^k݀u #˫ O7'D+kDn4}k J4gGʓ,Oo6N׺m}t7<:n_c;hezov{_"*jKXRw#R}"%)4w*R1sp 0t)[B{it-cHOH]'$n) Qm.;ӓJ#]:`WƗ=_}zO1\Mc\WCZE:gH0#8K_kZ((].9TD9ʘUZQĄ tzc-,>b(mfapֱ6` yOa&rͬ,Swg|AddaԍH+G`b,2/T<}l=049~sB%B3vo\tHh+d MФMcBۯ} 8#288/QWy`o5$m^5zi Z{C ::L(ҋt[ #nm5z6upVFnP YϨҎK[Kq0]Wѿ1w"Jl՛0?Fd9n*"D\] 퍠[aWPm)O*Y. 忷sd a8:*NQO ce Rc؆_&^R2yF6LOcު$IP,L bvEQNW0rcѤAK{KQG%gsk]autڲޗOD*тIѥ|{nTK|WP TRthr]^ޔWo+C`]oT8蔛IJ|knRm>RnevJMÔ`"1CO7C' ז8-MC]=HgՌɨQkCf(^\TS#uqn2a(k]ݻ燁QA\a*GzxNMw6ߨ< (St+Nob7>s)_ِ;tnm"tHݝ=&\GMԚrhm9A;tbuY*#ӯ@aOIo,[z.YhT M1a[ TϿg~#d!k#Nlv_ ʬem_YB6P[[jxϦI5<+ *B8LT 1p+*͓P4aVTrf Nu0Cޚ&UŔ0pLİ!u̎.z *89:eRXI-)E*{XMC7ʱud$t>2? X]NH_4=~N*6[XCQsڻt*DaMAD)u 7cl4( 2hK:t2q(y}C)\a_KyaF^NqѲe`_SiwneTd V2]g!0w9)T*L u&ڟ2P͞&Ր*ё1t)nO3T{jFRyz?=~M]pM)}`!"2t!tM:ҭa__ZMYJR)",E/Nr@ .GB<9؛Oy{,e-I*\. TiL(9'9}_YIGWGZB8U~SLQH@S/I5T|n *8 橗cXPI$x?[*NտB.jV7[В`eOU)bm5xSUF. M!Il'ӔpZk=Z #?HOA>iG+F_X0ǯ|ѵc\^;ڒ2-|2Eɺ\ >D[v#쌳4r8ChiQDiGLQz &1>~tYJ<َOIե+ʼnigA֍x$G '  Gvk1d=.uu!Tg=ӤℲA}K| '$Ēeg>Ɣ:z,tm04rÉL ځ4Pv ^,XcqV,XLX*TbC+qh$و1WeDaR;iCph6}zƵBAV&ܙ< ,؃v9X|$x$8*%N؂7oKoھF~ /vc+$*mnقWZx/`MrӅ\ӮfZ|o{(jzJ"u.{uj[ޛc:4-;/<;F^fxX oxv%ʡ}3:G r#̀ϛ3Gx;?Ԗ[zABL|2AoòÍqh߲㖝Xvze4i ;%N#-W'?a*U?P%awj:rv]xdNGN/g[|!z]g Bx;ж ,ydoIݧl"  >naߪiwPz JIf1.,jکe+<v|XfxR!;U@k_e']v񠘝?݇/.O!hzI"xJ棅fJ v_Uk=i-Q 'x>}Ҏ0SY(mAz09Yd~ hr11y"~z,CPn`)О*1bĵH~sF e$//xh}l'یlF%CK+ld3>ٌ0혣19ALo xlen4Og31gbS|lYxў Xɷ~lƓJђ2?8Z棅eylV9:rcps߯&և@aUY%&#Jt9Vthx{ 9d@YODל̇4?Q pp8VlHgZcJ V>]*6GM })~ZRx@J!uaU]k$n7—qf6dO$\9{v>֯V_4 LQ Jj\NX9`֔UKA(ʸ5i̼p!{$r ɿ%Wu:b[aTvJB|*1]tP)ןn4&bj4RȾ#s#˃#  yr~ '}.'}#'}r[9̝k#irlit};u99OyGn%0(  <)'5Ġ hӊԂS`0w򈩜pIHH0I8tѓ Xx"' 죂KSVeGf|\0Q3Rf`]1, =U5EػI[L;v%`DvLBh xIN:S0gƩP9a6^ ナife:Zvu]r?(zD֒+t+@$_&+8$'&M!5$ނIhcI8Z=8UX|[HL &Um±%C}r6t4-T뜡 d"?uU?#G! "D ~OBn4fIVY*BzD$qCT yE%RE,J[; l_1Е@Pc@>ө%: ܖrC)]t ͍N0U~F><#Qd%[ +kSʜkV"uR"= -;1Qs%PbEiܙ,/SP,˃Ly $}4{}\Oq =WJIVTtsV0]41fvvJ2IuJ>?;0xeNE΋T٤h)"?N V:v|'H5V9E;%;Nx^etͫQ%x $[ l3kF$yTײA$gH@I :1g$%WSFk|Vg/.nl7Ww{W>$:(,;VWp,&4pѲ0R& +$`kFXG*dg}Ǻ}ª:ȃkݲV\OP'=B2@%uy<؛Xৠٶ2OP=@X},0pJIZ $ q Mx%:u`V-ޝp҆uJ lPW͛&^3{6A' 3m!'b?B̡ 2H[˩] bG`kާ˂ `~dG@.\ A$%r8 ,jX#2[୥M΀Sp+N)j0;T2Sl1Hh^VF2SuܒY@ ɀH,. S%q>@a#!cƑ'慂J)'dW -o"G-wyښD.uĤOhSR gd,ͣ\ÏLXjSv6xKa.ge9MMd":Pl7aDP S)"O. m8ͮ 8,Bp}PZlKpInN)x XA+^G=` IhSajRf@H"5QD&~U{O^)"[5B0V#a\V If&#<mVyR3bщ$( PJB |i}XAp\ F+Y8 .P up5gp!_db@锍>j^Yۮ^XKnU1YQ-Q'%iY>$  YA& ZNihd|Jf*Wf!Qf-GOl\ 5H`"&y4fH!h' 8 #+=9tmUr.AKhnnfU%,s IЭ$w0Ej#ۊp%.p] 8 T6@j "Fѳ5f"$<+]3bLH&+(E}ab˰R_'uAy) -=iZ2JF<$ udiDqà 1 R,w UAȼ"7IyB2{C8~,]% -" 4Č>8e! F9a9`Dk!WP*pH@ r)b5S<(12Ԝ 'eeiOLӒ* -YRRɍT8 |KhR-JbAc,JN2&e}w&"|-DqZ{y@ZyrX5p-In%b يb$| 8豪 P;\Dx~$<8^qXr!S@BB%i/J14& T /euVԺ.9%NLP 1ƨIY l5DZ7N4vg1Us.)`RY)FFU'o8w/L&K4Z,FZds έsk4-x0 )"jg]v8TG܊`:r3H :WnBD#Wxeed <8Wyq%lrѯc4 ɂ`==ˊnӿri:Tё?P@>/goJ$I>oO [`5A/ՐR64f!jy%O1=# [5:t+֍ngV)Kyl:Cq5iHNQ@ǒc cOhl )YUJ/$fʸnޯmH'#ΧI;ʤdHYp2S1;GM)C. #"EwQˋ_^P5S/2iy?/LӖ5@N4Zuqvt^wWIVG%!))\IHBщ3UdtmCJދdB2pv9;ڒhevM{HJjZ^a˒Sf 2@Mdz5+(AZ e)%S0981 šlT:< ;WOiF}9WØ;g#WOԉ`&ŞpO)6E1ZhX:aJÜ惀 /9#-v#kӜbXw`Szi2c-k, #1Fdw SUnmhɹS"r|022{H}k1kLZVj33=B9oI7L@{͇P .L 4(CySFJkZbT-5]*qA0 n [JJ-,dCަhItއ\5`6|Ib҉6,yg E#F ZUwD=a'|{S:jH*`d7Ȉ TU6ʆ&e!ʦRvnR'J`iBX_zUa^rwEmiit VGʢ5r'CU.gTW5sZC,82u'33# GeS7en1]6BٝG| _֌w~&RO?k4[CλO!J7'pAdG;Mc9J#Pv6QS4m7^KrsЦY%A[聜 B +Q#5*&XI[)3Mjpz()`P!SK 8aY bcҞ,!>G P$ mtx5ei:5h. P Jpl`[)XG=a(iO?K+vUi;0(!MPyIƗ: )Vh0#ar ۤLkJ,dwJ:[H)tI}kNhg3wߵ)fŀ 2hANEDQ6Kg'Acc,pMN @.G Eh#6X ؞|Op1$>BS1u7"1LeJ|KHfUq$92-;& ~ c9"3+m1#n bPx uȶYO[9|H`6%JT #T7J̀ p,jZ0MO7bRHws󕋣aQ 3wFLT i͂Kӷ8p?(hO :O X."9nޮ+Z*|U5O[ӯ_^Vȷ|D M%mzH ygtpߠ3P ̰ 78U"dZem)'7fC4 NJ%R^!sbl8wN/* y[G{I4y(#yV&X8E;*]OO03_{TVH0*_Ӯ Ism c/&!N)q$H{NLU @׾nГ-+-2j 05 8t)21 2f?>Vhu"rܪ E-/ L|q/y-@‚O:!`8imliPY&ePL1 H&C8)_Z'e4'u2`6Nֶ&f6Mȯ r9KUR5ziTNݡXL&sOŨcUmCkluLPWHk=ֿqO2m{I<̵\i}iFR]]rꠂ>`Mle-H0ku ^nVT2tCO>#ݹԧed@:kƦO'D^"Uݐ{NFPNџ7IAan+h!-&[a#op @)4z/!k=@ --^T;wz[[,%T^hV\ n7ɍ㳩qkLNra"h#(%6J%\Cx3I7DݴWPq R AeZ3@3?.s.#ı1zzO}R2;]p ͧ#Phq֎µH蚛:qJb>~m7ayw[{V8vrq 7G)̓Oc~vL {!s?h=>rw <0l6FFNs>UWN#}F \&P *u9 ѥG<@[UF#y>s?Z#c i|Rfs4-&7gqZaO6cylC}#FJÆ}J8@PA%2&D़%73_;:h x)pP3? tt}x!~:0'lHq4^~laߎMz*zo|ED.$H9X yBNdL F znXr4xz22`x^ D؂'OS 2pw¥Cj_-nGSۛۇO4 jH.7A+86IpOTw_mNQ'_G |Ӓkϗ"Z,Xo绫y~כoE8]ݏ1 ׹?QKU?7s,]4~߽pջ ~<} emR3'V|nVx:xrx{3L/8,r͌ h0s#Q|Bz9QNS {L6"PIw'3˒OݱmøY^PBnY o(}جK1c)d )M=j=[ۭbN1!sаu,Trkدf"N(Ey@; 1FN muD#* Stebƙpq 2AFNQJž]%e/׎٪Ezo}Ⱦxzd58 &=NϤc}=4L>_|Rhs k%J J9?%=ϲޯn^~w}5|Տoٓ㴇_K{q{9gW[mqoL 1@1R| RrW(ΧlʇaΞr T.o_Ji]P%^o%r5:<](N {|I֖E„׸${~D% K" Ad!kv@'#R_oɔ=]"7?^'P)TAo3z﷾Eq5:v-tosAs=œTnޣ`/@$Zފ1a q;A$:V6Ն&o aEsÑ-AcCM?2seo9LOz"goTx|j6"u`H2GC^Ll-MhSF; Co{q7+)>9럽'韏ʭH/s64M <@T1xF)@1"I,/ $Mn_0r'9y4y6=܆L>0e3 n)] 獖Y=Yb0>*#0@r<,e] ~-FKGm CG֟Gi&|nvl׃έetRK5ya3^onŻxyq>ݎfgl}qt1ɻZW'+t\scjԨgOuQQ:ǽUj}Plml8]Qs7z8?.o֪|lf1v;Sr7\4|j0I.?Vwg>TE.H5֔׶oԨjwRC|]N.wg~ }讼 _T-5\`t?>WQivt~?÷O'q1;<} 9ߎoG1 ɘn{{$ݤ{+|nW5]X0 I.i4y:}JR{'h~Qw>(A{*\]F *=|f2} V& :X#7&zYoVW\ct>TFWk=fmm6+mrnw#;#fcOzwkMw]M ƫoFh{xiy·#?Pv)&z^$l4lQ "a:~#94PM{!Yu_;K6:6hVdPѭ(4ٸ")Gfmkn#[.]E3Uc+bڨw[o„jД *D|BWMA㗵M9@2X3iˠ!# nFkvI֞T_{X}q3Ç%̯w"XԴ):F6\'g;hwz5{wwɴx~>&(Ge7oaq]Aw[3{N9ܵ|A^k<0.QPt?Y1K ?gGͪCW?goJ口c hR>qY[CPjASGZ|Ls!elh|e$ݚ6 4w]% %BWI;AAo(ihQ0!{J"KIJ `~rӽX?樆龆SVܯw#C}5mƇ^}A}qѣ,ͺ6=HЉ1dPWbt NJ PTt\~ HC9@Pd*34^o꜕ )(3tK 4(^Ys>[ڮNo@J5mRFZa5+4tY(M`+lO3E#JL$!ڝF&) <|q[cµu:1JFZs~-`=b+-؀n@(kE f}hX,I-O(㽂~z4X;]n>ۚWsjtq- MTWNѰƫX!kNZRYnMe!hJ%@}dSi5^C`718xEH!HVӯiB,X|zd7^V%E]y}6o>!‚<6 KkZN(mo_}]O\r<3Zb28=4R Mh2D ;/*Z:5!.5zg9?7{?lzYEv1|n/u Vw!  :9^{`մMA`p>``Al`o-st보VŁ9DG5U{wAFmLAjc-!rDТ]Œ2 !yqX 6-%0+m櫚`Wt~0ac 2/҃SmRL*r${4JgM51E ޏzwM[%] '>4__vhmTu6inD ʪ BLB.h5-uprпO¹*?6*(jj-B?W4[Cj``T7(~_~Y$c:$*Z BJHΦd$Lju̾9<;Ǎ_xpQOR ;~M^w҃:grKz* [xXS{D; $CMCt_1mkbjYkɭm"nii[q{~#=WekE3.}#]ot8@-o d@dn15Ա.3ZSoK2v ^ V8|5R-JUR=k1P+6mV&"pWJCawV  ;caT#х PA3ɫXl:A{k|,Rrjeʻ}އA0 G3(яյt$piCvv[-@)̍._ǔV5P4C-[ ":z891J)^kqU26CǘMm_0r;ns ^iÉAЎ8J )u9k=N%N{ؙzFko8hĶކbZyk ]4b7-B|/LE3Q {%{q"`=5msj+掏&bv& vPsǠhaU`n9eX}1uA0nꭄKC6W.6z!K] _2rqC;Ywv>ME;ryJ/Tw{Ш:Ccv{hܱaD9cd XmZlxcɻ :ۯH^)}yCTs}l4ʿ(RIiW驑VF5n5ٍlx~ qV\S=% Ucn L=]uia_uo{͠Kcزa}DkYa=t߲V{߮#a}澶CrָQvs*Fuunuq[lo{c0#Mٶ mwv;Ӟ0M+Z{wԞ0M%կf ehEnI2? nTnI QN .f`O r"}Z]olILӟk%Ä t"IZyv礀3՟j3uUTX۲gT\P֞(n򰶛'XReO0"S f/> y 0p 5&oL̡rD䊭̻'ND= 6~pxFү1Wq0<ބA:L"qU3B@nֽꭋY?*#Yym &E<QфA&٭{JIǭwayD>0`X2JݛuK5Pjl6p㫏nM/XaX7rAR؏(֗`.]Z]&`6qub" t:8E llaGi tP8j/C2&B2v &(о*`cyJx)yK0MN럟:UՓވ] 1R@)Ɔ)D?*r=!:< :Ӑ8t4k:!pjL ՙ%9 2&>@M6f~[`h=;C1K13BYg7^&g_;v4MzEchȚ[&sh`wslQ;a0nn̩Wvcm6) FהծR$_SW1KkBՍ*=Y=@mN. }\IE9HBhJը+igN}KQ 2QYKIp-. 4/6`Qǰ$;@cwnlcLgO ~7A a _qX&Wt;w:+t͑_H~EtN tx*{&Ut F8).macoˆ'[{,xJo߮-rx:k/r|ws*J}x;8}fGh)Q'tq+ȟέy,4lr ߰~dr+$m 3/W7_sorrxYd?m<8-,ruPa˲هɧ)a8Zow*SlsW?yi|}\b.ǥB]^šxlv7o&7ή7m>o_F= KHϛ|gg\i:Hzܓ3[N?o9Y |0ysϭp咉jt{N?Lzܵm>g7Qky} 5lrwy^m)grC5PŖ nOfRZج%or@CB mjK tO[6A7i=IAwOcRWoGp%% zA˫㟖od<'ԻxK3|~| &:}O7PKkhM3^%v̇0 i_:~}/m82+;_ \]_G77Jor4gy]}畀0P/,K+&Ց_+]v&8@a$*W96ޕaBHELU7ElS$uU^9ÃH| ܯds`ay`7-g8%ktexCGX꫈V-}.u`*@ kC;f̹TPJ32*ȷcSڨyKZ;`S?Gr ݏAiNFP70TZ-MY Orו8tcߞ3NV?0,NmءEE{-{]wl~ 1d0cvb)BC UXT6.;|3^E蒧,mJ,t;e҂$y_o*&[%ӽ, WF)4`s+(B5r i]*By;ɚUT 30#DWx|OmtU6<;ـZ>#9 T (BT(vCOZrٍ(q@&?Fu/Nc 3?2H4ř**>hp'E eB1rB8C@Br :IR^d^d1EE,m{ {?|J̓fJ!f&@r%k, IBe)H .,q~`,vG.9z,5!zɅbeb$uIIґC&!1ܙu$F5DT'G3mڍHc%p-V7C(b_h82Y yW^[92ѨD# $wL/O"$ZmHʣ`oM711L͂wgҧn7ۿ'( +v:*ZɇzyIʷИih̀:AT "%Ef@KS E(OE^CϛH& K@ovR N.7#0~jsLdYk2Luн%̄8 ӧv l_>2 iU3Ok0od.1`^*̦@u9l h-RV,ø6tl$|uNb>+vvrLM%햚ƭ3dTZЫXƳ,;y\%&ŵrlBp_B$hnwEP(#S΅]r){35۝&[ٳYkUC{(RE@V1J#ґ&d̺ߴ$0';'?~pV@ҁ#D ;0 N[ xtzMK+jrS_4t LR )=x 'j}>?Q  3jIL5k6HEWAeO97"/!(Ci:p \Nwȇ-xQm̍Z&GǕ (D(W3DN_vo][)&AwBM%0=MA6 eWpyYu"(O#0ʻQ/ G<7KbQE(!g3s$hd_M};QS-]P뽂ĤZ3+ָےv"雭8|$U&H3! { ˭t2Dh18E1gYMo4܎2XnQ SB.Wd!wJ -Ii颼_LfCqvԘg 4|oBblcY6`]CMr)5pWatB (cmd/,*=ݯ c9z =Mzt[`7r!DMOd#$+:a.5FjD餆f g ØK!<ˈRj4`H(e E]+6(–-ҫSeCȂb,6h]Zt-SVh D$9TPnC8\j@dt ({u` ieX6D YR&>VgDS^o2Wi"EPldHէy9](q_o˴NYIjzď1E;L,W +IZ01kVTBCMufJ](: Mu3vz9\Slnh93-p;b+- ]ޅ rwmY38쐁bD ڭ|C x%_OMp+(ð)%k 42&1Hm`+Z' 1A9&.[5<RvU]=)_0:r=b ll)o㡔>(1ʉ. XFgMb\$3cgl 5 Y8X! 8G!SN`pkkT*C/GL^Wֳg_A ><&x|1:ȸȡ>m ]8EsSV+%%(24YIY\υJ֍\0E'MZ(A#AΚ )A\&K\Iy'NjкqY \"Wwˁ&|E X&y &aȝV:<24jS6/bcr6藇h82&`b ߮"V%!G|k^(S=3i AøPPsTtG9P:,,Yai'[N0ŔE:p D& 8F;-EYI" ){ NBFK-66tfﹼ]EARcH <4MYǓj@& X~`L-4$-Rm\|iGɂ/`.0z?S.H ƻb,yħ %~T}q">ճ9W)h9ͼFٞDrLJvOwooR㟷nQpa8?o[+u@~$}^%L(icoS_~ݟ21Kͻ_~_?N|t躡_~Y/W?ٱ_&! @0 O_㧿^z[KQ_fm~71\7 <2ls>_!Oٚj?WK˗1N/OVw4/SV_>??W 3N3C/Sl!a~w~xa믷̏qןOqT ]q7 Yޛ|;,{]'mevo:ozdߜplgq9۞8my} -ˋ?B˅ҏ,q ?N+8E -LۅqMmu#~?_ 'r_tݏKOzȲ@R%=ZzH˲vwۭ߅ ?grzMSɮ-NEej厷ME'*ûNfя3p^!7'c2hx"SiX}qY3pd\ *[v Wis8it)w^jS˲=W=1EE'P,ޥ?N 0ʰEyq(?B}f=@,B1/ 1ul4A ǡe@)eEotyQ:L^Au5R;YEמ>v%T(v bQC2euڞ(ACee΄]Sm{2*|ϊ,&. ;+wHɖEIc^H=Ml{1}ɮ,mH0[:c '4LpW bm+;pʚǫsdGh[q&ͩA>dCA3l?>gw/\ r䷯7ҧ|:jGb̖-O;fkrNQ`$´a*IL]0]M[)^Y!:Pw uf93x1w F0ecs۵=,|IZS^$t&ry'imt2}>c&rU砙Ҹa v3ɚ+& *?r*LO-5k#wD3;p] /wӖ/#[j og* dx7. bN%۳VDS<}|0rye;JySz7{,ʲUm>AcWY#YM"&$Һ&%J"9]܉5"jrP93ZxYt4=ksav\gaEoZٲ~bEAץ-0?Eȩg|`߇ + uaAx3Dc Anޡڰt7G:IM`'5Er|w`}TZ/1w#ȢnvΤ wyI{4)Pޗ5׋. ,k 1vNjdiZg^V32@`C7yN.Yd(:kgrso4[tiN>Q92gs,Ho3Xa:#ibTMiS.=~{#o!aSxS@UDS)V[] Tm1%=m $g]5"#AFGMV[wm| tۮQ]ل1 qJߪʀz w,/`,e:.@vРRu(_ 2&]x8 QFQ_V"'vW19aȖ >Mk Gu?`'&&G+jLÜ͠>=6DkKeNtFU\'U:>{|G4Vt)e!n4QNQp X#>j84~q1+@>.4>p >']dhv%aZ% H9x1ie@{V7;yLC9@G;:&Rr "u8LS2;# '̥; Pgn;յ)ɱ? òE[T!d=B#_UohLxfK4('Jtr`뫘~Zǂ`lYv3-i\d g+kMt'§@?XW'Z%szJRv^!!cOm4q Ċ?%q6x㵾 /dσs~ *# wD0:>9JLhlg%XT~%wS,ΑBM$ ` Hm3),]3v?cxb/+{KԵipZ(A᳉a[3 6XK?k0#h%Ŷ--8dPJ9Sm3q?'"1YtK<_Ċn磐~}(-,h;x\ܼuck9: ;vN:_&qrqɜ%#QʣN>ЛjT7t0@5iFl ~;j wJc/\~}3x!˲LiCӾ%@{wrs?psDs*&Ka<85h[VtqO0g+x_/ɒ IogA[g<#B Z''/|0_ NC# lYwtT_;0n+IIAtDE764 NHnh$,m_eO0?ySȜa &6O+;1Eݶ*g$WoO!Vv9W7n&Eq G_ǜr=_uTﶎ%(7Agܵ.㾓:X,>=jrn|xDG2*}CR6y6KQ ")r|RȠii@忚zOk|?Xjv ]>c}j^ 7KՒ:T4 3囱<lhgҺ>"t~uNEw!Soe"كMB, Pa,Ӡ('6mH8g0TwڤWv} .ߢ8>_؊FPu ϢNbTKmUvjW8.:hs2 U[%3)>@#;a`Q^5|+)/?,+ãLٵ 4}i234#6en(M[!=*W̚/݄&ysng2peR.WMp y*XauxcƑ~k2+;mB=+<Ԓ{R4/ ZC_B'Iߡ''rJaQFbxx|}Jo~BU0ze!U}dj+JDž 0c垮 `-Qb m3k"S[ӚS^*4ͬ׺=VqZ jޥw}gjbŨFȐ¸gy+,w{!\k>}/dbN\sDaHED!6#\;H#|ln.I@|ʿ6XOl׫;MxBL@?|a h`3]§"^[U keԾQwD-X /T:x)]V>P' [* JNIS[eMHvhB-m޺NMED W2Ӡ5O#F745 z?p`i8$6JVL|7i(}NNټ:ofozr(>X+)B'pw^}&ިLY"+hvzxs6H]u6U{G~)#e}Ѫ+Y"Wt=}:B)*!?\G/7-be82nSЗf?,Iw>Os.Z)!pdILj} 5ﶾib4t @~S̿,щbʐfyerˡiLȆ.FbVvi`$S%9}&;gm[7{GNZ R[=$9Jl0p H>Jkqՠta&3z~ͮh:Lyx(i8ax>#9- bCة'.6*̊:-\[. Ѣ)XB~@Z/hzZXA;yJe"eCXSۥvo- fԾh.pȸzY:Ǭ%=科ޖ#z]#q^<FVr➏e0<#C40:!l n"dj<|TU dx/Cmg@~wN#~crr'E)Jj wBҸ|d` OAB@a\ZM(Gʋ _1l7GJ&}&Cx(fYPaɤE&yC;R"C]wY&gH< 4{EAK4wP ˠmdb>Hґ!0 5-Hxu=vYowf }ZXB)uL%h.#$DZuC~> q2-(2p+4x\ӨZ%~x7MUM9RQ``anNM]rѫpT=-p_n{]7]tȜ3yY‚-^3nsYXJrngϱT2dtbGO?l<r=<s{(m;C؃vh͊ok׺ `4HݕYBE_rG!o) pIAkH% #/:@(/\ߴ#=b*>Gc mYE"5# C?@Hm XP{-þr巔_%9{Gm=Md@nR \>a;̋ N]iC5T.n^Q1{p-5薹EBUe" yT%O82Kka^rVγ qڟ8lE1I.d >э@ƲkUU]mٰE nNB8C h,xw fh)d{|"R"z5]M-OhW*J]*8ԷIꪒaߏjm4'ǃEqMsԛ*'"{ZQ n0IySXCxثF׊qT>ޗNdxJk:8;C,k9AͻR\3^7qip!kp>cP)f~DS.u|ڛc4H&C`k,Ry|Țr`*14:`M| 颎 (Jӥ}p&KS.aJC7Jj'q&Y8( -KPV ֈve]cl276ԓ&+Ƴ&؛ŋ-6B4Rm~9NCx@pB$}ݷ^N iPOjaDuP6R0 i^NtPNn S[9Z&WU $M%9HD"N,"~| [ӱBDS\+DKP BPtYYh_Z3FPVTk 񖋟]7cCzws|s|GMcHɨ jo/[DEk*Rfu#Wj(w WE&]g. ͜s?#Dmo,>GDRޠ}e'Tb]dc%S/T|NB9#ݻ7s!1h8R6xy{Gx -S<)*W5C>B7qs:WF(UkDtGȓCs^a|5 _ E8%] 0FF=-#Ԡ*Qs$QM\'a]w@ݶF†eIbIL>X.?,P魬xsny-HuOrwÏGQC*b =d3hhy aU}# mrwfso]hjSXPf 9ѳXX@oFwm@!m~|+\V٤j1_g|fT]ݵ ۈ eYk%ޕn!}®(u5+>)ߺd {= ẛ[IJkv9|j}7wYބ۲'=[˂a'Fɕ)5.HX=K.VzfF}х @NoK9%, a*'ʂ59oQdB$y}4cEL*^exN۪#[&2.1 uZk<ƙĮo(1n 0j_`)Oq%L({b7vNB.)CiyW{a|W tz^*A8Yj -G8_e^-A qI$=nt{B!-DŽz~bN=4϶sTt, ]?ϼ Cݶ vjLuYEy/}c7} zC&"EM' om9U#t.xH/dJ {Ґۀ+;{0?A39ʼ#(.Vh)>ܩBZnsݛ,P!r,r+[zxw_OnrP@rbur"\.N; n. |pȰyb8uxsxn!A'lr?n]U`HwYD/AU#oox}o&n2Lح'ʣsB+VRaS,Kȣº ZŹؽGhH'5|w07}Jl{(⦯g{ˎ{@oVHNĮf$=vϫ #MU;\!1</CdћR{ǐjj?EWMKT|Cl_uS»0z6?ѥN64{پ$F2kMFa-/̞Reysss$ 8|(p gw˜ l_oqDIXp~0٥k<*|$ۘd)x7eQm\ .kŀ~L9›ɿ]sm2 -)Ow7F+`+?y8~Wg~YOn@ve`<>žF)T9zrW54[[R8A4[AMTe,m?co-TF02;UUkf~ki3bM\x󽽝iu9\YYw~ԮSP WDS)MF=69(KшHݹ)PL, {HOwժ"gar9ڳ_Z8s, Ei ZV?oÿ_XNeW?g}B )|Ru*Ljr1x_-:R8 M/ЮwOkƑYQ`ޕyL?dA1|֗.?@qc"T>׈ *|J'J #Œt`o<EK3ST7+HCG`Y $]Qt@L=LsTځz`2㋉aln]ouL'Qڡb߁d˚wUVϫ_MEyn!; *]psn|ثu>K)F"M"74X@﯋+w6RӦT cfwħ|g {2HigϮ_~f!2{x~+1nd1k- xrUx"o#TSr8ΛbftL=/.\*; 45 GO`<92 -nHC\{kľ/NUag`p w4rDžXLÔ&Irf'xy N[z+Gނ[%`O굢4}'zŁʓe*(sSoz=7`|i|llųYd޾#ZWy+:Ӿ]jd<N B'8Xtѫa05xCN7]&"݊Pl/}bKq>q-39 `pUEֿ;X񎙀%fY59iayEgvp.Q&s%PeFo@]z #b6Tf&{k0ā:Ea<:`6 FDYt>9uRady ZI 4Ǔ`wm4mbӲK!ېnעQْܷbv1z"sl <~,+,FƵI)#Ȥ sܹ~݄[‡o8xG}Hm3VE mDVEJqe^;[fsJk5g|qLݰBAKw\Cj .Q VYn*_20. kRy}>uR|mKVe Vxn91ڜh2W&Ύ^nYLj#S}'Bc*4p B4o*çA x8wv|QelS*ro٭}K^q|WݱZjY'6wo"՜ |+d$M# &\al.ҧw$$Vywv~ ᎙~"0LhLReOUk>rϠ؃(dMF8S{IP PlKL )󗇯ꬒa6 \BՀ/-OT1dC渖p EMUGV@7f#+4FBhah4fix y )~@f~uϯ^:`[{ExŋGt -!ֶ6ͦƴeJ][a`q+߼c@TU#ihy"/Ӫ=SJjBxny}r0^UۑؿyH콆ȼme:`ͣ87L%6 Ü;|qݦ[ruY?:CEdʅ8'U<с,#|^r?;eggpK|fU=TNA[H,R1n Hlj3jdDsF(hDGf"7ߙx"}C:nxy8~|}~r}~;Əu#3(M =sFԮDNf5 l!Yca(Z?V4W!+} hV0@rZo_gtmHLi]ʅr-~胻-mjsϴMIڗkmFx hl T"8P>.sI]ɼ!^qMXrJ>b4,/?Ī?X.6Fypzrv :'oQ5asPs "mAzBXL7nߘ2!gF1L G\L 74/YqGs5O'g豪xtT76ԏ/9NR;uA5lo&q{\Jx.'5HE#0~ }O%dVaB^T\88ԭ,B-/Cd.Iȫܣgdh+v{BG<";+ =q vy@쀞$'mceQM2pǦw8d%٣ᴄǺiSZ*ϔמcrГd!'u=Uwb%3487Hg 5 *~VCj1ip}vx,ug~ z:IF:eדL (őocc/lC#d}i2 缵;eL֖BJq,P8;Y\@,"]^kSPw}7i-[ ZR~)WUn-DP  0b$muلOܾ_=*KH(b}5Ȋ WqR.8DI x-cI.6NИrvkoK{:dFcoMBy3Z ,l Z0,IY0u>x q5e;Z_'S}zIZF8F#njNk̈3|U7S!IE9^?]mO n1)?fK;u#<^y^juѩKG4$:0=nx|AoClros-(Yj l|z=QKp;* v9K9$W('D]tgK|ǻ{%Xl7 sXĽ/D|eߡOEł ቱsKH4.c6Erd~J9磅@RD4Dmk; OC'+a9z`=?إ#ax; kMV%;Z^5Wj>]iCK5J>ru$3=GYU.`eutZ} ߏ8 =VuNG ۪x~ 59a"0*z8Zҏ{9^ȿ|{@Jbgn!\_m\+%x_| gem?^!*(`O7^ɬsj>DU|Flmz\kӞ4O'Z}ZC1&QE8C68ۨ~T1OuwvƇs%)f d'X‚K3q.w)|438XRژtz1QzCm"Ҁℯ~h49f$VʕPmY51czUt1INД\Ds 2&9a`"'-䭕3G#$unlu ͑~M*6b5r=tM4"IqTad*n&|ThIәC=+OGٮ1nl냣}XӴ} Wqi"a,ɰcSl(+p FnEqW( _$Kx|a{H:(mg{odL@FB#4'n B^7Y1C2"] φ.ͽTk>ߧ/8N|Qqߑ'{Tݨ*Ti^_٣I*. i絓2%ml|mr'|MeCi%ЭQ]Kc[Di`E=3mzh%QiF-]alv[%T*xd"! äh)Oӄ"(H~dtͿx JLwP7 j4Xw@"l&.KņŚexKKkm1|td,L.$h\S\K__O4:am> i LX*_9,BuV7ØlRśG9/Te^ oǨ=ʪw1)3b92܅6M寯5+rxXдIKeX!Jcl1[A0T|{]2k4{+N#@gl3%SzB4ϺϷqKiJ:RR;g?jA9sC.rc86H5<>Rb-O 5zG~9o$X~dZZ+'Xb#7{u# 4b^_WӇq u& FFh 2;bj^RIlyS5^V-*|xKEM:5x1Й3EWkHNDgxDPjZ.2ӊsBו8'IOނg $=P{Z Ͻ\ftuN+4ybKGf eԹה]`^8&t?Tϓkg}nȄC7mQ''}8i4M kZ~My~UƶXrݾ6^&ݾ1`ؗ 3! -Bk"ЌM?haV6X/WbCB1z6h&qQ@%\8!X~$ܘT+yG8aJ'-񕰿Gh `iwzrx0{{<ŻݞRq<*R-NtKmɭ)C}ƍS;F|6ew퇜daQҘE'PsWdu3/(qUk`kv'7Lp-(0OP}< KY"-\:OB9K}tW} 1KNQ5yf z46ܣQ#JϷw[rO}Ո.sG q3DHыs$iݠʰO;#N$)pz4*\<5R.Uf>kȊ^4Tؤ1RPLEMTvݵ zd_y|Gҁ%y4h:Dt4mZmVN.QǙw>L9 rm4}?:/tL&~d̩|:YrOGZG9F+(T]2`5'{@z9aa~Q=JUI`3U+O!"QiE柃|s*YۂBǀ :g52Ql4C{cf+%}J|DhݰӠY0S9Ds)x׍/k6}n zoq#Y?3ӃuNR+,nΙhV&p*S$ټO=2S3JI-! Pp;e 0zQHBB7U䅞qQ +׳4K#Byfʑsx$G -j]ԥدEٖ L?c6:9lpѱv'4 S+#I~lfc2" TC$}~_Ur82E zP\s<-!Oɏ! pNl J0CL)O|@4,i@W#afj"6 0 z#-YF$POo^3^.GTc t+:!ih䡲x]$?/f޷cӘ!<+Qk:&k1n 6mgOU v+~&v;'ᇊFP0e9둩he0Wr-hl9axeU}BǯLNdTI{:­: ̑O6]])wЙbhA}fDk5;tOz8kUzx5b9k&~VQf@Jʈ~D_hqo4uo#j>BؤضSUNv9I'ZH,XQd,Z@IFDțGH 'LBTstIAo!oP*Uޔ% !|xF*SI 3P>_MLcZޗ(Ƿ/e8Z6?}&qx/^ncI{; xV,rB8z!c,,QP RD]k~DƲLx‘~([v ?dq(LrCҬOgo퐿9W!RJ: e^!3z :<==<>u`-ː.!)_PׄUzE؊4aCDm:3U?+X~:A#^p9Cuvmk>-l%h^ŒM@k#|b=d"iC8iaYRܮ1zJAb!okS` !@(IVA1#Cf[JWdiz1{LY‡̴X|sAs0oP[VDҚupx@}XmTaDvf"vngQU{Dr>eE4CB Gݧ,~8~A[s9>-M[&_pn[HȲ{4|zh:Ǿ>4~\CT9>ئnd3{-SwSOOtuc9a01v*Gcyb7^:MpQ̖g m$7g|i7zz^5JpV50^9xc7{-/AxfiW\$bI94&Zʅ Q?L~$삫񓭏Pq : :Ӫ{a$/K?Czl=5I"i4n9BfC1I%|q;Lѥ ~: Vn~Es86]"l 3ɻemKj+zKS:vֈz1KU2z_=ɿ4F'px y(%ܿX$o6dO”rq3XDjcydoK`c;qV\PfCp g_ gVsN7C>0Z"fD~eRj>^J>8HyЫt9`Td ^wW5x 0 FRj8ַsmK8Vv$7~4k*wTb"$+lt*.\ O|& H#fxG B7MxXܿH/ ͸szƶ,@'X| x3W_9_6r'So/ N1}8WV}s17u@T֮i S"%}EPEw`Q,$LCXs>2`^-bTKn (`hWp;'-֍/ ͆tR`v˜p~;_1>CID! 8Hj*)6_wuE^.ό4ޮV4gT䍜ޱ%ozÍ =yމ)Xpᗁxޚ%{|t }`CΩ`9&N4ڈYH;|jKAK\yH)7蜂lĂK>I{Cz8ng>\ +ZB,v!#r S#ǒcYb"0sjUŴ%sQӪh 6WV;l-R&|76}4BƁS2K CUiLI<}V zzx jg- p Ȣi>~tZ KD ]^fCs-۔x7F̓meƹ= L˥7Nџރ@d{A*u S(u{:YV}r@:B"'ĭqn A"IOh9N׮nj8|R_uz:mZ6) j:l{" 0t**P1F'^@HPv*:'3e͜iQ6&`8H2i&(01g/ޟGga_b{BʙB!˰ 0*#֪BN@&ygzwV }ԫ\}QPM_e0N lHD< LڷƾT?|-b[F>.kl@Ixa.n?ؓH!-Y;lO$%=p ]j`fb~8^Vwu>'4Kh"|8Y%/{uB4GD)C? 04ޑ O!<~L^pzz q94ykj D3Z  hOilb:ADsADPVfw̋\%}K1=]R)߹^4idA{T'I֌Xi|1*FrɠOoOw1`Sj]= Dd,2& s7n-0M)8?|O|ssTC=mVv īɉ`S_OPuqL[J.}VcLfR큠 ~X;k-%RXK Ea vFЎ*Oھy~ki: -}˂,Y Q 9; ΄PgKOb5^^T j3t{@.Z8F1ĒT @z#lf)^"OӒ(H6d!m=$"q-ٙ)ϥlܱyROGjFrK9pٌKz%_A A1-Є]nbl\7IOv_^B[Q o=XĴ61N)YW_W| =G,̩w)kafƤoުTNӿc9¹"n͈J$18٨Q:%L%?h21vs3P+a/ډZ0R*]Cn0ayjb,u)T:%2;6rMvzfԞ%%pdžXRfwܜ?NJH\NADcDaT`ۧͲ\?L&SeM7;8a@>ۛ9b]dbcH;zy]{_1P_ d%=E΅FUjqJIW"{s7U}޺Lp`a7˖e ʅRa´ v'wJ (DHL®|?4.IZ5)495.UF+3]?Oݿ_?wWVW-42hVM_!D2o>RʿeH֣'f-M p(5W-y}فGX8 iC Yx~F!OrdD ΀ƌGFCAk S}y&I;]doE'v:,\Kh׭|dSoBD̷)ˊBdo1T~ǔSuT4$Aq_?}M!QoxYR^谫\fls7&=(H }qpD0@0+= 2`M|&rsϮ^oUa*f\ J?*~qٱʼ:f^戊}r{1Њ$kl,Ҡeư<ä}!ONJEy:mg:Q%`?K20a"&c"FB2K"h)U݄_'QW 崝T?<cHrH8WqPєe}LfișVu?],rүefLKTջ#d^{c%pyi\]^13-߸w\$ w3(>r(?d  ;Jt"[ʄ,vنd -9yk{d 6is-^|2Հ01EdƆ ˀ7xKWL#:ǂEX¯A -S^/u}RMNYpa31Ի4X`)eQ`LCUV;O U>2K.vK(LŊX5F[s4fb_Qஇ&b_b[sr,p95Nf(](CyAхB-nɢ%[(/Z{jeڲ\YҽE/n'OΆ&Oa^End٤? f֎Re цh\ R,g7LA ɟTG86MkV(@ӲrW:ǽI.Y[Eq \\0)X7T7l>I1@UM-c%T8]75%o~6+Džt][痣 [_2+k>EUb@7'*zxEtnbds׃ƣENO:tM(y#zzWw?}9o0Uľut+g[viIhh׹~U9*Q{Bn0!Mo=NB;KyCbV/(.ן@ih{= Vzi(yMQ.-btY_dhn"L(zۡ *ȄlWIl?qQ&%lY(zLTQq kԔ7/H+ EY1:1ki1Zqo$KۘxRF|B]/'e5;cHc_NHZW$ϖ*Np '=3eeBO,'h)]?QNgK$vgY:HIV`yV%]I>;A_)i7J5eUrۂ0\|]Z/OM@ӊkpȇCX +{ݿtO1DkCZfv un6G 2̦7G  9e6zwv[0xae~Hi6YSuvxC.=nH#B7XYùd_ghvXKi2s%dP`NioHJ'C8DP+K\O"pP\հc?qBf%='U ]Z81AiB߿=8!m\ MB?ADT_#Opj'^)_Zg 9//on`}Įt/W0nΌE(no 7޸1nO!N)*!L%r,ai=?Ζ;|\/* x%'um ~TG84L67wzA]fд_=C ޱO8no0@]%T)|<Ѷdҝ. KD&m@A};2>, #w4S ޒ@iZʩG% ǒr^ЖF4e@y) %ӄ`%lK G,ʌ4N#CT7Bs|@߰;v$9[-_aBKd)'ڿGGPAʥNu$*ЧzrNEC1^4Kf_:O 1WQ}tt{+!$⫝̸VN} Le[X{5eњ45#EQ`yƚH}T#R5T o7PI>M/gMH6aiw_ցjƵ VA| f`d[__ RNw dPWV_q~2? +aI=SΊ%ȕA nDg1אhay87NN_3H0D8VyC%XcJGC,ͮޣd0D:|zEY#L}j')qoSr[w1XT$=UT.[%:d \h3iΨӣR@,C_z{.~ TZXnmH !,$u <' iFBI??;jD\4n17F, +>67ٖo+JVs6{ !6't*iIɥ'>l!.aۃ, N՝jk#=*M5Iv1ңg(IGN5Z]hW^TM|`f)cB0o,Dv1|>FGӧ+^ >adcv63a1>'1m&@4)o+&:Sƭd`-1b˻KYTr*\r̕-el>QU@It LMzC"< jU\׿B9@/#YV`o;{Z.]O>~{[tTճnFm9LicMb0 W Ю< /ˉ)D9;LKw%ю@JX1*wL.ZnA=x h a戉j~1NҜ~\\6<(\;G|*1Ԓ|QJ-c(d +'ibg(oNLY1zMX /i!_ǹ̫.UO>.?oO;dbk^e@7/twhv؈k5%z%kHlaS`+2L>q2tq3ZD> d] !Hxfn}A߫ ƷUD:q Aᛞ3PYO%uYN{t)۹ eԑ/rEc XIod1*T1]}eǩ%oD^$DM.e}l̎E]g4 aZ0 q>+xQȳO5ovo_K8E/AfϢ'63DnE ?^ґlLH$bj@ TtJ'l_eaT]g|IHzHsyQm/8D I@$ [S?gBGvE&] P͋t8<ˆɣWsjWO3Gʂ1oM^xީ|+_2us2bG#^:_ U} Zp`_Ŕ|5箏f={J!n//Yc털q`nZ.X~NB>hf5d?a_'̃P 6Vk`ΜwƔ(!.|gç~;V1ߏ!-(<3̓zb-woKkѿ4|j6$^I⮭,Yd]IHam+~GtpR**e v40|#L&Ǡ*'ҍ̉TH^mN~yT9H]tjY: 8)m4yop̖v7Ehz¸4HWKJ`7/3WvT oHi8>tX$̀2FF oN|~-Ѭj9zCu>dƄK̼\,j_kWPG wژ'#$6֚~tu8cĭ>O FW ִ`esr2(ב Զ3pY1ٿ ֖>x[_Gy$,nPKo8Y1a.wʹ7C9D?7iZklTb8B@p v'cDRN+ȼ=.F@ٛy_YR( ;}· E dj%l#y@EVʮhHO—''QޓBYrHĄ1􂌗SzoRRqL te܋ygW+{\uHT#:P)%,uqlSc뒫QM9M##}ua5^w1n>~w%,Z'Lv t`I9%/l6d3 5NA>T6/OutI@9'E_Vߘ73sDkNEMd"a\9 &Jnd䛂Վ q/Xݞ 4W}zɘ MNajMGbk-nC4YE;T4 lD2=տ2?3]W5hs)ލ=l kH߃R-8fШ(fЈVV:ىFލYb6_Y _65̋xrSגItߏ5kOÊBr.//l׬/M{ Ԑ`Irg^poOEs(bN:߯A2@_4y9:S_( O"R.I5rh4HʭM]8YAp9qq&5iO6ն_Q$!W@n&*ٳ+bh].@7@Eֶjfm)X3#3)iMy v$A̯ѿoadB Q{ 4ѩQrXH &%^JDf!xu cVH!4̀R^_rfFFsfM6 `NU0B1MO\/޾H!Lz n=|89ɒ"1>/Avzdc+ ,ٜ60I4|C,Zg/dN\sDNp&MWa(s s.K+yLSοhzaH|D gجi2-nzvQ>N(Sk_hydrJTYD/E2FDPMow6_W7HLɭ9G!KPG̸/>vhʜ4 (:>R?qiWXM=V@N^xs(B+anyL8zk|CQoRg*;ag|!.DDUi}vMGOL_F,P$1c@$.XD"!|+i^=_((LEӂ:_̕Hn@0^&g1{_Ӷ L .nNDGbv}\d|r WijB3d9oX1HB=( Z2lHoN?Q迋d[Z$ڨA~!12QS'7 fW<&{ "K'3wC=tpDOnKTH0}T#yyh|cFž.yuq\^r=9vtg>7hDK iӻ%fws=԰CoSHZh6sGF+?)#`.hP2@·VnoujW^#A' `le>f$PH. tChqiWb EL/2_VFԧȂWGfkGhe:QU!2dkE/ ĸT)T L`$AW+\颇j>"%  {ZoT7cy|sߨ̈ :V9dvNY!"s'Y|"U UzzOr$ IKQyQ&^ȣ!I>*] ̿ǧ8؊^F;wjny"IQ!T.#?FGo ؓ9R0tH}mߣU4y7|,i-sSObKQ3fTsX<֏K*mk& Q.oߪ]cQc{,eX}EmmSA[UFFvɸ}%|:}h|M"ѧϩrqmT 8!>B5d$}s#&^sKũNùw3̧5:2;dSw~385e|f.I",\ ߃R:li&0h95MͻIH(Vò߿L Pu0%f zR=sr]e Z".֓a:Ì ow I[>Wy\'6z/X9#7l"R/3z엜mܖ{JF)d8FFq@Oм$r\߲O:lPι&2@03~҅/ԃ㍬D%= },C ~!6#BC+ ur!pnCLKK2áb XO0.5y}nQ_i;FBzc CJBi+4?rпJÊ2|_0+b>[U'I`Ќ:g"s">%+;3ρSf&&џ2% DE9.w*HфA<\& !ʨ'#]{1E^@Bw_3"g ^ɫ3Xj@QiFb%IYNlT6ǁ!_s#ڏ+PΊFw>m0ĪAhGmضqLY%E^Kgjn~0OHj`U[284ضJ-~TO\ - Fb6 ~z@z|1E_򹉣3g'E;Cb+.bR;Țzq7RR^hCcs A \htH /4vER5_uo[óL11~5xA#cϤ\'8Vs>y޻TBz\̢oz`r|CrRcw]K_$RmSǷ-9WUo(::B'٬U=Lz>D('dAW y+^/9nd[,y p>al1&.(̴lZҍBҼ!BsyOfNܿ,z " 7im%tYlirT|_Ժ5<_DB>aUtyFmS/wRz^)<ǣރ!;= eF"41_HA,y`Ԍcͫ7 'ĕ\QM!xyu~COv.rT_Jzu[7gOzd!-EpNQ?L?(`o O軏Rb ij8QōCᱬ(F5.10:%AruK?zN}P ]X_Z}sMHE lsBW~Ǔq&[砭^U †q0_Gƌf'J;Oן)/8Aaa%yيZkD}a`BMzdYHޟj,{(P]⑆#؆۪G$mD ]%$V\dT% 6 -OsUc,B"mC2L*to'h}y"\Q}]"E'mۅ]\9W93Mj_kc$;ѭ#7^1fֿ 5I0x8V)|4RprK|%J,'`T [2LD'`h o{;QD~!>Gs:׉US1Ciy.r U/Kk˱R\&j(RMzUBi-[_0n!Kz⅙mبo.fE?`'9@ PÌZUS5l>3;7yg,*o T;SW"Vޘ` 2`Դ܋'iIzN&6-Yd.: ˶.."- 26H"*kn" *֧s|cxoU>O}bZ[a3G9v4Ccu/J̦C4Tl~ZD$|oPkX5RXFWW`C/T_ta?b :̇ L1CHjs1xM\ѩXU4ّס Mu WxEaHwm}SVN~{O~%t!(ToUl&5sq5į$qqbǤ#!;,x,9b;D㋵pcA}$)XXL@P2yZhpR2!lj+~ ?wmiGbɭ46M_baĿ =cieG}t ^_:VoV"S)bcUj͂;Vޟmߌ8gz߆,cz҅n%z8 (i/VJ,͌;4SNXê̢?Ӛex+`gAv,]_ ǥNau6(l:I~5].Yɱٿq5vz0]tFDӑGF:i u4Q sX:LVWOdItԈe3Fϭ=?q bo8q# {XcͤOZL=<AyJ{ˣrn#-SyN8%0%ܡD^]QĺAtp1 W[TZOf&ZJ<"ȕR;/ ^s9ٹIΜ#&⏢|7?s0+LHhar&3fK:2<rB8N*2-k\|uU_nĐY<q9kWӴ WWs߯zqdmc}O괈I#)O3ah`#UKXdvEoRKvZՉM2T#_baPq&pNFf>TVH^1w#'5v I(A3Puj輕\(+xb 2GXaj{~ͩ I+FIesbw{y]h Pti_).0~xvAG2uPBn#'5sz>lҋFi,x)Lc6{\uݑ~J Lc˛o@n,SDm+ĞzLW=1tGqJ:X*{U͟s{š&j ˊ_w[Lג R%[on]D딈{Z1=}ih?  aɔFZ$j~jΏ :1.)HJ}<*W.k~-b F=,Dm^hW!L:fNuF.vGv9!,GPw2S|,Ph."L>v,PϒRE2W+u$+03/`GTSgyq->`Zl·2̪$ӕ1]\3'ķBbO$փ,`uyUvjI:ir`a!=d-oqIЍl, Jp2 tjL) 1)uhNH4{RYLN1X.A\* ᗦ8lX}%L!- +ْ>5,HAŎpa\:3c$ϖa1|4+xyo_Od#Epc b'GwZj}.',y}Z'Gg(p&z8 B-dfc/qbU%<4bb}'NE <>t#@dV7i+hsS H"Η'rsx[g&;o/֤Q5.D7~lT2Nn94b;˦<VpCu}\7NDpf$/@[( 5VQ(No:XDO8ھΖXe,^Ri^qI>õePYCi5*tꊣW6=62q$~}Edf\y3RJ̰}D;`rjhx,oE.T6䶼4e)A)VdڰvfN@%^*/q+fՈ"_v_bx񉕑W8ZIn&L@&>/Kj1ObE\Uz2*[줹oήDz&hpc cR-E)x#N[xKW_F-5]82 qX& s@۰\Mn%x4&hʤ{Ǜx_xX}B_ʱ8,ю.QX$NzgbsB>{5y Crkۋmbsgy]cNt}gZ=%JC}YX~z^ XPMi.@0y#jnt6*Ͼ@wL  *śJK1^ə~?Qs>VEvq_2̈њx0+"y(N?ZbLnqD*xԉN"kn?КwJrG11ӵt yߩ% :]Ot@u x'Ex7I,e;A;vK nM8mRPcMiWqD8- uhN=XC Kau%dDKhf@A#A_VG;WzHs'dCb i /D@{"xtU`$OD~A\|mGH7;r L_I})ЊJ B6C,]%0,!VM%_HJ!ܿ7u܎:_Zq3ݎǻon^;_Ҷ(X,Ƥb_PbIyYt#Fϧg$M/,ޘQ > ˤ6= )(]=DsP#bƏˡS6p_ w >&fؿCTdXQyXn'Q1A~iNc&!t3~W5@PFz!4m]8΁43: 5nuJEoUTJh2~PI |SNwhԏZF#'TH .>Z<8iLJaIsR Q)v&nt5L~.j+/!A~sDm% VYQ' \/<Bd[k랤ZKtv`U4YI! Eя0Z!QQ,JKY\Me%YXJJedW(2pa55E]18N /Aהּ056YB-Pk+j23waɄ_{+gDKյ*enWPIe}!ltХN1" P ]>[uJ2"bW~rrEkWG [QoyPl2nOgY7߹᠈uzcL엖+a؞7GHy#/">d{VM:_wdogKC-) t6,Vc$!l. m15XyzMP6#*e]~A_?/S`p>]јPqR֚;MK:561Q^]3OI*EMrm|$)P TʲgĞ!֓/CH޻ BHm}Y H:_O-`ʟxF x:i@?!Toҽ :P~lEuH\qe ,Ζ;);X[ V.w==G[Y-"z>wowzQ@og5e1FK𲺯 F%mUU7`=C?U-JSbk |3Ì-TEIŚ,2hAis& gNZ #/s|$6w|E/73̽<5P-ε9Pw)EFڎILdY~ 5ǣ7X^:ӟ֝Z.;nm n8AP:B|4JV`geG{`%Z? &`%n W3ȔKEj~XL #|Ih:ZakDdcD5kȿs0t]>on-(0+tI8SiFNF_:s\g{p3 ?"%'!6e_Ikox},20 RD%+73P}nbхmgkX(Q) v_Fx,d1hyʾP}e JwԤ}{O`&KJ_d?GY>ܲj!yj$\f(ѤJ ß+KQn]'kR`0a8<˼eVvA~anu*BExcPMj6XRԻdhZޣs17@Wd B.~n;zglΟ9/HdΩ /{{03wSC{7bz)σ)jVH2 1*KMR I[ 0=  I5+éK [*uDv($&Tkm<i6D>q=(yYʢ4]ro1pcnKmUP24 H $F.t!$jDXecl?(XDٌyIګCK(Xш WJAY2!P{Qrsբ(ymv+eKe:q$JQOƞɽZ](R`[|p˯NIMU.@ uc*K+ 댰/}'ۆ >OޏM64.wPF#c{uXC{B]Q%_,PԲvyٳs3 Z0/bAKe2gz Ғ~g^ ;"rtǫiYr&p#cG5Xݑ-'{XTx׆kE ވuVt!x$Լ/;J^κ6uGbYV VdƧl'⦈ sg%ҟOO6mg:ɏI{NoXPS4㋑ WQe -WIZ?D2tH.53L/(^Ck)u/@j3h&%5 ύ&ʋ(yPWo-1eыfqPNO^{/12r,.%JE|1Y+^ՙsw&pVJRq˸"@;}<ZYAĕ8ncCqQ:e+&Rux>S)WPɁRa;8Ǿn"%ԪУ}}nqq5ŋfq)o@#n} ރ-ϲ&>lT+IJ&UE>:_kd- V_^V*7%(ʵ*+ 9nVqoZ->T6^Є3TR+ gQLHD'uCF:[ S(ҞŴFVڬA%:>mpP~`) Ӻ2p.]U>1v%OJzJKD٧Cku6P^"ڋ_}6ŠdGsIR]_e44*5[_4d)ZD!_W(#A~/-0A :>ÿjrFM?Rϛ4!AeP{Zg`W{^@ZO jփ,%%uZuH#X4qB-k佫ؾ `6(SؖH+;[},%")L|zIs&Xگ7 Z(j qVeXLهGp ,:Jrߔ; m 똇/pc7x$?@[%˵uu}A `P9@]@$E.+)eC›cfkY?Z8DҧXFZñ_}?IiP:&OVH_a^< +!u`2(wY>lWY.tl9k/3z8ֈ=@؉\/P&`]/1[~Lc)=T PdT4$͏f+9rE'/`=t)0)ꙉ))uK Fߧg%}CN_(*V/x1#+ )&̬SYlX]0]U+,R *:CRr,]KnѢJ~@5Aɦ}U` ~VN\lK[!~; (\eyN>)S__PD4@C(y8|r vǑM Jh˥ JFy_A3SZrgSn)(sÝY*qڸsZǍ6I-?-i$33B O9rYQ!5MPC5eXZUkou Ez~7Pe&(Iv[3A"Hn!XHUSpLM5PURVyG`/-[Cc B=5Z<8`ao/ ]L |tzB2:Ǒ|#?\ɽ>e*ۮRaf ] Z#JmS!G }CDEi)ZdCKaGb?jLo;0ᅨ*0=zvBgK\'ey,67o'o 'V7 `Oj;PIJYZrv&nܯʚ m“ycg)&iErrB!2={uY{dM]nG^+(&7kBմ&CJ(ak*zvܪ&tM+M׍bª~F! PkXplϬJO.H=,~I (sURJcioGF5(Osd &tPoFSˤT5`hRfkw>bc$;,'GT`1 īY5ԇNXxNS LUFVASNEb+/B\G&?b"1d+e5!k.wP$@jP+8p`yc"ĊmSs׍0 t`:DvdsǕ\s#2sK9CLS^@`A $oG]Lgkp7dT<dZ8J@΁,+@~#/wZ@鳌gf,ah/x\ H2-%rWMEnR<;?;n/-{NHT%D;pso#@k.SVw=*tpűMD(\\Dyqvm,\B/oX"Ρg59n "wӾҞY-ˤtpu)ٰ_:t-LiJdܹt+$:q`MOU-_O53[/I&P`3jV<[kWs̟J^Ls|ڰA+z^<|bz+Uَ,RJo|P'um8LSkx-Gѣ}Z9=_xg,hh04=e\S Q!\ $>\UHpW~O ۪an,y 6c}tZ^N\Ԅ}S8Cy݅XX AxqEG!m0yVicLq`VSĞ.M@_ZrB0Oen%S"MoTu.P3p3?4 ͆GAW 7I5+YԤrXܡČHes5 6RK@/__Ju˷{s%6_Hj{٧}CH^r 0c|E49|K,P I9@/[}pԇ}liAϸOx.y) YMuKnLJ~s'H>w" CNt1*F{2*fԮB8R ?#yP*:<.'4ط>Z'?i^Dm>xZ\7&Pg_̲A" B)[K0m.u6Rʑ7U-zxaॅxWaO.v ɽF]#GiۥmjěOWKey5?%M?ֿ|c ʈ4B3 c+:So_-~!n>Z2Voy/?2**з5ۤY?j}GEl&feH3+N dpFGbRsk x_y;6>]Ô;4ňgE?ƨlm;̒ι$4͢("}eDқR Ͳ| )kΈfr/YyWoW>Ņ'6CbBi㽞#..4lQm#eGmR|8ZV~3aVrH't~ćuڈ,Wwsa+>H(V>pmk2K 3:z_zQ˷KY#._Wd[pȈCz$K] u/#6qhykl=.oY鿃+XF~̆+RX obl4H׸jcnQiŒ(r*).TĈ!XCÁq7[M٩V-,9S.QS/q+Ji)m"ʇ[]U0pDr!Es2NDb}̖:N) V~-U놀|)jMeA)=q殐lj6o}~_m#<}%8kuJ.[OkDQ2R(er#lM\`#+6j9i`뉙\h3QS@\݄ȫy9 (K^xEQ2kׂg\a֩Y~',N_H:c8%F[\yXxg1,ǀ4'+J,6;NF:]BR|!Nj|d|BUU_9g؅*މ$/;麊Ѓ:-\;>yQ6JWuqB(cYg( aX,M SIV}k}6XCp=oܽ\o7>cQʴe+6lUυ_U}Og)٩~Z_a?ZmӣP&TА2SrIY_є*0 x9yv>ˡRY,„&-]2-ej&+C݂0>[}A*$B`e{# tp>>:ϿD!p/?'fl&bS7Vg5yQ-ijlmI1활RzS#d]GLz-Q}h6%AeRެTV ]+S"Q/lEJwvoLu顡;Pǚ--:tx`%L3U*qEw-2V͒P#7hE'8z 肶VUub ߤ(F"#ZU#:|2ȭEljh`?jzgݍ_`6,H>5SDr͈ؿoqfGA#.7"el}"#Zc kdye ݋tOLޣӗ FQput ^j"Fv!/qæVvCz֏'ݯr9z61KUz_FH3>>Ka'9H H}? |GZp%T$FEdNul= !ܲC,-\`>y ]q?GmEP,piHbCZm>u\{4hFXg +s8_p}fݺ*6uF^*쩾4h~.l=C=O' P?p7ߍ鵸O‰S$-8z87Fk)~-:zu,e*+Ƶn:)Ά+c#wXir]^-Zula2@[^ ,KMP|L~4cYvW ZeŀK˔GǚvU% /e@}v}Ϲ$# _ê`D}~>Ӎnʱ>4-E $5X{oGq^O",J#dܴzgB"}:8mbXT`;ϺP`x ExƸWm (hVwgdșf{L AY[hwD TCu1G'^}9טY yJEh=Ai^%5cyH A߿HQ{ B=!9;NnR F+z+3mtc6ʾ'I f*y0hp42Oq+J[v/[~ |HjteFlb&ο,&H' ~n? cp9d &.@K9R;8;jr C1|a}5RtUyacLnfEq7U@?r^Ò̃~kz* SimnQ!=q%3ڹ&.@,|\AmI)CH70v5a@ =pi#:5>3R|7 d*cǭȘ'iĀsYs«U _ D Wov5?캬On' 5[IygIT ٵ)jӼA퇻&JrjH zW'lcABI<"f7uN*%1w;ץQoINrZEwfCF<}$GhY} @.i yrpOQ=>T?,v0:SCqwλqW(ݧIJrd㍄׸d^<Y/>@@mE,d-^N@Mן%ҨUUixd!W> &Qɱ'7tqgaM![&iZє:p @qاW)B(? ^Pq eFtk*B:j٢o\ǥ43E\T<y@0G&f h fTgd1|bysn_tBΦ쓀^ߍ3t+?qTI}!꽁)Q|3#{C;϶Nb~?8Qa^CPj}@ɋm5OڮWF'l/m= ?J/1Ot"Vpg)faЗxa^,_i۔`% l k?Au LV3ȝHl-PmrDs%tOO>[hSu3 HVl;"ʠz 9HlWcr.c|C0|@m]s_u]-yvcňmcS+LƘü}m߆ n$ã"lNIi}} ݈%;[uc'>@0-qĿ4]eTܰrCcbj.=WJϊAVG:iwĄo͏qmAMG`TWh>zwK~oA5+ ]{lf;c \y &6̵VBvA_8p1 dlrX =;u%mV$~9X`qj}U.vׂ4<`lj7:O?7dl.νݧ̍.>I,ۇ$O-o}vET%>MvPWy_E)_lEoWK@*WK[EtyT%|U=viP"w$KGÌw;,Gʚ ,Gi]kkmUޙPކ/H;!O KRCxW?*}yeaj0U\哞qQ96{Rb_c7aimzыaf׊k/PV-slEqExa1 i>mq,yBc~f({lOnWiGmV*'n6zeXG7o߭f`}=xRYi<_;vؖp-kҏUGn|%zx)BfzoKO|uFG֢¥EQivX@-nQ?LLKu\_z!J T Fq7ߪ!0YneB$%}-5H)/1mmJI S++Fxwt<ʇy{D3PƂ3ärVY< 4zݑ>0EnR@O,5bUX㷰)J PDfw:ǚ2.9CJt VgMXZ-`S6y[Bh⿲X#됝 bQN7AeVz%(.  >yS>6!7΍:̠Tʹ8]jIv᩺ EM YK͹- OR3[9!A#@%ؠUL,T\O!OEXD7?YZ&ۆV:Gp b* dͳk,&S=n6Z3$#ǻ?45^]v|<' LӦpʰvLTm̔։&EgSIo~DX^%jJehaiﴥ>h_.Kpk>s^U;J|J.^B `uaz Ndu?o" ??g"d BtJ Vf[hged(^_Wr;aNe2Ae2OM*ھv3CC3Z3lQGr_J] GgZc{Mt`F7QXj7^ B]CN uqFie&x2I:m]bXԨMڞJO@ ?Y>L>D)>mYC:]7Zc|CxIDקȵ(]Bj}vBq-`$.h<;H w?kͳe t%VԺ# nFbIY7نJYɮ|'pƆs3YY Y9MT =a xDk}uIE;~d?- 󺼀#ԫ(-8F@I_:U%t96>r :Gt7/.1ViS=l`\.NۈKz\+H%*$毩y#}Zڽ[T!#庽O`v,7`,e6hn\>Aq`8b ݈1l" z4߿`h,匛pT'pƦ ¸oI>$gˇkd`=RL@QF#2r3n0Ark{*TslhɈFVJ@[]h[hY's؅h1imElhY9$`~a+ %h`F2y@"wJi? s'е'bN ry!f0 ssǃ5ȼnͬ{*B37p 7HqsX`{Ho!> |D@qHRHy|@D$sIQn)i8W[]6_kZbTPai0e쪦/"vaXgaDt>*Z.fV,ߐWRMo[:8C[j{tWf*v@P n"doJG7s`yfVeJ ).7wJ ) J?%58TkqUTHg"ЎII/Ywtq#p [M'al\$2ڢ۫:3ۧijY/) Ys 'ꂖN)wR>|D ,JE+1_*p"|E%kmnrzaf@\tgYUoɜJ r؊,9R4a #ɕq=EZRdtwy汷_Dd&zJ͕Gϛ|\GjMw& YR9-L]9Di( cfoif8dž C?OUpV؂mMV-UHI  -L-v)j:6 L)#OOOLȷ%I*7?`Ⱦ~#?Ν&ջ->~1:r3O,۲8IXC$VfgUDq]O'/IcTmYG\dnYL c~\K]^~a+Z[& 6 1ɎeRyL ۃm[M&)X ҴOX+f{%-?i<"2;DE7u HOdݔuYdᣛ{ɒ4jͭb]; ۷ m:]/I~N.NIc7kŷ277E0, p W}^B#F֍o7݌H&j29N7@ h*3}@(ޒe8 1{5뛸8co[ hG|WdRe(0sLy{)w,,Bh70o#~ /rON1m/~~@rSC"yfI D+ef0kNo7o=ՙ{]ݲ SXe:e-7~W(T`C6y;#M* S+9L,'/1~p ONgXS\ݜM]nOXķ[;u Hda/Tإe,ĩ墘h'ҵ"DGwiAʁrbNLx>~[/=2HYx;ϲHb"`U < ?撼%fɋTzaو{W .OϫHy"bˬ8d˦M)ȻW^Ċ)/%JmwLYsLG+~į&^C3Wy'+gܿauuϟ`c[RZ|> PAt"Mo/qe ]n$hƙ/Y~6'cXf)׊TI~WKz:90\~d"AKGrT;\t\Ny2QrY^ /5IZp4]DZ즬!JW{;hqm%]͕^?'ckwfAU~gtzRk227nRŋĵs+ NT\>?cp?͕Ue34x^e'T0mF87`Y`S o4#-Fj׮(97҂;^GK1ٴ'ܝU8] I$#Bpu ^dž'66$}368=v ҢBKN"?e)hmzcWJi}h2|Vq\H &}HtR^BoꪗG[\ kT >L%feTfibl`S9Yz'_ F*%]uLzx.l}|e8ߚMmH?bSFr,fb}f9*8Ifmus_q4 ɺLL语.Y:ѵ~*gJP:뭉~9vM' S6Wm=v뫾ρ7Ǔ FuGsMzO4O3}WU&2lA'|/|*ˌ6rjrh[U\s֕4:].&}}TN,yABOࢼr-Oby.!&w҅Ox\)tf@c (eG稽{*Ȝ<\qK73k!_%A,Yj6`a}/e 2(b@]>WL<}$GV&D 2Ɯ"z@9t{c7YvUttK`ixA[e6wf]΁a}8lh kP-1YXҿJk)snCSz~F0: ָ+ Mԟ1._j҅\-~ۀ]));82B{NA9Ȕ}BCdtPH#BzU5К4ﵽ/a8>k|Gfzg}8EYqH)9z%\f/|R$!XayIh|.Ia)i/Dc=@x}Vpesl錟gOMnC Cm.rE8-bR`مO-C~+T*aNѷ*L%i51uLuY2ߑ<5n@,8 Jw%#|n=Z>|;]Ͻ^ޣ8 *I+scw%bqzbo'!T9ִU>Yꕻق{20wR+"lcT H&}|F!Ҹk{ty1^q;5'-A'v; \tr~n;ЗO IPg&Yp(PQ<]NmD wZO~Tś n1#@n4z+|*,Sɔm $Mq )[۵-F9|[57yq,y^IU4Q̉Ț)®k^bj^~ܷ>.f ⎭Ts V&N:qD û^)6 Y.1Sk׌ZOM1@_瑺RՑ5ކGL |R|usy-AâC彎?ӖV{<襵t~G7/88x.)PGN`\`T9^dÌ[p1E]m̾s=,9Fqgi>A~ $bS<:p @1Wt# h!Io .fA S,=?]yp%t’xn"rG$M:7CYôLRx蔅fRk|u-qe蓦~*q575P9.]1EkUO\_>}k**ܐ~հ[T¥UX\T ^ Α>;#-X|)/ī fƼ)݋GmYhVxiu-'$vzKۃKGFޔi%*u[`CV$G__ΧB8(Nrkಢm*񖎊z.旼[M*k&62 7= v_Wh pU m#:ܿE^zf&yʛT c&/C3R#}@Z>ye;۠=PX{G? KSm?[i{R)7PHS~gn&t)l>wymKkgX5Ttg!As/Gnd/~uҡiERN7a(P 'Ukݨn\;<+UR G;0؏Q_H Zps_yČԋڊ0@WKQڰ5Z%R$pp-jZ{6;o8a( =UಛX B#GɨӍ&|>d!KcmNtBr!V? hgړ{LXJb<Bzz"x2[7@ $-K:tFX}Lq澆@G5mߠ-IENutۄ>( :)8l4joWI@iE7XV;j^R28nVErOx'uHcO`ZM/@&[j. 8l{~ ˝MOny(M}oGSDLl%ȪiI HA3M>(r8~ǡ^2092#3a欦ͬn=&Ts#o4K64rCقN 87fdfIjDg Vdg6{h|΋wuΙJ.Jn1BҚS-rɝc5R.(Wtͽt!=Wvʹo>J`k^UbY h:8S# ZQC _ﳴbPE n+ hV`ǔNBxWAsu%<7k'URRWb|h椙 l{B>ͩm/K靳W|PUR*xt2is3lv?)vlSװ=/ -e.DӚJaDeoɿ$>}?6'Q= nTV^Zc]L'pГ56-{::zEMU;H#HEh" "XLwOX]) 1َ2/ t6~At; Jes*P88"2T G QUݥ);~ję`$3]/n i=eA9|}]A]ο: w&wd0gI%J7ۭ՝G k ( xI|U(ah8>% јhIQ0h/J{l/QִNsDR<CH p?xyv&`BhIvC$BV*^(UXl>b9@*])W7# sBha4kx v?XBGxR מ:Pqf'iYԥPeS0I ΈfX:o-g% 9^@2a$wo0ٌ]uj]UXv-}ad^$)_o6:ekM}G>Ej`;"x=hOĦ5Luo87 ;5|"ЇjxZFu/#w:'_D[}0h5$VO/=7a7{,ʫ=RQ# !|T0(gWA-߇1}Qh #ʱxela[djf?2ȫ\CbpxDKIZ/ItyGyRYrB#LbTEGgI6Vlk˗Zo$L{r m;[TԒ]m֢"Gqw?}9^8/WM7uH {qWdJ);.+XNRx/̽Jtߖf(]=mE8DVbUfP ݯ6Z5(?d'VX^WK@^iKTǗێJ6&^_OqDžǖW6V!ROc0G g/2zOu g>ZXd řG3SsMß\8Rɓ McQ:%4b~M)`S>mُ8C֡7x4MUjQ3+&Z !e̹z!y{b=4_iǒ, SSv(c[P˳¼䩜  QVb_KE&QR_x3p=O_*IM(7NsO[m3 dǏݧCn3ԀƦ"$z_-I֗l%.-M5+__9G(84?G+M"劦V#oقjzEKXbu* ,eXpyH#}MR("@ [lXTZu!_@<*s^7|qNqk32Potsw}c(yů&b8 0 טPWHiDT#Td-^Eq᎒ՠ fZcR9F1k2׋Gd)̤uz0]+o* >nuM60 ~JvF},85Q b/0 HP)+vmﳊSB]yñ-iAS C5G^P$jG)h_J5V'@H;#fpOWl;9 06y˫F'$ĻU9II|X,3iTb=~mY|+쾽~ǝz /WiZYz|BٴS AS4o~|_gHwƗM֌Z 768~KaL8 s0\˷YN3H[/~3s4/]A r8M}>SLEǍR0^g z={̻9'1}0Il'QG'^ ѵ"/NDuݑhݲ{M11ˇ(IA׸o6څ_bŠbο9~>d6uW ˩$ R'j7^l05ᘸ`() <ü7@\W)(kI;8y$ C9i׬ײփ+4["WOUI`~` -AYCDPzݍYƛC(TL4mǟN׺Û #"8ȫOn)VB@)ݛh)@f}Dv/Tc,cEz\=/{*e0r58V<ϓabGΠSP. 6@ e=)o_o9H%yA`Fض{ҬGwBx;(_0Ee^߾E+Er?̃?[[ J%<_s#fw/\nɝƠsi"#4  |cvBe~uٻ}DЙ;vh2~ǡM~󠥰a$>^^QHцIVRpj͊V]}bɑՌ^~H2 {9lD'ldr,o((vk'?`ԁ_wRjTA"PHplO ֿqOY$3,> *b|;uw)1SB7X~gk*y?$i)Z/2n xd4J<9H$( hԁ˔%:ߝQ i"f[_Nh !٠J#xSǢSFa8e{qu5Yw_ 닳B+k< ;9D/V_VfF*>ktdƧa~%O,} ;bg7 ٮkSQY*] >̏E&C"iB04?7~}GTT"%Ng/}#LhHSه 6]D?[ g[L C+nwoX_V"/Y$WGv& MAn)O x}[@.*} v5/]!}lz}!7]}x*bWPwv}Q }B+~'p j|`'\xm bhꆠy=cһl)}[`mQZm߸lcԌ 1B4<x뜈{&Yd洘Ķ)Y7|.؏pPw#owʗq-M2nU/?ХvXA"C1Z L|>"Zo9H,+,fKGr`7KVs2C:AmU6d\wiZnX}6֕t4qqH7vO~4 }w8f.ugx++z[a?&Tr7T*%[0yBQ<|ɳʃ_XJ$.s@|y2FdiW֡ig—9)8} {G{*Kn9)aL@B[~N˟ ͗hD+6CcyF,3em>9X{:KFc,pb~`ىܽ_rQ$R79.G5@sq'36ǃM05NZ忻i^iaWu| z)a3a[D3{p"GaS,{1d  95S5h~54MH٧W7䯉X=`:p{ I:bu`~voJ d~Q'TV*M#n?kָO:E6pt#O [/Kr' |ȇfa(^J[Nad:sܾFKfO}Y]R$,cXX uZ#GTRխ0Eo7ho5 |0!Do]՛~i%lDGƲ8.~ rͶKue%ݤ V G cM=VF4{^ ᐺ{Ӯ[ ig]]ܩ#تi\%5 }?-ԁq @dԙ>8Mn5@][Sv+2}_X7l.gfwu<ֹY4=Co c(YQQ{/e?LRE%ۡiviExN Sm3t uY): B gn()Y\'Hd -%#*)uLWdZ}}k"U<#<ފ?-Fi2;o7BNEM;q )R[Dڒt@K?Bܑ/ֱ @,}䩿ih gUd}G"j Ʈ ׊$ ;ʩU9iy#R*YsC?WQ|xiZKbz1H/ݧS+{7{>%`'=sdecy0ql|"SLa89~=6v[3bm9dO-Dbsin2IOwsߥm'h9>X/bWڹ@Ds)~Dxd/n-"]@+v>|i i .7*̡=H$YmlM2gP gj' &A%wlo֓:9d_3<.ȾSNs~,y(f7*i$1O5%4V|;j g30+_6yJ\hk6Y?2T)`"55.Qt_0I5ScP$$d:>G6"Q5%NpT/\9 `i6)&deޘ :+e'Cu$@c }9)vcAuD[DoKS|#k)a{>.)14cP $B}c2,al?%>z~zZE :/(C K0H' St 2PG^X{ '-.aJ]OJ4)]5y]spz% mCɠT<g싄= KxOFTFiPb 8h-~#߀n Y.*W/8INT"$_nA="&>l`ư$}=NYh- ex:PE\E^*d#ԏb^oq(+Q2Mۍp]ij L3Öw7i<Us>q;Џ T#(^L"":adrO'+\kxP:Ϋo{҂>!<^>tW>ڈaDSQSprjb)l;|\@QL}|ʊNOCދ"1Ql&ggHj}LC߉z!sa4T;z\M߿W~s⋕.<7)0Z׶ K>$*i c,tu|lOo="SVgpQ''7Sb-U~'zj(X0Jl#:WSkm!J bCH,|S_츯nH)9/_@jתKst' E2, n/7ᇏź/0f8$(ΈsbPԄrͮsPR]]V.;íd-rD~o+|Įv譸:7B^FI|a>L&ڞ^C>~jXn4wHg>]UIg.U]&^r?uz2}'x9z=fW{m3֗j G ϔY%YomqoS?-@ J[ûح p KP oJy4gw,(Enҝ Tߣ6h/)e7gj@xrŜTJMX$YBI>jwصAOYV~!lHgaUqqq@n#SnJgKKۆÒ쯅Q[=4a{Go+D=z-Y 7aޗVb/ ~NE"5й w3[6{^?SIh,h)V 05ۢFىZhN=M,mf@IlnOu{5pd>uoɲ)sq1S@*< 4#+Mx ŷophU ٿX:8z <6ws?GGMf+}>e R:CU\JqK6d)LڻPz ,e_+eP|$#ồVX N ^~Wzйjb= JjKIȎ|x2 "oJ0Իx_ 8%ڈy]rg u:"NQol ƨAd|8OA9m&t+$@ Xo#seZFυхUL[0|{%ޫ*fs=i6sCW`7OgS~yKDKCm8ߤ3DhY\l|6`(KN;ՈPQg2gdr7۸^ؠ!TuMcMΨe>q5Bt4VJWC!yCRR]{m:L(8*7?5bIR6A&SZn) ѯͽ1Ϙ3(;Q{\ՠ0rs{òsE:AbSi!ct"7%!M3_eޯOZ(OZ; n9yOa7i޿˘(FvJtʊ_yPk~ ^U/:DEz+LhЏˢ{0.#X\ S_}{#Y[bgdlrE(SF*y}fHA|W?w_C ޅ%<1عq_Ƹ3Nn_iii+ wЅdhh&Ab>ٜGڏ B۹8{gT򥠠8X#>`oͫ~G^Ykul\y/-|C"PG-o.=ưQb`,A_ {5lDv[.Xָcþ:İ.G2(l}"}Ui[+;?ڍu2P<\]\"-nɫ~;X\ zL ƴcF07Jz%dJcn6&EAo>!l;/CRHơgDP!KfuX v@{lgr$i/Y8<-C#FO jbF_~v*jn Qۃja-6ɻ%m5c݁@觿w0}$a^{9&cɅv"Gui R02z\\f]YĈ&ߙa=8O)3Iھ%:@;j7TT(Sz7XjVB,#JeD4^m}scG#²`}DkPĞeuUqI3r.:1';h[cڝ$N>k5#f;BߒC2CFt XEӾ&TJi> xeo8FlGGRgv iXrȪTzҳh jhv{ej!gk+-{;W|>^9x]|/FW,_=s%BIee_[&oɜNP^O]-V6Q*5Л! [ul^"֒4i\xƈQu M)mHb6{w(,V~P` 1Rw-OyoJ͔w0UR;IJ`)C]cMiβt/2>.Q{CmF#rq0 ^GljTuؙCRa1c ѿ.X;Ɯ1Y$ ɹMΤBT BUͿ}ɯ{l"ؠ_Iu鑧]@)'?mjT}ZhI(]ܺ,:'.DoJq|nYgǂ b*󏯕!*-U1C4E$p%.,hNVV"S' H#m]΁-9~uL.a?=w7f&Mq^/ ﭽkzFόDCG<Ζ߱o>ҬV GIWBJ묄͙*w1tP3}6h x~n{ !GLAhUY h4~xӒdxbAyl;g*"BKZ>?ޜ,I YTO}d㇈^5߁GB69V"C$8N-́LDrH|_)n߉vhAWs0 aCqm> pI6x6cc8sz]RLm5Y ):ˠ0vWLqϥAJ2{el[!]MsC B /L>J-f~aO*q7fYc EdacA;Ob_CSX M58#~x޴CmzX ?i C=+Nk%`= ,bͿ.Ǽ3l2ůйu('Riq\7Gz$y1m) I٠JcO1`zJ'>V3H->&HsԔв\l>>%O ;۷HC|Ć玽ȩ4U|& ~YL4w  /I$ޫ<:{=rSj;AQ 7IZlछXQ<+'3t%T^I[ofoExc4QT=Ԑӛ?H՞Vp ߩB1CSI Wu .1:^PgJg/?_ٙgq:9`6H^%p*҇8/*%l.y 6'%c~Cn^bOopސbXcwQdEs~ *KD\6#\l5w`y9Y Y/ѷÊ]D?p|máM2ih&Rg%?”Ŀ: \;ywp;8{ksb*ӹև=YTZfuz`%\+t}`GȲH^/.~1aV :jh|S┗%EAI]y8S@@쉩D-^^R}Ӊt ERv|!:XVǒQ7^ 9k@'hCe |XbN/Uy;_M/1ekzuk $kYC=076>7 &"Y~$K LoXcꩿf͹.*d8ie)S]ʧ]_S.v[bljWk( t~߈"8Mݓ&#MzI2tI ѲsjScbGh3sˉɗîSS}WZKfbuXOp]@Бeg|ig;~bLO~Qm`9(M8lYBr:v8 &s8ԟx>E|LvC[\ 'OLʄ(B*tmH+<}Oxc}qh{U8b8wCknşjdv ݿC 2wGHo^Ⱦhxk.yWE]c*f3B}*w5& =CXLM-Xm\LlkS*z~kq71tHamVq4؛@B9<7_yiPà ܼe&Ѽ$p?J`W0{@o sDq E2x\zIl-ED,}~Zڸ#Zu^ȟ>dhJc]JؖWZax>T(?b>?r"|5H:ߙ<q:^s 7 *Ui/UBl# p`%F77^w^)ӗ]zU}@5#[mla~gȷe_ep[먽 _ _Js9(c@k|0Mǭ&R?xsAN^f~Q{Fgcwkm fJry]AZc}%B-EW}m @V7|)7ңo'_ SI1ϫ{$<ˆ/h鞆Q^nobIb<*+u4j\2م.xP#Ie Us6 c;]FieGroF4v]fJ!yT2=VV07v-3*SV9x`W(=<]AgNag8Xǎ"hYNQge"VM .XC`3d ȏ~Y-Rr|.ׇ(65 d]/,FaE >^ nE~^yǫ{k 7}htx~@{͕`BOŏYZ]9\G7 ^DEpbn8;a| 禌ZHG~}IK?qI W*w=7>X_ݼQ8ጿ]bXOYh s6Pw@{hzqkM\~jhܦ䶁K@QnJF{kꮭ;q\Yϯ`Y `I,g-@cml!IO,R꫒H3)Kk;PwQ(.kUqǦcy" zw[>+fuzSY;w :\Ͷ2sƪWqz޾lǭYWmSx>)~%zL8Hl/' ;~j1Z;0uPSja pY*tpW\UPT%16:anQmCyi+fyy]kݼ=h\yw2E]Vۉi<0'Nگ4o gFY y&̔FT%U4 CR^l2M.>/΋oUk[P=Zjg^tH_OW n_V|nZ3ڎR|9d[2htVe 3G;ɥަkʠyͻUO^Ѣ_6Q#i-g1o*W~bl5Tش kJnaflzzmJm˞=?ӭJF6ua;c/{A:)+YvγUyיS4G/tJb3V$N&3Tm[ a"P+%wjMz-l.ڽWYbN6207l/i4mwFFb7]ӯmX}?DF(egfaTWӹS^CTՊ(͚4(lѪUMb/4f ņ^CKmŷlPŃ]y*9+4ǖ5ƫ ;Ӭ]vKؽV_|bZM?U-5M6^Kf7*th1~[3<BY'kMkjg ު,{ 948/^R-V}pv$jfföY\ng!bY=ϖv15P2eݵTf+z'F~~e4ϱd鷶Tڜcj eo:k 9(E~ݜ3a6uR8eF%3~nߗ\ovb<^%,R `ũvh/\5:LʙEe^(5Ud0słiU:AN 3àvjkX|AE=՛Vm+a6Z(l.z1{K~1GZ^[3Rw|ΰn 1ތ fFc03MkkÍaV1Xheri}ii53|XR\σiUM2SobeX&'["$/usQf, *潾L~`M\fLS{RzsYeKnoƊx]YUs{m-<ӑ-kYƼ?M}7GoJiحeݬO/GtoA-=T Hz1h>Qor Smg|`4A&+}QWrXֵ~mf^Xu,OS~+ {K8VYxo/e ]o0U{;yud[WY>s3z<[^i)|Қby~YO*vbSS4gʲߪXg^wb:B~orRꭺeՖ[͒4^ێK{YײzZ. ,Ny^$wWT;)}尋)}z!b]<]ԹRL3#:(ms]w{U4k~`obTyƙx2|]`%VcWvaSW`s(f|Sލ՞o\ei:HU +=_ZMo ) )QDanP~Ѯ):U3/lS]۪7/D]01K1ΒU\I4fP(w^Q_M:ZF'eQ5Y)F%77D6w2o|{fVp\-s`nٵR-^-fLZ*`aR+A=״N 2%vC˝efywh|'cחlRtai>gE6՗캘--2·}&[ΠZbR &hfFj6Uh*xla7p`HX7mEIhyeߞ AT%3oњ)Mtaӯ9D55Vcųuv=ӼmWmJW;A1*;F//Rfucex3NM):{jV&-{1_(;T=*̋4QΞmߍ=ozsVRω礝WE[%-3w@, v6f77 :3b2ZVr^8Nf ם6[Eo%JYJJkɾ'[coQ{ a0 b:?md\sb }7瞶Mgx@~\M# co_Һ1{xש{1wצ Ex8&{Fu$`A0i!݄͌4oZS`B Z7kv@?{0 ? 6Xഠss uaH >3XQK{15VsX*b 3aDZnƞ^77Q])y[C<>x}po78TwE\Jǎ;4OcUPf+fgmٰ7~c9 %-@۝,2?/!;-RÛR9C}ٺ_ػjQ/ (zQ߫wX>7}Y׏%G7=3ٓ0Φ]V `zr&Poh,i,Leލo@t+Je6rmq\5%{e!~ڣ(l*ÆvO6Fn yiH-!J̈FG%p! jLx ЕxŗrDҢA 0dFB=T!C&LnyGM$ !bU~޹hHz'0E 7!ö'@ Y>XeT`eQRe8r .; bt!>K򚏇P1>R1 I JZ+/4ʏa#a3ڹw c!4a'ZÛ(b2dD(&3d4tF Q(@ 0#vN!U@`AW>*S.; ,@!A?KUSELR0V֠DTzvyJD90U9( ]c,4ujΒXk$ͧD I@"cW.b XkDy`Nx&H40Kvb.]DBPx@ P F}'/X`i7\RU w# ⾵7ZͿFSt\9*Y랥;U&n^s-gyR;ݶΓ^ M­DHflS!?v,ݸ,l$%5UoׁsǞ4|xVeP 1&wL'(x:~H ;߇tGYiX2g{3U0hC/l}d9p֦7yNɟtXW"Ѫe G 0 kdscY! 5@QzaN`BT*""EbODh%";@;Z'$4 &`QfH4"-)p$C‹`ߎ1`DT J'҄!Hp$Ž -8<@eg\ZwyK\//G'.3ɯ:VvʣSS} k.^M ?{D/|*3z/&6P]f% JKppޑuȩ"Lʌ+$ EHR Aa`dKp (>k)0=W.gیL `cj\bjPFeXD(R8<^00AL%8F72 ?g)="@2 9&ߺ9qC㧲$'K(8|YqeA{Q(]|e?JTǫBC#K:(dypM~.rJao%v}<~[cwqN !B! ClA4/3&ለHi,fB7ZcT =\X 㩡ѸFQh=xu|mV]:9d(-LSnjw,?ݳm(d,n%N q /rwmN;)+JkH= >X*jXvرbLW <{'7DHfG>ޖEUr}dK+>a;&+Ŵ[p'vaB~~S]C?NWSd{y"@}MDJyv7 Em6RnIzeI$􋲸V뮎e/}3#,(^;_eQ韞 w h6" 1\Fyy 5CM*fZlk@`0{sR8H߮@ӓ_)%P93ɗ_#=orC7pw}U^͐t?F8%G9KJA`zSce[ >D_v&ͻݤyv=~;fo ٍQDU MU]lU5Ƚk6m6uM]u3(u~upWEgrU!mFߝ_Aeoz| vB2yW@t0s 0I&ZEE%~4|ы2c\3 !mBᢜDLuYr̒<O]^V9)ں:87X qBf]a}Dq@/SXlӛxT {1uڛqDgG _^.{#`40~ %{(PJKj3"|[}ѭ+ Gzlh9tcIn|SWD#9E s7>^k9{TqV,ʒ}fU~*_v7y=\^.XMS xZ Y_7'{7=We>t%RgsRÔ3ˉyÜpᳫ8=Qw”$8>pD(X N!_7x8\MRӜ&#ˆr#bMJ$l Fڙ9wDbxBG;QN4Nr9tL9B)z.Nqk}2Zh)ݴ"ZqEa|Rn 8Z&&(#Rs<Šυ"*\ů_|nBFRGt?m(?_”Ke}2 quzo*Y 0nwGׄux\;L2vm ˭}.pI)aB3u VJQ mgtz_@<Ҏ_1I1:MI]ACM^L˚Fzɸx@ @(CdC>lҦ!{G?!Y vSÀcqA]P;1 SnJ 'QE,>kwu8r]Y!cW8 &?ꑾC^C>"$J@y?^يbo[A nhs+Eëjve62&¡r Lv~:|@UP~NbSPWyXicaZRḷnvvZ#<U>= R|/w| ou8y xKڷ~Ѵ0kԜq*)c o);NMRAc:f6ͻnyusw4&OϋwwOfx\-WWo?-~|~~xq?o㇋&ٟ~~d}_-x^xXݮ>.^^^7??=/~ߺzs5gыl)¸ fbyySnvSK?VO⃟[j ^WƛWOk/g .݃rn| bZkJ/*&C^\^wvnf/vz 97^uad$`˻nwNɇ$;fubl+wbsۮdeWUۦ#ֳr\vtj?/ -9OPHF dCm.yz/X,2aqu-Vqs=tq;&4Xxn Jg<.آjW|wuuX̸ ; !.>5Y4-gߝH辒V{hu^{[ag?ӷ_~i1_?nȤ|JɹLjOi~-OO}p>Ŷ~2ljn<Wr,'fA<4ν)%,)]59W&Of_$MIѤ;lFJ2kfnyF"Y@6@FXF)ndeOT ,eQjj=Em`ЗkU(r)V] x>p ^ ݉G͋ o\Hf܎S5I-ĞM#Gh6jUm^Rs/=j47NT]vPLr_5!C5T7qRʼxQ<@0ᗶ㧹̡Q %ҼXh/3Pؘj&&ٮU5V6~F颴u<  ^ X2toL_!:3&$wP1P?/odg^ L[B{Ʌ gz9$ 28xD(j(}4q@Q %U TtDAzU 2^!)ث.vY`a{k9n I;у^ǝȠxem\xm^{Lz#&20Cj}/nd#ײ+g&gZ!v" w0$qM"|0ٜ=};6xf-skW(kztŤ b%~wnkw qiHt^jmk_;~ TAxRaP%Thn'A[+E gJ-D.kR9bkuK]$kq,rpvnymhFN9x97W~v>+#'I["'ّWFNz1zs!'`9aUd<.LjAn_.i.(Rd#Ź} mǣ}_Tqj,i`zSWֽ}j( W^84#B1M9aeP9u_0" Ut^__E7 POy?$riVe9@qXh%F-a /PCK8z!aRȞ8D7&l_~{z*1VqE$!~4ye=®I%}-q^XPƃ$i0u}ChX"牤Aۗsa2m?TK!7PZ"APVcWҎG_/Fb^%qX`ѪӡVО 2B.yeD CE{z(aPLxƃ7C2WcAћP\9E|[f0(hDF0{=˵{#WC[l?&(/%n/3ǘ`/HqP/} ,WⰯ8J«(ì(@Gj0 |Pa*pqP @Bfa |.CaJ#2$5إ8AAPt!fEfx5(B_`Iᐗ%8(a]Dž d:( t r^ yh!M'fHbkXo@0<Y"Cl@cn,{_Cq<ɠF%68쨸t9hPo5$hhIUt>dHy( 6škxaSrDkږ]r)uzmnc{\}͚ Ҁ2!WυaNnGOr7{G찖ոbp v{$f,0J̃F#G+8 d3R9Zk?rHp6}ݧ3=W l߀BQэfG"'WFv׀oFށ{;Ww$ţĴ'NH␤peL8ϧh8'3!erbYNGXNVJEۉJఠDFd/ ɞbk:?b\1MbKjGhD=m nnRHb yZhPd;/>yN^` 9d;96N*4xAA8S:֜֜]sT(ZSY-mk>Qu!HJ7:5BuL9`V=ż)G "$o2JU{{rԙ[@ 9IjLS1Kva׿^{V@#,<! 9r1 Ӹ]tu(294zkQ@4{ Ȗq tǖ"6C b @r m%EJ ]yV eIǥ09<VBrqB> t9\{V0ԘT46Kǩ1wxݘASl˽P@F.y(\ G3paC8API@8]zl ^Уߡ͵ccz8jqOP@z3 ds H<1Ռo hQ5S":Q,u_vMq-s5e;dEyخUߞ12UAMOc;'S7̲4.=cyڞtcjS܆m*8-5N50. }<3!9+v+,)ig ; 1W+a qC;p"t7<{ vLM-5cK(Q[bΙqDS[>ZF7'AH7 6{8I밢'rBMYU5f;ȚSnn{^o(={֖/v~$'q7@;n3Ih%-fЪdQ@EBCe_]8!v-j.Q0TN'{ sЗKm%z$qVSDBS#D;L*W'kf6|ŗ^Z~}~˛mn=xGӪ=qO7W~fmqV^~mUrVWƎ|tx}\|7o~Y]l MAn}#vf??=ǕW7wҁm`??,?|op^DKIv/=_$doZڗ}{{OO7O7@ŵw ,^uV z…RKsF"oĊd P`ӭP|ŅI5|h~G$M 6{In2S۳9X+2uϋɦ}Wϋb++.n?m:;N&`qy uWT,?|]<x ]bbOKҏ/.>=?&7ſ?37i67y]Iyu^-XA?}舅 2/7?no'{WFۂ͞ѽ#A&#FΑڏ=|k?qbMXDn,'<σcq ,XZ6Ib Å 9Ӫ,&uɳzc.EJ}T Ho0|e\D'$)2 ig0 L Flf4[,žgUm>G n3VX: {8|["ƒ(S'hO@t +!Apd&ouX J4ۥG7򯯥ɓ3ϱ%SVaXvly_%XNEȹ)O AlTZ 006[-1vh$ -[u~xTҾn I7u !9vB{A˩LOB(ߐOML6%  R'A3ȕGti3fZ('=bU?/z c*eJ۳N1H!'. ($լX!`G4(VzVV Go } PTo_zVnАwzp `]#p4`+X / e"%q?>tοK=t)d"J0@;\ iV ZBG^ '߽,5v̊Hԧ)qbֆ"_8m3P `j;M a.dG5l-7ϖ %x߳r3ȥlW4,֌?]+lQY+6#7-e/Pdmeza @a۷t;aPՎa!v١oBUdA:k`gr*Z^Ŕ ]qQCk̀ta߫Zw&64ٰ7jӻ)4.ӟYpmv@**}T@J<-| p  7MBq `j4 1ҫI %$'Qt`JhbacP (1iT`aꎡy!mQ{@.eIH6G>xdch$A.( `IQ@ۖjh6M{D_E1N0Pn5q3@I{VarY9eVs A>[8|Ql.S q) Dj pFgt5QRgg0dWˊR9;Oǜ%db!3GuN99$+husZI g7~-ʄA#/4Bl0craqCG$sz-M{Ϛ}"n8;/N zpʙ%)f Iia運᧕2!;11(8^<$bQ/C1| Ь.7hmX]:,MC٤Q=xNR,ba{<*O=\=64Wj,R 2D4h; S&X-OjĨB:h90C)˲4b1SrQhcbF۲,&bh;+6Ha*Ѿ&_Kzft.D!g\cnj,aû&d {\qՊ|\Y2!GݰC 5Y3ӐD Dbʸ9P&"E/,7b٤bF$vBc^1u4ڗѸƢb<߈q`YFO.Om,X01nr}ֱAmT\Zw--dUcS۬5p8$ԪgxZ°@Zc–&ސ>j0 jRp?89BXs+4пV1KR qPuMee`f VEkv6GZqs5'!v ]@ rԄsPxЕmS-R},eXæc5MsZkjmmЪ!j"CJ |S`u2`V?emlry@.YkUZ)4,놐9:BX+Rx cbȭ=m+jWQ9}p f\9'P?3yh21"LXaI,sQxr86J08Qfa3-o!C$1 V:L 0(rbrRQD } 9AXbEW*ՠZ}ȭ7HSط!>`uN-~4b`皵E,z)beC,tX&#ÆFLG1RH$-p S4yB( ly!XZ4#=^FC#87h14=4گ@+~G{q4o$< 1_r+]cCc1Ѓƃ؇xL|bO}4cN} vFc>FhFFGA A#)2*"'.0ZP \Q#o1&ZF3&ęXsLFyjz+xV3Ec7٬+2`42'u$a1NqmeC Z<5Qϱժ'5Yh(S,NkydfF6ieFZF/p- AXyY(o]a;u AL&‹k@4FyKj2 O8hI1J h(Xoy΍͚[4ᢶSYʃkZD0`9UF듀'@wkCŊFjcq $A@`h |V݊]V!A!Yjj;`FBѩ*ۣ!o(NČՂ0ƞD5pj bu zNNEWI,NU7Ne53βrhsJbN55C% ozذlV3f#Y Sf:P볶nBpBX923Dh1 Ԯ倔Ik?ǨAc_AˈtcSY4GJJA~:= c7Ggh3Rya)R @?x29l&Ft--;EG !LIg(.47eʀ}4_YŴͼBɁ_=2"eGy S܌LG4E0aVlm#`չYqN)K?I N@52Oysщ` BxEĚrx`y~FNg tVhΐkȞzQRT ?'oy*WpZKaÔ.GALyXIxh m\/)WLU0$NOb5v%zh93Q }^b  aļȉ:+֢1fryLt> VQΘf2Edlq be 鋓;\gtr];w L[+F_u:Ֆ;Ȏt?(] PEIi9'>+u/_'|Zơхi>+(Fk{C1,Hg WkQSׇCBRgoysƘTd1I ތJB(jSgyJ Vr4OJLJX9Ue"=.0xUr!RD%Lء`.M3)ܳyvCB{iŽ s9Ež+c P+~RUeEC0Z:d1k \F@D&rӉ0:Lt>a_ngQќLٳh 3\lSjtQ,ZW)bDfaKZsDc XwЭ#2gPRԎxFPvb)IӝD62qAKQcgˀ 4L#P&% G2Mu]H8zB62~[@\tp=r0Ág#!@P{u@ThMj:) :,_:ɩ=y)&5W\۬Tj^k`3V=0zvbn"8V%2^BA-NukRKqk;)kMFE;s ՕU&˽sD%~̐{!s9esdm`|Pa =qS 4ԙu$/bq8_ &^ $طV*mU0Ųט 3ѡBʥD4S^g9|P6msuвuҾK ϭu^'0+.s\%=/>mڙy58MPml+8)ypa>CƊ Q :Xl-f/t*KP9G ?ioJ쏆t=MaUIe[wDk},LN+KXj,#88][m}AlWc)TWMOI!3%ddYg&_[U*H+ZQǶ|86t'GA=a괂ӌDFRв|5fkɖj[X5ct*)}H/*"=ɉX&Ha 9QT'P0YO(Ai21:t˗]1GI3)8HHU8l]DWT -z3읊[k:d\@W$W'A@C#@`צFkў$LNcLxϫ#gma S Ex|k;0DN05fK,~_v ;a1Dsh-h:k 'bMl4ȁځ[:L+lW`/9џNUEdSHM|#یdO%ƈn֐*+6a$I`&*%VфfJ֊k\h#ػES*1š ` wD(!BF/B~z`fN=qRA# uzP'G9/ùx۔u2,Cq+z9v 1UX4KɠWh|%r|:;2\("J:¿6SM*٪ `d-6RgYL #AXd)zSMȂ8uss97i$D,d3Ԫ>TkQÅ=_ǜf~]۳X+g 'Df@f1j' iN#Ѥ(PVǛ%:"ڱD- 8N>o^faࢭ< iH?W3Ȁ%Mw_a[`E͚Dg$Cky9,k rwb~099|Jĉ12ej8:ޒUoIDkܑg,ڌƃ"52 V5&EDwC4ǑHƺQXMZ"s ~qOz! 06G)QOpԼ2S=nWxR*gok-lf^V'tTxeS ^GG@Ϛ3 GRc Q[kX eVU2Bg <)O`9팀B&Ktu_Deh l_t!D L,ztn6{8ma`P?{Cd9h2=T:Pe -CURUzP&d2Ty$G*~p۟iQ@O:#O[kst+W$j߷+@PNócZ Ep·0|W&-^᯸$5ڞ=+' s!w?Faƅ)l_񑹋Yw_1aNe|ɕ9hmaTyY)ON˿8R84̋)4/ZG-^jcjFO# k{+2_a}dzd8V% k?q'o%p26F4=j( _+uAVZ2Kba)k6G6'^5,7,RuohKpF^Yjc64/,gadJԓl=je͵:$fZKz=jN1Ht9Ft /+ۥ"2רc~Zk*n֓hW&,t=I,{p&0db]cBUv3v2 Bʜ g >p_5{h y[=褓/fOc8S"۩jL%b}4ocsigenserver-2.16.0/local/var/www/ocsigenstuff/scalable/mixer-video.svgz000066400000000000000000000173441357715257700266300ustar00rootroot00000000000000}msƱw ^K99%JܵJҮw ).K@i9:!&0~g8o'7g_aWG3?/N.._|f7׋t폓\ts2ZL~97/&˛oL&݋'_ݝp|)xzr8_]\cF?_\}{yq^?ڧ{;5^~8/P|f[/_NXw'EXo?U ޞ޻ݮI.o7W񺼞_,zzcaٛoӯ~d`#HL˪eW ׾Ir?=0{vL_׽|w{ŇBEFUO ,>\]^^/Vxvq|uvQԾ,;'7Ǘ>Y\N ӎyWٲ񽛽xԇӫbZY{>ctJGd˺R[賘_z~zV%5@sY7Wu% GM7bzhJ8xXIN./_nauòKx5i &QNk]' '5v,kscu(|jb (?@2,j:[]^9+^ %:zO9R*lț\/nJTyه6Ɍ|Y,Sc髣'eOJ);%̍(.E5eވbK}C8-5>,>ldvvztyA_s.vq\`~M~$P5(MO ʪkk7%Ȉ޲Dü^yl~wvoy I0?jn@$r; F#hI;fڮ{J]'O~zQҥ/6oCrdr%a+>$̭X)@ł*5?V^ssXXMMX.IU8|Mt#@7H$<ԗ}G#*w` h"{: 5ۛ`cponb_[f/GNʑ׫=2|p, |'WwA%v4RDn!ϜXP;HfmϚ!h}'c QlX-v(U$ 8 pH|y9I n#TЛv@+yG#o;!)"c5Sxy>'#!y4byHYx]) vCheǬF8D&\!NrH "RC f=vrg(2a( m(ߓNtH;{dFR5o|=C=h.hibq?[%^}XԸZzhf6|@G<{qCVGcaGc:w4pܓ!AmFmFA±G椬'm%Qhcb#01'\v 8'$7k8XBHHi:d]45e;p"$੉sdʇ`#Q)!q6YSm./5K{Ŷ4>ccʻ DoD)Ogđ$P(yc"&Kb+=_^u{Sũ3vvfd@G%_Od|]s2v)JiɓjfkHƊ >wRɼ %Gpo+,!L- ɄNzBcr *Oz8:?$A SNDeRr*KWIZ*gH!6X2iF ~w雃;Jۗ1A; NZJg(F4='{M}Ι8u[Oh tFT-$O8cw>r)vHȸɖR$lT|H=zkbQ5rkAHd[Wk?Z4n4jr@l;&tʹۗruEh:}M2Թ]ː4kxP&ۯmۀj$|kbP"QsDMǾY ;:j֎Y6PSNej5HRՔ3Ѻ֌6G3em:#NɅ+~}>ss[w緋C9"F h5$l-dj ~Kb}rv}r`u83W'rԸYkhi;C!ұHʔeUP}`OkobnBj?f!f;kTfJ`<<=\L 7~,* ) nSމF)8^2zEr4ӄݕlȉZ=㉏ptz97v!*Q&ێ]s~yK<ҁsqݞ MS]m! C}+|$lzw|r'86cInS@S˽Yjpyg{t}O&}0t7O ;pn!RdAHb#mi#C]0>h-g)yhBpm#['' 6ƶ} ^:Zr5E QA+ʙ=! 8 m#;ڧmf}L fH#Mgɍb؈f2tuu2ŀh UB()O֝\P+t:i my"?C #HtƉVܘ2XB}xֿ.R ^!;pD}W4Bۈ>* p$XQ}TOj!e7z^>d/8cUs*'6Нz'V$ ݒl]i4.c2Ijq}sUD/Ľ{kŧ7gEm%\qW?RK^M>+SX ~AL8ҷ½{CljЛmHyݺ-* f=T[TAҼHp C m)-^zݷh<(U7C% %?('Zv`nFx8HmP";O&Y>a? ` ߿ϙa( sI\z$p.E?$Tmg.=KY3p\r!.m.v.VIv`P4̠]]2 'XNkc=2Ty\VQ雑2,0r3*oזnmN_Yl^8\ Wueba"Fyf%Qd.)ۯ cuIi`W6g=1E#E/J8<G&J]v) 0o@G`woGUm&Cfl ?.˯b3ɗ5%oxEZ3Sst_^/k{bZ#{1/kߗoqu ק_o ?5X|W:g%wzF>y?>=h W˕e7O?^<>*JoEoI-Kijܹ⏥Z;5me9Xð l}v :~wxx(QjzվH`_sʚ%%5b#ː4Ssբ(Ejݦb/.=oȐ$SD;wFϢ`Q5sKs)(!W.{I _@֖htԟ3gMyS_TンFT3 [ޡELjnX-9`/,dr++jw݈7?YE;2YRo> Kr;6/CT.Sm729ٌCS-|]P Ɋ_u9$ l?|z3uNT9bL`*&o&e_48yh$GbdO$SmR)n*s34c\l)S,J o3dVYINJH2y|Z7*;laGc7{I)k"RdȔEnV 8)N2I>pɑP H`ZzDBl"w`cM^^3#;kR m L!o| |2Ed28-+JA$܍?O  EJ-k;'+uX0lQk|STd@"Z #^>$l[GAR#um$>`|Lh z0'%#IĚD|>D>D56{l9-Uf³QOȉrD=(2M~t0CjkQ(Shy@2Tp%GnRVʊ [)sL Jڎumys om2jw{8]ZȞ+Uk׊g,fYgئ&Ĕ"X%BqU*D4e:%I"ժݦIb40rjNm]5}uWo./H5g1D);oWJ,2}3r'((ں$#'g٫OuZ%h IOEkrQB0SVz,dA>(W8i8+O랓MRHμLOf1IR0GY XeLHmxA0+{'ooI;)>%VuIdPwWk.k Miˎb(N &x!aWB6/eqmM퇥.L^tzg2mIzn?=f΄HrdšAh$] c.~ }_ȊHe{ޙ?(Xf{[ dWmYֻdΒE޺7לX-w_)x!źt^^^_?w8-4,Y& p 1Z&a8 LĈSK(:ɞ2ORFӉir/%T53,,Tɔ|9AF+r()|Z@4daj!oV)hhܢ{_?SILocsigenserver-2.16.0/local/var/www/ocsigenstuff/scalable/text-plain.svgz000066400000000000000000007007521357715257700264670ustar00rootroot00000000000000}YH nQF}!hC҇J,Ed*##2#f*uZ177>sw.K_V"o=n;7ܳ"[^!ҷkCc!<\#(ڶ ѿ!ww?%A`ywon85ePsQ?3?+>w?wڏ-ȫ;?.S5-P8 ,qWymwwOomu+0 }.RR(o6ǵCnx*x5*nO?a^=zG?IvW'+ۣWGPgèol?vOE? 5 hv?AUP:ЇƞRߟfewK,C,?.ax~P %K } ȷK#6=z[>zz+c䇇VuqXKݧP>;ĢbRXAPbѵwjnՆ߮T?ЧoH%wqw|!W )`ϗOѐ{ "۵~.E~2ÐCɇCK,џxzG*/Y }Ͻmʋ]78Or#Gx=Cpc=ޑpR#w{GߓQ8p:m|G wa,r$>\"E9p 0{Q `8M;-!)C{8xo,#/4 ;)Cs8C4 w=D 4480gȡ!X4S,ww$l w}%{fgamp}-A!{81cNd ק akmwȮK3{H| A~T{j;ҩH!++%zUkIUf=Q즪v.MySWo?r'CFq_5}~1g}4u^;0@8NTYf KNCO4|,`S'M'`夡ﺑ[R }QIlܱYb*tLD +"xU߀ӷ*f9 w=b3cu/4d7~k o ͗ޘ ;D,`ApP]A/"B.!#X#A?ıCpH J8 ~9#߱W3#8z^ЧVRAƿ)wG `}=3;-GAE!!볆 sMCy ԧAhrcBO+Gܱ#)}վk# =*pgp {J hm]y`FW瀀|JAc 1< ؆hlH=dn4"q ^"h돯AF1;Ѱ >Ĝ8 P8&C 49䆡A=E`(S]25ih |ؠBu,%h$~O8{yOHn( d8faZP/՘B>p>Uȧp!!nP{ *̷J=)X5pU"@']y8,M!A۷6$\,xSÉ{⋲]|%vx.H?rZ4د5fϏNҰԭ)Z蹡Yz8>n CfuZH0|^^GyQ:HzQ1xhn.l<ZvԇXOMI:x]˪Voam ̐¸ߍRKH &-Re!6fH/!-~٘!^7F̐K nԟw3fHr ) {I`එ1C {c%ς3ɰ_ I4Ǜ{&0 3|[fkuv?3$b6f!f1C{ i!ۤ3Cw I%n?3$gT1C^"dJ)!o 5I ~3I bo?3wdah_3$'#= 3 a9O .bfxkK8w'q7@>0nlF"<6Nfl(Ncܯ%%^lT>?ץx#3Ϭ|Ը* 8iߑ`/!$CXKr"6q8"ؿ`ct ?K^`R I)s A->(}4"WiӾԎ;VI6`2=NA {,,(!&dO.2΀&:!UM"=N4B`_?x/i/4>' !_gp08‡@#iA`YH! E8zH@] B8IPIEB!z=C-U<hXX!OG7h525}yEB8P}G=rKa=q?{zȽ>/FSjOpK~qgӬwܭղa,7R*ݜ˰\Q)nMj)^5䁥bG\R8hJ1؇~fYfrǰà]%uhh dIpHE9%KsI=ք//mli FnyUVyinqynfy.D4wL}ƽݮDs_8?rᄏFԟ?Fzyw3X}cP%˗2v^]p#樿 syE>gc  ~v=+5Cb''t?oM?jO9>ҿ;v!ݭ6u?r.nyϝ|Q#-^ŚDO5>I!yK~taLc  ՇS^k,տ>+"I{\OdTNG~.ҷOmy{Cqۡa!'џ_xwELv̟\Ŀ'n, #v^ϋo,XªS^Np^ NJ,N!P%@5wG@w8,M"`7ᄈB0 % 4?A}HONAB51p@߁@ I:ld,>sfVa!IcD g;A>f$闞$ WXß?Mp{* ߏf_sӋMq_f_$Cəw <,t6{L*_|>;h}\ct$?zN0#=C4}ˁp˻Lq K8{\{}4fj~>)`{ ҎGLS@Q0#Xq"k ߾E/ Lݐ#DϱRǿEzD^7ce7 in!Ǩ/ ^#}I?^w4>|n>´_w VQJO-?TB:ß-a_Ƈ°''s7ES>|St|3kxJGo?,O~90kMB}c<~ Z=&LEJ1 ϧG'S_$2)t峈$Rc29ċLL6؟5,{oL}\<.骁 ~|wqsB;7N[mN|>Xȱ"+jh0nq,Kz|>A1)~8sPا+UxK.oowUz25WDX+Fɖ?H@<`Ê{ *T\| 8C 4{dT !@x2E  OC* Qza񂜯t= \ѾXF'Okw7(?Ya?ƺS/ɚ~v ۇr.; 9g/`CZ]Hk9`qQ aE|E?:~م a} >Hh8#7=GAx"(yh0%^2 fI?Ҿo;}3/#n$C!?߾oE6Ś׾oﭿ|z_pNrZ;_wV~ò䧻HRkw| Q 1D y6_:pӝI7䄿/dZ$xAT੬ F_!Y-U>63쳂ïܵ5!7M V jP95d)4f/7XmF=_GBa$K~>w8 cÙ7?Mȼ~ JՔ %t/7w$1$.CzOs4_L<$;HCk̿tn2=俜c٧=G>]cQNN{>/I 6-󖠿$xeVvU~7PgT{#?ؾw {;▾]/> eE^!eiMo:fv?̳k۱+&ߡ\3Hf)#"lZwJr3Y9!Uͷf̕I2Pbza>GX6Mh&!jɳ!]T{=OM?Q\`mԬ`Eq5Jˣ ]kwUK/h;EU{]kyu`Z.O'rXkcd8.q+]skh\p V`*&:z^QB3Od'~kd3r;B? Ik\׮ÜgSc9^2l@&vuqJX>G< ْ&FU 69rCzDt(ڐE|D/W3aCYJڹR=q^l_Xb;U{1bb\?^ x$r\/]|$0Xo˙VۛѸt+c%Vu/Z~QE=$_.zb\]I36bn S?,g, !*ظ;*".V6e? un olu+“ Hf (f/$꣍4_IWUtI14'_qxIMn{uHyTTaP +g_jޯqLd;C>$*W,5׈Ui* Agxϫ}gKvQŽef2>,M<)YNDZL'ᘖ☀"y!IӦNK+|(,6VȻadyHmT+(/ŵEB t3ݜ;] BLFhi:k22CJ % t]1(SS:9O]iKslؘ{ܿ]Qx5Qk0rvⴺ<)/,N -m˻4rm:,=#P"4\O]ql+"#DZ=yGgoo@k(;aN9ոNڕ"h)O&huQ'Mw Ֆ.~}Qsγ8p@:T?cͩ/XU\>1#B 'Kb=Xօ[izsqME䌙͉FHO7d%5|Πśmt ֣j.)-4²~idMoqVTZs9«Ou 8+Bm% `=]Iiä͙|e5n.ebFjRnf M%/W8Ŗ_xhnQؔH`ƚ] cIZv':Xw!!ܮK'Vl-1ϝ\7Ig戦d{O\v:RCK h̅xg#"Fu1W():٪5bUݩ_m;:5FۙiUUZPQA]/CpVhkjg_%SJe >ECŅ2—ZiZfdא );<"\$G#Ѣ*# Z$SLH91EEsZ:֕8&-/~\YXN%٭4KȾZr.m1w1\ *2MZ*GJ1 ˧>r3?t37_QDŝ_;m̹b[ w4l V^u09[RA#Y{0-Y_dn;"5XJV̡<4VِW Hra+!&@TQA_^|2Ɉ5Ft(xŖI拍^NV9ew5HE8۷Kr,֕]β.Yы8EƱXjzB=-d\r(5h{w|]EUψ.$#iԧK;5g׾ȷy}\q}H$4Ah,w9sS\?lDڙNzj͈){n*s.:!qsiRX=n*_҂tIimubAvx~z؉ WEVhF/hdɗ]6^PLӆGR,`st - /b5fנ4|!̨Vz6}]Uig7.۰*IEFHN钱ssb+#8i]4< *mV3@1K٤pZsY =$(,AGN+ ȐtD3*֑Jvp2 ":13yȏz(@yhdX4Øz:w]:$<YHGfCp]n]ҁ3:_0Tt+d Jcu \C<fdl-'iB[gѵYl^(vռ&?]k'4"w6 K V&zu^dLQ5:Bgľ?ښjXVadix9T)൵HȽgDj&3gc^zN\.?0S4~=?HbDP~nO:BxВVK]SyUl2~?A7#yG9ޑA m vE;P:jXGPgpRXS Kw'p3<)ĖDs~>krYpn4%7cbUmocg^S I:]Wͥ3#M ;5׉IaCw6L<ų!1#yX'ixa"쮻Pgf)Ysi`ʧޤvS[ON]٠yY2$|՜-o8ezΏXrO qk{E 5֧Do\DNAjvSz ӜugN3L]"f9 =jqT*Xm,̧%(QJ!Aj6146NMNoZNCʧC|j.7vKymW+. 4es)2?dZU?+ąMXM5:XH Ĵ ܖbGA<^6uܚOWz8O.K^97buNk 44tfVӑ#K'܋GbEj;JoBҬ(Z|=!)ė h[J1mjQhi2GʜƦͅJYI;R܌b5TK=ɰKvݮ4/lb.8Wi}72-!JJB"NXn:r>@Yf=FcVRlB by@':ܭ"M1ݩRKWьpeXCfΫ>u0i֘] q鵪8.Ï!J*ObZDsi%u{/FxZA~ɣ瞌\3mQvuf8kf{~$(]4=|0T1EfD]G9wbՍ/$E.w\VؙPKJ$o$qOᶌ,qw [z[63<^T'UR~"~ ۮ^t``3sc&~ɘf$Ozݹ5oТLeө Y,YDYS}=睢>ߺc]0oaOTF0C8:3 ǚ`-|E2I_Йz GU4K ? V)"ӒnR#=_Z*{2eĨt5!"-tA$8ʉZ{FRu]W6w^^>)̃)87EbV:(.lYN2=!dֽ<+&4 ϖ2/ϒtў/v rC9s=$}\5kM_;͘ ;'9}.b]6cPpQ=6Eכ9G +8 @il`} $GtSԮx?$:>:,lĵ} ]3xas4R_4T4G eL,iO􆇼4vW,=9se8qMkWv^3DqÂd)6AMkf;KmwBbU 1ss EdBŜJk]6\Ih+U8)(\=Ne1.Tt@tXyV+B;Ef.WL3sԦwYx Fe-C[>@Nx2bf}-G.*`ctE1$UaC(fu坨8޹.28@6)Yi, NfnN ;l%*dՖinG m92k%8:ĀF^КIA1Z0B<w'- fʢ3pmN}ewlWLxkYd*BMoqC %r;]YLEJkEۻ'u,xr`^X =t7>V^庎dS(iqTyQ}Q~KezexE{ FM?/tL.\ϏV\4.$E2x93xprNL$M@:Lęd k&4 zQ6v="il1,I& 1*TzlCd1^Żj-w LI um}-hdEtYЯlSmSč#zD~kNsսQ*UtBB}u lV5cQ2}"Lq4E:l|f 2sSh{#]p*[[6t8xw{/:fU:f"xKm>*;x`*hfhmEhYk4q6҅$11iO ]؉ᱛUaAiWWE%* gfobd^εNC*$ėT|;Y[H]K[zhf~E2YOyY,j{;PjZ2qWWR s(h sݜ.p>OGAt#Vj1hUG\H`%1RV ~Y*Ks2UuAVhMIq>oċsFpVSJ@m ndδPl{ֽI:Sz#ݐ:Pf OP:1IFm$9@o9ԧMsz Ҡ TU\Ja 5r"Yz\jkл܄:$Gz>vGn,zzNkѕYח&R-^81}tYQ A;NF1uT.2!CfBA W&\mتm .vٙ''HUVfZOGmewCOY&ascFʼԺxvX3u!zDivjx{M}kֳKX4XY˦:kUU݌WDu?nh 39nj C>`e;6kwəY Iw@$4yT,y"kE,Ui}d.ܓk쭩̊4-|ZA3-=,i 5s\cl7;aΗ 'YO\_.E h4y25$YZy3.BMōXB*˩/bP'T),K$Մ;,J)PM&xrI42P"mMR{1rIL:^QVqYo$5fиN=q9y43sh o.9.WK?DsȻGjڡ7]`Y|Zf٪BzOJ/,}r!c9PrRHKԮX@a Fe df"RAٌ@b S6;e([j\Æ3ō˕ yU-"X5iLPVX3?kk?:2By;LguT:YszuqhA,IR 12pW{ x,fxO*RFom Jf(fT㚢~ 5b 8nݞЭdTe;ͮX:%7Ǖ[cbtrW<rfx4*L4Bw2VGEg\R-5u̘Qs Ǿl;Z+$q[ %SϚLU/XJ_R/]nB[E@Bpʲ)lutǁPUohj^7XXӄYle9 AK(r}ܟvcDR ?)5k"mKo05pL}$_"R:qg!jQL.~A]+} PKWTz(0j̼ .(ۺ\xN#ъh9c^~>Ӡs5{؛\RgKSm׬9"%󚗨R]Qn0-&u\"\qȈ׮l!W*#7V303u 3g:-z 6Ï-˩e` 6aVG<L&&NhArsӛ]{P.Mrڱ7{E(U5PXs7E kka3ll,1МV l 8F\Ζ&V (ʺKJ%%R+NWOp Q 2'( 7<-`[Ϫ| \:nD4ۨPWܐvt1sȋ޲)~d%R֞W4 (D fXIne<{&׋rk&v htB-5C[<=91֫Yt?[[\zJM/t$#.\T͕kzSHPTDl@U_tkefψ0Gus#)lIgkL)E Z2yYLŨ`J{W}h{:Ā eݯn,XyMPW Lo,,B;sf@%;5}} 4ǽ)37z>;PPN'-VҴ$^f&1I 0XE8H"hg*b;yY-K· ۫\LPƴq]e"KYrqK&HeƢ1Qoy6$6{ IHQdͭY78^E7p @"gڼQz0 eznL E2C7~!+.YD.ϏBg6fHCe,cOhnX^xwZRy773UL$S^,G=-k*h-Vݩ|ҴCK7inW/#iGl@z07NS9l~eL~jN;B&DXj|&mii_NK*D l .vcYz" m F0ʎ Z]w}050a8$;|wc@,*'|jL@99Qvq8Dhlg3̃'zvX3Nص%wXWlLg;{rYgn|@9m_5wR$3̭:ދMJ`Gޣ㄀m 5.b߰6k(^ɷ Lد,JѷV *QɀFc_ w/>0Df~Y~gۜ35BOyvt7A>kc'Ƭ(:>zIG!+ FmD|TO8tL2NG[nh܋tX{~:TvrK!b d|{yݦS#p1K<ڊqߍLf];&0ɞB2jx {+,o3 'RZOs5.woggxp`}3!Z":gڤ2gaUwgt_?9?zHZGz Y.?=AaU?6]#C]4j x_,V9yy Kik1!)Vz^]3}b\ >Sl+ǨKok8h$f;bgel־G/Q@ؗ7ؗH*L;EDTɴP*THfn U^]ؾQӜfŀmR] U_ek>^'î\is{7, h]tw IS~)QayCzB$E_$q 4k>5U|Jʞ̲vj,@̆!_'M#W}b#~yNDE {| z㈟ (XH向=|d]3LSDٯ WDC2,.rPF O`ޖ?\#ۀl=(UQm_2#lU9_Bt |iit ōi ZJs }zz1[X [XۗLLՆRE L03̔yBEV[[j!j7r㼦Ќ^a,[  09Xe$E2(7|oc䩕 6[%H]MtC Hw&=:Rؚ "wĦ`ց= /ߏVT?{q0A!ߣοoRYξv;yXx-e, _M4۵89}U-6u3rϛXi+oHXAOgrulDc-:o,C(Lx{.ڍ=ȋBZx6i43eE9?XlI)B?dUeWԦaMtQe (lװtDl{2?hAI_(WCv نJJgv.JxOv YZD$tIY &,.݋/֭|iyU̻sb~!"s amm ݈v[H` Ί]5`%KZaU *HI+=X5e4ƱB[]{{>3dl}bƒh. ,ˏ #R<\y- rZyƅ/UE 7wP6Ԇ t2ŠP3TUI+=d0n7q0# BSk2\ja/2j0ʹ*Ӡ!L%|u_? /9{d kn#w_yl"X&;>)xoO&&MHjmr-~[:>h T낓M Oϛ9.D(:2 EB%r,أ| \& yC:H`3- L+ a?j>c;_%ʾk2bXy^Hf[¸!fVE A:DXL7nޘ2!MF1L{'~!>oI]Jxgjg.豨xT;J|@@|vZiW Up.L< yRIc}O$"BXaB7й%LNK{*Q=FD)1&Irꮟ%q΍~ҩ⹐{Fm;PHe=MG A|Z^ITv8^Y-=slEzN@h(,!3 U䜷v'ҐRQ_ 2w{N匓,l(E$3i۽`Qhbc&SHcxך-&ɚ~f~>D /3%x~7GI:,p *ILF{cCp&Al+޼qGP^!Π)/C[7a4>;̲CCJȖ\r]Y)21S88>wrA;8)}1U+-DPrYA%\%f4̿6q," p- yLW{|2?BjE ĮIwv"+<7r.Ꮤ4 N,?-eutD1Q.^L1.\lm 6}]@̈tݭI0af@#Cm\^F·)~]{. 5Xw\%m |S4% UgMztwŃקY.߉wKsz*͢/} _ @w%' \~^,a,BY).L3[D?Ҫ^VK[:H'7޾j^y2鵢akMo@8GLU}Czr/fyNͳ{ 6Ff'2R߾䚲EY `4J#s+c/d5B>WluN;ލe0`{ǗBvo M,iqd&VV|g(QӁ;,ifE!KJ+sUNo&VYȷCDjU@GLDDԸڊP2T͋?}B>E$ LY,H+c &VlNFw) )To<8[oηcXe.eUEP6Mᯯ5, r=0i.˰.Bg4|`~c-%S{Slr8 OMė@L ~^^~NίP]Syjڑ=P ʘ05/xF}7˧R] eIVf^mHy߯$p?-ϔL 6! \ddfG2<{[̪못@:nnZZفڠJG RK*oB^%YziI0:3*Z=DtH3RY_v͇aV<-=p{[xԟ$)Hb@YB1I[1C,Z'̣/_ +Ε{]{{搛н@>ך=;]fﵾ-ۢ߿w0f?xa iQԕï&łkUl,(֦vSĮW8yl\P)fJAs|e;D-|S^ @Ԣ^L:k=``>sC\.$<+QwWnԃ~=s;trx0{}im2C0D(iL<;˹@S:s`zs2tŷ5 XCGW.K uǪ$_=\'X> 7=A̡Kc(gν*co̒}Yn(k8dTH_$WytyR]%jH~V؜UarɸKgbG IAZﶟpEkYV'N7)^(Ys`!Ig7CbZ"K.4;ќan=xgNKX@1 z{aAJw0dz&n|].[R򈝤}Kr %&[D u4ruyZš N&QǙ6>H8 b5yȁ`'c 44ٽ>S"pe怜dcuzHC_(pЈ~!% CwW6Zdraʣ(<= aGp9T'W&Ϡ0=xͽ0m0"I)ڵ2 eSOql _<'4ueDQ}a(޸a\䧋/LM|Q33kP+$(7LWFo8)77xZ̭$Hsϋ {1w]\w^=)w6`pB$U?[1ICjC1.xNUcX2v|^ Zsٸ-`x*ڴ=IVrTɿ^xn*.lcCꢡ\5Q٢?R M9T+75y _ɨ9tuZskyAL1G{tǏv]@g9̓i .=TUT6eV˰N3K¿2}RR]&@sOYGSzT!FŶ ʻß8[άrd$JI4OX^]B'#S5 P@wЏū F<, o/1gi̮> f+?e|"x-KcgqlO8`=}jO@ϧ'47ׄe@)S8ftpdnȀJ` RĚ%lZw5ez͍g%t}>޲C!ߎ'azix C^Cg\Lsu+ *|7Y bO^MKtԁY<17:}2gܧ|쭵lPUUE؊4b}H M:3LE?+',TF?@Sz!/8uת+y1xa& n7?a뮌q/D8 Ͷ){ 56Jꦅqq8)Yy^""$An;lyO3l}+mŅ=+yMy̑~hMMiiٱq/kǁVt<7ҽD[*ì.&W,%я I|7 j5J;5G L l{.P40Kа>BR"؆62*D'z肁fG:LN*ˎG}a 9ov^1H3!=9CA27sIXkG3"CYs&yx]#kKuP-+q}>kan]pᄺ3oZ1N[?GTk_Zbl#yJҴFdHɖUJYt=5bY:jb+oμ hb %rE_ZcZkkj*~':xbU^sk+ƯF`HʄuÈptjCf i,v M=B'd?EO Ž$ [lc(PW^f>z~{&?k+;>%%'H[@GiQc=IڙF٩ݢ^uddNt .aE4CB 񷍇]d#6gH.s;ɘ#ӗ'`x7LpzI'(8P9j=3ʗamgaH3WDfPZx- [NɡPLk_6ZLɥ?O +~Gy2s(MW]#"l sUwڮ|Z1V t<-稞̽QRz_?([b  k ",)Mb Quʝ`Bؾ#H?;-Y ȍaEYo먹j2KZNT뼖 K[nj>,QZbϗ^KYc!ͷI N>PN)hf EP^CVnJm`~ }hEZ^ߚ̻hGR2=: H%V/hu|_^\\?Q#fwsp[BNV(liXN@m/mߐCAT}Ty-kpBp IY&YѼq{X{5˄lI{}|{E$B{εulP@;mГ\jEa#O?:=fA |<*+&.§I.=:&߀|9oJs^|SeavP^/ET/c"==%*Mq>S}VxjE`76${[F1J޹3=~3* {@34fSKC,vZӍj`ڂiki9^(2jϹjf9xfҤc[:A(BMzJE1' U3*7HDh J+>Ho賈q4t#}nx]ߨLY=@)*c_WvwNEv UQn$Ѵ&#TIOGlwxz7o+͉47R(]1U8i|+[[>؊{VdttCG`;Inj8F`lk"PUkI/'DV1zOՁg 0 }.$8vAnk<<{\N\β+b2Ҥܑ{ oԘԔBh9 {i.prQZH9CjOI, ևȬ ~zyP!UFʘ|ډy⭯Զ?1\CYGҦaۢbڵ1/Q$8SwՀSι*_m5\gG真^HQ." rVN?\CrwwhOT& No\wsMXаWy:IeeټE:BGK<|[\*F_StAHE>A!eV߿eE ENUMH+GH{ X*Jw[ PJ#eTB(bjΨf[1x`r ۤdi[ qA{߼=~b`uB L] _C@.+>>Ε42%^9MsRFX|"f5ts>קmFbAN7LѭWkUq3✼wfOP)8:tӠRKE2jYG( w+7O ?HMO m m2g6ΒXa6[8ZY9x ke0S`wڗ*5 GŪ-zD& cp!bc[ur7Ĺ##pZn>Z_3+B,B''F*$"< /ࡩi BEɦtu2Lv}AQYceRފDRgN?R%EQnu)jy=.!n2GjvDTqζcE,dR;C@m8,27ǟZLKe@Њ`AͼfI臌7*#!(}1o8]6o! N d E`Lѹoۨrs2Sęes!lB\$Dy Cf &8ClS_VL6ndH1Qw>dES aژzՑ TСR TҺ$;Ck!i>Wf?`bzMOv-`+" 9WzIe# m_`cK!zpH*SRKߓ"Zئ-ǎs P@_|љSSK 2Yh[+Qـsл i ^|Y S@Lz a*; ?]AHb(pՔE$ $Ĥ&鴨j!Li3RjtI:0\ Tv{_9#kcS2'F~-9*2sӢ Hlme|Fv* Ogq50 M3$Kߧ~[~>-GOZ MdD:|+!̨%[n].?5Χ&/DVN% xm:J'oʀ?#O$382ڜ_xfuihp]5r*CqUQ76bp(@Go$E-gSpiX*'Vyc42RsV-=][N06{h ZNA=E0!źåoҕ/+?6olS=kF|GN@}|]/)qb׫AdƾݶP?|-bkF>kl@/qA?~'Cf+RIJz"/2Ļ8/wU Fz05#~22O}撛>:!S#l| !; A3&_f\M`35-rT 诰gp ³{Gϛ՜34*"⢪,P&٘d觕/?Θ/И JeiKwo!ǻ~}"6H8r3J?Ib|;OQN>TKnt4x]${ ?/bGfyN' c8+{8iJoH<˾ˌAp|z V­hPyi i'[7Q! :yx?tȩ+Q:soAhR-fզְݪfc}lŸZEQMQA V12 @Qjy #%_' ըM5/ S`vJ:@zl}`V%}q'sSUt_3J6ߙ}SC$d\'al%}s񂻫m L;]Y!bOJ̎܋%l0VMO\mH\;)Ts07 qyLX܃Xd'I O\aVjsX\Pyr~[leJK9p7|_.j!y>QPWdrh9v^[^+=3nCI~Fn qDo|&dUsY%V7%A&DO&߀)lZVS !@/(8A1 왒ssC0n4>eO^L6#Y6RYA(Ldb~[ݎ"OQ1mԡwPRJoa< Wa4[as7'ػs`^r` aQ}8;cHM5=ڸ,r!q?@nJo"-VmhijisÏ?lHmC/md7 ;vWY]Bl? :|i# N,Is0L/4\=+vR֎D}6 ćxfCϧATd҅`0ykv1vrvMC$╔IJqzovf&^y T^mH|A?ƾCpvZܚ"eܣݥE+EfE߾"~k?Ic}SHi!'[`CYH=H^ c \,*1L4Q*Sl"CB|=wD>?[b_/wBLG14 K6hc@}C)>rO.ۧ,޸c›rPN[vwOր,ܷʍ8zea`2}kźem7p:/uߏm=;x,ҋ _ 3kã 1+ X { ,)w,Q xRGͥ$?>ob}M\)%e a f\té~~(y $?b;T՟[ÿ9(3̹*6扟4aX~a[CapDž$=43(}ܝFQ˿ohvo7[>=󽄃4gtWfJ؇Sk}; OAe%+M uB`&Cff)S$-󁌑r`!{dյ%m>̂H!B6VTk$דy#8׊X]ͯ-KeBt{ET:C+s&hC?C){ks!%)vkD:1 HG$+ٝ3\~KsHjgg^trvA|210dCMPC+l=_d|>_#g9f,eEbf;NvK&[#ΗeU* \[qV M.]3m0idB`>ߥ1q^=@e'b&Iui+G90IKHݣM!Z~nzzd}_fFOW={gr%2mbٲ_` [N+Y-" [*a-v& ]dlFUJ7ׇǼ.,1paRn~E\})l%n?|}9u3w^t0 %$Xzl[(wyr%ʲ6+TZܾER/9n+Oք$Oa^FnNgYƎRy ] BkL^p= =pl/P|)ǃeeժ:ŝn, asYab0o?D7 _.||bq,/QsitoFhxK<0%vL'L{2 ì~|+*> C)וRAQ \-~`15.Iovys3*1\Nw1iˍ MHO^}:!zH\w]"Bh|?dɿf}mb~ I:dRMaLyC8_N8)Mlh+ 놚^Ūi' UcZVw/,Rwvn:lT&ރǯ+18*XD* '/F~PE_y6l]qLUSCZrDLi~JBhK"#,P_E珋oiNMh4jy0F ~GL=s"߳B4-W3Y}}c vlS)YM(|AoNtW\ʋ'#,u4H7'+qMw/ :Xh2}-&m6zxU;K)_`o|UNbN|'bn^LA1oU(~]X$C۹~))KA~Ra? Kv=NB=3Mbi ۟@io+{52E,mbn aEI`뱴d BOr;Tΐ/3Hm;<|(N>IKz j0CxHNPSqR[wdi٥L/|(eӴ1CO*ƞ*,iCtse\E]uSC[b: Z'F!.*"ӆhStߔIF~4*0+=PP?eЋY  DrN⍲ăG>~}D!Cظ:XX,V /0zujUx.Kfa>R j5A8%=`6:|o(cb ITc0~~v&bEr5Z'˗i2W]HuREɖhsȿO|9[`#ʿ#j~:mNpE+hAMOe!m܉1[<] fpsL6 7_uw&N2bY`3n<)'#_dF̚kΔQCyͼ9kM+,#/|RybŲݣ ^|rpE~N$ܣިL(- Jygj_Q3GCZoak8e{X53D_K,gPm! \ J#m U4aA/xSjĪ7~0mR3f# >5Oqe!u{yrwwc^n.tsK4Iw# `ڍv2~NI]m k/Xkp{CB4'dW@VV\p#U'@(+-?ֵ1QYC_?Ƽc*<ӽ"\-3mO/=TK~ >K$r$OU7lx7zo>ݩRV!=m@68 C؝/RNN s IchJ7P?C˲~zNYэsӜrۆ3,H38j Qޗ#(/=o| Ҍ)m5}I({a_肐e`k9WTVבd^[P֊A!IڋfAoV aқW1&]=83ո?^ BIb<&턣%yBYHJe+pnZ\NWKN>`wI'J2{|NbjIŖ/ u8mDGN?ҸJ DZV_[P:, Cv **xO:QE@Nze`LL|C#e= je׿³^F\ O* ^G5{Z .4]_?y{u+ls6 1CG y P0?_?B>^}-ۦCF !CrX'Q2(uvuT߼`G$;Iꁲtwn]R22z…/Lk4dANK|%*4qlMlf*3Dž$߽)+LD$?~gn\% S? mݯ9F}wzhK7GiNA8ʿWVAi]uL@. )C8[ f.a$@U=jپa:6,ߥ4JXnS)DvLQ#$frz߆.Ը[OŠn%]_[k3Tn4#Ht~g㴟;a!m2@M hƭl K & d)Iq0ԓ=\(DҸ:A+/aiكE~ݦYFܚu~ ; ܃%-SjH6M|֑VZe*0M˹|hu*Lhz1}Hg减Sl<ܛ?2 {eOѫ@o n8h_JNͼMÂ,e}\gSw3jX!5n'LJ`#*yM={'0=ܓseW_~*>k&etkki>q7RLepJ+y<XXk~6_ӂRՇrd3ɁBoTE0ᾤ cG9᣽j'f+,v=\fo0@Z0CΝcfŸ9%T_ ȧ,gecu]XY CY1|PXܬo?/HrP/JyoHk=Xr3}s$'ߒէ󄾘PD(+B)q6X-Rgx4Ij>@SB]U~h# P;l~8g6$S22mNj2sl1n&|2AzWAJ g ~Ǜoۍ,Nޚ2^% %L2~@Aj6 p+KOax!I)LD|hpe{ eΛkdNxni(Fbwm.@⯫ yuO2VO~9[ ZI##ka敯8llnl.wE1Q|It N۲IGg[Pݺ%N.idr*}}OyI @1% Y[m|OhfN\ 0w,nn|v6Kf6K@2Wt#;X#U ;J4ƾ`{֗w_&#p[ D&m;zT/!x$xHT6DQGR5mB*=9,OzcKWCжe 6+"]sKg,z*~m:P4 4%66 nky#)ێBIkbڃ~eDݖ^̡newWb*./dW=Js$Y/;]SRllhn+h!m t!Q YFU_}dᝏD_0/P>k&.3Ki6ˏ@u}5yR\ TnlseAH?b{(߮?@Nu Jܲ]<;S=i/C3o )6mDD( 蜰Xg25 (7=w,fP*1c@Yʃ&$_'@ ӆ|Pɔ+6zWz\T}wٿ#=OcBUB[Y .B8^]Yy,xy. z/eC"Fb}! &<,kfChRbl_S_bŭf 9l@D@a^c^<>UH L|W v=XY,"b_KU^]015Jve]W.?d}S^CsdɏX'ב[g/dHNSDc0[#$+3J93Yd sd@'j]Kݼit>=Z_$1jնu^ oxj~b8T5lg6.poQ8y{%&R'Q/d߭IeM}YFxt 'b$st \ҊICպu1ichMhTޜ-@|9L6IDVҗ:+ :uSA?Cj;4|-Oۭ&SGݷ8}mp0BI#v&(uwt*gK ѓ%A#@fӛf1;SB/wwK#@k6I3j>_;u2q? !I/`d@H"!C*B 8FK$|T4G~4q`1||=n$,[z)DzJ 4{y "dHVmBػC^78zj~gQھƑш'O (H8|  _I P_!l+6[[YXJKHda0 u;O'[ϟ?P ?^.ypIM6. 6P`K/u`IoT]Xj9})DycQ`[ڮw_ 3fqˉoR`8uU9m+.TAT9<"%d5 yZmXվæA\VA[t(u93{qC@Dƣ׏Y|w6;7Dx;i^'!)EmU6-}KȯM+kvߑT&*.ūÖzպ9X[д+$8B:n & O̔MĆ$.!6cׇL&b\Zا{*kfHL!̘v,nXaTCU;8x$1\m;Q~&sXSnng?sf3Ai0Wfpv1,bﰟo8LPN:T@20c[BFVq{$A |5#BC ub>pnCL K4áu䰐`\' zAf\p73ڐ==I&2W0(sЎ6B2h]5#|c%{XEN{ný:!$?b>Ֆ']sŠŕXtUj#` oM mf#~~*w43 7q2Y9H^Iǃ'@0EV'4B eC&AH13htޢPJ!te^fPDr4DU7yu u](*l LjT eژaf/ML>9_Q5{YtX|6OG'4әhv$V h@?g= i@W㘶sZʔ`՟XB1;?#fphmH=(/cp!7i SCÍ H!֜M]5Dd#&)Aԋ-B,rR]RG#F`\m$/EmUm+\x)&d=ޭ矌"%7IM&fa K%/,(1T8_`DzܿsD11鮥/H BiR)?1U5gȎ2`ou"٬E9Lz.DH#dFS _A:$s!uW`j~Ut`!un>&yoϪ࢟hEnf ᲝƐM{ʵ}ekK_ K y~rjzi Sj]zW(W֥ YV1k E28+ {!:sJj,vheˬFʴf1]HF=c= ܾW7;›'N5.l(RS ;42bV ;Zkf'hD3+}ц@$4R]9MF>IK[(#ĸh%h9UDLyx 7'D>I=A./٦=PMa5c.(E8ܤ\Ud^uBL/z8~W{xbp_jw 2nM y#Hl"BNg3(#g%=Wa6o̲2)uqDC꒥MF ߑOfC%"QI#Of*侲/&6O|?1 3x(<lw4%lCX]0 qj#ULI.rvEb+_^?,Hx W8-G(+Y[ 'Xp眙U $+ѬXK2uy3 ;8|Pba -A0l3~V \2dvʝOOz16ӶuaOMõK_.I%a )Ou"G%=A :!\_¯IVè_s=0,ԹAr #LmZobYZGctwl-WSDX7w E~_ɴhlM||-i1,50n\ᣱ({@v@;?nBm)ie?Ti> u4z:XԺ==PC%_U cA/X-e@M'Pm#g^)Y}sلcQv,DE_:ݾF@B9GP?;Ɩݕ)(2f=03Wy`GNw{bmf!>nwCVdso˃"z p||˂8q6g ]g+k%@R[-2_PR$,0'[~tKp Yz)<(`N=D?Y?G=kx͡b"mwc"zAASxB}}0̚1|5Hr@עajTHnv{KФTX s83Ru>^p(O򰤆ffƊcVў,9g#F:~fse#W{6S>ec4g*y\AGmoA΍erO!DlnI’I0r"[.(bCa&UI[m ozq-x#QLɩ(F,\H¦_eΑy|QH3\;LHlMf켗Ld~۫ 8)PʲAVJx - Ɖpu}D嬍^Q(O>\=g_p=vY.@HHI;˙7/Z!uxv8h3FI{k84$c$ޏJԆjS+L I ד2u_"0ΫG=rL5?yJIqA ~}.*$hW~夦4}WI@b71<0 n h1HcŌ&{v.HE{;wŇ?my eF&.4@¶- nBkY[gsLpu*#KL\it9#c2 ذ@;&7V9JN5kXB2L7n;ĵ$_A}dUh{@Q #*6O 2.DI㛼Qg#pIv^ۤL#/{UgxXvqan)L) @g^E fa::2n`誉Xì`vv _[538]i~HVB;S~9Cuk#yjwM'z W:/`c#q[ :`y*I\H*ENbzQj,?!o\\"۪K"']6= |<+[i:B x>zc.4|hL>_ZW76Mc>J^eGpޢw,X%H7TEI@1koC7[%8*upi"Ex;:9F|*+%&?fOmrDdɶ8{z`r$ħ pd+ iұy_!K|q)_WV#/6"*غ[]uf}z<1:@ؿijD^j%ry/@A!A5]t$&i*~TOt :nF&ZrRkWrfݧ)J@My ׼dVDhOl fwh.98W6F1EUFX^z)xԉӏNبz=yLk*oI׆R/V?ekcRFe\܌j$2fJ4ޭ񣐞;%\W!R__qmZO XQ:wM?6z=&FIg ͹6&a(Mq9ϓתtgy!Y}'7r*Io|^f\} v3k0ܮeuπq@~ *F; WMb 1Eoԏ9*MZ|G"d3|7ܧ?咝GRVo]̧}LDb#y[/M1juHW `Zxz.žRU}['v$Ud u blRkCC !?e%{,X5kt8?k!ԟr3 5ݢ'( HW 4H5 &{ZzyYN;җ>Ji_aHVXW7E_SǺQWU(t% }4|ԪRp.z mgx)݆iQ3u|]%[+22 =l0 TabՍ"c1m/f1DZ:b{iP>N&#g3pKVA Lwox&|xa?#]D~A\_{<r0 g m)ЊL VS ]ӈ,;vM%W=HZH~B?]#/7~|6=}8jYT7vv~/N_/ܓAbu:N܃SA51 +c+ՐUU3kC_QդGgl$<+fܰo*EؖokӰn-ޙbUb9=5FT6z[7jwT_;r^r8=S9j PY_Pu0Бe8fu +QO,EdBI\lWܿ!sgJH-uܛB?^ht4KW!HZEmo¯E* jʾS QI oɎl_vpKxf=փJ5 \:^qz.}}͝3$*;Zw>MrBPURH~B azFO@@&%sCRA\6/$sIx=jp NVq K=xVqbp|[&!?p& A *ȩR'{a3\u걈#I=.\m\zXVm6*w$UiWIe}!LTkiR+ P<]j:WIFd}pp~Ru_z0D:Z޴֨WMP橙 \*q^q炙e$ty:{ʂRxFV~|-֦Wkk}Ƽqdž֨Ѻq״Mz?tA*Y…VŐԶ_zy,գkӂbA\٥ 7]r~HfRAٜo﯀3159ә(`#>X$njwR:nnDZ9"}=Cz,E#k0>1×5#EE.͹hgW}/vi/ȂR ^ӷ Ku G:j #g-켚4k Y e]*u!NYVgNYu֕2%Ħ38q%NZνZ\V{(R{)滍nQVI-l<,Iϟ+֚AW~;O\hLb4{PF[u&uBM'JK7+mo01g^01?" DڶY~#pY?Yahn"E5z#GwX}A)L@=4z=d +6!Zx`ֈƿTӤ{ܹ~P~{yw`um'-g_$|󭸏93{?"pk3ñ-Rakѡ7sCuҬ>ꪟffhp(2 IC<#8)xKr' ?NKP(offSYC l-|4[ `"V3̟!^ 0.(Oレn]Y(Y|df/D"EdW=5Nl|uPJrKÒ=D~'ƬJtD'N-9&2/J0. = UB/x0") M$¨迧zd`v׷κndGp+$U=+qsKeҬ#Ҩ2$g@:/sųL.]4 +}$诱,abĕ{" J_؇?+4ZJ ʖ 2C0_AٳX]ػ_cKtܥ?bv2i'y g<!w 2c :9^i]W)a=tNdQDǤ%LAmc1&3dRـTy N Qlɜ)?;ynL(8UNz<Զ /Ժ6 ¿cќĵЉq>#POĎh& ϰtG6jSG[2d,^wè1Yv|Ѥ T*eYwM68~v'C|T1;_7W.%3r+_B˶CHz_iȧ @g !p1#.RbU!H>mcE,mxPkxeQ·DwdTD=}3_>%zDW"Ӟ}]MXY3b>û9%@0C%ǯVXTM2`7JMNȱ2u,Z9o n,;”+_OՉqJF&SG)ǵ, ~?`E7 ./vȕ7  ?VNZq/3{NTV貛? \A#eRc*=N!?PXȄd7zaz֠f>_A$nDH.k`xJsL  a%Q`┧v-*)I>MEO4_C#7~j KHxq~tj8}tp6ac~cmd ^'+# 1 ' 9IO$o%_g\g}|mu@>`AT@.I }˛$PZ~o n=pڥ-遆U Sg`2 $~(?U9P$7AL/UWޏE<$CzVCz= 㱽l#PUk-xǃy'1X8熉hH3PY %ܠs 3}O<x+^+zWFTX>rC:̗*z.R$ynYT#:XGmGDzZu)@yLaG^%ڈnPimh#&vypTGV7`Gj6ԝaWi%f *{% N]prڶ8K#(5jqu]x;dgFMY ͽC2o]=G^ԫKɣ7^ 4=(ĭ22qi'XEMo*~ |o+zq0Sv&o5N8lάJ0%x;:"(VJg{) peVMR)UFH )fo΍`h|-L;|xŤ 4ז pp},|td 95z _?ԇऩ[MxÓ=hG KJ X#x0lAk{ Y: ;q2Q'Pl0o%GH@ցj{2V.'yM" _Ïfy#dDOY0&ОnؐGX76m;Jj3 !O1};pd"X;oT'}`>Odn=$[+Eϼ1aK@)/ ݄/PR{oQkvUH>ؤ6^<\by˃Ul,RJo>ޢ0e1cZGVɯ^{=G?G۸( :FHիmO>S``{E sǹ{x,?7j.'[?jZ* Y1'wu. ٽ@Z_ND߼m˺aF>YşL]n-@QboxֺUhC_Ss15 *bG` Bc9!#rcp!N(qU*x>.qa LWUݠ<XT?(Z^.%Q"sCa_SgV~: BT :"˫~B3#^}sodmj1D6CRso3Z3mZ,DR6n+FSu__i"'_bLCvq F3,-HYF9uxZLI^5H^TEJP^2~q<;Eak 0s׹7H6+3ѷbBJ*'`/Dy/VjoI:Cy~/u"8GֲI BfD R4n`-7;pZVtjgh Ĺ:DlF{]H\jFv}|Nh\T{?Gt.hu0BF/['p@ԝȍO*NJ;jp: #u%4E))ld bR{j疐a\ :_D#QxѿUp*YuEub-wq"Fǘ:|\Zz53%rm& ~_OҒCUI>"ȇFSqs}W$s!FS2V)5'X0uHg mLQ˵+b֜S[HKg]~v_<͗?gJp/fVo\A!'>ٗ|ϣa#h6}b T^6x fS?Сo%}+f}ZQ,c^ҞJvqVa={ i:j 1gW/HclGP_wRkY A &0hMHB?ȫӭc>D2s>r >*kj*NٕogVY}bN"*J -Mhu-}WƶqB(CYg( &3`ڟXt̫WSIϢ>u1۽=a;}J:F:XV1[߫*@ן6v\2cgZ_<\gP_&dOpt6]|=wz @FNNXq % @u0l65PU]nA~/ e萅rUس %9̎@ D87hGU|S+;=E <#p]!3ըqgKP,'W:rUA_IZ@T[+'뉃4̔k|C*g\s^~ ѤͪV`òD69d]3ڍLQquRlM3wRUHnOKN+6RjgyxGAI K%̼Ϭ׷ők=8 ۑiΏgɃcqB{U/O1d&J?ۨ!Eqy΀!. X1 &$e0׷zc1״r!FӘ} }^N[DԳ"6&\nBLFhCNlM֣<"> 9}~p2L|;AD? {?Ês 7u_T-:ϗh9??jEVyȓĔ 'Va:yݪ8Õ(K6f6 " .6BJ;7Qsr~*AwU`@ִ@;1oj2IҌB3CO2 [~V`,Ǟnsbb{ڎN׀\yaކæ"?vuG f׉lϯ䪓aS2.Akp"Լ,v;R+i$ d@$H0*O^Ÿ>%YD!$LVimR)Ϳ߲ ] >+3H K P0TvFNUl*Xφ׌Y>pNa3 ]rTy~V%_VQ1]fІJџO>LE,| h[§Ů_ެ+>|6{#Dt*EӁ';-tUbTCMH <fCF9A=~( 3ߢ`Fj**@dX۩&5 PCHH?] 6fױN?^ѹ/Iǁt3yxX@zkJ^;7۸jf֠6ûX(a}VamM+O [lGϒXe3GQjٖ t ŭSEzD=>_`ݢ4׵[*hLidr$A5 R@.uTL' a}c/dk78%n7fPi)7IPflEz4@;nNf\F>h+je[tP)G=1CFLlš#|UYC#dǒK|\ԟz"{YgZ/=pp!VɨWc M)l)WF@ Q(%;f2Dń⥔FS e'~܃ɾ4)Z[y`(c@)vu'[e?:dUƹeہ.B|U|C]\hK(*rXt7DX3R?Bf^/Mq|*]VT*мxaGYO@hkUhK[x^{q]ugIbbpo:!: CnkFj@@ PY)5[zx!E$G|? Wp6*;B(ŀGGCm9F8"!)\uM=4 ""' ˖ʢKs8u38coK=B?>'={0E\0C: :@ JKET#:݇xi=э8r24S0'˾(Sߵ-qw_7\bkL!'렦8,XU/\h[Ku VP"3ewuU`nk*?Ps&S;5mIx@juCQVkRRKƼ('l<|.GC~LJӷ^5VsyǺ渷ẝ?o?}q5&UpRyʰ2NFx!$d0ق`>[l2**b~6LSS+T M>ot1ƚc<+mv_jAﷴ 4/p*ʓpe硘z0E9(\m%j%eh{TtWC\9,_[eXCcTi i?6PkAR[|?ۥh9oǖ7+{ _J7z3@g qB~vw_ 3Oה'0:q&y68AE }P8~šo"X/1{q *cGi(T9˒ mv[eExڷӒ B)/E䆌Ͷl? Ѕ ,vlY &>Aĵ0ӑ F+-ܙNoފM>wZ7/Y+#g|~֤P_47M97KmLCQD @n1|3^e̟|y(Nw{cP{%Iqwwҁ?߽>.)A yD^{潢dCDjGnfWzm>3.h kC(V*DI*]e1g[K2?xux#o;/rS{ =n7 E?m)qLiFH! '+gAزƀp+ʃy0(TW]rȸ:,ywf5p?yVӗc fPKW=zBylXBQ~03qy&01+eemz?4t^?$}z- /Dg?P7 ;ԡ@!.ˤg\ # {'ES]0]ɼG.C<ҤL~H~q|CAHY׋z߭%;kޔئy/d-XtDcGxȠڜw/-Ugz23u>_3ú0JM )P%dE1BV@2冀2;ލ9٣y?IϫXr|C ` Փl"KInhBq0}Q_ PZORxR'f#p>aO)x s5 3vXLI3d!춋׆'\{S؆Ǫ&+ \5m+Im[-Y$8Tr`+füLi&,KqA, q6ȇk6;o~  u~wblU:x5%&*f G@^dͱS9sa-R[72oI_RCGi0%:䰀8(~V'JsJOZ fӶҺ=N/Pi+ǁ܁@)M*= Ƹ܄A$. Ct'شr6b mY9, T4l)' ,@NG=X ʓeo /SpϐrDXSCUi2*W'E=j0MxM8}džסy?K0p;SHQKJ^88&.h=IMhDZoSe )J;1ZQy;2Vi*ڳ1v}s%z hq@0F =n02R|ǕV[b== WlFWpLѿ%'2lC23zGMZ om{e+ \1/(ֵiHe7ZsrZ9!aο!ًzנKr_p\J@E],0v2kLt(&CuU|njQr0 zBBsm`?%Y ꡁ55k3Ӝ?: ת ozaҲHw^КJ4򊦆.p#ErW\fDǝ}R-/n!VqRMT{E>[:i\l˜,ŜZ6*/"埣 <"t㾒!ݽT?-;r#<4hhbx #\ p샯 y_Jc{N_K]Ƃk]T>茭̄_0WDdA;ÕVJ % ܉;^|*! 26E"`藴;w~(z{a8a܈b#+zR4`rMt~u/p!Kk{Giފ0 .^P{?=k`C^9ffAJ ~U8A[cw:1ne&VƼCw[XW#Y~6ݔ aP'G>1> aݽ8|&QPJ\{rZ:g /\s"~{>*x: 6>)5W^oZ0,6_7 gRw/QX,IZó |R9 Iq ~ޫ=#QF=^x1*hjOk&~6؊mMVc$"G%k)fۈ^qZM*uJ%}B303 ]ErnBDyy(ЈO䅎$y a/ pZOt4Sdw{;gb3R*'um4Giw1O 5aАܸ׺ !_VÔ=i=\֛l3kW2 *xf1B$hX'gZLZrG7$`溊,ӝ(+7|5FȞ H%!q 32Oqrr^ ][51C3vm[Wq#lͺ,#Jg .K,r?}CM!H5 )_ѠLِ16=֢MYtbV Tt x=9 4aDV}Oeyi7,He6+цZE K5,dDFЅD[q9F|\(0v9(#ͧ 5 ڦcjO=>}IN%Ab w9#56 mH>90kVNOb'DO(k@ =ج/A`4qH^AQ\tK,}s6oyV!'%_Y7ahtD>h9WͿl$GG"G)hhvGTYZވcS5#a5>5:Ưi+2,eu5+ď(BoYo'\˅Uۙ[ @.eЋc13UO(d*ZŊ{Ti*㧆A~yJpfEp:)+(3TNg}N76 ۸Pёrw ~7c 1:d I)͊Uh0:8k% OU4jqyp$Fz 3QS/%wk#@o9&._of>%15d[J9Kx%1j0 QG3\'rߓNgR1C{DA s#^5TWZgn^פPn-{PkЩ> _5z2t2[Ӏ2BSʙH>\eΈSGG1͔kC ʁf|]gP\ ^yDѦy=;3{6~geOd,:B/[ pP1n@\!TC/>LjnxnTi(7Aƪ)PőF ]BJ''nķ%ȱ[^Md6YGS|;ߡ>|xڭAW^1>X*ç.z֡7`{9 qF֢%{'csv0ƴD\)s~%?CCaÜ:G5D i{%, 㩶mR_# HGG*yo6;1.iDDRZnf+MzQzi!q! EE΄a!# -mlpŌQϙ!V/Ẅ́7ʎ|ſ#:Z7ϟꮗ/TeS 7Jq-p~C Y"V 0RJo}sNǬޔ=L-qeL#3vݒ.!+Gq,l^2?$"~O#gЏ{Eg58(|p?M"Aʃ^^(PQ\zJC@FN^.f*"E^_ϟVтZ#uXYɟ!I !{(< eDpK6kJͿ1 -Ňx64S I$Ap}v2'6.,'E] ?..yOpusnwwȠ;[Ej'zDʩ6'5ud/pi6ųFdP"b9hhH(Q} k mw ׵g*/w L/ڣm0-Zy\-9ޱ;(89S&IrUtV1 $cz ]`PM;ѫڙ.?͖Q{Ei;+ma"!h@"PM_[]|fZ{DM֠rB'2E,*욕WΫN]*ldyL:'~Gϑr=n'L ŏFK*#dųNeDP^V;-:% nF~E͈vJy*H!T+`/=,&x?(TJ߳%`|O\o 6w6O/tZs͂JIPM-E { g=[L KxTqb\2˖gIƀ~VLN }wGVY6FE+2#f@9y>w#b4L&c7Qǩ{S9%~3ڍt8E~8ߘqv,\Ƀq{ࣼ\h ;[a-yZ*bc4Y- 3 L`˹sQj'-WQ{3_?R\?X+9ؐq$;%=x8H 2M@. 7ʳ?Fζ[ɾH'Q+alJR,B8qL!Ca9X{U< stPf4['ӊ'G&4?%IΈ at[ \Ve|oi5MVT&wa}):i¯T}q4RD#Z[J@-5Aw a C3CT߯!Hy|1!\>_{N:˺PQpZmѠ;"+YIr9תȓR,c)hs\PT;/UI(GN!v֣aMufR{[ƮW!^P-DY ala20>u7f vRJ. oV  $,/Է6&r˧$_иIkܨyZG`ՐٵtbhO9OEIE2m)LHXD9ԻczVc Tȭ<Ȧ2afq~zAmۏMh~вSyd+Ho1ic^bg QQvӭ=FRQv<^ev+B[%׷f16ۤ+{"3|ƱY ( <NN0&~l:oeb44v2?M$D.~H18$}ubwFcjJY3c|mzW<0oNuu L2տ#Hs d]7絀~ռîPf k0>>bI9 BZfA* #^6?Im_-H %O JoFf w'F*s}EEmHF<ߨ&Lpeƅo'eK^ *0|0n,^RĩpOd廽3EÓ,ex*Pj;EMȥE,JCcCڎ!s~&WXCp7B& |z%맛Q09ӌݑPս7gk" "փ|1HaDezЋ'C⽻ޗ`& ݾwZ5HbP!5W5%*j]"uq27'd@y6N|5ifD* Z֔q:zOKTU;4XWZؒeyǙbo@ D ?)9w,nE)S[^ @ MY1B_ZI3P脘-ML/LW*~R&&ms`/5 ,TkbA!,ʜ $O/e{ -"h ,ioW^j}F%QS7B3@.-g XxJp YhM֯w**A6b fXX.ܪb{#7cZ\Lk(`QIV<w)Z9*:4Py2muHSb(fi(w0mt[M<ʰ57zN Sis Ěa.i u_ Ȳ%L |q*IY2F5UB/_^-]>D~|f95ҥM9R5 Zޖ!ޑs?L- Xzi$f sξ«K@][hxumG/"(n~pcEN8d:. `CQeļFS-`LNjIV&9K9CE+kg.U* '?~RגW/ʷ1#J@ rT5ツU)oJkfx!'xσ94r+e`~Bb$]5.-#ukq@!Cz\,<ٲ1mr,Ek&Xj*SXί\#An/'~bq|oRDy6C#ܺ]? %R f3d>D !FyLʹZej]栢Vb˕ . lg @=7IX¬EPR2w2Ix%dvZk"jaƄRK7I _&Yv>o{Q/?8]NŒp\gOjڶ.o|-e3#|i1'Sվwq H6bu!FNҾ96Baٻm)c|v@&WBڒ HM" Sbo]]([j Lz6k)o湱5:WP kѬ]Rm,%ܗ8tEPY/[JhgЄi"Ħb ]Mv=n>d,o1'G ݪty 4eh6(Ʌu #{QV@5A[FN%Z+ `:awhfUߩfǓo,nBXup6,? I0N1bZMGf[+.n]ZrEB~{v ;0ZW.M_*5tW暖(!;cE_NEB赫3J”1 ca.Z̦GPcb:" ~_ʤѿdyQ( p]mU]˹C0k1NB]Ra+|عPbX1EdM]ij»- 5Tz*6iu>>~XV=XVtΘR.Jn)&bܚ/j9N d+9Ͻd%=[tS6j`ԗkU^4b]!4;8`멑Ry- ! 4)0xkJ݇eV/J'4׺GAǦ(#NPqBB/ >+lVy&ۏAOR5Yɉil{ɰO|С@|e,BF_Ss>;z3kJy AgphQJ/VS >D3T`nu,* `PJ4lH~E#Cm# FK_)$P4VRb׉w(ųV/xI*eJz(X(u5>SM~d@;T2-M+ݍ|j֝-წ CqVw%LwW$T[*g:ݜ$Įgm3E)b"dhNS7g?4XaUI+]#[Gsq+>3,阵C Th5؆ۉ97)e ýyNBh+9أ~LJ0:F~ǨgV]/u}>erlWlL> XAk}Li{ ?Afu%azUY^  c<`|fx*XA;x{[ОS :0)j+~" "i-YYAFU JȷUcɾFN"n#/Uc]KEz ^gP|c= 5OQ6.ۀ<1Tte܍R+x.|@+7= 7iLL@F>tID*qQ#d!C;n9.ݠ& !TJ$]=Rf3ĥc~sA/OZj^}%&Rk3V+P|ςA2K?>»UM3IltVR)d23i`6|^yMˬ#L}У_鄭4Rt%U)?\AHjR̔_Wf,sş Xg*=`d[Z92x5H.6ZB";h4q6oH {#A* ,<`rl8d‘H$hڋJ)~֪ 8lPVNwnx( _`*z% ~)SQڴvTۚ?X]zOV02PG`2R0t3e:꺺>pAϴQ Ƴ7_h|:BLb4nvBAF,Μ 2* $@K?&tձx6኿S|-TwL rϾVSCVP6Vr:xl!RHd kK;RkfQ7]YDSX"ߺ'N8nw%_{Fvjo5-e\7߻h"Fcp UF0HIA@7 Qz( .{%'O05?10Q;4@TZV?687*y%zp P`u/;صA xgƂ^] 23NFM~a }ҥTJ%Yſ)!ȗ.؆ 5ZxϥqxUJA}ړ}LF Nw LjKft?ሼ/Y18zSϣKN6`~YD}bӨij$Dp*avI@T`|cu͐Bqev[_eVF i7z^\IC}-,Xe>a>lX)靠A)i[;z7|3UZA;˃F |_>+} TqZ `c;9d.=ys4$CΩFͧPW^  yOE2kgV>L\"I6c%8b1 aəlNDN_ ҕ4]ݨvdj3*ߙz)#{PR9;rw..蝸a7y cK&?zvXxA~5~Z I_; ( aRQ3Ӽ⡙$p?(~?0Def;﯑oψ=Ո|]:BlCIsjg{䃒veI:\Gc8}\;\:rSp޿9 mWp|!\vN~;H.i^ݰYooڀ?Dp6I"nIy'ӣعx+ݼTu*z o_ͺ$}xq绳SڗS~  7 j{^+2wZe=Z Zēgɳ#* Z3qJ5*O>9O$]0D]$ؠN%D䎙/(r* 'ԁ_wRjPAy,":x%$ }pNYUz,> "bt72mB"|W'tY~Ĝw**T'h)7^9it2&UF Mڰ"a'O Cÿ3I,3kZ3!H|W|!eQ)0Q;[/U;߳=L+KZ9 <;9D7R_fz">kauyHo]eƣ~vO,9nlSNylIrᩏn/PD&K9u_NP".4{B5/~PT"%I݁g/ }!L`H]A~ofq0`6lW-;~~DX[^3q_ zS\yV\T&e A*nÖ'+%]AN,0 P SĶpl쫼K!@:+y=C~U { W/ٱv:XK/ &Ѽ}낶[IzǠ7؎I#"{- zZQ!7 $9#0C6QW 1]48/x9uKkj`ԯKSlYռo%̷ _wv8V[5_%/{0NUͻ M7XFB]Ll6"Z+1ZH,D k[Nԏ@c],ƍSNQڢȰl8)>m_b#`1@NNhia5͘}k7oQ"讹@+"S/UG6`$ RHQ٦LȽ?X%.s@z|.iRd}MgcO'sR#rM?x/|r*sfڤ-gFz/k+'ei|fOfsدgq硂uLv xü4HZ{?s9m%e1ۑ@?fّ rA腩+4ǒ7&G5@cvFֶg^k;dtaqlc_pN+@yO;>nD}ynk9K=Hzoub }A5F`z4F>!VpW0}yCZocLP[4L]ǝW-IXmoPD ?*?Rmӈ|(}V;=t݆^M햻܌)I"RI0’`=,J!ן'~ BGPAy΂,*ɭwt kׂy 19%숥&OSЖnrAiuA~ rSn*z0o1c N3p  Ior `NWY:*↏f!?yU)1E%uÍgy#55CS>[Tթ0En7p5(|lB5ggQoړRN*r yUa,JաM >t3h@Z>VXPmWk!U}wU&Nov0cB`JO}T8[B׈S{ƆՔQ{4&WIcxuomheaY0?/ Yoڴ^yc m=Sw SPγLǣV=;k^7  J5,46BpMGHaA"l~UQ*oF-mB#$oxs*|Lb);;i,Rq&d>Op+vUޅYb\%?uT;3 4` DMδT3Ë9cR6䱘6iǁ ^*!?Peu0Th}kX O HeW=%0<6v/3x&wڮnϓu)Zo.R)PT:E;vcGw9펢(|YH7؎cÍZBx>]iI}7ߩ-(; C&.01SS T1K+zxvX#wLE,mA!}3tx wQoq:n= Lt"Nה JhU$Ag8z>8Q)=ZƯ >+-C;ΰHc=V-&nNݺ1{s= w{EP:D ŊMIJG VXwAG^d߉ɭ">\a|}lns5BcaNeO7XM$$`wyB>xq|)[5''짾R5uC1+i ΩC^/ ;Hr:u+>|u QT7`(.-@:oa*sh%T^G Z6F/u3tz x[u%1G|3D8r}C2++YX'_3CP=ESc(Q\9*fH_e8l\ú'$ǿR+erNzI/FTLBN}{zz6Mef!`7#LhMc, p Z!I{}<5V0ERO̠H$pq[K޹#2~*^^<"648enj' ^G)(f&f#[7Ÿ%rֶ;ĉx nkT@1D'ɀ!jˋɽ.APR1#EyX"OxTuKB9y< )gmMv2^:ap s9 GL$P?3E.r5ż+*4(}o@a}P74(*qC!IAc/=CPw#Uj_wocz(]>}C4ךNTE O_-DlZ|0b-)8sJ16 _7q2q'٘i>Rx[%&x~lMI K1vٻKn\$A\>'o>w;=N%z,z=z w(X?S/Q},3WP![b&JjV?BgЈ) Lrm U:l14Z%kGfU,.YV0АCcB1U2LSJn _@!<ok>D;pZk, fг _'aJ. 2 pX hK9,OpO]yR2Z/Μ#{H@A|7 $A\IV]oxhwt/xkc8Ȋ8&Ø;CΎEQ4BIU^us]e0͸_zٕ|@} `^Ll ;%Ƀ[= }$ڎו5r|{L^VaUg}1MBvUI{U]&n|?uz2)'z=@E {SI#EJʤ78wa?Tà:9|W?UG}!*%U_M@o@}3e ;?hNv^fLsVڌDڋ'LOixR$S}i9O-/"xy-vuS;?]D:(uX}g8 \gX—`^SaYSӂ4}rR=zs?H$|+h``Istf<[_{-NÈ Kg|;mGv=edlbb\wo4[z(+Vٱ#ܑ-I"~n&r..O0%?\-yQ7]&#jKd)=7mu'S`),J)]ByI㋎;F2ka[?Kf":Lxg)/11GCɮ6͇g2/.tW.eC݋$1ǝ+1-X?T N𴦎"ZP)u&Չ=>@}ݤIL7BEaOce4+& [bޙ+}\2G1oAa,^Fq=_3>7O]ztPzsOEa'+]ڷqz~ݣeQUqJ7L9iU=D]D}c՗Ǡɔ=>oer^XT4e5McMk5?NVv{DQ[ X^A /]5dqKv!uǘ t PpT?i9⸴V, M̥z{P0Ԅ{ӷw[mugU6Y;:NIApeƺuieыNru \t<3$& #eܯc?OZZ n8yj~iܻʬ𥏏)ZRtҌ^}<{z{)гfsyf?2>'*~j@w~,a7yQ= cd^?)fb*yT2k1x(Eˆ _/6ͯ_'~ާ;7&o :+VxTOq0ۨp/Ld,wpvP ab$NY.P#k,Y)_RPPUlIǘyk^}M؄a[*+Z@c#&7 az0ǜls~O@cCte+,OUƌfg "wBfoWޔbt]\M "ɲfe; :p?X\)px*yc1jL⇧B7dAQGaGAy >g)$n=/?% 3{2 ,UV3zYqms16vPo+ZnCbOHggCUE_j@,,y?'4ܡdR.F$:00 7<9!S~$r[K^s+ר/96,& #=b㔿cqM~FA Զ%RtJn-]Wh[ZZoK3W|7Q.Ҷ#DTK7;O 3Jڶ):@=TjS*xdvkrE;n8?-5ɈR!M}42r(;Yڛ|DX̯k5X<=iFNy+"d{/om6jBۓ>qzw St ?Ty"Q^=i^#v_\JӏVo֌-) “J;7 B`t(RGdUw;4D?~Xhve7ʲs}XtEmV"[v7GݸVh /GskkY:> YRϡL *s9m܎|rpNzޔ6 IČeHdMv'4Tz5\+%4slwWk 7?Y췐`y 1ZR4BIP%i+%d7N,4z}@h.S[=w%YnJ0TR;IJ`!}mmF.;<[x/4.6V;]-FCrp0_KGTߘCBapB 11ga& h|cgQBc,b?N]rOS-a݊fp tT<ѳI6,+%PY x]z#OR #1OШ`m9dq2Tgl3~pa&G{Ծ~?i%;3m^@>2{7g>S |׋IwkP;!#g X _i~dKZ uBZZDR_biNЇ 7\*k?5҂hoBNZY2}|2 iJp?o bRPoX-!~KZ6=lNw?`T,e%<2}Țo#! hJ‡R aM2 }G SB9'|Wl&)ꖦoAc(Ta̦Eqiþ pqڻ96CK+]N>x ;8V?G MO5Ewb&Z2RIMjm12|fK5!- <a~J̛cϯG]PvaXƳb^%Φw :a#3qSk&@\=&$V!a^&mNQ-y7$w5yD>8e2!B wI8@@}"?DnY9DdnGWV MI*(# hJfʲ?0zK9wȟ2L_,&~#:&}/(-ϗuOLʨ0X!>^ ^AрbxFS4ud|e…pݽWw[- GZ5Ѡ}L>e88.Y )2[4y zs^`|n>F|;${P/ZeY QKh*Izgwj_p/E)^ۥ¢Ûu)" ~R#Ǭ 0N,7C7|O *N⑰sHCd_IAO\ aCAiueCA*^9uOE9mIRa>Cՙf#!j\^_{*k[ر\J~r`1bP,]YlcP6hsH̱ITh8x{wMGPOIqkY88XBVBK3ILܺk 8//aSst[K&$}t:SqӀjpW=C!=Cb'R2xLG(,zTG"'Yl!8lHqJOcolOB6B;UL.?ޫQdM?f&*͚Y;pJL;T^{.)/D*5 $!}$nK8˪A߯5dr$ )Jpc  zX"'¢ncWa aU;8 }tT 7}Cv&"cuR&+!ٴ{tZ\{&aǾ_nUR)+zte٪b慏,wI y5cl1 ?evI6,q^ t۬AK3ټC={wp%,:}N[uF;/> wF= 1!pCH._!5MɊ{x};# R޺E+~,=ȃVw*Tma\uX]\h[*]m0- r$ȿ8ېb5]@V+ ]hOFd*H=J}u#[ o$ǝ rc'Kqj t-]U{9. cwO{o}n)s(&#!؊oQ:X y?)t8s= *:fxa 9bXy8 ud\09٥-WHy})N% zs-2YA^>͡aLP8rK*'uZtc(ܺ]O+fF4hn*\@ՙ/9ncPLk=7Bѓ@ͩ\q wRXDe>@j2[(FjFWn"tl08m Pxng4ۀ:VwDvmw\H/lX5\dy \5ȑc(+<̝H gRlpD֞˪o)H坯u8; 2q`p2㘻u~8@\WmFT|mtA$p7NJ^jgiT&>UwFU0`lYvt vĝ8_8J֨QS.e˸i+xe)*?$2gy7c[d7ֱEigmxkS@W>&HQyh8❟qLI;vUjUᗦxa~6!L qHF+a=Tn"RJ:=lc+Y𳘴SyHPkIg^v=,| oޑ$Sr+lKyvb\ΦRT.d?0v|ݔ$z~&Z%> 8~1k0児J*/< M_~\}O⸻ʮA> @x"*n sծg1!uԺJ;&hHx 52Ϸg` HnKDL$)-T.n#$C8g 316|ק5(ծֲ<XӎU, +b6*B}.(*cĭ gfrcY/k L9piOBjj&ÿ+mC5 T=[-O`Llf,_ZGczT2csk@Y]!:Y鱂nJG~'lx,^"z+~(唙=(UԋC}^Es -:}bAi7;2 !zi^NXWw} sN+]0=(i?Q?K,@`V9O*һp]Ex|4Xq9pp_:-eE{d~55SфwqĒѽ מ97G6D`:2YV Bl6`TX;|>}NSJAJ̟Tuk1@9 KGENGvYG;yBaZݽZE뽚ͫmRa-2U&ϾA k4g]+~1A=1ۻGEE`U1jg/uYqc!Г̫cB&@mw^\:<յߓ.Kiot.$݅ fë ,}lFN ~Ӈ{\v(F)]^:gO(vj6f{ A0Cn u0ekj].Аr3 ~m x}›J=X4ȃ7J5" JФYVwA/9_(Xy^RR:&c5J m:kkpx96< Wv֞֞jc>펚OE*CPJƁj]7BKV4mx԰F_z}b蜟J 4h5\ ǘU:Uw!:}4W_ RD3y^D뇰vq 1ݯ^gL`f!:T="6EۧJ(w,߹c4ꊊU~.U7FJI팤«vIFf^Ŝ.,A8E[~ڱ+$&@ /Fʪk5\bnG(;dg3e-){208)/۩,LSJx^j}  SUËB7JX~%{h2j_U!w`\FqKa;] D&dm"4!%uMu[OəS#6ѦL꽇Ԩ#eG_iT<]ֵׅ 75ZDҡS]FՋ-pΣJN:^Ngba"ޜTWj u"֠o1zNPNgYq+R+/1 ^2 0a LXnr#y(Ąqh))yM(>:ԞXFE9]2v,kÖ ;h, J>D`˰4=U`*Exv|OT毬"qF+7C "`)!4\ҸҪV/ɸ( 4_FQ#)yL ~}-v.ZHz(̋4-ѩ$%3mCKׅ~._ײ1%/hwp&47Nσ#Lԝ~`pļ~iٟN})#/;~![MQ{v?Jc@'Amw1c~$ Ӕ7]\ca}/_X17ќG?|_'&"7GO:UQ`)$ P+!-(+DhHD[Aϯeׅ ~U47o+A~~M47~$_m?M Ͽ&[B~U4#N_knI#24jW]j\U~G ~٢\lJoG/~x Ou?O3ކ3Ca7?Я38?o~GV+ ' 2~Mm(u_8a7?# C~¾a~~㍿ceW4H:R_U~%s} Sk@m ;O< gL}]aW?pӾ8!8\ o!DaWCpr5}}5!8m5(jNOՐjoo)X"ME?R_K^GZ ڿ7b<ٮC(XAȒ864M -d-@>ח_0X ߡ0W#s?JedFTAU,IڿzW">2?Cߩo=H|X\|)(@ː?|_~b^ﻫ m~` ` F@Q"OJ EʼbO쇏t 4@FX}sA`O$, oqP~poB`ӻcDBw_y; ~B{{:H2Iil':`1Yg4/c%lA'$_9 Fy If~pAAbAX}d~%b+2C,;<Ma"k))wuMt@oa0/n1 cGwވ$goG0'"q>bh&!,KbG/>0]" I|߿B0O,΢H- t] pP 0` ,C"J$BObc Qcp}"flP }d>aB3?`po4FGy'X!G %EpB }$NB Çػs,Ϙ ӧ,QhfJʿ@s_ͯWwz䟀,Fì$?G;@`K_B;GQkog(B}20Z ?}O%P ?1(ҩȓa0$E>atl˟X?)z|;& D{nW*pxbt%6!?QJP_\kJߤ߼hG 1$N~8j$I 424 u͟_X+ѿ D鷝zF.~;n./3_Z4AϮi" 2/ "!ˢfKԿ6~;s?Ư$dgx #$M1{7Vm:+ca@~X`5(卋ot#@f A4@yj拫qyɳ,A|I 64Kd8RoOQC>!qᓠ &'?_E@?V\Vc0M#ffgRFXm;@/׶)_w{τf;_,'~狢mi=`pMޠ4?}#tMI ?{z4OIB?LnJ %c3@&l,e.}'ҾGSJlu 8ξ֡z9NYǝ@~oEΖk6rȝNm U _l'؜ls+ŋs5=' (r@Z@H<ܚb\c)7'PPA`u[|W2w%tG9".鴟8:Uz}rF, EUT$8!)ŭ<\0!L;l 7bƋ68XetHPnGI5 OqqBk=ep^:>d.TBȨNG:#⮸֍M0].iGW}Ml3gUas.WWs{=@7>nl&V\7|>ir^& ,kg+H8?X^& d꺳Pk¦Sh%InHMnл/\c,@Uxen]y)u=7NJ[IIjI2+y)<1D3AazkQM8Bzg 쀐Mc}n!sX>XRӥ,iCgeUR'zlRPJ+ b 󪈃JY!)ϸa0RO(U=b/w~MN$ϗMyUq3;ֵHpp!!棐[ι5VVdNu?i1{պkV8aQ4] g]upB9B{OE `CyͲKvvy*T)_xb.Xv~_T((k:%vhԑN7mi>t=㉭ݳ)紲ȴ >0D|}}AT_rkd2+i:Ks,\+/LF)>Zy ǹߍ|-[ p̂yئ-CUv4zb-m&=lX}opzs76uV.惶O{gZ&ֵcĎؾܛ14]q+u%$pʣ*2 N-zIТ܊fwyba`$mg.6|tZzƘVhBN{QǼ ? Y'3{&>5Nlwf XZ4r`[@_[djǫ<59֣ -!`<6r ZJ+2MNw/JJtF_㺅8ժFn=e]ُ^vJW5'H5SzrLtoP<+w, xЊtDV{Kas-cE1!H a9dH0Zdzu\KSXM<y0Y+=IaJsS ý>;U+ʘJqg ta4 hzs@iy=uT7Sj]mtSr$>]ʦꨚpYS_v0yArB_S Ktt4t> D|c:%!ů#wa ,Nʕ$i#"pk|U2S&<)"} @> pCV0nqhmb@%H|^3f@>@q/qPiv D11)1-IF|SʷYp7^v^Pf=nqas艃dmFv08O? Az< )goaIXB?[sd$T.z91o/y`6a8 #R ,裝GcZ##O5{h6 ųLL&aSg}@^2-2wVꙕظf+X~ZdF>Hu4I!ċ8@gr#}  X74m|uƺp6,ٽi yXWt5=^K:.y |'sxhp"9-03 ?9q -[p3; 'Av ]DGBz;0зYtc6ZeЗ3D楖8 o&0Xl]12LT~=ج{ D9@G4yo\/+1F)o6'0KJbRah!fq 9pԩi{ퟤy+{m /r!e*>ux=$1~P`#e΄4h9v;|,eՒV=S/X[גlK3=j&UV}?bwth=hYxPYK"Rt`jmXb7/Iz*X/ eALXbFX:r5 b>481s8un6(n{S'7jܴ+c7(c b3¤wޣm)Lhz@2YGfo$?Wm>ŪI>d%3=?$I,GP>zo(G(۳MX R75gnGl.*4CZB0ZΒptS@cTb]IBd)Z􊺽 051`R<Ɇc,9p(\n/XF6S)'^s^L?;7!WxkpL7;k/`y@7zƈ;I gPs+|PN :/Aud. ?OtY^LCW7A?0%Enf2;-V0Zv,4waNTNڎ;T{#֋E\sy~>pbY.AfS jN;wDt>r?F^$9q?Al=ie=+%,w/M}]wU:)kB.LNݻ~ "aM=(4Ayeߨ6g{RI[zt~,0 &T.XDKfXD죄%E7M\ݹ d$xhӞcla {s&;iVCUKv7#~61hO!VBEGɼ !ձ ?d8?02j(Vlw𤘝6 5cvݳ =_5Si溘"`|(O`D] "P a:' ͹0NȀ jq<pfZHvdE:z6>yx .{D8 5c.b sD-~qjHONΉweŸX寳JgQmU;2rZ—(7{/Ϣ6L6h yt`+<^iz5H֋AyG'y8x d򐑃-,D gHۺ93<v'4"Ee Oʱu-I/[[:_ehACd[u=2៦kAd/vܒk5_ /.ΰѻ{%:D!θ&CbR\+QڲU0r3!̖ٖ#WN0;‹ tu+2&mEdS;,BLNZ2364lf:Yb*c9<@2n< 91jF4!.ވЊ.3C}DQ® ~=EЛx6y^+_B7|0%wmE>o# &\.~8ݠl!́,,]鯶[>Nw_8X[-HC79Xt:-,&9V;KirB]-_;iy=;qBwi2+{NFӊk Emhةoz-q>`z΍,ߕuzT,sSݒj<x,]})]rr&@u_0曉0sSWwτDh+Vdw3{0"ڋŌ|~EC BoDzW3^e.Ci`lxYQ05Ob4@Xᨪa .)}RYdbVN}rO '< KTISitS+7r8g"P㜂L*wz:ݳON.\q whZ/0!69v۳(_jmSs|4*gnScAݐ<^zf,)soDH8vt sGWnӫuDS,( 3gJL70?ihwXΧJO|jT'I,eF7衁ڤ#ъM YstފQz $CI%%gq_w̙}֒i+iϑ QOCp30Q YvsVtSbh(;Tfd-v9S 3a,̙/9Gj[z4"w nU'@!#.ڟrv54TO;kމ76螠ًOJhNթh4P~ih[K,Ah83L_˵ \[4!,5y=+L7ǔ+}s&Ct,˟50 5>sd4q>$"\b(\PGJ3U%R@ `WN@ns&c u!·7KM=%^#1He1=K0J|wjap'D գdQCY1.\اfIJҸP P-X)Vhp:^POϫ ^mUm켄g$V`^ѫ}:5) O2 w*pV=ų;P! RKw }i8-\!*(ߔo^x L4XQ<}}5T_'Э^|ĵ\m!1SC.nXG+vY.R>zjPrHA5~DVƏz{tm-Vq$wM4U$S=9 `׺NwqvK3PMC-SM &\\*5!rfqD;d+( LJ(yן >ƿٺV2V߼46q_+aIAIϯNd~HN2Aem2cqwBE(`874lw]JC)aٟ3n+Hzu}MTx5(3>GRW/]B_ 7Y?|,DU > ڭ!&c`_?'́Rli^mo1_,EsC{s+/졫Zr؈ؾ_oSs ({ل yRX1UwQ՛iꩧn~>Hw.YHS4 GVQ=26Ò1UPjs/x#~=JQl M[~Z'l0zgc(S.k"N $HX]?<MKSiE`{7OeΊl߼I3S 1 ?B&_w~a颍Z_HW*s|tQIgV'd26>ZqՙepL0iEhE_KA9֋ZCasLd1|%N3:a@ii⯎]o5dk6fD F0bKVSDe+iQdg5B06(Ocfuԃ@Vb:* *)Y=H4"YT i^syX.{O(b(cV3 vsJRhyA#}Aꕒ=hD+5xϖhsQc\O7YgGr38pͧ\p7ioAX.1+Qɋ̕cu*.GsM g5J?evQ-yg:m.RGaA5"3|*,=zK"z~ItEJqDY'.G(k24[rW3M>3 0kd+cf|H+W} (JP{qīKum4,Sh,,:>&^@sTo ~XD^vo~2禋?-E~s~~<>bSE'0Н1ÏW*f f i>?z^]ah/r־\R 0>(ڜEpN=OP-Hˤ(Zʼ#<&˂^M=]¶J51J#6-#qywQGǢ\caU4D$SEC98f~髿Jzz?b˦^J(~q.GJD)A¸~cfZm0`yiեOb#>òtWn 4IA87Q Q`)|>d\-1mdynC펅g>$ƻp6JA>g!9bf9F򍬭CY,H'­2x^8Uן߱ A fڗɘ~k0fqòeDxrSp2o!h Zu{R_ԇ~ŕe3H| RF?QG! *t_יWѮ|8n7"-[Љ֧@%pZ&|\ "w kvT΄1W ) hXuآ*YSd1UZQlY6ykPg:Asl q +-c(@Я~ZF-п{6Y\b:"6{~,R FyBQܠR& xĞru ÷MZGYgEXX$!䀛 U ״P..aQn61Gr;bɼcNkfNB dxl*7n'j#^z/+WkTwY be pRisi'ҫtuh{L„|=~wrgraQL*F3hǕ$@O@yc7ۚmU%{G ӳWEΓ½?eMulEj'|z̬*@5x`Pnij^2%u? <#fCLrU&OêMr>WH@7$T :w KW|v`; 2Š]\ Vea zdI0]byo \ɵA21>SW>@IuHx: o=V_xc|˳%$(ɟbpq^iGHmpR҈G-ܕY}oGI^``[8@f+WcB$֌ͽZP.w#덼WEp}!5?́-- I>"tGJ ^kߎwIh{! tF 3scH6rRȇcb!Z쀗/z*pmwa;k1>5u—RН=jժU>LKX-Kr>ًwMƊ#G{b s]܄dl ?P۱eڥIεqgͲ~wgN .i~^3ɱ,C_|,?fG|0Q@0x49o'n-@l!AX不( :VhcG)z p3i7 &IgjfNeW,`}rdfHJT#WuVYSKu*711GgX|1eԓ ih* 6DALarh,14Vip! -v<{ח86t~<"]#ᓿQ${]jafFV-]m{18Eh$0bMөqt%#Ov[K}iSF+X"FW;%Z i퓇?ؗCX}^s [L]TGr,c>09jE(4&Jzô>˼m֠vb2C ׬vC{!Rm:7:4`gX]]?!e45!`r%&cZZMDf_2x3sQ'=}N3Ǜ3Ytei]C)O7I|Cv]8@ >XliuDWje9|bz$AfcqyY݈wGL D~c I64I*|-d~⪺#]VFOk:hcR[[{{=T;\r.m"#R4Gp|u? $'jlyA'JEXTdPivJ7귙Ct+$uQ.Z_h^62lcx7Ŝ~ uhA&( {i݅Wdg"V-=mi9/B߲z޵a`DtBVDO6͢\$Gl_GJH[Z_]&Ԛ͈Oϛ *9:>%٪: j*MAأB|.9N SS>ĭ̒a-MO+ Iq?*xPt q]2܍ pvpLN(e531)fNC;ޢawppaX)OИ|F1̈{'<- Է,.EcpWs-Ϫ3X4Y:~oh]()U|nrªg_-4淟ҡ]3>F+Q&,5Q޽@:]>lP齠GpfRO& 5&Wb_6dzDz̤" i`S:΋ ;R!o!9BcA%;ԶL#Ȑ7Q<`HgPuf7uzR2ҥDs>S`ԋpk#YE<+)}%9aѷGE:,x4?"8r Ɩ->m&o'!ŲVW%7y'P޷CT;!Z|xroqľ 涍 IJ۠0852x20`U_aa3c AUL%^Qq$WFz1=򏶞1'HyBNO@3g(%9_L`}V΃ؔ-];^Rok1 Qal: iBKr1Mi5lD P b]{#/蒲f4 u %^?}U9Yb$;7+G.WFOv+5˪XSǷ?c2g(00Dǹo$5(e۟zZPc]B cdK8 DŽgN 3u竛ʖ9\;.Og"@7Z/(,Մc3\wQUiɛ|}j=l/F; s&ȣOub(0UquO.N\O]ܼ{HI-QL.Ŀb-5/pޕA eL_ҁ EIR8%J/ЀGjBU|4vJ;!|`  C%p6u;e֣n 9&5J?"GWtw~cèw*?إmiOt1ֽ0h P5W̎O&=Zcz| VE>?M/8}+M^Ա® tBsS&z /dէw/S>Z J&B{UádRAYٻS)ɍUL˩1_y]*%ET{~/<OsBF #Ò]ׯ'^,ϠNk,/NC 󷠸'@sOo#qP}׺(cQg{LL ۃK17 N!/OudHL(or4}Q5_SGۇz:SҶ!3Π hNۋҲmX,V|6&'/޲%ȯ%\rmwO^Pu?kIZ&Gu+minqbὉĊz";wt&3Kӊ)wEtuRhHQo:|S֎1] Rj݀((yuhtM{2'e^!#̿h VP[H(L"1KtTI1AeDĶWfi˜elKh .>|tlΆB#$=h^cRQ,>Cau. 9B5fx9lR uV71 畮Wu ')/wۍ5? ׬}ڮ7 g0h+x2,=, J=`/۴/Rgoqd&zc- ᩇGxΆ'J&C" Ef bsW?NWj׮KsJȟ` 'yXz{Q"AX􅯜y5fjybR+y ʳdoFM=q"栒O(V€2$[,,#_+#;ИteYAy%3ApRN|]BTh_/]tc3gqEroHO` $2i|Wz-iߤ|?e0LIQR+k,%bz$V^c\f9rq dHQ)tsb摛4HޏZ=;]n*Z>۱i#Q:7LѠ`]Ϗ~U9ZđJZ`QDʼnF~jSAdvoW+C{+)=U&as`IJr.TIo(!qi^[#c l&?"\%^(QdsڳC|&OT=2bugl$ ~Ȁoa;W[VNaE[htM8$(eND ^_> [`D:i~/^ LvEǺ cU&OneGʌg%{4ÛJ⁨RqWw14p 0P %oFߪA?ft2*reR2s՗HmٲSg\$CĄd,7UB\Q(#_1 [Լ-0x2JP㿟/y2+~_2{ &r^fHVn=kNL\)3P-EM\ zPkY* n?>?J ȝbB[I5mh~hW" NZŵ\;mBATKȁ?ŬO:V j{L9F8*%˗ *3U`bZT.2V=nɝh,; F9cQ~2Y=IU-ib۳T+ &PiE58{_}MU֮lW^Y(?H{謁J*إU<<9}SW/uKŦL' [sK6M[΀5gЛ'AWľ;.;]7r- qFYt_ԧ0opY䊓7E #ͭ`Eos -C>xa@#JǦ-nzv5 ;'+bC;IoF'cܭ"8+d[IEhR3gqzo i,3CSVh4ٜ?rʱCv&mQTBFzhٜ]n}Kcu a ;LiAoq-yDg,'ܢ,hnx]H>pn4d!L2,Uo:뢧l:&pbVHw?aS!}Yoj_Wci)wJ@AWtpsbpR[Y<:;v?q|vDȤ@5STVl drzG eC2^tѐ:!c!KI`v[ZfxO5mFL#UfK47T)Z14I`;&P #7(&}iyovQ2)XH1=9CIk0o)XoGr2Gs.xS~KW!v3V7]^" ɳ9lF9N[?U@*GTk_[Rl#W\:D.31Wo~ɑV<>&Z] [p7]oF&NTy날Nٿ7(ng: oy&M{E?]z2:x z3".PMA`̪:aQ<:8'@s ĀpZK$n^Uyl'KB2PI61iTkr/U?=$`NfyR.}x/B}(잤z&. ݦ_tTՅMv.$4CFI6*Ʈ?nE[r9.+ϴWxPpadr4->r @?>pn!Xf^ix/Fv\aӥqcGyxa07:G`>T1x Kz(V+pOYHAAЃ|x-))^:$Mj]R:W+4أ1]~+;as%2 ^6fe8J92cyR.RY Vw/ <=2*ioe`HY%Ű~,/EdWdu2yoŷ1 yrh?FG]9DRd3M$J;1 ,>-~8 03Z6FޘfV4q)F,rG2W |?N>YjdC%{iÂN3L^~"w"?pNoѸfu}IBuY6d\&nlu^+?~jYAhh/_ +E/ -}nSᝤȃ]VwdO [mDq\{kB_:mIW!'x$|hLwbn1G Ρ_ZUi`Ul''!~j[ЗzT"`H=>Xu1>Dw휯He$7k_'yE]0a#f?:dVIAdTVL\DM/#8߂|'b;00*#V2(w6Ox$ȢW YK؍e"q.X r`߷awɈ^-vsY{'IiC|+#iՙ M; M:+ْQ(e&7+A^=qm#4 /]2g،"S \K3lLZtGyPk?wpo%PacnRQLqpFQ@V@5(LáTXPq|5It0-/;/˦Εd\p MS?szT0|h`F˦ETۜ$!,!K;5O6+eoZ+Q4$A5p~qhsno}QK(Lh-:¯ZH̨c润! (UwX;qRw48>Ԯo2i}xllzxE.?Fp{:L;U W0rgs"c ͔ofqGQh1;W7%Ers핵 YUH2+zMb9Df`3+a e,'Hagrz#+2r級.YXt;_Q9V(B^Ra ԩʿS4ι*_ZJ}9QhY-" XrvN?\QM4-b:EJcG+:״JDLqu<]1QV֙-j)Z#( P7DqC 0 Տ)%U'w2C8W0j'݄oYQJ i :AB4wk AxZDL+LWᅤɪO$?@h9_QxږRn97 AjbMB L] _q! G +>>ε4EKdasڶ8.DBu >קmFbsʩO6-P5luؐjh qN{k ([`z 1(I)=&xyOQS Wn" 1TH& 8'KL6_sg6ϊY`6m n^J>qմHy\:)`Td ]wW5x  FRZ#8ַsmK8Zz$7=͎dF|0Y^6;h IUz HA\ {4 QQي;P5%d1^Z#rL{5&tr>l~`S0HX6DB5*_V7AtJ俞Z#(⢻x`Q,$DV.|tpP ŬlP6w -KaǖMtR/WYzh}BB@%eqiB|)l h tiirQ}G88|.EwxIE `9|KiHk<غ ~w-Rxjabh'lr9,W$pu1DH9Sa6jma)hQ2yɁ+ORj88:யpbA%jR8B=poJONݭ'6׌7XKs_(CxjXrs}̚P>d.zմ#VC3-*2B6؋)T fk}%9 i <\H(*aVY^2I$ђd.A459=Z@pk Ⱦz*"2< ]UԶF6%"Eɶ`\nC&q`Rމk7zo/[Β9a nKG omه)jy# ns{@MvDTqwjF#YGޫpd6JtXGj3E!UB'm4ϒ7Ƈ‘_6Qʓ@ >'^AH^()ZM. Nfʤ8˦\xFX˿.#y$G|!H+DvL]#>3&St8s"&-}̠E%;VJːsGg_Tk@xAҡKCLcs*DE^Ï=ߛ95VOi퉯P[H:]gdo/S\vx*>N DHkiok'>ö-G_Z_P 1t"ؿE3o߲ qAtr+pY;I@o6Jy16%5})38$LN`jϞ/g{t7SvCzÚn8\_5!qs1ijF9ysso";MԗF2ZZ:Z ݦh$ J?-Bf၎S^:sO"*I:46ol[?7y&Yf|FA@5}r ]i )r70.o}{]|-bk$]=ӿQx l]ĞDBMii 4ΐHR.zAx:Xb?+'ϥnu'1PWnf< .ȣȕtg!3  YP&魰tHP|L-$qc0[ˡ\bp!m c?NVb 5{P95#PVfwL\!X%}K1=]R误@-ा-gj$ɚ<F;]H/T55.L0">~J-a$ꜭO@u NAsunEhJi nH>NTSOB^Au]؁jB%'jUP q-f_Ƥi[wr3sڟg,AJaEF6r.|[ QVonݰ̞aBK`u['I7 ΄ ^P&uK '07)C@A& bv*& z`V$ Ϲ.qQZ~?!ad{\'Q+~-{Ewûo L?z ?|_Uޱ7BI~E{ݓ{Dߪk `:A+ETecTc Ex¢ß)_Iz*׬7$TD,!׼Eryٷ>.Jr.l\Z̋9pيKІޯ$ Akhh.bl\79D>E] X?ݹ5*8=s[GK[/Ɍ:FR>^| w0XR S N%-hmZ rrkUOΥ qaEP?ҁ8~Sbϔܟ[c^}U9Au)b Hk;UTuFJŴKM6fA21Zy*ZƜRֈVAce~j&oz!VԞ%%Hdž{ "S\)3;nΝSPqUX{># #fY.zWvԖu{(w}:YX>/$uy]{_(~8]g)wK(bwaY>P=2 OC&JzEΟWY7iqG*%g^"c Ti*A?gOp/6`3)6l_Q_A^Tr0ιjVNi5W4G%qpGB3 V4o. IjTԼ4UY!>u勵,^-47/4odֆ{SWh$KLE=UIYJk %\.k9J j[ކlc?uv}ᄟ;"?"r޼_fʣW 6hc@s#%)>Nߧ<لc۳v_2 z!u+2m7YzoyQp0mG7K~ǔSu X4%Aq?վS5@,72/p)0:"2&ıo  }qpDY@+= 2Ěv6Lzn?{eVVȶViE#sv$>'#=,\U34c);a^}9[P2#ɞrac'( s XyJwR*3m >ӹ50/;wY\ܪl"aDMv<Fhʜ.9i 4m~Z(RuQ"͋J!82FĜ-vU PnjbM뾪W.JZل#R.͒346.8Pw[:9O0&9`mdQ݅HZgTZ$ , K7c_~xwP pE_Cm3{ q݆6PQT" wx`@yDv:Nt="-W)sGVY{6n࡝{ *s'ݳ%/Mo^gQ$\X|v垘K9'e,DV .74 slPH-~d'5KCȴ]DUbCƛ_rE\i/;8YOkESV֥suv13Οw\4 w(HڋBh@}%eBlC2Q9{iyMZlNpry1!;45&4[yLŗ˻h>1{XĊU/e ba;Ny`QʝxPDBq^[W~Bx[y9߼S\Fa&o(iB9ߕ5 ޼u$СΡPO+|sa'14"=$?<&OA1unN|v8b2L|314<^qP"$9tuN{m[2 v"ϱEwG[˝sBpΒ2p"Qі ,p;o]M.^}Y9<f(YX*6DrI"8!$:ޘI4icӴVA—r}hZVNw \=]NrPʼn^(~CUf? ("t o9!^CF^(zFik;8 `F͍r`3(A-*We}[SL1njޔv~ŝB֜>MP{d;uzcʟ{d=4#e-J2Q\v"?auBRLrM(% G6k#6Iײ7W@vԝOlXմ/NWMzגP. i@ϭDڦxeM^Tɡ i4߄_񂊷)SBwP"$JvZFqUj4B;Sk,Ɋn̎rCM]5F%g |ț0jm !w?W9>Rgܯ}Qx':=Djm bj՝:O?&?MT==j ^*ʧ)W:8hnON⸭g~5.^~3 ȲmVj6_l2~xu'X}S< }H.\M.N3r~? ڷ.U?X3/$C۹~*a{"Bf!%M{'eP3EˋM۟@iѮBS zl.Q6E\Da9|6Y$ 3Q\@In.X"ep:gICW'/~-UҬ|ӓÇVeWOssM. }ra2nWg홶-yRvuA}*8W,B6^wU=5{f2OMb$BZ>H&^0IИ*8VaݛoM?Ӊ򧿎x, z\ӵ)tё7U[VU#0U?)Cڄ&\8,VA`FC%xSlj\a 2:C۸rPŪ!-T,]HPbT7Q\euqGFѦ,+3Q%sPS`apbt b ۗנA.nybȥI 1 XF|]ϯ'ϴj1em4uZ[$K\aH#ckwL ,ς|@Kr,ku:G]"; P<AɗX@u&\հW5̓KzfՕOP]Z(6yh@8mT M@ i{E/hTJj'*qZ 9[.nHo`=>d +V0~NE0o ס޸1nO.N+*֣)Or΂uwf ?$DZϟVg9lC&,Ղ.<rq^c]CP1 杼\.roZë!R 0x{}U7]b%Pc)x2Ѷ ?qн.U]2JA3Q_-PF2_ԟ0i  ޒ@jSKQKR͆mi;|mDS4 ήP0+gpĢxD^>2Dy4"~_{1A2ܧ&oi_,P/1U|V4Y zxÈpuo!C~~Pe~$nQo=~qb4Kf w_ć+_tx( IAHUL+z2g-= Bf44Qȱ<#Mh!#RT ǃoWI> MH6aim+_5JKt #?~30d[U)'; m S?jU\@Bֿ³$[u=֒pQzⶫ7I'o>zy}ڍ]/eA F3Lb0 ؎kڕ.#d(aZcAPh ayiEKzPiO^q}[ jv1NҜ~\;~ni/pU0˘f;5*D'T};|v=keСcls`3T3)l^e"bi Yt:pb^ Wf| T-=rS <腥Z >#;2ϙ m4״{skb_'ԑAed~![dj">OkAf[׋7} #1*XknDd9>dNY2ЏfOnh!uI؊Es ,A|[~cR*E| l-8Bӎ7%'O<0?|>L.z?=G|/K1b|whz݋uBrI׊'4R<0mC|(yV*,MiK9gBx5եc::rHEd;;u -~^o~"QޱkJ PxHl},deI{@ C翠E΃p`=31E5dn8}/SϊW}2o}@e9K>aM6넳ӹ Gtg 5P۹ ~˦#Y5zOD#镲Duh/cTndv%׈tq2 խQ0x}g4 !Z0 qb>ˇwpaUȳO5ɷF7b!@wߖp 9OBM΢P~t8YSʦܙ VDMq}hNxf \FեjfIMBJsAZŞ)0EE.q/@9zRτ~]vBtin&q\6BͰkzfG0^N%UٰL;L:o!, =G`AE@uN 齃pQ"pO!drW ^ud~P'L_[{_p9頹>_p=+k+LKJt.Z[mR|v<%`n#rx' nL"p14p^ZX<*WLt'ҼG,z (.l4yξo q̖vׂ&403`eRX"Aj3 )o9;-$Ԁ4FF:p&?]hV9f¯_Y?dC G:x%7ǂ#CNqdFZ0ܨHߩ $nyJ6lZӂ%:@ia~ !JivBbH-7)D$r^L8NWƷ ʿF~O% ɐPR%w_N?*`pWit] +z}ka5^w>sY=c(fJ?AOmH)g϶&IΫpz}M׹ܧ˹S] <RA6PȿDj|R΢˂k+^_7S3DkPNyMd{A􌿜zD s7 Z'`8og5 q6EZM :s@ȧZ!JPM;xV ׄLyC$"RQ$3nD)m ߨ ?~SBo[^;n v"7#>jMm&&Ӱ@ȬPkXf!?sYQ6/{8zCDʥ}T2%S@;sAvXt2?rV;W"Kԗ̘30oDhFCԩdx7R9`dHFS* ш %j-yTD!F~~ Yl1c0R>n<߄j4RVA9ʩf?>Zcwd = lBzO]uJ8z~ig4N'OH8 Fzx&pjj(_|_M^*1)dQeqL,*&Ϫ$=Po٣a9<&`nE^PZKo؂pYY@S%/Հbs4\sRqkU/ˬ LL -deQJ88 G x$?LmtxfBkpH}kOV7$h 7,aSY?,09 ??RfO 4 )[̅(hӟ e~!˅=N\_u"8C Db;;@=ZeZWYr7zh$4e cf=KJ c}ˏ Vi:U5fo-qRTkjOc|"JBGrȊ-9T[е<RL} }lý׮3Nqd_# wDPŒ⬍Ț:>:g"CqSRs`PMeN=xŕӊ5um5 m} &w:&,ׄ@CGIgofkF>VwE3o]'#|7ZumB_E|9,ŋ}O&v5y)N$U>%@AbWS쫼 yFwcBgkI,%_PGܩO%:0I#vv"xǠ+mvN(0\h]6 `e=62 jfcDVbמK͚`R OW}KO>ɖ {~h) h,zscFAL9_f&\/"J/SW÷^aMmYOfjîM5- ^\ [vL'Z?O/$axU$u9V[ƨd#]YȈ$U?rJ(j]/v),ߍg,Dt_E񶛑:)|}pWUł5uŁs#q+HrZPeDƉRD^%:1c._2GP4I{W"Ns<1W UyLb ד89-^ F :3D9Cx Rߺui0HeFŬA8E~͈Cč;.$J7KD<lwWbÐ(t @Nw{bv!n]{*Yߖ,Eb{sՈv $Jp6kf [g(k%PR4;Ҵ؃|HN)d/ɞ0gFbh@AXLΜt=s#ޕqLe@ {nYpn~` A׌luS sߦ0Rw;+v!0}"轖@wVXH12=e+qQt{lLgrxo ?t0̫wC5C+̨j[{K$X4D{DW_ \z^S_W߁3+I/3ez*&N_q cue]qEpu  C=RWEד!_~v,h#@~ N4ikNx0[lGG:{:I#d0e-TY7(YD}UA5kk待bT!M1E yak@CM? SBjwGv[$&]K"dw>Q C7<.b%ع5@'!"K⏿rdB^H" "YG6W&; %D?o3P6?qY@ أH~!DlniI0r";/(Ca&SI;ol {q-xd#gqN1ə*F,\Jfɜ#6#ex2N}Qb'&wmS0gbbz\l.Rmj^Y+3o nWx_9_] \/"usF1.gc͊BE߹{7㗗U4h_c0aR.2= C(efw_]bN^ꓰTjA?v-@a#՝ɞLa;툺$P 5-|gaM៌5Y⺘15`ȥPhokk7B v1mIHp:XSȻ:c1K^U/n $T Os8&# [ T crS`3YZ/ _ƮѬ CHbT5gJ,ܺXM)?Mux6B?' N{ZѥUAޫ:k㲋snoK}`}ULȇ3 }ٍ)îw{3|m17|xH"ZJ9䀲˹C_˷&|lw꨹zU6f0¸}크 &_>v#POe:9-{u4+)`GܐgE-"blV44v 1hC\s_J%DM'bޥ'H*7Ʀi,H)o[K >f) (}Jp@3b$=Be.MoT'Gވ"2e$f@t@l 7 +ЩfNM%N#+\)]JN|.ƌXKQB ~t=ؑNPcJl}5sE>c|:?< +xuݷ/Kyh"Y?>KNn7bZTp~e085T_yiutcҋ EcV8[eƖWyWZ)><@ĽvL2|2[!lxThPΡ~NJt0W&9ş}oޢ]2Ю<9\ixV>l554<፯o.:6䶺te 1Egܕ2퍭{^YI}S7;2 #IXv w>FL$D8EWRJ +?h͙6Q7*bziM1"ﷵhUQw5ί'ִӊLhap0 Zx[:hOmm-U;IzBϐ^%>UZ[CަJ|m#k\:=>4r%lwƢ+'b?:^R30J).͋]8749dmu0kP5)[_ BAb2:P@3[ct0s%f4.'iEyc_n!fҋ0NBoa1Hz͇Fiӎ<>LTGPw۠BVjd+'upg#9Su׸)OD~nNx&`ɷ&R%QLQm=^ \Gnl\h:S|"o: S^[%auSRɱjd4>]>o__CDVKlw8yh}"W73ǺQWU(u% }wܶ|ԪRp.z mR^C n#X,u pT犀 CA#Lq,]r!`t %ֿdOnj ݣM/ 7 N+VQ97<`s`̟"? )~HW= 9iF߮4"nCJ&Ӓ~Ǘb4%KbPI5f}. 1{e7ަg}P0=}G$EFE{ڶ(XκNgb=( 8t}a6&sI(YC]\X=\1+}:r j H<$T7y':[2 97,›=D,j " w~* Ȟ *i;.;(.\;/F~Uj PU_tE(:pwLȁ24dEU1WI"2$.1+ߐ3|-A$K N:]M~723_BxqNp2қoE* ʾ3 UI '/a#WY8 H%:Qm%JF \9^qz!.}}_F}3$*?^w>KDLb¨C-L+樆8ԹjPm^ITiف)z"J4C K=xVqvbx|W& ih& A oɩR'u9b3\uA≤m/=Q ,7;媵+Vȴ>Qnj~W44 a_i (^`.zQBZ_9WIFdpp~Uzrh~PI3|FYVNꆂu6 , tNYt1_TBq/NT|E]T)>I#%JyW jͨ'`|`=&2yWjk5sՀIVa:'ۺAi?`.s5ȜZhcp"1r $΅gh+M3 mƳL,)T(Na#M1(YQ8b3o]^vscP$ךTl&h~ 1eeXz+|̗H\Yfl(>mAk\TaL79CsJ :tɳr ՝(k 1g {J9[kYdhTDŽH+4ֺŵրFό{垚P-ϥ)Pw.YFI^HWg,D>72ؘ^E-|u nh0llA_գ>Q{E MCo/5[1$Nj8(gz ̬걶}d47[/;p`]6T\>r!ܵKIr+_t!fNdJ]S\g{&BN\g£"ƒ'xIGs8?i =x6虜uΎc -OD{FD)Gbtaa}bktG$.\sk'DmX>F@RvN|zAԕbMX; R`HqsF0cmoaմD[4b(R g3mGUcgk&Tp(>3M6Mq' _)vN³*Zlue 8Ȕaɫ\x)a !6/"9KzEO3X^?ҙwTPGt C+2 !G ӮrP56xxosKbn>H%|nV]n12tH2,0G.=Xmͯ$35e.[;ž2{-a^H1m,Z~GdvdA@:ᔳx#52ǒ+gźT*Ȟa8ze8^le)޴[ Dt>* Hxrx/ ׳@BBAt]Nt_YAtɩhΛ洽J}@#J@ގb^Ɨy{JAE_- ~4)bCTu|VߍueL==i> G\ }?c0'b×.^\V{@zr=@@[eS2K?B` -BI{b~+9$y-@ Ujdh.#/aPӉ,XQԶ7y{/KkaL_ĒH6+9heR.粒}_#" "->\t&o@V/vN˶:Ih| #HyaS2MG|Hlx3;7{˯tKw)i,CÁ E7߉ZIs3> 6s8{`".?:[V*:{3.?6ߤ?!8Ih(x1OhfJ{ԍ+U[$]+RHS,~H~K)P6_S87ev 3{Pu>wjIZb%^f7SRtTve<1$c~4;SUTxugubܱy+i$WƽMޣNU\e{'Y&to| [}#8-($$[[7&rHݶᮏ #\jp}k$n6,cxQy!8 ,a}` 3,-1FK~Uqdgh?ϲ@$}`;M%> =zaʈ|[hŬf?%"R*` ]PĀ"}=ݺzuPq64XTȯfTkd=% xH(OʾO[_+mҡ 7LpP7 yYVqY?gQ7fyX~ PkK?z|g+rѵ;b\&!xl|',u ?=XB|;(g#u;•+P.He K7~=bBw8Dfomʖd/ *-\o{09-/ܑyŠ)4p*R?׋eoboI9i<^|&nY"ke>|XjDV Vl7_B6?W0#xk^@}o*wOز%'dACYS~v "|3b5%xEyo~/;zp"쥸o&:!̭`zp6v#aBiA?(ȔepZofMO4Nքo_ 侻1ϋTDm{jo!܉hAZ{8_ >SK{4QAgXkcAΣ'aԘo90xi B|쾓Q0:wAIF_Ze]*MQ|D|w9XfXLJvDJlsܡPO˹ +oL?Vgt<'dfqN&T-9QIc3CQk5T%7JcU_$QP%YoqlR{@%E_ k\DyPbB/@}aZ͎\{-̭{ ocv:x11ݛp:*trMWٯLJ)x{nC~ nii@ bӃ ^IݘyA3;4NvǕXD ĩ[ ;rURO>MEO4@#3Gn |BTH*}]7rT-0V uj8}r%pQ?ߚcm9 WV" 1 'Kۊ9M[O$Ulg\>?v: 'p0U(PH2H~}xvjA M1eٍ '--oL4R9Cu[$mO4@Zϊbdd}~)! ѳ՛7QxR?D<1j-yǃ}_' X:hH3sDnP9Q9C}>'K@rK/~WƷTZrC:̇*{.J$ynYT#>X'mO@ -,]QR1[ݙ5Ɗwɏx)J*8:%U {d%9$A_VMLxاD1kc4ml}AqzsVGA2scFO4% 'xxKj vԛaWeM -k$NEG?h2qVc!ZN(5iqM ]x?΍\wj?m|sElBXK6kRyղ'#.$Qe_jjvStto_7)_G}^]K_"G(TV*g{) _/m jb_ |0q >rl.SqnCc!UnEvWQL@s?@^'0,D'щSkݨ)7??ef [ O?F>,)%SN8=dm|W4XX: ;Y'qMAq%Dj+mɨS(|>7\Q$Ar HI]MEc"ĊUmSs7( ;vdsJNYeDV@n` bOÓ;XaЋWh3)/q8ش1qƈ*7Itk $\ Yax(( D87_ߪ&J/*_&Y,^^~H2Ũ쥄o1sηM12WFD^|cX pxBE*POAޡEiTDD_{^kخd]aTU0e;2ꚞݴ*휖av)-̆Ml:偯VsTPZang@:ls+WB$'*Y:$- ;v5 7 }P9ؿ\Uc=曐 t|:I)sx$0Rˏ=v$b dR_z_h0ԽO3kYo~c`C+O /}j0'Bg!:^q¨x$)jZVvЋe;āichN4F/- ޏ ,8t{[َ.of : R`GlB6xFzk~ȷծTJ燄Ҝɭg~fVwTe |U~$'sXk=d~z&i}\kfyK['VN0n偮&7Bj 3j{g%|CѼo9UߨBwйY_͋J.?v!w>13V-D3=swͣW`)cgPݬou9M!I͞c/oKA5})C}uQDc;9%kF3wVj,\IJ؉REJPQ1 yˇ(,߯@|(F&|L}ЮujO2e/_c *&<jI:z/1xQu,^_uõnC֤z t[J0m-Mʑ7Y/qFtvͰqlBSphOv _5wIt>G-n ˞G+UAJgE~; @rbz:̼LVY\-~n=Ȝ9} d q5B.|5 KHLq?^.."h2 5_DبƳK6p _cGEER8~|M6E=TA5c;胿d"U7fE %ta{8x bo(T_F01vG$nxs/ j}+ h]z͂%تx|4'-5ӓ,"vxj) Bygd]P `M`U3,$#e}?LJ[Gv+6ܘG6+Y ʺ&pr~{`dYl̓8፾j@SDLb ȿO^ lʄF-УlHzǨf_[N=7[#Ò;LfŎR<`TU.Ņi^.Yf{^VJ E2>)1̐], #B='6}| ýk<\6`ˋ+X6?B͂_߮o)gJ- i-ocb\[)8~4s)9ڪ$6&/'e /cW*t ܑ-yTUG7E.F~XTb\ F,b M-v,+z_.Y鿃jXF~kMO_ѹT MJ>G̋ ݇{PjFPIhjq6BE1=iosZKe9ЫʹRtGCR_(JYoUYcC~Sqs?kYIzKp!s:L69 -"'Z1uOvdX5v `!: 7ȼR"f])9hw߶{~_?ҹgJq/fכNoB8QSoCS%@!'+җɋ !_Ik_I_y'fs{СO|O\݄ܔ8Ge˙Z~K~(={ i:uj^')zIǣ3ū('㛯Z@4HY4ӟ0df}Х$!vzg*Xf·(SAڹ>]ڣ][/Ov&i-$~I5u\MSYqQ?/.II#{$eF&0˼i`n8t VMy1pyJb tuR6;[FmoSK>'WYW)YDLׯ\ IݒX?hK|T0퀡@}ܡ644y"D&-gdJ@uƟ_}SJ ߪPL(FtSx3ɐSI& < QB#eȅ>޳3+tm9M4tjip^gnVnq:JفOI}5MLg7S(L/?Eg~R p v6^6z%9eqӣP)ߦ-$ѝ8ǶcD2J+>ƵMJ*B78Оa+:3tl*0\4iY,fĔ_',^7GP[Y5 lXж~s,KĒZoƒ}'T~Mrm9di/Jym#-(-kӷDڥ@'Lc3^rex-@|}qw>!o݇}MH\Vg/D7n /$t"*[,SiHP|0x?(4C:"\6!(f&3$PiA5P$ɡGH3P[C/:ONGd$pOA_l>7S[} B 0qaXيGΕljn G݃edbAB=3i,_tTLa}=GR"qù"`ԫ;b zA7',+D>^4ѓ D0[0uD%aw.HlNޯҎ*Βӟڠ?נ3V%\/X"r ļJW|hn_M{;.HUVY1ʶ#AUɣiP597Ԛ&ﳔ}=%/7(6-UI٣R~Af7aKDwSl| Rԓqkmo&R*e/fGHo)Bl[uoe|sݶgG۶.n..7;J:'_Bj.h0Vʒb$Ԓ EeǤ:QTzDXKXR(}X/eVƠr%Z>ggA'  a x~%MCޓ67栌[*sC$R QѸ9<'zVX광)O%|p`%~/r+4'n5ؠ'shɋܗT z7]u/TLk$+3Kzaꕽt]0 Ň^ cٶ_$_ZtvT|4^Џ6;Ma\IY0W槊aɓ2.^py"伍MrROKCe.1r!΋_AC#?9 ~kNr$ c0Epsqe1^:1.@|gv}#1'CP~b+ss,U J*EΡR׌zHIڱv~A Nw+gm +.YA?2g}x~hলJQLUƦ@s'Л6#+29uĂTIjV|(!G|H{@=]!V$0vp?n/]s`1-ٕ6^&Rg8J$攠QW7[Ha bWr3GVAV~ FGut#~u~} /sk&%o'&g%P1LQfdE|df̓N4 :=WWV-M#2)rj36H"o%*(z艎9T`bR X/^ֳә2BKE"Gɼ w*S}@b,F;t,WBXT@ a#fDAƤJ %'~fߝ|iՖA eG:7S^iJ[ے׉:?jtd疘Tj)6UudcLE$2K`ќXtePˁ8_$}*cG[1O@/W-K;o&Z~u>ʏ7 oɖrjK#EʉE''JrjH oy^)XO$9eAX[/sN$ of(0ڝ:Tr ˪%Bb-epUKP|_׀aRTbt-6BZbЎr|},t'%(LS?2GjF!c?fj!qI9 ˣG]ZT%l% 7$L6ulf5 @JyC&By'7Rg)L>m]#`+~z+NTI >?zw)h= FHlj8k/U]qW{yE%+v26|30ZpKH]M6O^?ixOyn"hP3Y@U+.eCv19bso"-*v4.@8SC,]8E䃁 Bx9u\S 1tvOMׄբM=%', 4bncHbjZ+16rw[2X,o1|-ٛ(:\b`>Hek5ng*IcO!$kby#q֧oFq,Ƃg>.l!*Q}Cq1ü* % D`_3H+S/0h2^×H85L.6I &F?I}`kn^4P+d5܉vkܲzj7W"GdIAŞ&'ח/E7C*\uDCQDuCG>ɟXN@0=q$yaC(* UUhyt/8 (yU@ĖoYca#_*sB|jN݋qO&AԬJ=f?c ¸[mj e>\|ds;A.ǯ,*a{L k b`r  b<#%I] k.n]a=R##]y"bx.{mkŸaߘD8y 0z*$sϒ2݄rA(lܥG}ƎoxX{Aʾ g':ybgPfC:[Jhpna&9MdW ѰZ +aA_sKc|Ԥ8|mѐN  ɒq|ٰ5L*VTN1OU\%k}2鳶L;WV:{GACT6\W/ہM .&ngyIfJ홴+|l%'+ė3QOB귤d{̌a:By[xn^ x*D8e)pWb VTVqGz@ (؅7s4=^Μ״_Wxӏ3Ć8Lgu8#9;UXۅ8eU)ʀ#u)[/#t|Pk%ߴ@`~VP䔟TQRncdpAK`m X[baڭ=>*X8ӚkܤKD0u0 PKfak@U[Y *F|;LIX;Da^z$Ő y]`p ^7OXNF|xOL\4:=LɹvZ-y&H]fډOzX+I5+$y_s# ߷f:aVϿ n.N/Ah,  ́P0f],dEOR~foB FR礹ί hJe=0USLp|Y3&sbchMXpƭJE0<$UGaEK|SR}ѣ,_JY9ub_96Z8v!ztz[Q_%B!(ZF3+B]8”ETCϏ,U]dNQ9x\*~#PiL{](K<^AЃ#H"ji0G0g<8]ЁDto%,`Ti[ :-d8g0@_0X")?Z}7!ě'^]o e HG' {Tws3Mg#~UѵF1!X؃]+T **@3;;0~FZOӍUBSQ) ˺P;~Y~B:_rKcUM+2Eⶹ_~ȯmrLJ;dž7IGȑ/[zNF3 |uXtoצa<% $o^Kl˔s\:(q0|ucO0 iӒJ0)3J$j?\~ :1 >4g#u'G: A|(D S!_5 hx^S4ۥx{?A>[DU]R)TuI[;Kς&P.ZEUE3ػ}:m 2;U  y *M* qT#YsF'0E;Ƌ:³f'VF<̣)yiI5WZ_ki,:h[Dӝ C߾DI b=dq* %?;_y/<h3{0C?6riXa+5}X\W!)'䫗ôC`* ؐ:_XD$&XPȲ8sUd/0b_S͝&P=~~z5@]xo$ayt;E/[l-Fڷ(=]|[$3} ҐwZ7 s{j9M r)1o}eXpjo$">֜/"&;Ni!QZ/lu٦`Eq5;L >{#$P/6D/qGfcYį~Lor ׷ɶۺ*?თF5i3_Ֆ}0mҷeI<~ ?mC }tV* Z<$]BƘj2$fz&Wd NFz9 4`DmfLX^Jt "#JZň:f.A"Rt+FB= }-WiBQkn6<.ZH;1n+>QWHCesHew,,B9ˁLۢ f{S>ɩf v7 +kO)<=Xʢ-oq{¨<YPfe6w:l 8{-πRh}ޘͻ,$EGN6=d'VΎꯩJsūrtiq-y4-|k_ǯO_*NHQ=84t=*}`iJFe.| Tq騌ţ!G'-C xq4rb4 ":c/IF pO'G;TysJ*9(% 1YQsQP 5[4<Ts(:]`w)K#5ђc} f\<sI v~%*BG(D+'P~GM~ /8屩Iև%v눉Ͷ k35֍Ȯ_rn"ӮbJ&"n:Aॠ^\Om1|8%b'1?,lUu+&!?nP܊CP17 paIkX*DL[eJFU=CRή E" ln_II!ͼe6uC,oׂ]ѲĨx\\YYO-fְb]$X0q췶x)afDm.h] GQgő(\E0cV}ҟw~"jOh'f3ӎ{ѬGl^e]Z?*N>&7ndJx4࿾d'CT4Y?,^*F4#mFתjOw !s($?Jx(I7Ơf/x:$XW ' ( zµE.LxDt%gHr0J:8=t zђAkۘ_%Nv {k+%C: pU 6N9XH`z)z} uT '̮ZK#,9\VʜŔd`}0-Ksmp6TR%g9d~RӆD0Gg)W֘K [񥣩]GTҨ؟|l':L/_/GC"ܺb(dAmuҙm )AY3Mr [!O;dPI(oFM8{zb{O$@ԍ7/ax1Vd]YH/Y"2OVe|^ T6-BxmʷzFii s4;C) o}tK*A,xnG8 o/bVjTuTg!q?ǵ{ʻWv.|R|OAd87"L,ӛHRvtگIqJɓ͓tf\FIJe\-bR-=0!RL X1L 1$~Ǿ0 hV8}pv}.iVo9Ú mB޼=efEk`@EOY{Jo1b.U@\cxͬ6D0egMJs b;`2 +؝XLyŪ&]& ż7W#' wgQ1_qAb~3#d0%wofB <47t8vAOo~~9Xd G)8VDAr)#ȮaJi#+zw=ʻ~! [@ۆC.͜1 Ĩ$#N+[浠Uڝ~auՆp |׳i.?.%Gbm&? 0UQCˍ!Jc}WUz+!"c?*˗PV&Is54pvaV6^l HO=F.e 3ʼ,udNngGg-6V,fr;z>HpIY9  o;tE21Ð9sglvW Mk,_g.3LdOJ~~iZٸ^Ӹ <(o ߵ2A?!4]>dWuA_1L8HԦXDMd_nS<+M1ʂ.H=F\g6m38n{?: BxC8_4/ᘪcb/D|,a.frVdE0^c CuS7["~/N>qZGMeG!0L &ʠ ?I `5>e.cFCSW5xBzU MXؑqHD}qloN} ĬB^3!V||3vqu+k .u!} pAo@Ɗ ctй(jN}$ZK |8&:x>6w?6U %vC]`zHM8-bR `مIC~+T*ݝ7ÚZoMS3Kjcd0#yj܀"Yp׋$, G7T݂zH>Uw繽eW9q@RAzgh^ ΍ʼnW1&7V, 7O$b6T9ִU1ڕ{ق{20w;"sTMH!<ōVJ͋҈k֜ķŞ؁'//TpӑFX>'A~Fķq~Fqynz}GT(OA;a4Q#-UIocU|'.rǣ$pPOYr1xj5Yr0ؔQ#doKC16zvmӶu5`K#<@2oRXuy+e𦃽+b?/^E?M_^C5 (ؙ,y&bL X/e(ft7{3$8gJQ!}|S?^O?R%ןO~qMcOѫ>4Fʗ#5Y3EZrKLͫ|9;![kaXP7v耴`x+ӆ5%O6zkF+gHh)荾C#1j +?&Thȼ@|б(PeC9zcawO76/z4x.YPGN`\`T9^dÌ[p;b&VzJƏ|- C*Y:W)L _ҍ r)h6& a;2ȃ#%`jzq'L iPtJ4J˕Y0^ K|4Zw1NҤ{3eI_>6IWrݲ-a%oxn%,6c5JqC K`G90澨`/F*)7O>5?*ݼy~հV⥦U\T ^ >nwGa[R_Wu[z:Xp-4&( Z$nz8e'̏OJ3;TOFjHȸOMpQֈˊD[>*ź_n48d*X/.Ŧ~]9{ӥWQ>+tdrwU"|Iorm$ 8.!dEps ~$=bDrmZP*X{G:?KSm?{2 z R-'7PHWgo& )>s!_DXlēd*y3]X.Gndp^ֺRuy'@b6(HK"qpar7\ ҕBY)#Ǐd,g]g@-PAR<yusKbfymA@XTpe(noXsNf-VIk988E 3ܳQfZZ 4K2PI{uwQVgHvlKaym;VQI|bYϏe~#|e 'Swq H5V3C$sX [d&ߵI F/] 7 aDkcG 8w$"3Ӡ1ۛbc]6]4+-}>虹l. ߿}kn[# \amUKj̹"->Z6'o8q(L0|M䲛XU BΣƂPF ޛYZ ^%F1.5Z'!}y{W}'-G-Q2C*]@$-KtET=Q`&`8Vkk_C#՞6oЖϤjPhɆVqY`=df۵w$ 4G,+ߠ5/9A{z\M4;"R=x'ucx``WG~ A K>.ͅ"!۞&'zuM}y8tR\"w&d5ϲT ͏!+r8@]HPf\O QkҀfVqն92MRMPap‬?΋e}YqRB[mz{b?Ê"C^zfC z.5 %|OTF*8}} N~ 5aFne3W)*IZY藅f'ˉN&b}P= əJ.Jn)bZlrɝc5S)(Orͻt!=Wvʹo>j`2kU^4bY h:8#MqZAC _ﳴbT n+ <rJ Y;sXW+|B127o T<D{aӉz Uu}YK#˖|X7hv`d')ůHxb1`@ꚛ QUyy1u%<7k'8>\4`+,I3C2=0{}<7S^;猯_ $5;R4d4óUT#p,c_ b \~0hVP-s~ "W-puVd %Ш|KH0:GMHpti8?;3`$+56-{:b|&]"yLӆ\狑R,',U@譔ZS&ls&:?I FIib2ֳ9C3L -g>vUwAʎkq:2l#k'eZ/Ow9CЯzh@믯+hHעPԀΔn]_>RFZd#_Ns'=N 9ԗn+J?X4H[K Y:ٖ}ҬPP~n"d2l±i^b?2[en~ afrJFj> Uٱ ExpM}ef j"֏\,tpiC zu::pH;(*鄰k'SKWwPR;@(ΘڛOЃ^N΍xe$*MiDAi||v?XBgT~6O84SR)r) dRWwDҹ ΓH5Bl'쐲 m,,: ү,9:HZCbuuA]SvƢϼ!u`-ذY #UĦAҬʋqowMLʏ?!Y'CˆR8^=r̿| \ a=+8{N4 uu x(?E-Bu?)!IKO4QT/wu7 &A1KRwOZk]ȍ>z@p Xaݟ2Sdjfܣz }mTum'WRU?Tǐ՟*1QTK@Gi?]A/:TSk`чHl \:X\5}$$ɔRwŝfrx'SOTm&ڿ_Ks3J {P"C]"P\T*3(TAf 6ʟ/떬D/%q=8Sy,xATǧ-Q_V/+*d5*4z;?Lh?X~UϷz:h_q>H?ّ*A* 9,tAwAܤKY/4~w!l1Q|[]vSBAX%OTH":-L\7~73|͒t\ r>}. cmum3|,j#ђi6a -~cb\_(IA׸/UXY^}Aabο9nK_}7cl.Q SI RlDIC/z<5ᐸ`() ˇy .VO+n~)(kI[8y$ C9i׬:4׽+4["WGUI`~`8 -AYCDPz- 3 7yh;9)TL >-Jnn:E%hޜ,c"nkWbZ>FJ$HN0RF .Ϩ~g/LY18 @zmu$=O{IϽpY(w."u.U]D$w]A !=<8rI)s[JDS?A#@afz^+swZdg=Z [_9gɓ` *)0IJ X1*O>gd6}H揻a0a& HaI63{_"P4ρqS.$IAsԋ@ "YDtNKN󓆻?*X{= fW|qgQg1K_ɬKq] cE `s߿t2e}įh*{dxhyr34IP^U~ hԁ˔JtĿ[9"Lvߴ"f[k'{C-PbʱeQI0ٸuətܢ`S,AbenfVW"3>++yb!8q6瘝ձ[IϜ I1<5XeN<ߝIXd4$f'Cz6  ))w: m>{y蓼aBKD2.D kEp9d~=_g۷am~qd%8*3Q h usLyRpecMrQc/ᖫMGvc^tҷȦvf<C3ĦmBvO+!@:x##K~  W6NNXKE/ & Ay=cҫ~َߤ)}򩈛`mQ0-C>Ԍ ݑ1]B4<x뜈{{w%M4h`i^gcVh8b7Aޥr6W/~Skj7_{ȸeSW=K+1YDb|,0}ph,XJhc#,Ϥ2:R:R`],[ʍ[N!fbYYa^qmRaU1m !_aWr1"=4 j9׮ϿĮ?V~ TrW*0ypRDUQ<|߁߇كYMvr KPWOAสǡǿ/sR#pޭ wXڟ [)=lbGIx@yh%Yya|,O#v|c&74^0 hi;*6XH\M?gA 4%R79*W5@srG{ƽM06NZ]4/83؎:~G׀DfvDGaUǞY C =jrKa)Gh4@{ |:kC[a91V'F1AYр x: I:lt/؏"PGTV*M#U9k\nWeo} ]^-햻)9>egg_/J[Nad:s\6#ӥ3'6Vee#"(ˡ`3V{CvG ?:3)}|CEw ݳCz?4s~SvIJzOtSdRj_C}\Tò}aA\u۬2t|WHc~( )C2k|.o**Y%M+<(մV+ڼ@ubzН5dRtFD6)~1PR2Csj|Lb%7i,QIe:C'8'3'b*|ɤuwVah& 妢‰^(to0!߃"OI:q QʬI!az9KFO_ryo@*#xZ1+䬊qzA#V} y?P`お}䪰'|>_뫍8N)%Sj:#ґz\;ve`%#}:c}W]^S" v3G6PvږCcƇCi(Iկ'x~e#wD'eۏEv/wԇYmq:= Õ , "Ζ @z4. /g8z:9Qo{J^E1|BO?Wrc8":X*w4uS5 D/P}= e)g "Ҫ{rIHGֈta~p^d/S+N{뛇^c [e~97.J-&z<`wOi zeEB1-xyohn2JOws_QN4uC1+m] o)n[^/˶L یG,vx5i8n>`+aDW6 !EU09 AD,'@Jɞ6A3 3 z Jde7nb=Ƀ'o>U)x5W,u,_=CX ?Il3m*q\6*jJcP8s>.ˏSc̹k4=H. yw˰9ȖKhޓ@5t^.:vQ`%6 3TLэn2$pȬdž:r/%0\"Pw<xR>2T ?ٜ.x(}Eryۭ0䐝-WjInߠ\t~59/ gǿ2AMs(jGt}UCa I( Jao$P_墂o}@r77&rw3 1Xנc\{3F! d]s-אQ鋧U͉%̒iS;"@zSr%Tv<@h$P0^:lIyv_p*eCazj$?2Ӛ-[DdBZW֏G-;k/;•GJ۞|@x!<^>Wiqh#b[W=NF1kO™rP?!x@[[΃>"++,IbLK >{wɟ{x'2? FǣݤѸ'PMGsb@| V5kwٵAOZ&!ُGِnês#c.J _U~}KK? oA0k?GoyAPD"Y 7Z0ogc{ziܢ ~tFx\W3,u=>^SIi,h*Vu 06ˤFىZh| ;Ỳ)X'ܞH-~k""IF {Km@܍QɈZRgYI+/P_'ÿ}nϘ:8: <6ws#kͣ&3OCj5lPt5R^s ,n:KgJ*ĝ=|VY* u*cR@3^\:^ Bdo>}6 fa Sh ']+}@̂Gc۳hY ̰ pAnjf޳r,7*hpV^,v=+gbaG[6zn58m9JTAۓԪӞTڇ]w3Ja :^uꤧYfX,nqP?dzY֗afqɛ<|V Z.3Ug8Ԣͪ3:jZeU=;|\Pll9tobnjG*EE? cqqU pڙ0Tb^>̦z RnT\Sf'뼞6&j+QP3gtC'U4[R/v쏊Wf,Sj8 #Ce6sΡwlA; 8>Cj.Z^2QE.L:WZw-66v˥7k.^wCG&~V߸RѱQ uCߓKeg"۳=*eV͌;6yM-C_[+*ev)5=1:.ȏKKb֧'Y~ێ1 it)WQ愳/@ԢުJ1P•b{3+tJm;ջ=7|]  pB5Yb>SihCk;:F} > ey#0r0|Y|lGt;UT0Pjѥޙ͆`WhnֲWpc?e\YO(Zeg7jc.Ϭ7N67i2vר =ٵeˌr|I]~SC3iC3% `.gNzi猚g.|Z]69A͑[d:FFcK6)lpucϽZas1>,=uJ(P n1cdڮgJbNN 3]vYmd5ke}"϶PQ(ɧj-~o-+X:kRu%v<(+rC=:jAʺ-A+ uI]vdKRy.fFP}"Wi%+OJaéF,E03Wh[5=t wNL7#6;"\ѠW}2'zVZOR-NGRG^nfMpGXdD}K3Y{^(YZZǰWˍ,t9*v]ޢ173 >;F&'@Z5VnI4JZSe=qf6c[dZ Lw(#f˦Ͻ4`UΓGhFѪ~ҳ|Q۳R*XEWVPMsٯQ}tXΣ @ݗ(v~- Nz'ӦOx.5"CVkA.RgR-.ڀF YmCk3_\K'~'wnZT4WUgX-ܭU]'u0Xm "xQZo:$ȗ/a4O3U纗>5*-&?bƵؘ}]:}i9ΰZmvn?;Dv|8:9NW@}PނI˰φQeDz+ h Fah8ՇUlEY:ai 4Nc'il; ϕ*/>_ݨtǨ#&ݳz. *K|ZusBBٗ#g"iYb]*nH/٪6ZdWU)~[Ҧzc+gfh٬Dw;=L/n&܍b8=7 e{=)U檆v9bg{mHQ /C~FD2Av8ck*s3\f tmw4aOo"+n}J=XXn-fzǧ}x39el]weBÞ'1er̃%;nU/n'B67ַp=4ƶ Ay۬Yʆj瑵fؽҮQ]֭:g3+~04iCuЋƱjJtAJӣ0'tHX9ʫsgLј=اsjϺݻm WXOehJ?;1 (#WL[Y`VdXUc+:ai{ $'n0Dpsaf_`ǭxEi+q;arNnw(ZmUmL5/ӛIsQYʢle6Bl:ة R$W/GRo7=t3;k0,u&-(x"ZhMWseomsոi,?Vyydts+[2fys3TXPșa,[.2nai~5꥜%;w.nZ{_Km1>xv&+6Zɛ&MǕJ@N.`q2~kilDE㋲_M'հQkS;pBRYlJlZ 3rfLȡ2t2^3 iGaK[{.LZjFnbՕjSCXҞHtdsg++̟*)2ɖz^uy:!uJ)Wy-Ǖ*ىk3]<ƚz6p*]viuN'Ѳya{p?'25jSxLմUطwnTVfqa|FRbU{e6q̻5XGd\)(ʾO׋= >/MGv|*me.G"hZ͝`/IW!h--Nfll9;ؘGI= r9>)Z 865}89;N-4,n::7gALR3`RuW}?19s_YzS66ֺޮzϷm4Vqtvш2 ;HNɅѳ6![-g*MwoZb: 9G`["[LJ%wX,s] ~QIg]tYV 7jWf.6||6+LlU<tF5۝^/sj㠵Brϡˍ8ph׋a;i^DDFg{^㱬wKT\[_xYaJU&~yXmAѺDzzަ3[NlۛZ` .O]OƁ([iYcKAgV +Hjn֌kfDnOV |e]C-x+EE, 39 |Xk.(bۘ.׎zLn6Bv6Al4˵xaxitA7ȶ9#irYj|Nլ]VKS4tZv3wEJi%WggVMW.ۅ'ur\vhǩQ{c5^dADƊ7̇b3ms6J6''[7Ls1A0)UkYRNShiߠZ56Rٰrs[-ܡhC49Nۧκ>/Uɼb#VmV)@`18h9;[k64"攊 (KF|ҖY:,۔vkcVc2xJY5 Ln $KFIjqw b8Ssq0~Ml(Kxq̪tсZ22K*Uơ.QFPI)cVh5kH1qXԍn2\-2*V8/%cz˾3zl,/UhT"1mZJQploj [rqZp˅g+Ce}-Gh&- CkQ7楇} Kٖ܍ܶK Ex6^GeV-,ef!ݜ|V12KoV\խ<\gXx<8ul~mU\P>I|fl;JybmF?l6V Kٽ/Gſ"?]_/8;si7I*D G<\B)_ٽ^/W{wX[ۂmdG`9ܿ{kUܿ }BL;0S7M}o*~”<3ͭny:a/; 엎RP7o/u>1kAoY8G:&y~'U~ BؿkPWܥZaRU)~XF*U8梒wJI~oo7%ާ)̊~ڦ_͹cY^Ծ5C?~{#c%\1 R;B=*ɘY&Úϊ(/;c Bz(:_>_|n-5KD?- WMZ#o(+Oj{1 ~ԝ"S jN%o'uxʞNzF/o)N&$ATsBm!&ob? (ON9`?VQP+Oak>'7t?7'*gN';O(qPˏ%S#Ͻ UB2F*j? TJQ#es/j#g|?Z{委3Pk>G}OM? T(T{}Nc?Z@yff)o|WmfZo'\D+8oڏ 2`exqk {Sc7o۶ly}Qk#XN @ㄿCPZcڿQ 4em} W:Ɉ ln_|m{= a%BӉ$2(w,YN 껢p0q;;g2%7E28C0ɻ5I| @GUA߉7&@Q$7ETMл&5ҏ jў͝=Wכaa"- 5<mf})O8x5qa_\oa3NC5/\OєAQuEk?' ^؋Og޴GǸ0?⪪ʿ4Mbӟ8YgqT8@}!'9!)4")(qvqF ;&*+zb ̹7xJ&$4MwLTU(B"M%P01R(%J2SiE 1MѠ'X\zS}** 1o3fIwD=*#Jo2ewzW!t` @% .2+-%7EК"!4*ooH4C4NB*RtbOSFهY*/cOD@ C@ '~'AǤ7ͮBPD.Uo?>_g`x8=zC\BO|#1tqnбe9S,ˠaι0x>}޿Z}O܇kd2 `ȫ HR}U%74 T9 0|O/d$gT7O>j8K}. rb ui*bv\S`Bh -p@$hn$ @D"hg 3*p L@a\R;e" Mh3!(pHQ!Ɉ\sL}H;!smd$Pp峇}YL=dJu)}CrQC(2Їrߏ3;AagΞ;?N̻ͥ 53ϟ+|u;3+j/}2^/σ_ @!7C0OK0j6?~9fN[3anw}FA~)gY/f97o{qEO>l板>r\̷{4%s>~){2prw3ιGH|Cŵ ܳ{o˘0tÇEds,n =Y:x0~x%V>[Īݑ$'AşY~m4trg_ʺ&bżx6{Hi7\0o' qAչ^RHVI !~.knKeSmA -ݘ N1dREcX?țwvh|Aڸٻ?_+0ò~{CaPyG;rsW(𿒇6i9﶐_4k8* f}%TsM~ٷ*yBxJpkWf6u<4foӐ\-?9!4zҧ~PyL5z??+qedB[ 3 _p~R~ Rd,f*0U' X2AS *EO$ J3&V U0F0pBL(rMT %s`pMX&X1)&Q%DBQb *V*H*b-Mid@NRh $ES -) 4t|MA I(i ŷ7bizQ-Gl2(PD_^|2Μ?bys~U:v}3I@2we}z,.oͧ|@ A`E&rSf2`h$RWv`~E"Be%-]ڸ.&[mʘwr Hp"D؂z%>r'a^"U9Q]J TI^2`*U!1+ b«PcXEM ]"pzqgab (4x Sd'YM -FԒtwB1p'LrTt|*&C\L0RMtOk'%o)`&cĞ%/qy'l`T!Kşe2w߀}J$$' @DlP1'bA,&3}MEkNN[RVyRS%i剀r(([ךV,Љ,@yj@$(a%c~(f+ I5,$ʚ"rT% 7ҬA)Pd[+$IN*$%I=?ԤhMlO4 e%͆J5\)<Ƙd)8tPX_j@þibɻJI*N>wJ!QI)Xȫ 2M=vLk.'e \IW0b]=O4̠D&4?N)~'FśKҧ׳ʾPtB lN 2ց)2dEu:X s=Cp qؘ*Ru /HLM@uF=DHPoHF  3@PR H(&&0'Mg8~=x\rH KcI2]L$$S1&1jD{ TDr$7E rdC  LS(*`:E$'RBt=`̪F0ZX<7| ?^oCܦ;ȢdEl& sfnk~׍_! b!pc?mmf2)w›XMu뿅kSk[OԨ8o"ÈK0w69DXJoo7=-gXJkg7nJ'-YDt[Zrn,Bv[MJUwEjJzK>d=OW[_ US^`@Z ^*2!K"gW8 S "DݓD!"s)]CP,.z[{מB|PL(dklT$Aљ[k5dTIf2.&-% UtH  X1) ӕ8ICL s!fuaIX4.`.([1B!D 0_ ̈9Ir{(e?>]TS?]ս/prCvnNoK%_{ˮK8k@n5K}'?BvSCE,H

    >Bgz!dɂu=ײz7L!2Sw2+^Jq*~AoM4}LaB tm@<#sv7kfI)aIjO6> #ZyXY"Ɉɋ ?IJ^~g3+|_\/DXU_)`On{YBi(M{n ^bczT#=%iv۵Ou>bS! fvHs+nBMP+Ue"YO[DIԭv=O<8Y?(t *{b.a rL5w@&EMWHu["6^b߳p:CJ+?=CvPg|_i5$A 3.v1ͷ#bM4 ŢNEwOSt7 %NryC``@19+A2ЧcaQ C_$Srdd#"=n7=Ք8Bvk[S4B7BZkFF|FErnʡT,/quS\8STM`Sd7"t4Zj?89IrN;}rBV0mIF)VoCN!OuN0IC9AszJW>N(xs!jM18<`^8i:HjY3PWzɠ_ nsL1h u,2IbH̀fwS1L|X'^@?z+"k3"gb*P#X(OoB*7DE! QXJ{ND9IAD厦?k> #,U552c" 4 d+8JhR' Fx2OXK1 1j2>n@<ē4o8 t&qL8B5*TM@OC7x Ƌb`&j:шC7&?"$4Q0LM~,cՁTX} z(k|wp;*"@\IxSTnw^< kr(NF\ @(p ]F.?AvL3 b-M`˜$wDCyJ 2xrJI4J.˂`ޘ_A?EkP}FA&f! ʳi\8dLY)<57A8bBvuxEIE%AEՒJ!ê8b%HmTI 6!V^XWX@ )nFݲE%ъl dP,%/"z2B OS -ld7K͈̝ ,Ķo=]'Z\P=Һ0P2_w$~&v! DU y=rTIȄ]8I&r}TlLtOFJD "OكZZH,'LT h䚉~&Xi!NwL$RR,TCKDtBF-nzoflS0BSP p& ,\Q,RD<^ DO$ɹTɡ|KFCx@k!W]782 1H{5 S|eX ƥ&JJsGD~dBI'QqF?E(HI'$;)rgSPv%q{d&L*v:n ]DEH)+Q ;vW*$ 2ᯂ %2,cUQpW2Y,{B侰/n*TKqݥX1Xyb#ʽnIpK"!'/ r^q^a W}D b Sq{5[r?7<ȋG[w^פ SyRX@/ȟ&SUv͎x"U6i qc >o了-٧z&B1:(ld72  ;}Hp y{t1I}WëǞ D(d⬔>ׯ#޽b R{.~[|TP孵&}ic֘s' @jOnʤPxW8F|P&{nWE>n *x޷7<=M<Ԙ/0 t&{J`]G\!b?Byڏ`#i@*yߌq.gu/Bt^7,p\>Epx !{\b+a~w$^f=Q~)G_)x"N?Q\\Vg'v^\9*Wx-Q[iK1%=JPh;\8A Y!e$)hv$)"(~( y>S{${6Vzl\ɔDjz@hv"Cy Nh9Q 4ZaM?4ES1cx,Loq 4[AS"hd*%g'E̅3"$Z 0wCu~)U}/_:ãqԨD#!@[S쎮Ds˷%0e6RRFKqS2\oUGd4оJ 2W AV?Ų_Dsiy毞 3(KC#I$$ ciV2#N?J{H1W `j>􄕼ydNE|^Bٌ{!_} t)gK:[άV A>̎56G#/gbvECa< Rޘ$^ מ\\jb_^|K7uqqQ  =N54F,7=g xW̧&}qY\p+}hZ*\=Ch1jh)Ħ9%s2Ri5p(˙0IۢŷEh`E{A) m5mH'-ly]h6V-[3Eolo2y<,|ϡ΃/>XNC ' ,U(=QRug"~jL"q!MqyZMS;(5-/w6nZQ#* )n;q ExRb}(>Id;F2|HŢ+ӺvUO [|Kz@-]Yjm/?d,vUj)>-ewX+Dm1 tEyؤfx);$T@`yt :߻ZX}r}t93)*sxX,牄N[X~6Wd>y\?NKs7ۤ7T'[q+O D8vDm>}Pq+fuG8Ō0ꎳJ*DvGIGde[+C/uK#mZ&|"wVx){JWm ;+-w!Yc§!WȤzDɤD򴳥%d}kЧä6\ɤaL[(76NQJ}fldYMM 4r%hق@7 OZgs`̺7 ~ EjkdQPѹGýopsفE%)]z0tS֎]&.TȬwKT ?{K/M6]Չ*2a3$31zɉN!LJLx5'GjVXCI 0FE .(a%eQ=À{zzeuCV—z>@#Jl碜j>NPW6>wX8JQP{5+@p<<ɩ T,'Q?xYMzA42w4KB4ޛ@ ǁ{p~ HQ-Lh|&:@g}'z>՜aH5(Iww&oYd*53 Z a xZ3O2Yߟ(i/ Kzh:bb"ыV` Y~KbDK&Ә Z$b`S !xh $zL "Ee8/M( h i ,STatqOm1DK&'[M4KE\ VtÃЉE3V _7uIm>4\b.7.Kv! pc\.!WheTVpQl5 q:a0d&Ӣ=XyP<{`MN;tlI^6 86ƶuCwNR$`1ao+ Z!,p/r˦CIZzZ?$c77uo18b@ IZk(n%@/]}u攳Tx,!L9N2 ; L~ -EXB ]Fw: ,(>Gfg\Y,H4KcMyoE#*uLkhphVXހ#Tp Ш1ðI!#Eo2D$ƖoLFU^v $URѤC@Ѽ"=U&3PV_ZWDD?*"aO`*\8v6yZKdTMo[z|z[[-_#=ψT2y怹<5y]pzN&4Q~g=H^z>Ϧc43]N yw>0'D a8Ԡu>~y+fPCq[zFD̜7#f` qG+z,3MCnyȷޤŗ/`f׸ 63LH<}( HY~_;,^#g# 2B_`x v1=m~-OrrF^ߗ䃶22 5?ÅniN1G^xѝӜIFjkhMS OYnZgx9]7Cv7 ,g@fz#za[)m :=axJG@v,.!1BpED3_ODd]|Cxqc3ysͩ#}0̡ v- ',9~ӊDH#Ho?(gRq4 iцn-؇ VIQL3iMˌ &XEՑ-l~6}6zE4WR$Ck ,Wr;pYLF'6-'x콗.D[{DL #D*\95CLRhP,C])~]|.Yp38ݷkE4qL4 =-9sRb@Gt̄5I `sT.DXFA Uk %kOw,YN1 _{ k{ZK>vΛԃ>32#lsKBy)%Ys2#qզ;|@#@9H=ّZA4/1QN=&X|_'a+2Ұ ~?Ε>UشuCi[h>H ;#%!eEɷyp"q⢢pM&Rx  Szq~׹bƣ:S~Aـ}8UGzK"2/xe;=X^t P׋r  #{R #@޹j83(: j뒜]9w`2 !IyD"?ĭ[g\DZ]Uv,'ΥM-;M;By}VvxjZE,ȖwXGbۓwDl׏!"E1/IڙhcB0ɧbOCCա=!&J]&Z:Iwai5wkw`?TJ]@;c +J̋S-|w'IF~9Lo|$mr>:$q7) D~9QC]ÈWFNAߘi'¸'pHZ:)o>qDD0Ɍ紇E 8W^0~9*o`?OQ9ΓG42о/<ў쿛\MQgz5,m>. M@yh@Ȕ21t~Xx Qڽ9^z:+{]hQ%?U 񤄘;]ND{8JS/7)F*#O ޙrAM7 u_$6S xo2|}l퇿Nr=9p'N. B~r Ja..g'xq&c/+'m`OI/9H 6^LY@= ?!5Zl-xL EdA&O5?hdTW,]S#zӽ\%fz0CG9\2B[!;5d$㴐2I#N$0AD#NÖj >Vh*W4)䭁Jl @ CFU"hxx~9F(Fxq{_zʅ+0Ny)HFXGD|'xq9%OS~gRO]sWiy F,1cKTmZZXgUzߦZW1Uc&C%$0,NҢ&#C bƁ=d K>5:tt3yO{ORzyc-y@Kw>O\| !phC>:SϧӲRJj !$"iF#E Yg7ᐔ#OBUϜ IhL4`9YwUT'iIbOQB@H=Ҍ3*Y::%|@f)gcnGR[uB3L-^)|kMnzQGF5&݈ٟH-XxX9^l$-@eJ;>vQCTx{W~mx':R _,>*XsUMlm鑰F1B5#v=rS4O0Vٚ'ޙ&t P,.K@Vڠ"eRRNlt~a`MCқ_`6ɄujgܫWj7ƞycx'u[U9o[cxpJ@t740 ˾O ms/vJ-ΒTBc6V.@836yuR'ihY;1-lvZV>}U07-!' )}'.٭<:?~U'8"gsdAj eLJ9 QG$#=͙xzG1 W&6$rGb`è^w2:9FRJ D4@:pt:Iy*Xhi x'I- ӹDZHHs.8NFɒ?NPosN ~·A12h[\q Pz<qgN V<~0(az.㮓/r3hH5>,%,Y7t~xrE` Fg+u|:i`gΖb/<6tLh'cҡk dGl `mܮ[6>tiabqRiΒ'Mhd6^NF&sY}hfu+K_9Fڻ]p?zfvfGl@Yq?+GH=c?n|^`ecsMgPx}LK$5f T[HmF !15M#cr'n8D`w7N7ދZ(=d{\e)n\*A6e*R-hc,hFի:g65w!]Ʃmƫ85kəVzpl~ ?qV o4B䀎b9ǘ;W/Mdd;_QZ洯>Ӣ0*:m&]nD0SU"7"-:eť*&R!Qy{CFÔnm&[دEP4*.*TBJc$mEUq kAv  t%MdH(47,Fd!|c3pJECc^ƅ"$@+,0"()rPq@vNB=N\i[q"z|VUv$-@'Ap n E CBJ[IsP(" VyX/)wM4HCa:g0^SIEDPS:Y^i-p/0O;-`N9%YEYV_םwԃㇸh,օ@}lW{pS[v;#3aA%MF j^i=}PGDrt:17l>|X[Sli+h>ȴv̴߰#RqH;)|ج_'ߊ>2~ww`'ݰ+G QDVVzOi 5B-/{R9mDOL+tF/т(՘?.a$3hb)*P`p?׭,52D4:@':#[eLZk<c9ᑺP#yw[ xa& 7C26-supG2U«ADGnfL%BSFs0?]|'6Ѩh x)W6߅9Πw@o[(FCbRPZQ4ԙ/{ YřU|j2xb$; >h[3X@UWq%gq+Yy_1<J PE[. PRX~I]u]""mqܯ8K=il\)\{mM!~Iղ8y쥬-tEG m1H񝙦# V<*#~2j|UFP>*#Px#kdg0bhƴ[9,.s}qp۽1t13%p2#{ BcdwhRfy~ iʈoa Be%ej6(;ƠxsQۓ8뿬3zTH:#L=<o{6̘q@sZ#Hkcc8]`茳AqM@U xz|كWeE)6g hH$O-driI4"`} j*Y0%0Du:jsbN{.?4BtLNsd]ex2r Y'Mr1NGu9-3IFhZ,h5,"Z:snF.@-0%Rº>QL P)\KDAvaCD:/ӎ0O+_'~Ni—tzW"A{Zb H'pɉ,:72.'ǐL,LnZ^ N"&Y/{sb;/;W6!sjuk5 _-!ڿEJ$=(<,3 V_#ml }BɃm52u($x}xB4k$I8}kkONK(h2V< gD!5q>9i`|UV1 g }Fx܇$ČN!Jc 9kvh|塜F: ,D9K #ȣtgY[;Cv#!^9p`;;=2U /Tֆ$ş%8/Ŝfka1m|%8VuΨ7D1~gf$㑯@ffS"6u%%;MY\0חi_ ?M}jAs8_x:p<ö2Fu ߾8,]Fh@U T1¸XRObR 5STD߉K c\F=Zc_JbIxՓ(Ө#ߜ?A\u)?D%OU^~'uq˫ t֏ $hQTD$4'y02W-QqZR9`X3'xΌO`5xu; "%tLw)Y_a=CTtXj*es"|N魉~N@'ݕ ]O@q5^3J~y;FG:CJN)\yB! u{c0p@1BALw;_:bu:{Vtyv=&^&<}r檗Ɂ> 2UBZ*>"sZ[NK>8"(10Y?1>a}dRcPbuV=9 +ݓrSn.bdfZ#I$Xl&+z0C 3P֦ͫ m_ݓœ#eqorX"n8?-HjjY ;N\g]?7M~^6F|6CIcZ0F-j+Jh.udk=-9rgrڋ+cC MZ ދs~v-0g@z;Fx&QӒ 43E69%t{B x,loRN9}O'CΦj aLZyѠƯ86rMBQr+jL5-`$EDE ^7l69V:0D rBy <@b+g䠫h ʓNv=tLI|- GGʤ).it鯚))8D|z9T^RԸDR#UtHj3FR2%Pi\@5FF0n E^L}֑45>[mH P5fƔ7Ӻ?h?ʚמ1ŃaOIe-+l\*lq[ZhhlLB٤ ܌348=/?q+et _IX@=V`ߴHо9h0G3Poz`A Q'SX, Ps>'@MƦ ::žoHb=\&5+S@Lh?8AIAž;M.a.~0p-zyEt0+XZoH%0-(ɔb<f\;1lm@)wĜ%#BYlfyv0dO@|nǖA HvTJFD~"&EXSF kO"vj<_Pr hGAdv~=vsSN8p>O" Z}lq(K$j>:~I jc1̬LNlD:%G.K8)1GFx{6yVY*5|0>4.jeM %i78NR~{d %ѹW#wSn=rW'tӐ~aWI:v|,6SEKlYBLN- m5-t'keۀ=fR5]^WiC YG$w ]n#u#t$,5{\ ,}?2BȐUZebZ+7En+oumNLrD.HuI6EoJ ðgy|0nM򶕐!p8Y|YZ$q#pOx\PB&\ěen*6O=nBSfivXuOFefmܲ+~@L;JYсD/b_)˲Jr֟HhhNA: sEh܎VHQ^C݌i'&^籠+/N-2EU <)SMm> }%F^/Ҭ iI4;;BzT_-/A3m1 :bdK 5onՂU<=}Km:d IVk^&DzUA^C^MґM Y,ݜ!mVHS9Jzsɇ3s?aA [0BFF?X+am]~.GQ}9F;.VoZovM鮟_^5Qcd}p?9| {[1#iAƯ쾖DWݟ!Kaٹqh] w4%\#?<%c] 6?HydI)`ȑ?cl eI:-8%.OA0MM`V]J$VK9VR^nEp[R5BZwU0!BV $tb}k)fۓbݖ~-HHu,6Z}iF1&(<5AoG,b:cY!ǜւ/+EEAi-@R|H_ CN*.Wo:,E"l+hW9lpˇUZDJ'R{W߯g藛:t8 1 ơ&S ndI `okP?y _ Xv ;9u}Pv__{ϔ񄙻aR#ΜLzzcoDX+4p2b;4xy:b@JHM%(lFYӑv5 #ss6rX^>;] =:^ǪSx%+m'VtcC }>>YӅ?8(j-/H{&EtAʣXx{c,;hZjZv`YuҒ>ˊ%{%ї|&Ew#q7,7o]lN;>;(kYmeYϤ\Y "|I(AH}w"<EZ8QSVZ#|"sI{]? ZZw6bQbTԖe\koe?W,jKg, rKۿe:HFSʾy#-f޾W模W]sҊHɫ{>)=g1 u6{_[f(r[('6uk }.4*Qmͦ;7Μh1Zi@wr3M2@Ιq[ $\5W:2`TgfwWIzRFBlyѲM_ep4W,n>įE!}S-2= z!~^(b6 X])t÷wR" /_4{c_IxǩjƅƙO2M uҍϓD~f#;Aq)f>ZgIݙaOPI35ME]VCC.9 a^n`*9^G%\pxo,*{spMh9%>9J1YI 2V"!vE] rs= ms]Ge(GZqD"ḻ":Ict\677YGXNii5%FF](W;Jvm+x~E0lf ˿ &J>mPެ" !܍7Q¢R/HoTAPh 9"|DZ_IN?VXL/QLPnT&]G'2:&>;CIҒEҭA{v>omrOY ?h}R~(bؤcɞ/*%[oKt%nd\]QlHaF'WJkm. U/e{&q't=wwpc?1YQ\5񄈹h`U]%p蒂` ا~pE22Wn+7Om`#wЌ/ > *cJ# ,Үi fs7-JM'/^6q/;f d;I'rSs&~aMA&He,:f%ćϽdjcYB,NLyy3lIj;/~qeyW/+Cs Φ-^6{.˾(p6 Ow*lkv}<|z3X\?çpNuH>2L2>ų˧ħxzr {;bS>g<>|OP˧շ`kad[O)Ϥ;/L8$^ xx']qX*?H 3/w%Tq(կ/L)߯h߫Za)vb/)x[F68O_?'uE]:u l:9= ˵G ٚ_M'Lg8xpxBWQP~bui͚`PsCX/N;/nDoĕInyjcTpya,Zށ9 tm # =EjUEׁ@Ӕz坲]=4~⇊ 0tMyTa݊dA>0q^'oAY r<&'誁|:VJb9K*T1[ 6$oGC)j JMP}1љ";_6W(cl#b'W̱eGG0RP&3]k$GK\ tQڪyL{mǠQM))bx3,1qmǰ<ӻЀ!yk-$(!TˋxZɕ%"$r7GJ_?گ$`l{p,jA qԠN=~OAytc-_r?Њ+B7qj2b/ue%mbi %7NKWid_4=<6+D$7Xv˴Mq֙*dΝJά"{}͞x&*1/G, E6wòdHАe) Zyi;5v*HF!Ľ&ZV_AAoϬތ%ceWon8;y|YA>[p)8$;A/Jbq0*2z~K:LSKWՔAB;=@:*MH4D7xT~3v>7f%f{#3BHaP>Sov3 |pOTK Nabi3Ɣ8X8c4t6tK8CqЂZTrT׸ODvrqg{;Q*@6ҩI;Btlܤ0\'65I R|.B%WpF ư+9 1(k9D=v0TqUJNRzPp>'|<J8IڤUf!\8 u@Tn| OدQf9/bh:<. Ia B v&jrDCC'KKDH$hMZJ]Wqj詆 جBjKC)m8PT# TxLKhs#ݼ wMM K9@GX ' Zl^y%N% hhq>>H ٬=4HTYr2"bbuD{l FQ:ƦK@ 2b @%B% uՃAm.sV 7\ޗ]Q+[=@37.)6Qp3[C7tѮWM$8V'ŚiQ0XSZk1'Qhg#shE<(9ek#7j+m_`LP=xĆ#r#g# K't\E"vt"ʜш68r4ŗTZ} C'I)4x;.u[w̖|wc]=At_Eʂ Ee @ÿZbj'~R&EN哒e 0$%pVr>rEFvsb>L1t%I4& }ݸצ9mc3&r~n#l0n:EL!UFn IV2rnlUH2I(QT19WbrKr,KVMEΖT9d0QM@4lL LxPN2)Z8IkRa,1m\vXP[tLD` 4fMB/iJ[Kxې KIWL=3ͅW Jœ& l>%8T7$NK$M@ZugMM壛$c> e&;AnMƫRt&N? qVI5NIr'j @]E1#K*GK-%3UERRɪzS |Q!IsUEڥ䢲J奸M/bq#9 Ytr/5`+v2.taw$U''U"I:.UQ0QQhp !\*j{WhQS-[B \U UN=SP괪*a<+3! ʈ=8X~bT#eUR pIzg陵m>k=5QhEQ<,g-J,3{_\*OQ _aua |"(5YF^=N=N& {ӟ Aa)+g :T ֚8Væev-Kvӊ[ ??LKF s#v,QP aa҆^kz$;hiX% JV!Q ?aE+( ڬe&jTMs #Ljw:YyS bBqxk25-w{gz>xɃ|Дyh>,r2z\M}'J]s=%.¾Uu2ֲ4a]Y&K=ͧ byk8A V*KcVMSA|2PٯaSRQOl#dgfZKl%X{+2C漇[ jfH ;e5EEzpߑ}j*8ݰoew}C,pd;Cj1;i$:6ݱF=o_r)qsàrS^:jOFT[$EKnRRzKFccPm+gck\awx5Hs]P\Rchུ|<÷|\rcIPӳ.jʟyȠ\2m/Jv˪/sI?'C o>2R$"Nndl^31؜-/w?5'@avL~Nd!tq?L͆_&P۹v:ar&< ޿p!k:Cyv|6+'ĺX(]UIw^8;LpC̘&̙=UiőVJ HB=(ne#8?GOz7Mj{ !ٹG!)MdhCuºN̸/ӄco2|8ȣ nl: B9SQxyLݎ|I:hvذ?@.]w *3΅;;`]@hqU4Bi:ڪO_I-ր;dO@;h#>:t? }X\d6b w̴~D7oZvhmԩD$+_z>0Is>\*p] /_F`-|lv_7.-vʍ99*7ȩ$vu@;\p5SlA >AvHڎ WmZ4Va.FF/?|i.bj dVd!`7FhvgQGw0ǿٲvDv|fX z;NC:(#!I~`&hu-x092͗ov_x ~P\ \q {$3|Iᶏ_vԿ?I=b1+i7DoPŘޒ~z+"m@r˞I?ZlvZj@so9Q8Q vYnW!6C6^ss{da{ǻt Jt.4Zqf΢xpv;%ª3@Z5πzZU[L!x&d'"k̢R]疅^ yqcP9>KN3;U~U-+E6 Y q :# ؊ [[ de&8*ysw Va`@aJ-rȝJ{-:;vun y8*&a"{eWI( ͤ»rޅ JXx7e au. ^)jg-XR~P(«˩;?>/bP5s(, xPfE!7 RC?Ġ+3< MއAAϿTR` _y?c zK(K }Qk#vHEpU5*hICۊ>~I7zZоLl8{"PHߎd*}JSVFGфÛ.`l ,!]p]ӯnx/,\03=pr=mTA(sE@k&~9 XJN"jЛxm 0Z]^7S}YaȧO$4۩1F\KmlT"Ѵ"p?R,Y48xd1"btܥmC0dId5^puC~l]`ea%kji8 T |Ѿ5knv³<z9J6 vWH¤bV4Vv}Z [wU\j`F;~{nmt.XL荒'VnOhصb@-ˤLa -(?KbX~%ѥHK;n^"\c"JIӿ~doJZ h6n^ Y82b- E=wMs3 .EV6mm Hw+dyqe΄-߉F+ru)CqCM+7w,'PrRCm_~:${>}=];ʗ!U>2Lv&w=Br6\MwŸ:}OX &xr:c"^\&'#;h=KؤN6} 躷o`{GO@_*P@F5|87GGaECy\Gm#%)7׊Q-1~zFv{H^q/k VX0.P^##ԇ^ݦ{pt9dd])}["Hp&i(8mjCbӍ"'O|PKwhjqߠM͔hՒ)٩p ݩM^Q׵tߵOdFf1w)m;oA"֐ޡ(o(CT=%+Hc3w}rRҥDR"Zw,js:E,s׏5Tt! ETcaG]į^7d5c,_a!.EQxh9)$y%kjdHqFsXRHJAb&SIa8!/OJTܞ¸&{JÜt s T>фL J,Vk g b;pc]l!fcܤCd]&m< l\85ʫj(-13` Ak7Ā" ɸ`Gv| p2_ww?{)F biPAR)€v @9%i6"8H3,X,.EX1\ p\̜c-! pzw;LC %4R~1RŁ{ h"R꼬BzWO.A b3{g)`ԣ{0oI~`зb$%mNdu%!LۖIÅiQЙY7*A%1l`'6?~.@]I%3fCR5 ;dQˣWגhsvϨ 'Sep()T^^1H9Af}nqicO t.S,8>y#i`p"o)hk]wMxر~ u>c, -@6w؀@&hw)R<Sc_ٌ$q5mB@iVoHa)WzeG&Idlru$`iց 1Qv] "w&Μ̿ޠ!mfcLc=:Ӽ]+!`): Ս OX(g8-/ ɳD*(PcdJb&{UV28! %f^WэבV鳲Zdl;dRwIqZUpMsv{E*MstMut8:OhrPhmis2DX{ a.^b5RULcz,3vC\sݬOÜW}Ț~i]'O(\뱂J>=Q so5DI,WF,L zR0>5D>4?O^SPs5 |DdՖϗ[{|k*cڏyOdҫP;jg<,NiR;/ۘ)a=3S:kEp0+_h `'Thj'cX"^)EjQ=E>Z5OGdj=dꖬ{wOϼφAK..PO"Ol 485L۳J( !R$-UPH('*a'a*z 8f176 $+ 8XTsE*$5"j؅eO;t};``e(>*F[Q.*ZYNpQyX10\\68>TV}&]Q !D]TVb $m@RME UD 3O#FH`'> 5D-9+{Ec58*tOAn_LY@*C@u <D,aE7jÊo5b p{sZN%(.9<?e>xed\~i0S /Cgi'qfeܲ="?fb}->6N qS9gRh]2r!"lMI/S6 #M8\4 m~ޟ+}<&?3 W̪bcp QSYH5ר`3bn m Ӱ81478tel yf P$j*t telԆ܇sk3@ PP-2~QzRkwZ@aף\ q -7ʅyF9QvpcC]yѠEjr߮i%j5؂QiSKzMz ]kce1Q=aRyVwA"Ur=)JV<>v:' (9„Gف|أT+P3` P҃PlO0@G(~-3(Eb̚-LiMmR/y̿ @/GjrQ3`(5ӆ)'cȫ &FsӔn!iB/|:QӰ@5tW^\F] Áx+t1I lja?FR_):d'`dױɫ`yTڎJOH#ipŶRv@oG!z}s! ŸߦvJP%&B,:[ڌɊp ͈ܰ@*x3l‹VͳA]}@ 2%DGh{xY] ׽}Cۜ$n!݇WRN.>lO)1En_mK\9oGQ ѡEu19ҺGSXQPg.䧉)LQj0ǩQͷI]uͰ47T^'dxh G1^bZT=.,&dVDR` Negב wtAݵutup_ jḒM{ɍGrޮDSYG/͉Їpvtq(U Y2*<#viZ)`X2Δ҈ݻXAO*6 kzB#RP';Ҹ#$~IJZ@!3) ӿ5FF^ Wʵ;؎r{*iIFӞ J4Λ(Q'O={-s6Ӓ~7jm B"=/c (Jrَg va'8P)\?P%πlb(O~Lj(~o7ʑPn|@9l(D/`VNS? <Oسi= Ԃn} :3L tSǓg;AraKOx)߳s`?pN ig ΛARAQ,u>M@rf8tID_== EdjA[!7~ݜq\e0TאW/ u@xU@ԐGs4BM:mQ4o׆i @+Q3 Ӈ #VSРWK9BoJxh"P4jċehz1p'C& I~OGw],mԨk&)vM :G⨁^[@m]NVU#4ۙlЭnd"BDg"ceKuV4 3z63s Ք&wHdJuà l5<# ;hNփ{?j>_DG>8`EA(E z3kPmps2"[MQoPn K EgÏ"0oHKoPa/E rp„;Uaԧ~@lUᣭSbc U.>nheAU#"шȕvG ZunIW^.} 9vx#`2$}(b W8D@t|@v%h*< {It02(b Z$r#UI?rh"(T#k=TȐfvߴCK#n77Bcǩj<rfkk-ܜu8 HQ9I )Z8=4[*} FK:7R'}:7@,ny@*)PGh!@}h^])Wx?XO"JXT`Sih5%j>&>t44y(aR= CS fCQ5<6~` Z{ r&£y&KBXAu T \'.Fjd6'"aH2<^A~- @lNd&`۪ںrsRJxgBZ7yMi]ΠRI,)WBg#5 (2Q zBBNKe5~ }ㆈpU\Ge$[dW M; M3jK羱\o:sZJb2δ fO$PNs@n (#m 6<̜}kٔx_`)H @"gV M E-H_TUl;kt}Kss]"> ]Z/Q?6M?+YDFkv8rғD{ m$42RD δdv.,押Ci`yPyA@\$j}͉5qysmb-.6}]뢄w{ԦNќӑE_a2+~2ƪ챀֩#ɤ*7qZNO-oDFsH( ߲ءVgM2;UHgs·:QU(8ëն(Dݳ5j"/o?ߕ 9oN)ZNR-?(Ȉʕ>re' '#J9.*EfO˾=MG@Dpfr\(3鄲&:M@AE: y}:#<]ѭ[rX>+"gAt١!1P>{Tb$lst5=t GOAdsMock^ʌω8.&TX+ZQG2F oNKRRMQ®Uq޸iUYZgRͧ,ϸ9;:\G4pR=Ne^q+)Eq'Bwrn@v=9 dLnqT* ʣqY'doVMLKBCɊ{A=U2kd?=o zėܿ^ Oo (rT^7 ˧ew4PN 5SoW9⸹Xpag;8`8Dit޽j>Ja ߜk 5 Cf# Hpyrr<(9x1qs Fpz3bL+2viQjz$q<0DV ?gr3p2#xj2q+Qxo'[tc+a9YYRoD""l kiglU·3%dvʃqHl:G漍EVdS0&q&6!䰎;I:gTĶ"$ V/&t;#ȾeGaFYrK̹yhPN_Lei@9 \2U)4H#:\КǥKUF4,nK07@#8"H3=ngquѻ>u$5gሂ_9~#L#~t#,*6fF/ΰKLʊ^: !^ޝh AL_(sKfF|j%+UWO(X9W)LVu$dZ yzi%q[M&Al1^CjZIL %&`_u`Xc(: dfL QQVA>C&WD{ CΆfٖ0Ojy- 30LkCF탆ةՎfV|!k(F #DJAv«V}>{hĶ(V/KGڜU(VX!S[(Y,fZ-}SP 9=RQ 5̅Z3a* 1 %+ l,#k ?Ypg(_FI\zu F6xGnwWfW"vT=aM0̡v`jY5+:'l Jl)+܌^S>es 7v @Fv@33Ɨ)xv;4_<݌$^@")wh;teh!nxv3g-7!0o⠞R-+l7 D|&3N@.ﭰ~Rny}jGHuԚc]e2ׄo*W]5WitA9^Z $C`T#>6NCs֧ukZRV>_CϿ비A XP.Q7w;R*a }TugJi4dP-KQG+erڦtJ M}1@srH 0`)uyrm # ]1dQXpJфo5j#W6A^DٛCsfzJj.hO m#:^ÊE dݹXF!6A {LOFvkl. im+Er=ԽڐVصDpnܹ QX}Xfvg&ёpDPLZ=޵|E*{asSrsUaaE>^\ukgnE] `\zjK`s"„֞[.QxgYjg3.yN>q@DxtlMGZwtR7רι Nu/;}IU#1&DP^e5hZV4ìMHsӷܷ#qXm8"jv7[m Ki= wB7eD ?r15.t7&׽kf䁬E` lH=vdmۄE. Zdވ iP!N"\FD{,h^E ÎJؖ-/IIzb{Jg;3FIC&`#D@u{2I&Hmxb,dž7 ~:/3sadՈ׉ak< )n<;4VcR:P5_aM,L >sƔ7QH!NJZ2һ)̆El:CTa H v1[dNDy90@4ra6;W@9}(v)#2J%#Bbc: GC %[JSDU8xȥZbtA dS0Y sOR1y?p %;ԗ\.T 4C^P0mhw9YVtT-᫾avs޾g#,݂%S8yc4r b]5 QXQiwKk= rW~t=ۣBR=#=$/՘<)U9vdՅLԈ`p]L/ Ķ FЊ l۲M]s'<>]@cެV29"럥<40_Y+Ioz{pP᷇+i2&{(^8Y)w]*4E, )\y܏ۈk.!m\Jfڬ(&"BS#Auk(yqy{ 8.QiU$_] W=a!R< d9Bɀ"ڍM$PЋ7sL #QBl(+enoO|T{y U)~t4eg.Z9#@ n{ɽGɽP{(۔QScbϴ#e%`/ёzݬK00]`~`)wg"WaNztJ&d)=''+%GdRxЋYaA29(IWu1|.0`qmO78'$R`T NYGV6=J%lJ,J++ _\*5<*bi^bkfsa0% 7Rъԕpz 8NW413N/[=6ũGl󹍓WС 9!z 5ݔ"F~r`;Y!7:/6mWülE7씎܁?L$jn*3fD."g }^gS6/Co!.Z* ;5pH\;`xѤcG-- U_b1zkaRa'\?fW"}cd2g2m^(ʀL⫲W_G ڕ6GPW/"guNL6]1Y+w?Þqq?Ctq З %հ~< 5wW^^<TsӾ1Z2![m##O!eb Wq_^ZXdAGgbۺB!,7yICҧ|@sﴔGzT!G =q 2 q6)@Ps.ޙx22k1T6J9:WAHiAj[.-ԘX7{ @3BT+qL%M~]'!%/qޥ.Nd4?RFwwniwk@['ikvN[z96eXvK'g#=&m TY.jTm,z?1sϰt0/-n/g0P4}eV@anT'vB)W4jPɯI@J.0wi*SZm\}wp%_QJ TێЅSdAy8iÌu-S>"X3׬DWn* ˦%R͇$D#[c6[B>\wdV.d$*ot~O 1UcG4D5PVG0{#ZcWZ׸ο[цM]5rx\\ÕhG'r&H8s%!xu@L2bg6ǕqZ( .":ge ?>Q$m j\ϷڸjhӪr #nˑ  >R b5ҋM @C/ [Y2x(g['W1Tmq7:ZKy7#%!( 5.iU#y#-WmKwT\{',M)$0T ?޻{MbW23r3G }AljԪ/0(~>jLGRRCT!YhVbnzn ] z,E*U^WITӌ+ DpvѠ>?ENDTd*g*u ww aÄ05RmNQR{u!KE\,.o2F-i9tlTSTWU[^e(U삈]F%k59UX-mwo.rqLlm?YAn 8Yb-zgW4Jf| ˙ȄD2;-=7>&ǾQH8SFތ% k8ՕD~KݿW+]mƺF5ߎ wCJn:\mela)UaΊvS̛)@\j"rqrp>M5q'o;||G8Jh6 5f'J=@Fd5Z,z1IE Oj܉$f{dҚfLTw:ş%$X\$'5n*1DZ*܂l~ Q@*-?M $Ao\=|II IgӖ84) SU\L97O7kԜB@#ƪ7=E>17ὣ}<-[mi#K@9}GOvGO:$8 9湈'HҝoJ(G[#÷A*mn"+3)S8:V Wم6M7B"lnMV;z2%Aֻ{S ͨN4zl]jMP.P8+\vl=$|5|(j3zӰYB2\1g'=:[O d1XRj|{擞âC!®ſW˄':z-[Qei!:ZP!\W&,5tj!J3eDstMцBTF6>*1(+!R@s[YhV^ ?Z|k=-:⽆%@Ȝz` X4ˤT Rý-2m%xuٮ^ a82,|u_;py}pu zP;~3ʚCX kдGo6 [~ P$(Fr3F#r1\o3T)DGJjB4iz<lv8_AIP=5?4_S@ytm*H ,a:meǀ^Et טTaޏVQCryup0C;XuQPP ǀ'.kEI4v"R 8DOB(ՏnjK>: '7sw8 ض#&)?=x},z:~ ʘAnT{3Pj5rSN^1 vGfqGBLVl݃ ,Xnu=UzԶp },-T/ 1!b"tu( >H1XKoca7A .NYDϤe(ے*ef)z]l נJF!=$h"o“QZ7]<>E|](6 jڒ=5BG 폨t#L5(z=8Ri!¶!I1-J8|Y 5bAD#w&0>2>G02q %|,kgXBg1f"=˔ Ij=˻ˌj˜ ^*[rF[n:# Poy񢛜(sIK@"l?Bsͥ$h$! K+%G` dNӂglbjoF.i6v4&%=v$5d,&s$-P9S)ULM3ǫ*J: `<%C ;:sԻ_nJX#6ifCH)3,^8'>yx '\1vSi5ф/͑a9J*b4WEVU#Ȃq"{0EO82<͐_@lUh} W[n%JX9W5慸X_ ځbKIa )dq}x /E uq*̌ZVGcTAVT5 ;&< xU^bX4%1)䕍.b =I] !5vϮĵoEqk~l"rd9*K`ĵBEs]8H6VmI)9  MuHF?C d).DjBSȅ8jr TٲPkk}t>q(śueځi[;ru\P:ښ餒 `r݆@pwb&Uaɢջ(j!tD)EF Zg=;jvUD6@d!ڜI o%Ds8"vk#L9\*e, S*f!hv. vh(0 zrt8PC"Mxr䛊݇ Q3ȇqEa!ʥ50ɑg_.y tzCu!z=;;n93&ᢘUY0 ;=|gC/5Lw~3!nO2zJ5351Xd8Sg=tׇ8SXLÕLr~x3)t7PZ-#IPp1t(SeJ_(SG-xm"ܧ\Y3 8W^!aCsG)G)^@RT(>wkEO)^@d&ƑgG"9T䐯@R[`yMo. &3*u=|隡ZɸxP%L/EtK|YCnauh)ڐ z=#^.\jWY 9,X}F5 ],3B4kfrY$*EM3Z :E 6dmY`iV0z"m-qt_R4p?RF!ASƍg==_ncgVªO{(0oOqHmiL?Ljs ;kE( qM'ksİ~Ut;lkc_ܶɃNs+KE+BbW}A *R|Fjf[R;Y{D2v/S<#w]!\.JoQjƴ* /i5U _n5ʇ2/9N' _^tY!o7 +f9CSP*ߧ2 #sQKs("G@#G9xV̝vd e͟ҁ`ro X/.G6ciꗜ soBQ8'Ky`>]1|ZQP~%(V7FkʘD(LyoyT.rf|GZFG9`3Q@&V@ Ym fgۇoz.~ç?9?ρՇ ՗E~?~u/ӏ_~o_$iGc?orO ӏ߿M_| s?}Ͽ_]O?=wO~ӏǷ?o~?i>w4q̟~rM+Ͽ  C7~jYЯ²ͯ:2'/'0|ӗ_=gBo?~gwH¯O߾|v0͏hМwGH ʬ'[[&}TZ+By^#iʰмJP<V;sF>C<®cAsM 2fK?GqoLc9Jἡչu OgVs2kGQZIVLcF!h1zo9ےZߨ{4~B92bV1  EL# #oߨA)ֱovǷ TfG6G+!mO?5$o\W֑[9; ̷_>DLp6yrPاf49j,"qasX }goԣտ {$e@['z r1 !.쾡ŹdQD#"s)oj4GQ@7T; z3u}HS!\jHuvg RM}hޠGXcr5"A#Y(#q{\Mu}QȢZx "ԥ}H2\=[pJɄ9vHF#@}rw*Y > `#";R|D^I$Ϗ@7CQ }֗vHXЕ`dA˄suց9:ӑk@K[+Br ͠?RBs&QTwQ7p:8bQs2Z&. dtyeb3z=eozw ?>@- șK<;"(vV9hH+g|\XPpH{#+E9-clXü!zH e':DMP$혵lF~d$`{82̈pnd:^_B\dh0޽_l:돮Qb47߼<~Yy}g>tп/ofW?~,bPww4=9Vyr}|ԩ1^ uTh̛m*!%~[Vڙku_>((ɠe1*g?叹(g׵,h1%]9<=qx={nP!}}_߽__`| /zp#QhUU &Y4=|}ѿ0Q\Ne>le0|fm\x2oe]yy?1> OQ>.mmyy7+gA]c>y Owm>^cƝ,>+AW͔5)7IHWpUٟqҿݪ^UZΟS~Qw>(A}*Ƙ%8BʦOeU;oǓ 2g٧+y]ni ohHaVpyU-f1Y.lR)afK]RլoiVnmPnWM͇ ƫo~.;I ):du4A\?Lɟ)ciQ/h z? T2zo!Zkֹ^Zg>0)i܊SJmaPVRBWߞ:J=BUY6F'4'=ҋW??MبHa)F)Eu<B.iۉp!JqRր{Nw?|ڀҀn5?kBYeƏﻖ6jC{gp{{5iMFg$smtoqO~_[|߻mo-S!ڹ(*q#OXXjv˕ mxwHer'!XH+_[#/֖ rwkkBbvFD.Quf\|e.lŻga^^~GzwD}s l=O_?6[kmyU~(G <_El諷Yn8+'ҶC2O=>hcPJRCԡflev( Ucd3V :-^_ܲx \>I~NꤢRWJ!&1u 17X],@GUDSë'"rg:ޙ^ɧ:_<'pW`>:Щ%_VW@ؑӣa왇niT{;5;=fV?tࣣ?EuBPo k$Z&)s&AnbibZB?mV=k$DȪVAE(ȑ>NkPA߻EN_D9>"?m}Lh~]¶`9͹rs+gW4bv zqq l1 t>s-*0A+@HXM B^*Y[kjĪ?{?2棩hǯ8AC -?(}+/g{^珜yVBz9I:1:Uʟؓvܠi%ڟXY&=)uZ)''|R:?׉/aeN}\ Ds3g ͩ/7H`=kќ8ƹĹV@4's'Or'gm {rqq'n b7BůbGB}5;꫁ؑP_ C@<^V|5W r}5W0z}5j FȯUT·/ y{L y:JB/3Hu#45PP{ ޵i.ѸP Mt\t0/~XaCn$[dS60A =T:ZoOvIqL|oN&WOXTIn. ]jfEn/ ~&mVf mp~\RŌǧS*uE1{L%/~U5ʘlB-Dgl|8ƒ۔9NwH6:*]ܕOA]j#AS@|˵=:uka;_KPW/Zx.okYE=mkEr~G+fa$ 5heCsJ ,Ё쇌`^-F̄€T3JZSfbAagQbWBσ CR!;=X"Q}v$yQ3zf DU *+aJAe iiz8&*GA0dzAg&t.mYY[8)  }M*Zȩ+[6KOj1=/a֮K`,ZK]Gǭf +^hSWxN0uu+a\4ĿBEڹ6%"%VD\ aZGm`.ʮcX .8Ctxа]!1J@)`Y𑨹oxaZ|!z8|+@31 a~ԅ0'jV"w\Ӝ]]99EzٲZ7I`V >"yV)1yC40v"1B@t2,t0B*oͪ{oDyS|E'P&=ԥ,cK};X8~8Mi]y Kٲt /%[N^K#0冿@G.{ނ [.c ajcϊtvujQاFwi2 Oɒmctnmuo↢* أ#EJP@/;eY" gzOѿEdVV(+Xͩp:D=fkbRF 06ƌ11*NjQ#y*LLG|B ò@:JӰ̠DZf%Eq}/wW%q,;myaZz8q* *ºamj3G!п{pTyig2"ew;~ۿٹnsy@zb97V?Lw9)q.g"{}- Ǔ[N֢SpJCUKh 7b\EYVo@TzmVL}NooQwGZ)i VZӄ H1 TN&Zkɘy #*رt1e*f%sRle"r6EK&gZXh 5,Τ2UI+$* }nο2Sڪԁ!^SDYH٦P᤮ոXh*3b+,̋8m( 0 PHXgQaYӪFCJWF) 獷VRwWE P (H b*3J+0X5џVԭM2:hT48bMV&5ˋ%u-j!-<3P7t2Җs$W@s4q酥*QQo՜ZU &*j?3u@ iz?Z'曡Φw$+1Z1awM [nGɳ"Vꪍt,ITܫ֐|u; w Q~~Wj+z+/4?s6jG ^$bkUHac I)4A8j sYp-ȩSCHmвWm }_FP$$ଉė3(ȝ`JS(y0D}\$^PA$܍gIK(bzhqCLEbL aXwk$%ݥ r$&wM-a*|žkeRRFM:R q7Cԃs*`M-9VSa"NFܪ)\$9y㖉5Y5}egzAdW+jZw.fD|m< 4zG'hOb%Xpx4˧ß+ U4>_VŬ~J>'h(slRee޿]Mg%5w]qL/?N崊n'x2je[ñNvW o?]4|[N+%x4^4y'崜|(/޼h&ّ;ubL|n&qxn1goY6|2=r,-'3eZ,YK9CBwCA9l…6ʚ-Q5̝@tx&z ˾JxKJ9.] ]?P>̯!$>*< |Ž̾|kHR,~ *2sy 0SRjMm쥊zTIK]CaN%N &G&bTiErStgmDool/>w/M#.e6}Q09 ^_$b4C_l_5N$auhºUBl2#Q$`Ktҳ 4YaTKgTicT^X[4 )|:9/Rd(4MG+Dq Qԋҕx0đ02Lڤӗ0xH; 0gM:ObaЂi"kiO U@˜Ɋ¬yJ{E'nGp *X>tE6@3ܜ;σ„!=ҫ%nJlk9T%-;et"8ň\ÃJszP & &?1tJA2HpI]ĀZ'鯣7mͫ-SHЄHV$5K:As^!j("$y3)sg>Bk*e5'?[+:!?6AĴI4^40=8e!x&:"98L 7otGQ25RT Iӧ2A0ļ]Rl3=3G9 $(ό[IW9TH>,uHe6[ )P,YX!1PӚzÛ"@Kv%}{' ! J6`a]D&|N{lv$+GĴe q)k엢m'K)TҲisZVGw_kTc o]OXWofzX|QϕYOZ~ݟb(mȾJ!5SJLcE`rԆ1 Un-^cJ؈0C"4`>5LKi fa׌ :[G2q26rBW0,ͭ8k _juZ^B !ɗd vӡ{ [[utPCw _433$r/QR Cg+=7%p5wWe%y o օT-L c1nbD}Ͷ%W03 mcn_":\_:ٌ:hdLbRX {4aۊ0;hd:ܙj$_ HvsbLM#y@7ktrHjrTiZ)}Z]pR'G6khڈ ook=h1J 5k䆁+yBC7}flvwd0n$z3-M0-X3lWKT^`FV-z5CӻrMƃV&AaNWˆvLRrWA &w߯.~|3A7+:R5vrQ}!=]w㫳sۛS}'x?=o ;+?yMYW/~Ooskw}zՇ;,Çk'c^?^p엳]qOwxA7)#~_>=^D+Hi~vOgW@q_zqzx[x1za2P]{q]=~~_UG&+]!o i1ۘ^oޏϺ>ʕtj~9ܸLksqڬ1ʹ\A´zlo^ԍK_1gV7 9X(vmn17WF>na>8nT?է?iO37ؔw*z~)jw?J6vn3m.)o\PFwŨV5=>jڬm{ٟpϸUM^qL8 Lz!Vh]\&٠&n"V@Z-`{p:onlvq|4.k__,oߊcyM)awuۋV=woznnn=~NͺCUfL)T畚9Yoj]Ʃj FeVɻY*UvMJjYmu_2IR(ըgIk+Si;q>窶 13Lh1g*1Nn'+R@9i5F3U&:.bB2V\Un +36Zj=JiH[+K 'ϕVKx.lhrL1moCy ?o1p篝xsMg^;y߯uiG^l=f#?}|x:EZ-_p/WrcC[y3YN&2 h_SJc9B(8uH$AGcnCH;ْtT/ BFb \=vQ($RۄĚ>۪dOVNeekcAlmxD.M2.xTbrL֬Jc;/d8@u/٢>>?/HcLKfSF?=K1d e! +@DfhNCNU>Aeʷ9δ tp }uRf6J/4g5ė%HPLLof_ۏl׸:.i68vko&@~Yd4J XFnO3EG.5A?ك;cg4!| :ǵo/5~&9l(|:6 l@]w_PK5/=Ma}g9k:jx42+X`k۟g;/ʜ:ut!];o\CSN}jMԣ?R4TGi,Ćht@MC]zbE+sH6ܸOrONuZYCos{.?Lc2}6ر>1۫?  dVie?o4B}Ţd4 Ҩzs` TD<]lI(E2-ɐ熓|B -We|!m1-&i]+rliN$goq'cӨ9OMGn`FCmVjset9׸` oaȑoNKNuoP]Wj?֋⨥.:Xzh.DE+{-?lc ,B;k"kO(3{<դìQCQq,t<°:1^(ƬI)& C2`Ȁâj#''7@$v1?ʣMԭ'Yy W-ju &j6ok/F酝yHkTþ'l(zγ p.$1F:e]BoWZ^rNTa6O__Ǭ3YKY;,Q uĢݴkWa!~fh Znv&[epdd3+owBaɸޏ{%}d:m[:g(DJ<+9b.S(>)6lЍoׇ"L w섐nz* L[Ƌokz2"E:4cIL*GwrgdJVmg#A\|ba9pXN^&9Ev$)#O ]\N!-2S{eS֔+ZΈ30~n,KL&w̎#-6MO]L;LiY$F|p,w98PLmsC2nmYe$=i,8ҏg[N VsQsb:_Fnt'R&jBYr/lxBm5Ӵo9m5q5u &y1c#.H_jnQLqq.1s-я_3|ft~ݟ_jJEh_ mѥQJ|RI(Q6jS̵_h4ZS̖ KRxCiи]6*(7y`RBu}k#?^=!T֮-g6:$ ;|Z:Ia.XI oܩ%.80TL+x ȤIrW[ۗ4VtkLRi|y)1~x&̤a&{_f(RhRNxǮ@o&4{Фh3&P^4itRMn|.Фx<;俁&nAF4lhR/iA$Фiɔ 4)w{xMb|*X\A&Td(GФ-(QM SI ~JoZ/W,l44e97ԡ|M[Ӕw~cmo&>gpzYikiv=:I4sVY?osˌŷZK7ESؾERxrt v~~v[%6:jCU 9UMuώDU2+|ELf'7QRʖMi}17Ge!K~ؽKO3|]mj}Gh5ζ2ؖ LdmqaL(t̤C¸ݱl=' oɌNLIvf#X L*~ft*t6$IJ7];LQx8Jy<>\㥻S6ae:ns6rXw%fSLJ;͞lDs95:nnZc㞋 B=BfA 欘5ʛc&cN#MT!پ]lm #TJ9/{^MnGG7HG:uX娬I+Gc.Ja(&N< ux?,uW6 .& TP0Br-oCy>҄#M'/߾9U.!;&!0 p؞}83z=n `by\CW Z񊥚1A<JFO0 ¼Ôuzªŧ\]1F~YO䍘o /^e˜d3x#xqxdw_vK (b`4!1]0"QO}}sh:jw?hTl'F}Hΐ $=uf|6vfaϡZ:^j^!G';cj9|V/ˉTV0۩Wg~-`I#' Ahz0{Q2}e:&6|^cϋnK>v:#ˈ)@{%V$UC')jtB15-4v3p]􇇤'mZiVp؈X2hT*ҍq 򍷦 x]A nӰU֚&dLEUGF={bZ㸚P'tr&d{tZ)i!GԳ|r rDPH;n8; 6'VqZ@+M|Jbtݙ<~*c⿍MY7nǺ)XKlcFxMʪ͍CS‘!1H<& ӜJٵo6 Em2$ao7_f/$&haA1j- +&ED Tb@ >G+A6N;K!Z? i4uJ_)&vDհM>p127|s&As ؞VvM̸l7˜XPmWZ|N{j m{`B'ο;#dzmh)d~?X .MN[G?33J@Θh,Wn1vyHv!D36u{xmIv)\$3arB dityr&Q/ALM>lir+e9_y^8r-RV8s(U LsM{WA&T yB 0SVluazOK Iݨzɐ%1tr10" >݋t̐fAq<ŝn-vsLZ#+.O2Ga~yY9ezATz DЇA$Dd*C 9kH׶qPx 8!$LbeD&'W&Uq$dȀ&((0CƘ E.ڢV [`ޓ5_6%ɂт͝鞞~7^gc_?L#%F nkc)e6=Ѱ@0Nc%g9E/v]'~)[UaOy8"}+H$D%4l}V).PMsb&eRЬsK vgK3kacB9΁+O/h&ʘ*{H"(3N1]hַikc(^xOxj2U2,m0Q$B ]Xc  俨^s `) wAaQ#T |KH|f/sfxyqo(1Z{4fZ:-<6NZmֳ1\_ fKvjS٨,E{ǎKTMSpv=]v2&w |z3{ruSRe4l~{:T^I)l3:&@D&^8e׆arc h}c oEUB[;26.h;~\5Jq~ -Z?{h'lw1pH!=O'GMBT\O`>GU/ dcd|Jڝ^\@6ߒON0 'w1-!]fxu)rm=y-!cJ(bm8z\1fqm72i(eGaq rzKG""-[nDzu KJt M][Ub-&A`T:(HL 6kk[a&p>:CA1*7-Scs-^[Mu{3Sܲv.]olq, ]#R->_}VY.M 3zl?o{M\VstkQmLJm@.bjsuʶĈQOz̎OYСgðiIO|blNgE+{ܖ~] K=tH7 $_rLCKC|t1^`KO)-۾ǎ)SN)g|@'^fՕʉ~ra/K-kjG[)Lݑbo/U^}K_J/'RIjSRj©DCVܞgOOAЋ$_Zhzc6Y}Sq {{!H{m`Dj엉-o-tvˍy=|_tю+mE/(,.T|c/R wz#=32g=O*Ivgl\LJ}_,&xg`19Jrjt]-tE;藎LA q ~3NW j i]")l}fLNtڎ~~v[%6:6|*o& 2E߉ >N=0jVlwg>lK?/ _E"zx_{6YR2əU{MTUϒ1׽=oؾO?s<YjU|gȏw>^]Fo۷ʿzuWb`G,o98rħuzO+w].Ϯ٧W/ 3EH[0u??~r描W7?o]Vok9?@ҽyCJzO˽:6_:;tb_^{Tco0x15^u!g|8lŕWCPRZ>\uH3\1f2cli`qùly(悮, 9Ԋ?]=rP/؋7\߷8{{[x.P4=t/ǧzs/^wﯛwOzoWI:^=^9ۏ˧xxejm)x\n-\m5o?}/.bn1B~|-pZ~}[&'>68@AVJ2>cZkN^.0CLr wg* CE':6+5ڛ(ݣLz5>P|e,.3 Pĥ `*C`XIP0LauRܡ2$hq*mb&-(iz6Kp1,pc 3D]Z%zXy;K4˅'qccbШu-pa =4grVzM[> ӏ)FҍoMY}Xԟ+5h X\kCT*ɐJ?۫yZrч-c.*HvguFQƢKcI1Ǒ[O$& 髨Vx!)O}0598%0ZUwO`S*[yiBkغ+%p:Agù^D#8+BۍncaqU%?E(̎^\W_QnB;]^|uVXuKVnW`WHa#!#R`F3o]Z;%o;q5Nkf[NugO`;%z@3G+c*(,`\ʶ\}*nx{+T-!Ώ 0A)謹oڪjh ??G|?(Dg HO$ D2U\’N&/'tё% ٲ]è5,`[c K*8@ RarNcaBm$l`#qpU <( 5Nlm.dT]Zֻb|] ]&a5@n<W߰b};Chۑ_n{cä7x?<MgWn<1\}t^ˀ E8Ga xгlfnx>+V,/描'(H-'4mDs/@qWĺƝzƽGnAFtѪ[qQֻ@ tjDT?{~7'<. bJ+gfXbW4ֱ6cl@l%N :a l62n 7m85+VmFn L &Gb]gn^ξFMa:K zb YgAivXB z#FZ,)l<%d6_-'#tU8MWVw*T+I\!>1~1X]A`낰is&sҭAN\nnaK5"řQ I"- H8>~h͝!b #ZҐx`H$lIpP, Y!K,`B߯H4]8y)/P@-#m,z4C6?RvW ߲WWhHHH%t; OG RIdCZ676TkHj}jh1%$gƓC3j-=Fyb{uu`1G{Wֹp̺4;Q7"8uC愝`G( <3*Pw rP[zD%z969ٻS3-ě8VgؐBKNdꠇ[uEALBM-DY^!0M*^]c\o`"oW݃x^uPHO1 p g*X8E.^a po筚'y;on|\]ʗ+-6d}q󨔶*}VBlPwy\h Qiې.& I"A%`.j>Lsȇf1i'HHb䉰 EY3!*_KoTAdc6%ʈLfʈDBBfJߕ:+t6c&*fP4@&n0V(46`P׈m)2ViCkzN[Fy+rb FP&"_$J/H t90^6ykhtvL%Mk"7 % ▕Tq˨<([! jr@<^W)yLˉw_ Wfa<ܜgQ$hϮ{F!φ=;D0v:6JړXq6;u4ԩ_^4eWikB ueڞ.H+3 c:&XHDBeǞ PyJOt(tGEمTP3lHjtΆx - =خT-sp_ۊ&C؛ʻ6^|Ŀ1&gOsp#7B/ >Sfꏄ\;Ѻ2*v[L i#ۥ"ۅ+_jh(jS. h+􎀲( !n:I.r2AȲsk29 AC1Ǵ&).P;Syi}njP,Ql"L' ޥ-IxVlAH"3lY,c !Eozw&măML3oA6LA[g[w Kmb/K=u/@JZ QYh4W?z࿚N$ENi-ɐ$'A7|G'Ixd*U Wafhp"<4IJV;KLV(ᦘnx,K),%J9B"3]qy*B؂A8f.S=}o``KZ'PcCBa_vCSyi4_)')׾bO· PY}s ,ZT~Y/~vS=;#秠? U-<4>݉(Re̴Żd{dxy&)sP Ma6wݏ&- -i8!}d'9ɽ,;R$*<$Hdzع )9X4Ǥyn@붾|0YҶ]ͨJ)L؃TұiUBZA14|`>*ڐgeUl)' | i˧9+Jd!Y-<6}!:֟*O 1}04dW$C"Һz_X߽:-K;sБhR|V'-5pti%CkeFoi#R.S;q"{DIY`=h0ePR,~$+[PhEVψq8L2\Px -A)rM?wV?*1ȗE_v`팑8wUYn! ko"d)rVz#mt:...ׇL4,7[ͱ ќ`&{jW*SUڊ7zYf K/~j/ Oxg5hrCc[)1}j$g '\=-r357xwR컴φ{~ϧ&w,i7u1nMQ|G ;;ж ]RӃ.K+lZA{WE}Y›!uOZsZ奍^J&2PI1hɷ1el$tYb0d:֢ e!D+8b/ -1oo3wąEs0oՇ6c@~ hHk/ASQN2DsY`u BdlM wүLИ ,)&QIT ?b7IT0],p NS2|n)T6Whp@Mp="LsS*fX3.Md(UMTh1nN?Hwa6tj$3wST3x|0#(RI{} 2oOb$+^|Kxi #JPbd>y▀Z-0ԈPo-M 3;`2(|' Wp[Փ*!Jg#~D8[°w̵ g 1˓}mfpĴS\]E6q>жSJS=$h2-n!ZZI_Ub,ztZC.cYTl㱄+IhQm!|i Xfθ>8hO <+A^&z S!۴YI^A/.gL B "7$t2rlr$B,5U_g܂.vym.[+Dxm T&b!7/+eHGJV >oaOPcC`R\O} 4XŃ%j*Uՠt&: sj]"8U~=2 &&RD$-eƎҤU5K4OPU7!'2O(:,,#o C _ھjhl1FP0c\V `F;L+7Wp H05]' ^7 L!kT+>j 0+qMv`q#Ǜsyf, ob6e_okొ2{+EiQL5^ie&i"_kJu)܍ ~'p+7aw. [Cp.4W)DEifIYfZ5|ӟ~`ʟocsigenserver-2.16.0/local/var/www/ocsigenstuff/scalable/text-x-python.svgz000066400000000000000000000257261357715257700271530ustar00rootroot00000000000000}mGw bKփ,0[>(6[l%DV"{Nl*2##_-Gכ7zތo?WhLoWooU5i=n7ϋѿM?~n>Rfv5_͕7][_2Vy~Nm[l9_)seLv?>=jRWy^/K n>L6OO'՟f&7ۛkۛ|07WMnw׋LPA\t&֔k\:JzYl_՗~|iiQk~_ޅ|ܖ&l_0Zor^Z_=,~6W/_? vn!MKc=~0ޗLrГ` ?7 kY!=!ͧ뿬7 ,R <ٮu]v3Nfu9/jZ_vƿhZl^7# !:2Docz3y]-7C=U^OߝӬ̓fQΝnvnzlhVnm6?6_ 46ԍW/3=]~~ݴFxq='hn>ԅ_l?m뿓noR~xs7ݮ_FuN>aSީ;}ow?Iamt+ ]QR?qAݩR:w=;:]hڬm{or e)j511ԻFaB5HJsքXu2qle$~{&DZ2p:sOf55_8@{j==^c \Qe'XNƌ09E86X8NV)84F'ej&ѱx 8)3 deBK\G;q4 H[GO.52lk18hV&-&[3 =QJu@jL ڜ{ =t_+@DŽ} Ӄ|l::IQV 0R]*ꃉ4xAi0:kŠgF+=;9@$a8LQhfWUy<4B9\TlPkZ?QCj\IPT -)g%QY|u㌦goFB0]hte"R!anWҶU^(PN# 8\QaCǦ( )ތ(CcUp1&iRXFJ4u9 Ծ24c;EKZL#ί;GsލrԢ=*c5{5 N6YىU1rxeq3 ,mmDa34BvJ2ʆ&ni[4:L7^`:?a.YoQ Epg%4Az^db< L3~]ș;Y&gݡU.ģ!#٧*h^b0g6@T5oHJ$xkT#ZCbdz<^TѤ\sq 0};=C /As,D5h.K|jol o1.\ϣ9 s2 ]6o4lK徯FbI^4wM6ߴluOq/ 47.p)>(OޛIB3akң(W$}~oEތsЀtԗRtZ$syߞowN[\3ۈű=ݡ/1\@ؤ mZ`-yʙz;9^r$e?|:CJ"z$ʤGg3)SɣA H,a  8?I[Ϗ4q v2mFatg1at~p4pp^(nHcqPYpHBEuߪs~f {/ ߯cLDf=sd*)0J 0J}5j0Zi(k opt'31En:>;I^A11:YO{[3Jp" )IVr1u=WISzi|ݷLkV~άlM&i֍`PҬ KQJA_JyxJʾ)S $kv=0p$da8 Ջ3^BG"zK?^FNoVe7]B+vE,zͯ"hoۛ7'-V}*sO5+,_nydXm5y4oww*S7͑xz~e=Fj&دt9 54[ionՇtysw/ů$ R[w^}=W.}.?27~x+k WŇ=?k}FI:pY,thh4{8rDLzuwkf4eY_{㎺v/-]2|rolc_.?JgWg46W~ \}c 9_̗耣X>=ܭn5~ޚwmv7@*KFK)]v.Ω{ТUAEBPY1 M4$Y ;-롅GFqВ+iuly2*ȷcSLGVZҚqwlnC`^ i iP3*;їh Y@[ɝG?vfS&GMEz._Ci#DPN:RH2 4˼?dc xq K>%_h@ imJ,;AdK [$y_S(0nKdc`@Yp%s+(B5*lt:Ð cGQ@GD#)_%nDseãRI+'JtB[}HZ"$3" n5CS)R.1p R5Q0\hgL2=S)2& fMH[ң_Goxeٷ,Ik;q@ ۉM%oRH.k/CJ*p@N1&!eK zRȈMeÐSͩҒ-qr:,UrŹ6KAeo-+tĥX<S~cDaeGPtF^Z0(K`9M),ª("54_=bCdgщ4jZ " ^K'֋+5ؚ$,7.Z_PsTf74QF;yᵕoǨ[?&Ƣе712Er1s;g" Ƥd­28Gْ)0a#$diV2Îl3-b%%Bc;OjYXNx%ALl2 siq.Rx=U2ħy~ a/K431fUsY׵/ޔ d<__[ĞY_B0&10@P s CLJs /cɥltd1![7|a^2:S14"*N.ibCm$#CݕpO9DɄ.S A! 'C{gu4` y f8+1d9 .7 ѽ"GOSRcM$ sw˞PF#$ ٸ 'S6Vc]S& WʻAP sָQ]dzY D< {%$$$;lr!ց!IP6oakL dX/.#ݐB\mH %ylX m9t,w6^oكbbtdx5I21p?r3k$߼]hZ(|rPJPoԅK0}Fp8~A=4EЃ GPF:\VBJM\Ҩh 9 +v<ć^Kޗ#H.H(>\IUSAz>C(ِpp= )V)In1dJێ3mVgl-. %'@F} T#9ʚ&ifg]>M~ύN`7iUǡtQ7&{ہ4왴=&o=ZR.(A >/4 ]Q_O(r)OΓZ;U^uWC$+Uͭ8YQ7bm:KJh:AQ\-wtwxб5q`ީ>k` :2_O)!sQNO<蠳|ڷ֏h- "G KvS'N*>2*G4o.~Ҟ#ʊiXa=2pi :9@vD)S#|b[;ԥ=vk }Tr&~>8гDoz|;۩S認ߎv햞?!0]PPԏN?ц"H@(FRGek΍(  aã*eJBNQ 0h{) MmdbGhXZ8H1bʐ"hwe21MA|uPV>絯Y 3r#T wbt.fK#2G*45Mb., SgЬѠW5MPمJ$*p]tU!6$KjI]ʲZcma֋s)Tewdd8Ud?3p\5iD`  )h)j(kf}4K颉1XHO9Rhh:ž VA"˥TG }NխF~,Fy%0bmQRb9hnb)@6G̎/Asף1 `0T,8 SNDP[?mc}eY\F]`E}w,1۫SD+fQ06'Eǚ(%X(. d^"pPvڧQ]4h|;ᵺlU.gUcF*Z+/D$7b.Zzb6vqozEzcgb^ Voz7==wP4VůݻRq鲔2]/^g mv)οl Zrzf>[!]_#Ϋ#6,|]աMfLn[,]{\~֫fw RT^+>uoA$|\nwWXF>w:N`kV7X\S,.M?ץ7,|soӯ׋ҿ_s{AQ~t6tezowc|U UebVf5:/_Dqeo] ØH3'U._|EvbSSkgnʙܕP.GJdh'9ɛؘGܟ܋9!:b ԄN&7AZ}҈n{r:j<)YQ+ۮ` .isJnGaeP]ftD Z[`}D~P}];f)z/SaYF=U]NLRTy|&D㯟P"R9p3Q ^h @YH _*9YUOU!Q5y)-jECAU3%Am9A2:1Z.]-2\^1 1v+:334 Jb>dIiJ&vHG΂?-e:Qt J΀^:Ҝ}uqzJ|=\T=tSgN)n3v mg;"cW]˾lh5M m Bq&]bIՃ./%nN7M"=~HFB:% &+oL^Xt$mS\@+ZTCOP2>L[9zb<>V, m HI)ݓ7kVlUQ>=BSwo9UD,L% z4i|cs.*"01;~%]PqDlZQ8 =lo~-PB _ێEQ|ffI"5[@6lSجzjWb<۽u;!"CИQ$8cpk.C#+͙QZcGc3i\/Ryo.pO&/-VZ@H~ ֒=pc;Րx [Մo޹Bѓ+pw:1;у5;.vaxL!js]3oˡJhqJTkevn-Ccڹo|X5 t(Ix'wW/ Dׁ×1_>>}? p64==y!qMȤp{X|IXk$p.mdF&fJ'ٍxeEi#[nWd$LOȞɞ"j:J"9M qI;+VSe I3brkЍ F]^YdzJEZb( \KAFo(:Y38 p J[rnI}MG P UK,ȠL]Kd>JTR4x>,cI:R1>Eu \Ǻur!>(Rrbj5<:M֟6WCIkRYhHQ#9SY."ɌJ;o+K o1)hC`M&#{ tBIKZrܣrڨSsNO< LF6KOz3R9PM.ˎfM u;R0$јWQ?Jv2p2:.wAdt6~K8ցDD~Q, ^3 ӸA$ i+=xB9c @C ` ^k?5`cW01=p<ar̀Caf2 h1d]lؖaAudW(Cޑ`1q@xF X4ϸ_p#IbxQ)39ecRaB}WHuEaw򔧣LIAa$D"qO43 Oꕰɦd962.r>XjkI,)' F]= TG@lMVy"inŧ\+V i YЀfq?،F0禍P3ǁPm /jֲqxg,:&g~WK[ey %|[C nք:[Fm /BinY-_M\,5e˻n>Nc°2ӃilGjy5Hp?0{[`t7r`5__PGsrsMNDp$'q0֓JŨiC>"'smxg ocsigenserver-2.16.0/local/var/www/ocsigenstuff/scalable/text-x-tex.svgz000066400000000000000000000151661357715257700264270ustar00rootroot00000000000000\is#Ǒ_KĢuԌk)pqA3zՍnv&AW]/_fVnէvtΪv9[]͗7?l˫bl?-WgQvmmfӻͼ+lVwU]77n .7Wg]B*^E{.sl>Olu{ZnrQ6gJ%s>Wܘ5r;}b9 5_Waxr2v<:5 ~=kѲm{Yj{5qd9m7wY9˥#$yo>nՇ3也NTcMu;~RuI=j5 ?mۇmP_/_;_oZՍ*]]j狶xqu۞͗Xz]E{~;mwtX=s?pA7)CW5) ǐBj)J7A[ݵAvuN%cφQfhKzZRhͷ:bS=$=DoL6O:r{hjnfkYicsVg O m.gO}z^{F׋vn-w'$i)us97D^szRo?NDZDM{!?DGyDaM0څAR J}2zTZ:}ωN_s7mֶ{?*Q Y7 ޜ5ugWV{8e3ڏ"%P{KjSΌ>߬}{8w+Hf_']}yz8|Mws4G}|`*_asm/7]}X]Nbn š[_'{7Vv/o0vfv>2YP3ݡ_=x+ N{H5̸܍˻]OrI.3:(w/$rU2DFy2Щf^J&&3G&|p_cmʼnQ1Dz7&X65>u::}b0g{~ R~nWt@߾_xYV f{,}n~ %ⵘ|]^U~ nmrvF?s}:ow2fٯ[FoF|o<bzU~jUtuwj;>nq@c;/` ?wZ#"%tfk-3?6 2~*W$$Ye $KUƛ%ĨC"B3bΙ BA9y+!Lq*@R s [{M&dIf腗=&AU6:rҐ`?8/z@'z:GJck Et q4?ɹ- sʆQ[Jǡ$`fpyH3'^CՋpQz^9:8HаiH"ANASWx=`DcgGEw.VXpVaQ&'HR!\[BiIW]qSV7=3Qރ&5($Gc Do!XB Sh*& %;6d:H \NXtgpGmaTtB@.&?>)ɷҜzq4/…$ tN+'5 >ɜ{z=AYlbt17/ðY?y .,Pa:s0@n+SJ@ާ7fiK0k6Б E 2'Z)$  3(hGɳEh=9'ZxWc'y5-v%2DUku@h,Kcj= fM%,19 $P¸د׷O1,aLw`G!#8H=4R/0H vZO ,3/װ{c(JPㆈKYpy+pU~`y0ˀ֡B{ PBI;kqXy=BN9J"2ea1+JBw YeBI8qzG(^gbm::3u5 h\sߗ1v\v՗b.\_vtq^O2勧= 'N4 4D=^h7/ gȸMy/F>O\2•~f'BoP#?3֔TS8/h? =&=#P = / Ip핑Df~c֠@?X$,X,og 3oGV ] i˚a(ubCSѩweI^5j|V0{'j8lw Vۍk H/)"EEeȴehCHr!r9z ݤ\N׍q1h9}[xRP;P@zOMF*@d=6̓xRL)bĕ3ac93Ypfqy0It=bK@a+,#kkc3"fjd"bBKDx-*؀5.2Iia,̓H + %^\>=Łqa7͚Q;ے)r2CYnLJ8us7"|yNJ֡dwl%C%KAQJ  mpEdѨ*_vzGt&L[`] exy#+xxh;쀯r@xRYq 9$Hd1ɮ䥅I y"aa~&$;1r9& 8C40zdqD`sq Vď_m$3*-bBMg[k)Ӕ(6vLʑ|d4Wq\Sӫ`1`! |䎮e5?׷>}MaX^){x]Qw;k^[Ry=ozu.+y~>VzYW˫MmNd\sJ(W}czU !l[Z3`n -ۼTrR_ͺ?O.חfZ-||SnǓzuޖ?M/ *槜p{; J\iȄJIUzkr1ߝ/>+tknu=ϭ`^I齪7Mb.:uaqSNru=/09ޛM| nƳ^v/oNV ~@Պ53 y/[&5OԲyo֋۶n(A}-^\]j,蔭֟gu]NW|`<+t6Bn Rjoe[X{ꫝ~J:vTJpe-ԭV>ۤ?o0܍g}gƟW$waYCXac3qXM5ݪ^?_{W7n>R`Hg R5Dkc:f;|,kgF]9%rBJvT Ui}rsRSGKۣgT { ۢSCRV-3_ջDa@5hGu҂h6Y[|ud0F9 6}"`&#$]nFo4p6d6ÆMoE!mxiˬp?Iw՛ϱze9[~>9oSQv(P~SHFAUV\1)`Z-dQGoUFhUQJoT"j,K֌4("()Y`(B%F-Q GAJ QIgIcQ I^d(^#PIɲ\E*Q642=Јrl0BJ XAWFlPׅz|UжVؗ-;ŏ厤 2a]}aʿ# L?1H7ig/5d-{5κ^nQtX\٣B~UGc/j:n9ȫM ?Nja{ C<X u-SKcu҅uQQҪ掮a+i4=OT!ƇV{d  :WZ{Dt8=qBգ&Tm,|ga_L;h.jvPoO'mn nvQNAGЁP:*MW@Fc "FAxGR \"*P!>-P((DH9O&Z::%2Z(Qk"]͕A;^Wij?Ǡ_8&<N)cn<чS_xλ~[Sn6>aonؿХS8͝=ݽ]Cr<_i3Pt:|d+t ^> o1'yƠ-CL ~DNA{:y/gfie#7= At&@ꗿxK*{FA ZaRjKKP~}}K.8P^W <#!Ohܙo:G8[LjW |ɿ=֌ԗ6?ތv|]'M9g3y~#5z{sflu\ ?_}_.>;S],nR,4*<ȕ${RFGYwyen^[.Ya6\cWnL)[}X|^]w*{|V~i:-7y5!{ԸJG zj>-gكOo֗6M Fѭ^.nsY ZZ;6z:6~6YjiG['=]T' ;[1Eq=7^M}҅l>76s#8uў6vҒy~(%cy^#JXoҐ d/*Λ|h'6w_z=[ܖ4oW?oM!*/+ " 6_73h̓^emewrr3K]Ogg'#it?>v4Z#3˷-ҷ0!T~~gE!I>ےM]wcnpS8mNfC.zg|voƶ55SB0)a L+fb"G6@.LB쥋7YYTKcT! s:2A6Jic;E/>4tdɩfR.O"b-p ,"PD& s^lIR ? -qL.gE^\6GNdq}a $%Sh0HL䎙5IӉ@A=Q^aTJAysZ0 sMP0t#c1M:UYS?>8dÃ%+{<-$`pa]Њ:sAi߉#y"YB3"%rĜx !dv\ؑ?UEbz iē@xhv(tA@5U„1$,,|q `:P.da) sZ9kyCE[qԇPϏ`{ B :4SȑY(  t<?J ,ZX*4V%:M.˂`R`^ 1#}g@Zg/7@iKFz)GaNA%k7d\' FN6D0&`˷I?14 mtH -#LŴNʼnsõHq}tD)Z$ұ4`O!A +BX;蘱'IegDDBPZI"<IĶ2Yr 1KڦUA>[ ۖScn[L7S4}M´eۻ>C0'A 1D<6v7ă&|99sL ȝlV@w~>yO0ʎ?i84M3s`O`Q=@M(2ퟱ\k׫Gs$׾zTnxiex{ƶjQJ!y활뵤ג^K|-p-r{2]KP[y$=D7{H@j|ng~/ܚ}ln?ݼMX oಞ,li%2 V6d̍\Nls^~~R.zb)$e~j}~I֙˻DrCFfܘ錭5ĘvΝ`li<#Ǔ_9O&w7wy91 `4c#G*ƤCe#ugZ$AHC Vxp. 1%Z"ΈI=1*t0)T2)D3;#l\ʃ`%batgT/^%vmZJ{Y:%Ld ɦlak'p #3b_f;< i,^%^ϔ+֊ T^ψCuFc$9q;Ѱd\kb đt+34%0op3dBLG 5PBb6]iM |`iStXj܀ }҉HHd:MApVfti tنpAا<VLyE[u: )]z]^i%m9 G57 Η;\g7=/2ŤɮqOHyf.jcDL{F1B8jH bkgҵ D n,|݆`A d/O19A ]%AI&V#M b??K eՒ*"B tzɷ 5%Diَ:dc2Sp^JŎ%A!y,֐1Ǘ6}RvV@YnJZFۭ*zWk;;W}Sto/_\P):AaOw~?vRޮե{lfО`k>S,.lͮV^Z8W7>Ct=ǭ+^߳!Rt}ҽz^t^W/ݫKy^_OfPazơ~8Tȴ8W/v[VgG 5W~pz_=ܯߙcv8-?ocsigenserver-2.16.0/local/var/www/ocsigenstuff/sound.png000066400000000000000000000016661357715257700235550ustar00rootroot00000000000000PNG  IHDRĴl;sRGBbKGD pHYs cytIME %2]u6IDAT8˭Mh\U{_f2L;)UkA+$Kt'fQlARRZtUЂ q؍.ąP*&!?qaZҟeBy{\71xp/sP(3< xwi Ƙa{QCiR v&"Z_tM硵NV߅zGqm2]/t᡽ycZ)Ta/;JJTk =9n]7pzHr JI"!ʭ z O}FmpENuyN<>YǥY̧w[׉R*yKpgϢ8p ~Wch`S$IB$ۀEs͢0G.nEZl ܳAH9!hED&b^nREƖ( ]*$;J#{r`jݷ000 SSS5`v}XkZ++\},|H?//7$#LOLHq"q5Ɲ?w.(DJ'ϡrKL/,ՠZ""$qBi2>>n.m =3_`_rPIbc>Ql4 N%Zk'c=:2 ٦ŵ&y H!Ӫ -F7n>sssSwүlMAHReN*kchNqVIENDB`ocsigenserver-2.16.0/local/var/www/ocsigenstuff/source_c.png000066400000000000000000000020221357715257700242120ustar00rootroot00000000000000PNG  IHDRĴl;sRGBbKGD pHYs cytIME .-=IDAT8˭ked&&nb"E* BY1me.59]d(O"cZ777(֯H",FѯxoˆEahrG93}zX_=OTa2iNe&x4R<Xl v@,V#Jq5v<* CCCt:xu7# *Z <Fi˓Ǚկ|c "!v?CFaS$'b-1`,]1L S?G|]ANNucR4 ֳpŽS~N1h܂"B,3Ţ]᪪qrxWAmi+0E( KoQ8|۾'EҰ8yʏ^@E)E$$K/gK7oXk7o/~.XoXӍfSr]!±cp] gx1[6EX:QDkst|轨Ѝn-c-$Az'jsJW lJB*" C8}Dal8].K]?/hQ1$acczT*OKKwϟ/_e^E ?0e !Zk>@JIeXkG9kyR+i1={2Z4M{c 45q.s$KPu]Rgahh8vH&"Km= xcA!$Ƙ]ϕ%fceǁ~.\6#"CϥQ)By_"mmd!ѣ=8lP>wޥ\~RbƣQXb1Q" CrY)^s%RJ3MUǫk!5 QI؈%MSrRdH}BXq$Ipp .BR 8BIIf :ZgdA݌؅:0w \*jgP\.>yyG^,hjmݻ]z ycccaÔejI1$Ic}'g>^TwvH>FGGIz2ZhpwfFߘγֲF.#jK {{Q:׮^%L|cjjZkZ[VkmZ8nfV*vnv޹sQdu+>>xŠ4MOcedl67GG}v͛/3MaM;== \{8}Q}p|Kˀ7 8IENDB`ocsigenserver-2.16.0/local/var/www/ocsigenstuff/source_pl.png000066400000000000000000000021311357715257700244040ustar00rootroot00000000000000PNG  IHDRĴl;sRGBbKGD pHYs cytIME )0gaIDAT8˭KlTU9jL;EjZBR!m0`$!D1`kܸ2lFta4AF6.jb@(}Ѣ-mLsE@im_rsι|Fi>i4oL!m0K4mSSWG@1gS ZsRZ,Bu~(1|:Bb1,u]lFJI8Wp$BY,F(B` @HI(y)P YJECaZW/ݷ151D x)g/@ 0,@ k[ѧe|2_$i}Wgf$c#:?7 Îe|x[ėDX,D`W0++b]y _}Áq{6RopW\qW".taT([j)*i,6M󒾼wy\&_8 3 sh6VU"敪Z˫XtZiJϽ"PĠvo;2 YUZ0YF\Ղm~\mLMw@`Ceߥm^Ͷ{Al;~=Tݚ j]+R h}+N)L[EJJ!J6xVhMpܜsQIߌwŔ` vI9W >WtTڈw o3s$(azl(f?c`On#(kQ$*Ce T kkPӟ:' H&!wl照fH6@} 1ظHQ)Dx~4t DN5>a u<S !qp[cHH"n&LIͭx5$qdW z)g lx Q| ԁ|C_; : Dǝ.EDrv{M~.pmC+G0q`?<-Ƶ_v[BBkME.~9ui`rCp!F 8`̐9@Et9;6vzS$:= mAl]+ .OLgN[6!D`dʿ¼_!$@ ƙ3-+Wd,JDU1Tm7gZI>aYAb4T… ε/f^m9ʲwsaz'ZqxfvIENDB`ocsigenserver-2.16.0/local/var/www/ocsigenstuff/style.css000066400000000000000000000000001357715257700235460ustar00rootroot00000000000000ocsigenserver-2.16.0/local/var/www/ocsigenstuff/tar.png000066400000000000000000000017771357715257700232160ustar00rootroot00000000000000PNG  IHDRĴl;sRGBbKGD pHYs cytIME  :oIDAT8˝k\U?)3,61"բUjX.mR[ ׺qJpQMv@1҄4I g&3y=縘I&\}y=P'@[wt޼&MduޛZ:k2Sf*r  Acdz3#bfp `xCF~_0 h[0lNeb,G*Yl,,,dd2dHH{7Tc}6ٌqLeSN'N{[ j=AT"_#㬬yS9HO!ϯ~_"8~;~v˛ĉ'LݙƻC1~=0XI'pk2xUD8qjUPz]>l#EqΓ$!.A|rIQս͉J5Fii`*sR=hꦣVs("Pۡ" sBecco0P_ahKxzWVCÐDQd;{l9jk*P#,J;[ %bu?E0Sξ!"™8!}<8!0 31ZK I‹RcJ"ѹOί6V5~=[ONР3FTf0b1h䙁_}s`[9LJNM27 . vhG4Un*=L$. q=(sq*N#HĜsVVmuef+Wƣ(z]PhroɶT{IENDB`ocsigenserver-2.16.0/local/var/www/ocsigenstuff/tex.png000066400000000000000000000020221357715257700232100ustar00rootroot00000000000000PNG  IHDRĴl;sRGBbKGD pHYs cytIME 9!IDAT8˝UMHcW^^ .D*L1;7Y uׁ.t1q-hl] fV fA\8'YLKGjIm޻ua|^w1~{eY>]nKC'/3sf(-- '/++J$`@cy 0ߏ!`+!.B)ʭpyys@jRK4%߷>xb@ͼM[A6pBk.j/򐚆ai (Ȳ WMǶmҒF&./PSS R-"0 v}15>hr?`JfN>yf#a ŐB)u`MWt8ŏGsU),?RǬǍFt B!sֻソ+ 0-r(qa}r {8qgg'mGQ?l|M_dItuǘ\=]x8Mo_:kɁkkkE:t]#мv 8Lo]uxwXM<9gQP߻xJ ,[U4/G2ebfL|u1k,v\V@*Nh9=_-me aRq\Q"\|6lE@4bh MJed?cH|b=qLF;Ca&)ñLB!zO DjVO)U?+6etE#t_¶'dt@|ҥ=y֭úvpI!$+Ô _NuQ@1 3!HcכVZFc*Jp=OwR^ZD¤qv.\c۶H%ooBOWdLϾ=D<GWyXE,ÙgQO:=|Ŋa8kF[ǶI8Mk[[ʦ{BՍc;H]wljZ4FS*Lh4ZZZ~fP2Ly n~]7#&9IENDB`ocsigenserver-2.16.0/local/var/www/ocsigenstuff/txt.png000066400000000000000000000017561357715257700232440ustar00rootroot00000000000000PNG  IHDRĴl;sRGBbKGD pHYs cytIME 3 ڷnIDAT8˭K#gǿ3IM`hcAFœ'7xARIq.}ϣ\B o}ѹLƊw*VT@ӍJP8 ˲ !iVPSQ044tⱱ1$I_J)Kr!DVu(&IǶo: Hu7tBb1$ D"r9yaii AܷhWE&IuVxǓa<;;c*ǂa0Jyzz続kDX.yU.qfMaՅ\YYA4E"WOv w~l6 u{PqX$p}}|-('''J*^mB0Nscc<::&I !nuOoޘivX pxxZaT*躎d2 p]R LWS~H^j )%B!\\\X,"CQX_wvY[MI68T=;>pmm2{Ƕ:EUqyy\X ߿|9 ǜWM0 .//yzbM>,뻆RK~ #bW(83IENDB`ocsigenserver-2.16.0/local/var/www/ocsigenstuff/unknown.png000066400000000000000000000015641357715257700241210ustar00rootroot00000000000000PNG  IHDRĴl;sRGBbKGD pHYs cytIME )FjIDAT8˭jGgfeI諩 In}SH|р B_!R\|!_0ŮJ.bYfW;szbGNK,38t} X{8u h kI)EQPh4 (!TQUڝΊQ<9*f8簕 LDZ1baeF#>yBc l6(b7Wj F#ScDls3h4{TpInAjJ~嵈 -vvv^ ]}#"5,ωD"!l NfET,Yda2g(!Ps<} k)96IB`psßWW}{Ky"hݯxKUC̜;{ݻ/^|R2ٵ<(P MS7Yrζz!{l2!2<'s蝟+50'i&ϓ߼9~TZj*F"ωG49v>UNhoJ1NL&$9_=oΎ۠4c=99~PbQWRXzpIENDB`ocsigenserver-2.16.0/local/var/www/ocsigenstuff/video.png000066400000000000000000000023041357715257700235210ustar00rootroot00000000000000PNG  IHDRĴl;sRGB pHYs cytIME@bKGDDIDATxڥkL[elj>\X (-Җ:F[JKK[`D 7;af@2,lc5.qfLL&$Pe} .N]˦o{swy[f;pu53M!'+΄K?a<3E%a4wuqCt6}f`uww 6 MM-; 'OcJgg TB1?@X,.ĝ>4F贶cMr EbqFM J^+(r5b$4ˡTjP&M 0X!7 SÁ#0۬b.h!:T?A707#pfX@CnF1>@o/A;C%P LU\E{{~%l1-PD2?!)4X&SCb)褵ڵ Pk <C"P9 r$E`摑sf19PDm +R hm뛡V)P,lZTUU}G2a4-2T,.޹PTC$A~HH` 6LfogL$yH2νޫ;%|iTW#=]7XwС`~g >j5ѣ0ڑ.Rdca~w.IDDKшdq38^{hj_5Ͽ^8]8Z݈ xxhpDD4A&7x6I)S&q9"i`%ŸUl0GTTlhpL>TT8QPPMNR*Ut:q;86R8)"%$QP`   o6BaIENDB`ocsigenserver-2.16.0/ocsigenserver.install000066400000000000000000000001071357715257700207450ustar00rootroot00000000000000bin: [ "src/server/ocsigenserver" "src/server/ocsigenserver.opt" ] ocsigenserver-2.16.0/opam000066400000000000000000000034061357715257700153630ustar00rootroot00000000000000opam-version: "2.0" name: "ocsigenserver" version: "2.16.0" maintainer: "dev@ocsigen.org" synopsis: "A full-featured and extensible Web server" description: "Ocsigen Server implements most features of the HTTP protocol, and has a very powerful extension mechanism that makes it very easy to plug your own OCaml modules for generating pages. Many extensions are already implemented, like a reverse proxy, content compression, access control, authentication, etc." authors: "dev@ocsigen.org" homepage: "http://ocsigen.org/ocsigenserver/" bug-reports: "https://github.com/ocsigen/ocsigenserver/issues/" license: "LGPL-2.1 with OCaml linking exception" dev-repo: "git+https://github.com/ocsigen/ocsigenserver.git" build: [ [ "sh" "configure" "--prefix" "%{prefix}%" "--ocsigen-user" "%{user}%" "--ocsigen-group" "%{group}%" "--commandpipe" "%{lib}%/ocsigenserver/var/run/ocsigenserver_command" "--logdir" "%{lib}%/ocsigenserver/var/log/ocsigenserver" "--mandir" "%{man}%/man1" "--docdir" "%{lib}%/ocsigenserver/share/doc/ocsigenserver" "--commandpipe" "%{lib}%/ocsigenserver/var/run/ocsigenserver_command" "--staticpagesdir" "%{lib}%/ocsigenserver/var/www" "--datadir" "%{lib}%/ocsigenserver/var/lib/ocsigenserver" "--sysconfdir" "%{lib}%/ocsigenserver/etc/ocsigenserver" ] [make] ] install: [make "install"] depends: [ "ocaml" {>= "4.06.1"} "ocamlfind" "base-unix" "base-threads" "react" "ssl" {>= "0.5.8"} "lwt" {>= "3.0.0"} "lwt_ssl" "lwt_react" "lwt_log" "ocamlnet" {>= "4.0.2"} "pcre" "cryptokit" "tyxml" {>= "4.0.0"} "dbm" | "sqlite3" | "pgocaml" "ipaddr" {>= "2.1"} "xml-light" ] depopts: "camlzip" conflicts: [ "camlzip" {< "1.04"} "pgocaml" {< "2.2"} ] ocsigenserver-2.16.0/src/000077500000000000000000000000001357715257700152705ustar00rootroot00000000000000ocsigenserver-2.16.0/src/Makefile000066400000000000000000000136421357715257700167360ustar00rootroot00000000000000include ../Makefile.config all: metas confs ${MAKE} -C baselib all ${MAKE} -C http all ${MAKE} -C server all ${MAKE} -C extensions all byte: metas confs ${MAKE} -C baselib byte ${MAKE} -C http byte ${MAKE} -C server byte ${MAKE} -C extensions byte opt: metas confs ${MAKE} -C baselib opt ${MAKE} -C http opt ${MAKE} -C server opt ${MAKE} -C extensions opt ### META ### metas: files/META files/META.${PROJECTNAME} files/META: files/META.in ../Makefile.config ../Makefile.options Makefile cat $< \ | sed s/%%CAMLZIPNAME%%/$(CAMLZIPNAME)/ \ | sed s/%%NAME%%/$(PROJECTNAME)/g \ | sed s/%%DEPS%%/$(shell ${OCAMLFIND} query -p-format -separator ',' ${SERVER_PACKAGE})/g \ | sed s/%%BASEDEPS%%/$(shell ${OCAMLFIND} query -p-format -separator ',' ${BASE_PACKAGE})/g \ > $@ files/META.${PROJECTNAME}: files/META.in ../Makefile.config ../Makefile.options Makefile echo directory = \"../server\" > $@ cat $< \ | sed s/%%CAMLZIPNAME%%/$(CAMLZIPNAME)/ \ | sed s/%%NAME%%/$(PROJECTNAME)/g \ | sed s/%%DEPS%%/$(shell ${OCAMLFIND} query -p-format -separator ',' ${SERVER_PACKAGE})/g \ | sed s/%%BASEDEPS%%/$(shell ${OCAMLFIND} query -p-format -separator ',' ${BASE_PACKAGE})/g \ | sed "s%package \"\(polytables\|commandline\|baselib\)\" (%package \"\1\" (\n directory = \"../baselib\"%" \ | sed "s%package \"\(http\|cookies\)\" (%package \"\1\" (\n directory = \"../http\"%" \ | sed "s%directory = \"extensions\"%directory = \"../extensions\"%" \ >> $@ ### CONF #### confs: ../$(PROJECTNAME).conf.sample ../local/etc/${PROJECTNAME}.conf ../$(PROJECTNAME).conf.sample: files/$(PROJECTNAME).conf.in ../Makefile.config Makefile cat $< \ | sed s%_LOGDIR_%$(LOGDIR)%g \ | sed s%_DATADIR_%$(DATADIR)%g \ | sed s%_OCSIGENUSER_%$(OCSIGENUSER)%g \ | sed s%_OCSIGENGROUP_%"$(OCSIGENGROUP)"%g \ | sed s%_COMMANDPIPE_%$(COMMANDPIPE)%g \ | sed s%_MIMEFILE_%$(CONFIGDIR)/mime.types%g \ | sed s%_METADIR_%$(LIBDIR)%g \ | sed s%_PROJECTNAME_%$(PROJECTNAME)%g \ | sed s%_LIBDIR_%$(LIBDIR)%g \ | sed s%_EXTDIR_%$(LIBDIR)/ocsigenserver/extensions%g \ | sed s%_CONFIGDIR_%$(CONFIGDIR)%g \ | sed s%_STATICPAGESDIR_%$(STATICPAGESDIR)%g \ | sed s%_EXTPACKAGENAME_%$(PROJECTNAME).ext%g \ > $@ ../local/etc/$(PROJECTNAME).conf: files/${PROJECTNAME}.conf.in ../Makefile.config Makefile mkdir -p ../local/etc ../local/var/log ../local/var/run cat $< \ | sed s%80\%8080\%g \ | sed s%_LOGDIR_%$(SRC)/local/var/log%g \ | sed s%_DATADIR_%$(SRC)/local/var/lib%g \ | sed s%\_OCSIGENUSER_\%%g \ | sed s%\_OCSIGENGROUP_\%%g \ | sed s%_COMMANDPIPE_%$(SRC)/local/var/run/${PROJECTNAME}_command%g \ | sed s%_MIMEFILE_%$(SRC)/src/files/mime.types%g \ | sed s%_METADIR_%${LIBDIR}\"/\>\\\ --\>%\%g \ | sed s%\<\!--\ \\ --\>%\%g \ | sed s%store\ dir=\"$(SRC)/var/lib\"%store\ dir=\"$(SRC)/local/var/lib/ocsipersist\"%g \ > $@ ### Install ### include Makefile.filelist VERSION := $(shell head -n 1 ../VERSION) install: INSTALL_BIN=$(BIN) $(NATBIN) install: INSTALL_IMPL=$(IMPL) $(NATIMPL) install: INSTALL_PLUGINS_IMPL=$(PLUGINS_IMPL) $(PLUGINS_NATIMPL) install: INSTALL_PLUGINS_BIN=$(PLUGINS_BIN) $(PLUGINS_NATBIN) install: raw_install install.byte: INSTALL_BIN=$(BIN) install.byte: INSTALL_IMPL=$(IMPL) install.byte: INSTALL_PLUGINS_IMPL=$(PLUGINS_IMPL) install.byte: INSTALL_PLUGINS_BIN=$(PLUGINS_BIN) install.byte: raw_install install.opt: INSTALL_BIN=$(NATBIN) install.opt: INSTALL_IMPL=$(NATIMPL) install.opt: INSTALL_PLUGINS_IMPL=$(PLUGINS_NATIMPL) install.opt: INSTALL_PLUGINS_BIN=$(PLUGINS_NATBIN) install.opt: raw_install raw_install: # Server binaries $(INSTALL) -m 755 -d $(TEMPROOT)$(BINDIR) $(INSTALL) -m 755 $(INSTALL_BIN) $(TEMPROOT)$(BINDIR)/ # Server libraries $(INSTALL) -m 755 -d $(TEMPROOT)$(LIBDIR) $(OCAMLFIND) install $(PROJECTNAME) \ -destdir $(TEMPROOT)$(LIBDIR) \ -patch-version ${VERSION} \ ${INTF} ${INTF_CMX} $(INSTALL_IMPL) $(DOC) files/META # Extensions $(INSTALL) -m 755 -d $(TEMPROOT)${LIBDIR}/${PROJECTNAME}/extensions/ $(INSTALL) -m 644 \ $(PLUGINS_INTF) $(INSTALL_PLUGINS_IMPL) $(PLUGINS_DOC) \ $(TEMPROOT)${LIBDIR}/${PROJECTNAME}/extensions/ [ -z "$(strip ${INSTALL_PLUGINS_BIN})" ] || \ $(INSTALL) -m 755 $(INSTALL_PLUGINS_BIN) \ $(TEMPROOT)${LIBDIR}/${PROJECTNAME}/extensions/ uninstall: # Extensions -rm -r $(TEMPROOT)${LIBDIR}/${PROJECTNAME}/extensions/ # Server libraries -$(OCAMLFIND) remove ${PROJECTNAME} -destdir $(TEMPROOT)${LIBDIR} # Server binaries -rm -r $(addprefix $(TEMPROOT)$(BINDIR)/,$(notdir ${BIN} ${NATBIN})) -rmdir --ignore-fail-on-non-empty $(TEMPROOT)$(BINDIR) reinstall: uninstall install ### clean: clean.local ${MAKE} -C baselib clean ${MAKE} -C http clean ${MAKE} -C server clean ${MAKE} -C extensions clean clean.local: -rm -f ../${PROJECTNAME}.conf.sample -rm -f ../${PROJECTNAME}.conf.opt.sample -rm -f ../${PROJECTNAME}.conf.local -rm -f ../${PROJECTNAME}.conf.opt.local -rm -f files/META files/META.${PROJECTNAME} distclean: clean.local ${MAKE} -C baselib distclean ${MAKE} -C http distclean ${MAKE} -C server distclean ${MAKE} -C extensions distclean -rm -f *~ \#* .\#* -cd files; rm -f *~ \#* .\#* -rm -fr ../local/var/log/*.log -rm -fr ../local/var/lib/ocsidb -rm -fr ../local/etc/${PROJECTNAME}.conf -rm -fr ../local/etc/${PROJECTNAME}.conf.opt depend: ${MAKE} -C baselib depend ${MAKE} -C http depend ${MAKE} -C server depend ${MAKE} -C extensions depend ocsigenserver-2.16.0/src/Makefile.filelist000066400000000000000000000071221357715257700205440ustar00rootroot00000000000000BIN := server/${PROJECTNAME} NATBIN := server/${PROJECTNAME}.opt INTF_BASE := baselib/ocsigen_cache.cmi \ baselib/ocsigen_lib_base.cmi \ baselib/ocsigen_lib.cmi \ baselib/ocsigen_config.cmi \ baselib/ocsigen_messages.cmi \ baselib/ocsigen_stream.cmi \ baselib/ocsigen_loader.cmi \ baselib/polytables.cmi \ \ http/http_headers.cmi \ http/ocsigen_http_frame.cmi \ http/ocsigen_headers.cmi \ http/framepp.cmi \ http/ocsigen_http_com.cmi \ http/ocsigen_charset_mime.cmi \ http/ocsigen_senders.cmi \ http/ocsigen_cookies.cmi \ \ server/ocsigen_extensions.cmi \ server/ocsigen_parseconfig.cmi \ server/ocsigen_http_client.cmi \ server/ocsigen_local_files.cmi \ server/ocsigen_server.cmi \ server/ocsigen_request_info.cmi INTF := ${INTF_BASE} baselib/ocsigen_getcommandline.cmi IMPL := baselib/ocsigen_lib_base.cmo \ baselib/baselib.cma \ baselib/parsecommandline.cma \ baselib/donotparsecommandline.cma \ baselib/polytables.cmo \ \ http/ocsigen_cookies.cmo \ http/http.cma \ \ server/${PROJECTNAME}.cma \ server/server_main.cmo \ INTF_CMX := $(patsubst %.cmi,%.cmx,${INTF_BASE}) NATIMPL := $(patsubst %.cmo,%.cmx, $(filter %.cmo,${IMPL})) \ $(patsubst %.cmo,%.o , $(filter %.cmo,${IMPL})) \ $(patsubst %.cma,%.cmxa,$(filter %.cma,${IMPL})) \ $(patsubst %.cma,%.a, $(filter %.cma,${IMPL})) \ ifeq "$(NATDYNLINK)" "YES" NATIMPL += baselib/parsecommandline.cmxs \ baselib/donotparsecommandline.cmxs \ baselib/polytables.cmxs endif PLUGINS_BIN := PLUGINS_INTF := extensions/ocsigen_comet.cmi \ extensions/accesscontrol.cmi \ extensions/authbasic.cmi \ extensions/ocsipersist.cmi \ PLUGINS_IMPL := extensions/staticmod.cmo \ extensions/cgimod.cmo \ extensions/redirectmod.cmo \ extensions/revproxy.cmo \ extensions/extensiontemplate.cmo \ extensions/accesscontrol.cmo \ extensions/userconf.cmo \ extensions/outputfilter.cmo \ extensions/authbasic.cmo \ extensions/rewritemod.cmo \ extensions/extendconfiguration.cmo \ extensions/ocsigen_comet.cmo \ extensions/cors.cmo \ ifeq "$(CAMLZIP)" "YES" PLUGINS_IMPL += extensions/deflatemod.cmo endif ifeq "$(OCSIPERSISTSQLITE)" "YES" PLUGINS_IMPL += extensions/ocsipersist-sqlite.cma endif ifeq "$(OCSIPERSISTPGSQL)" "YES" PLUGINS_IMPL += extensions/ocsipersist-pgsql.cma endif ifeq "$(OCSIPERSISTDBM)" "YES" PLUGINS_IMPL += extensions/ocsipersist-dbm.cma PLUGINS_BIN += extensions/ocsipersist-dbm/ocsidbm PLUGINS_NATBIN := extensions/ocsipersist-dbm/ocsidbm.opt endif PLUGINS_NATIMPL := $(patsubst %.cmo,%.cmx, $(filter %.cmo,${PLUGINS_IMPL})) \ $(patsubst %.cmo,%.o , $(filter %.cmo,${PLUGINS_IMPL})) \ $(patsubst %.cma,%.cmxa,$(filter %.cma,${PLUGINS_IMPL})) \ $(patsubst %.cma,%.a, $(filter %.cma,${PLUGINS_IMPL})) \ ifeq "$(NATDYNLINK)" "YES" PLUGINS_NATIMPL += $(patsubst %.cmo,%.cmxs, $(filter %.cmo,${PLUGINS_IMPL})) \ $(patsubst %.cma,%.cmxs, $(filter %.cma,${PLUGINS_IMPL})) endif DOC := ${INTF:.cmi=.mli} PLUGINS_DOC := ${PLUGINS_INTF:.cmi=.mli} ocsigenserver-2.16.0/src/baselib/000077500000000000000000000000001357715257700166715ustar00rootroot00000000000000ocsigenserver-2.16.0/src/baselib/.depend000066400000000000000000000027121357715257700201330ustar00rootroot00000000000000dynlink_wrapper.cmo : dynlink_wrapper.cmx : dynlink_wrapper.natdynlink.cmo : dynlink_wrapper.natdynlink.cmx : dynlink_wrapper.nonatdynlink.cmo : dynlink_wrapper.nonatdynlink.cmx : ocsigen_cache.cmo : ocsigen_cache.cmi ocsigen_cache.cmx : ocsigen_cache.cmi ocsigen_cache.cmi : ocsigen_commandline.cmo : ocsigen_getcommandline.cmi ocsigen_config.cmi ocsigen_commandline.cmx : ocsigen_getcommandline.cmi ocsigen_config.cmx ocsigen_config.cmo : ocsigen_lib.cmi ocsigen_config.cmi ocsigen_config.cmx : ocsigen_lib.cmx ocsigen_config.cmi ocsigen_config.cmi : ocsigen_lib.cmi ocsigen_getcommandline.cmi : ocsigen_lib.cmo : ocsigen_lib_base.cmi ocsigen_lib.cmi ocsigen_lib.cmx : ocsigen_lib_base.cmx ocsigen_lib.cmi ocsigen_lib_base.cmo : ocsigen_lib_base.cmi ocsigen_lib_base.cmx : ocsigen_lib_base.cmi ocsigen_loader.cmo : ocsigen_lib.cmi ocsigen_config.cmi dynlink_wrapper.cmo \ ocsigen_loader.cmi ocsigen_loader.cmx : ocsigen_lib.cmx ocsigen_config.cmx dynlink_wrapper.cmx \ ocsigen_loader.cmi ocsigen_loader.cmi : ocsigen_messages.cmo : ocsigen_config.cmi ocsigen_messages.cmi ocsigen_messages.cmx : ocsigen_config.cmx ocsigen_messages.cmi ocsigen_messages.cmi : ocsigen_stream.cmo : ocsigen_lib.cmi ocsigen_config.cmi ocsigen_stream.cmi ocsigen_stream.cmx : ocsigen_lib.cmx ocsigen_config.cmx ocsigen_stream.cmi ocsigen_stream.cmi : polytables.cmo : polytables.cmi polytables.cmx : polytables.cmi polytables.cmi : ocsigen_lib.cmi : ocsigen_lib_base.cmi ocsigen_lib_base.cmi : ocsigenserver-2.16.0/src/baselib/Makefile000066400000000000000000000076701357715257700203430ustar00rootroot00000000000000include ../../Makefile.config PACKAGE := \ bytes \ lwt.unix \ lwt_log \ netstring \ netstring-pcre \ cryptokit \ findlib \ tyxml \ ${LWT_PREEMPTIVE_PACKAGE} \ ipaddr LIBS := ${addprefix -package ,${PACKAGE}} # -no-keep-locs is needed since OCaml 4.06. If we don't compile with # -no-keep-locs, the .cmi's of the different ocsigen_*commandline will # be incompatible with each other leading to a compilation failure # later. OCAMLC := $(OCAMLFIND) ocamlc -no-keep-locs ${BYTEDBG} ${THREAD} OCAMLOPT := $(OCAMLFIND) ocamlopt -no-keep-locs ${OPTDBG} ${THREAD} OCAMLDOC := $(OCAMLFIND) ocamldoc OCAMLDEP := $(OCAMLFIND) ocamldep all: byte opt ### Common files ### FILES := ocsigen_lib_base.ml \ ocsigen_lib.ml \ ocsigen_cache.ml \ ocsigen_config.ml \ ocsigen_commandline.ml \ ocsigen_messages.ml \ dynlink_wrapper.ml \ ocsigen_loader.ml \ ocsigen_stream.ml \ INTF_NOP4 := ocsigen_lib_base.mli ocsigen_lib.mli PREDEP := ocsigen_config.ml dynlink_wrapper.ml byte:: baselib.cma opt:: baselib.cmxa baselib.cma: ${OCAMLC} -a -o $@ $^ baselib.cmxa: $(FILES:.ml=.cmx) ${OCAMLOPT} -a -o $@ $^ baselib.cma: $(FILES:.ml=.cmo) ## Ocsigen_config ## ifeq "$(NATDYNLINK)" "YES" NATIVECODE_RUNTIME_DETECT=Dynlink.is_native else NATIVECODE_RUNTIME_DETECT=false endif VERSION := $(shell head -n 1 ../../VERSION) ocsigen_config.ml: ocsigen_config.ml.in ../../Makefile.config ../../Makefile.options ../../VERSION cat ocsigen_config.ml.in \ | sed s%0000000000000000%${VERSION}% \ | sed s%_WARNING_%"Warning: this file has been generated from ocsigen_config.ml.in - DO NOT MODIFY MANUALLY!"% \ | sed s%_LOGDIR_%$(LOGDIR)% \ | sed s%_DATADIR_%$(DATADIR)%g \ | sed s%_BINDIR_%$(BINDIR)%g \ | sed s%_EXTDIR_%$(LIBDIR)/${PROJECTNAME}/extensions%g \ | sed s%_STATICPAGESDIR_%$(STATICPAGESDIR)% \ | sed s%_UP_%$(UPLOADDIR)%g \ | sed s%_OCSIGENUSER_%$(OCSIGENUSER)%g \ | sed s%_OCSIGENGROUP_%"$(OCSIGENGROUP)"%g \ | sed s%_PROJECTNAME_%$(PROJECTNAME)%g \ | sed s%_COMMANDPIPE_%$(COMMANDPIPE)%g \ | sed s%_CONFIGDIR_%$(CONFIGDIR)% \ | sed s%_ISNATIVE_%$(NATIVECODE_RUNTIME_DETECT)%g \ | sed "s%_DEPS_%$(INITPACKAGE)%g" \ > ocsigen_config.ml ## Dynlink_wrapper ## ifeq "$(NATDYNLINK)" "YES" dynlink_wrapper.ml: ln -s dynlink_wrapper.natdynlink.ml $@ dynlink_wrapper.cmo dynlink_wrapper.cmx: dynlink_wrapper.natdynlink.ml else dynlink_wrapper.ml: ln -s dynlink_wrapper.nonatdynlink.ml $@ dynlink_wrapper.cmo dynlink_wrapper.cmx: dynlink_wrapper.nonatdynlink.ml endif ### Command line ### byte:: parsecommandline.cma donotparsecommandline.cma opt:: parsecommandline.cmxa donotparsecommandline.cmxa ifeq "$(NATDYNLINK)" "YES" opt:: parsecommandline.cmxs donotparsecommandline.cmxs endif parsecommandline.cma: commandline/ocsigen_getcommandline.cmo $(OCAMLC) -a -o $@ $^ donotparsecommandline.cma: nocommandline/ocsigen_getcommandline.cmo $(OCAMLC) -a -o $@ $^ parsecommandline.cmxa: commandline/ocsigen_getcommandline.cmx $(OCAMLOPT) -a -o $@ $^ donotparsecommandline.cmxa: nocommandline/ocsigen_getcommandline.cmx $(OCAMLOPT) -a -o $@ $^ ### Polytables ### byte:: polytables.cma opt:: polytables.cmxa ifeq "$(NATDYNLINK)" "YES" opt:: polytables.cmxs endif polytables.cma: polytables.cmo $(OCAMLC) -a -o $@ $< polytables.cmxa: polytables.cmx $(OCAMLOPT) -a -o $@ $< ########## %.cmi: %.mli $(OCAMLC) ${LIBS} -c $< ${INTF_NOP4:.mli=.cmi}: \ %.cmi: %.mli $(OCAMLC) ${LIBS} -c $< %.cmo: %.ml $(OCAMLC) ${LIBS} -c $< %.cmx: %.ml $(OCAMLOPT) ${LIBS} -c $< %.cmxs: %.cmxa $(OCAMLOPT) -shared -linkall -o $@ $< ## Clean up clean: -rm -f *.cm* *.o *.a *.annot -rm -f ${PREDEP} -cd commandline; rm -f *.cm* *.annot *.o -cd nocommandline; rm -f *.cm* *.annot *.o distclean: clean -rm -f *~ \#* .\#* ## Dependencies depend: ${PREDEP} $(OCAMLDEP) ${LIBS} $(filter-out ${INTF_NOP4},$(wildcard *.mli)) *.ml > .depend $(OCAMLDEP) ${LIBS} ${INTF_NOP4} >> .depend FORCE: -include .depend ocsigenserver-2.16.0/src/baselib/commandline/000077500000000000000000000000001357715257700211575ustar00rootroot00000000000000ocsigenserver-2.16.0/src/baselib/commandline/ocsigen_getcommandline.ml000066400000000000000000000000341357715257700262030ustar00rootroot00000000000000let commandline = Sys.argv ocsigenserver-2.16.0/src/baselib/commandline/ocsigen_getcommandline.mli000066400000000000000000000002111357715257700263510ustar00rootroot00000000000000(** Contains the command line that will be parsed by the server when Ocsigen_commandline is linked *) val commandline : string array ocsigenserver-2.16.0/src/baselib/dynlink_wrapper.natdynlink.ml000066400000000000000000000020301357715257700246000ustar00rootroot00000000000000(* Ocsigen * http://www.ocsigen.org * File dynlink_wrapper.ml * Copyright (C) 2008 Vincent Balat * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) open Dynlink exception Error = Dynlink.Error let loadfile = loadfile let error_message = error_message let allow_unsafe_modules = allow_unsafe_modules let prohibit = prohibit ocsigenserver-2.16.0/src/baselib/dynlink_wrapper.nonatdynlink.ml000066400000000000000000000021211357715257700251360ustar00rootroot00000000000000(* Ocsigen * http://www.ocsigen.org * File dynlink_wrapper.ml * Copyright (C) 2008 Vincent Balat * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) exception Error of string let message = "ocsigen compiled without native dynlink support" let loadfile _ = failwith message let error_message _ = failwith message let init _ = () let allow_unsafe_modules _ = () let prohibit _ = () ocsigenserver-2.16.0/src/baselib/nocommandline/000077500000000000000000000000001357715257700215145ustar00rootroot00000000000000ocsigenserver-2.16.0/src/baselib/nocommandline/ocsigen_getcommandline.ml000066400000000000000000000000521357715257700265400ustar00rootroot00000000000000let commandline = Array.sub Sys.argv 0 1 ocsigenserver-2.16.0/src/baselib/nocommandline/ocsigen_getcommandline.mli000066400000000000000000000002111357715257700267060ustar00rootroot00000000000000(** Contains the command line that will be parsed by the server when Ocsigen_commandline is linked *) val commandline : string array ocsigenserver-2.16.0/src/baselib/ocsigen_cache.ml000066400000000000000000000400261357715257700217770ustar00rootroot00000000000000(* Ocsigen * Copyright (C) 2009 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) (** Cache. @author Vincent Balat @author Raphaël Proust (adding timers) *) let (>>=) = Lwt.bind module Dlist : sig type 'a t type 'a node val create : ?timer:float -> int -> 'a t val add : 'a -> 'a t -> 'a option val newest : 'a t -> 'a node option val oldest : 'a t -> 'a node option (** Removes an element from its list. If the element is not in a list, it does nothing. If it is in a list, it calls the finaliser, then removes the element. If the finaliser fails with an exception, the element is removed and the exception is raised again. *) val remove : 'a node -> unit (** Removes the element from its list without finalising, then adds it as newest. *) val up : 'a node -> unit val size : 'a t -> int val maxsize : 'a t -> int (** returns the timer of the Dlist *) val get_timer : 'a t -> float option val value : 'a node -> 'a val list_of : 'a node -> 'a t option (** remove the n oldest values ; returns the list of removed values *) val remove_n_oldest : 'a t -> int -> 'a list (** fold over the elements from the cache starting from the newest to the oldest *) val fold : ('b -> 'a -> 'b) -> 'b -> 'a t -> 'b (** fold over the elements from the cache starting from the oldest to the newest *) val fold_back : ('b -> 'a -> 'b) -> 'b -> 'a t -> 'b (** lwt version of fold *) val lwt_fold : ('b -> 'a -> 'b Lwt.t) -> 'b -> 'a t -> 'b Lwt.t (** lwt version of fold_back *) val lwt_fold_back : ('b -> 'a -> 'b Lwt.t) -> 'b -> 'a t -> 'b Lwt.t (** Move a node from one dlist to another one, without finalizing. If one value is removed from the destination list (because its maximum size is reached), it is returned (after finalisation). *) val move : 'a node -> 'a t -> 'a option (** change the maximum size ; returns the list of removed values, if any. *) val set_maxsize : 'a t -> int -> 'a list (** record a function to be called automatically on a piece of data just before it disappears from the list (either by explicit removal or because the maximum size is exceeded) *) val add_finaliser_before : ('a node -> unit) -> 'a t -> unit (** replace all finalizers by a new one. Be very careful while using this. *) val set_finaliser_before : ('a node -> unit) -> 'a t -> unit (** returns the finalizers. *) val get_finaliser_before : 'a t -> ('a node -> unit) (** record a function to be called automatically on a piece of data just after it disappears from the list (either by explicit removal or because the maximum size is exceeded) *) val add_finaliser_after : ('a node -> unit) -> 'a t -> unit (** replace all finalizers by a new one. Be very careful while using this. *) val set_finaliser_after : ('a node -> unit) -> 'a t -> unit (** returns the finalizers. *) val get_finaliser_after : 'a t -> ('a node -> unit) end = struct type 'a node = { mutable value : 'a; mutable succ : 'a node option; (* the node added just after *) mutable prev : 'a node option; (* the node added just before *) mutable mylist : 'a t option; (* the list to which it belongs *) mutable collection : float option; (* the timestamp for removal *) } (* Doubly-linked list with maximum size. The field [oldest] is the first element that must be removed if the list becomes too long. *) and 'a t = {mutable newest : 'a node option (* None = empty *); mutable oldest : 'a node option; mutable size : int; mutable maxsize : int; mutable finaliser_before : 'a node -> unit; mutable finaliser_after : 'a node -> unit; (* *) time_bound : time_bound option; } and time_bound = { (* *) timer : float; mutable collector : unit Lwt.t option; } (* Checks (by BY): let compute_length c = let rec aux i = function | Some {prev=p} -> aux (i + 1) p | None -> i in aux 0 c.newest let correct_node n = (match n.succ with | None -> true | Some n' -> n'.prev == Some n) && (match n.prev with | None -> true | Some n' -> n'.succ == Some n) (* Check that a list is correct. To be completed 1. by adding a check on nodes, 2. by verifying that newest can be reached from oldest and respectively *) let correct_list l = (l.size <= l.maxsize) && (compute_length l = l.size) && (match l.oldest with | None -> true | Some n -> n.prev = None) && (match l.newest with | None -> true | Some n -> n.succ = None) *) let create ?timer size = {newest = None; oldest = None; size = 0; maxsize = size; finaliser_before = (fun _ -> ()); finaliser_after = (fun _ -> ()); time_bound = (match timer with | None -> None | Some t -> Some {timer = t; collector = None;} ); } (* Remove an element from its list - don't finalise *) let remove' node l = (* assertion (node.mylist = Some l' with l' == l); *) let oldest = match l.oldest with | Some n when node == n -> node.succ | _ -> l.oldest in let newest = match l.newest with | Some n when node == n -> node.prev | _ -> l.newest in (match node.succ with | None -> () | Some s -> s.prev <- node.prev); (match node.prev with | None -> () | Some s -> s.succ <- node.succ); l.oldest <- oldest; l.newest <- newest; node.mylist <- None; l.size <- l.size - 1 (* Remove an element from its list - and finalise *) let remove node = match node.mylist with | None -> () | Some l as a -> (try l.finaliser_before node; assert (node.mylist == a); remove' node l with e -> remove' node l; raise e); l.finaliser_after node (* These next functions are for the collecting thread *) (* computing the timestamp for a node *) let collect_timer = function | {time_bound = Some {timer = t} } -> Some (t +. (Unix.gettimeofday ())) | {time_bound = None} -> None (* do collect. We first check if the node is still in the list and then if * its collection hasn't been rescheduled ! *) let collect dl n = match n.mylist with | Some l when l == dl -> begin match n.collection with | None -> assert false | Some c -> if c < Unix.gettimeofday () then remove n else () end | None | Some _ -> () let sleep_until = function (*/!\ COOPERATES*) | None -> assert false (* collection is set to None and collector to Some *) | Some t -> let duration = t -. Unix.gettimeofday () in if duration <= 0. then Lwt.return () else Lwt_unix.sleep duration (* a function to set the collector. *) let rec update_collector r = match r.time_bound with | None (* Not time bounded dlist *) | Some {collector = Some _} -> () (* Already collecting *) | Some ({collector = None} as t) -> match r.oldest with | None -> () (* Empty dlist *) | Some n -> t.collector <- Some (sleep_until n.collection >>= fun () -> collect r n; t.collector <- None; update_collector r; Lwt.return () ) (* Add a node that do not belong to any list to a list. The fields [succ] and [prev] are overridden. If the list is too long, the function returns the oldest value. The node added becomes the element [list] of the list *) (* do not finalise *) (* not exported *) let add_node node r = assert (node.mylist = None); node.mylist <- Some r; let res = match r.newest with | None -> node.succ <- None; node.prev <- None; r.newest <- Some node; r.oldest <- r.newest; r.size <- 1; None | Some rl -> node.succ <- None; node.prev <- r.newest; rl.succ <- Some node; r.newest <- Some node; r.size <- r.size + 1; if r.size > r.maxsize then r.oldest else None in node.collection <- collect_timer r; update_collector r; res let add x l = let create_one a = { value = a; succ = None; prev = None; mylist = None; collection = None; } in (* create_one not exported *) match add_node (create_one x) l with | None -> None | Some v -> remove v; Some v.value let newest a = a.newest let oldest a = a.oldest let size c = c.size let maxsize c = c.maxsize let get_timer c = match c.time_bound with | None -> None | Some tb -> Some tb.timer let value n = n.value let list_of n = n.mylist let up node = match node.mylist with | None -> () | Some l -> match l.newest with | Some n when node == n -> () | _ -> remove' node l; ignore (add_node node l) (* assertion: = None *) (* we must not change the physical address => use add_node *) let rec remove_n_oldest l n = (* remove the n oldest values (or less if the list is not long enough) ; returns the list of removed values *) if n <= 0 then [] else match l.oldest with | None -> [] | Some node -> let v = node.value in remove node; (* and finalise! *) v::remove_n_oldest l (n-1) (* Move a node from one dlist to another one, without finalizing *) let move node l = (match node.mylist with | None -> () | Some l -> remove' node l); match add_node node l with | None -> None | Some v -> remove v; Some v.value (* fold over the elements from the newest to the oldest *) let lwt_fold f accu {newest} = match newest with | None -> Lwt.return accu | Some newest -> let rec fold accu node = f accu node.value >>= fun accu -> match node.prev with | None -> Lwt.return accu | Some new_node when new_node == newest -> Lwt.return accu | Some new_node -> fold accu new_node in fold accu newest (* fold over the elements from the oldest to the newest *) let lwt_fold_back f accu {oldest} = match oldest with | None -> Lwt.return accu | Some oldest -> let rec fold accu node = f accu node.value >>= fun accu -> match node.succ with | None -> Lwt.return accu | Some new_node when new_node == oldest -> Lwt.return accu | Some new_node -> fold accu new_node in fold accu oldest (* fold over the elements from the newest to the oldest *) let fold f accu {newest} = match newest with | None -> accu | Some newest -> let rec fold accu node = let accu = f accu node.value in match node.prev with | None -> accu | Some new_node when new_node == newest -> accu | Some new_node -> fold accu new_node in fold accu newest (* fold over the elements from the oldest to the newest *) let fold_back f accu {oldest} = match oldest with | None -> accu | Some oldest -> let rec fold accu node = let accu = f accu node.value in match node.succ with | None -> accu | Some new_node when new_node == oldest -> accu | Some new_node -> fold accu new_node in fold accu oldest let set_maxsize l m = let size = l.size in if m >= size then (l.maxsize <- m; []) else if m <= 0 then failwith "Dlist.set_maxsize" else let ll = remove_n_oldest l (size - m) in l.maxsize <- m; ll let set_finaliser_before f l = l.finaliser_before <- f let get_finaliser_before l = l.finaliser_before let add_finaliser_before f l = let oldf = l.finaliser_before in l.finaliser_before <- (fun n -> oldf n; f n) let set_finaliser_after f l = l.finaliser_after <- f let get_finaliser_after l = l.finaliser_after let add_finaliser_after f l = let oldf = l.finaliser_after in l.finaliser_after <- (fun n -> oldf n; f n) end module Weak = Weak.Make(struct type t = unit -> unit let hash = Hashtbl.hash let equal = (==) end) let clear_all = Weak.create 17 module Make = functor (A: sig type key type value end) -> struct type data = A.key module H = Hashtbl.Make( struct type t = A.key let equal a a' = a = a' let hash = Hashtbl.hash end) type t = { mutable pointers : A.key Dlist.t; mutable table : (A.value * A.key Dlist.node) H.t; finder : A.key -> A.value Lwt.t; clear: unit -> unit (* This function clears the cache. It is put inside the cache structure so that it is garbage-collected only when the cache is no longer referenced, as the functions themselves are put inside a weak hash table *) } let mk f ?timer size = let (l, t) as a = (Dlist.create ?timer size, H.create size) in Dlist.set_finaliser_after (fun n -> H.remove t (Dlist.value n)) l; a let rec create f ?timer size = let rec cache = let (l, t) = mk f ?timer size in {pointers = l; table = t; finder = f; clear = f_clear; } and f_clear = (fun () -> clear cache) in Weak.add clear_all f_clear; cache and clear cache = let size = Dlist.maxsize cache.pointers in let timer = Dlist.get_timer cache.pointers in let (l, t) = mk cache.finder ?timer size in cache.pointers <- l; cache.table <- t (* not exported *) let poke cache node = assert (match Dlist.list_of node with | None -> false | Some l -> cache.pointers == l); Dlist.up node let find_in_cache cache k = let (v, node) = H.find cache.table k in poke cache node; v let remove cache k = try let (_v, node) = H.find cache.table k in assert (match Dlist.list_of node with | None -> false | Some l -> cache.pointers == l); Dlist.remove node with Not_found -> () (* Add in a cache, under the hypothesis that the value is not already in the cache *) let add_no_remove cache k v = ignore (Dlist.add k cache.pointers); match Dlist.newest cache.pointers with | None -> assert false | Some n -> H.add cache.table k (v, n) let add cache k v = remove cache k; add_no_remove cache k v let size c = Dlist.size c.pointers let find cache k = (try Lwt.return (find_in_cache cache k) with Not_found -> cache.finder k >>= fun r -> (try (* it may have been added during cache.finder *) ignore (find_in_cache cache k) with Not_found -> add_no_remove cache k r); Lwt.return r) class cache f ?timer size_c = let c = create f ?timer size_c in object method clear () = clear c method find = find c method add = add c method size = size c method find_in_cache = find_in_cache c method remove = remove c end end let clear_all_caches () = Weak.iter (fun f -> f ()) clear_all ocsigenserver-2.16.0/src/baselib/ocsigen_cache.mli000066400000000000000000000135341357715257700221540ustar00rootroot00000000000000(* Ocsigen * Copyright (C) 2009 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) (** Cache. Association tables (from any kind of database) that keep the most recently used values in memory. It is also possible to set a maximum lifetime for data in the cache. It is based on a structure of doubly linked lists with maximum size, that keeps only the mostly recently used values first, if you call the [up] function each time you use a value. (Insertion, remove and "up" in time 1). This structure is exported, so that it can be used in other cases. Not (preemptive) thread safe. @author Vincent Balat @author Raphaël Proust (adding timers) *) module Make : functor (A : sig type key type value end) -> sig (** [new cache finder ?timer size] creates a cache object where [finder] is the function responsible for retrieving non-cached data, [timer] (if any) is the life span of cached values (in seconds) (values in the cache are removed after their time is up) and [size] is the upper bound to the number of simultaneoulsy cached elements. Whenever a value is found (using [find] method), it's lifespan is set to [timer] (or not if the cache is not time bounded). If the value was already cached, it's lifespan is reset to [timer]. Using [timer] allow one to create a cache bounded both in space and time. It is to be noted that real lifespan of values is always slightly greater than [timer]. *) class cache : (A.key -> A.value Lwt.t) -> ?timer:float -> int -> object (** Find the cached value associated to the key, or binds this value in the cache using the function [finder] passed as argument to [create], and returns this value *) method find : A.key -> A.value Lwt.t (** Find the cached value associated to the key. Raises [Not_found] if the key is not present in the cache *) method find_in_cache : A.key -> A.value method remove : A.key -> unit method add : A.key -> A.value -> unit method clear : unit -> unit method size : int end end (** Clear the contents of all the existing caches *) val clear_all_caches : unit -> unit (** Doubly-linked lists with maximum number of entries, and (possibly) limited lifespan for entries. *) module Dlist : sig type 'a t type 'a node (** Create a dlist. It takes the maximum length of the list as parameter. The optional [?timer] parameter sets a maximum lifetime for elements (in seconds). *) val create : ?timer:float -> int -> 'a t (** Adds an element to the list, and possibly returns the element that has been removed if the maximum size was exceeded. *) val add : 'a -> 'a t -> 'a option (** Removes an element from its list. If it is not in a list, it does nothing. If it is in a list, it calls the finaliser, then removes the element. If the finaliser fails with an exception, the element is removed and the exception is raised again. *) val remove : 'a node -> unit (** Removes the element from its list without finalising, then adds it as newest. *) val up : 'a node -> unit val newest : 'a t -> 'a node option val oldest : 'a t -> 'a node option val size : 'a t -> int val maxsize : 'a t -> int val value : 'a node -> 'a (** returns the timer of the Dlist *) val get_timer : 'a t -> float option (** The list to which the node belongs *) val list_of : 'a node -> 'a t option (** remove the n oldest values (or less if the list is not long enough) ; returns the list of removed values *) val remove_n_oldest : 'a t -> int -> 'a list (** fold over the elements from the cache starting from the newest to the oldest *) val fold : ('b -> 'a -> 'b) -> 'b -> 'a t -> 'b (** fold over the elements from the cache starting from the oldest to the newest *) val fold_back : ('b -> 'a -> 'b) -> 'b -> 'a t -> 'b (** lwt version of fold *) val lwt_fold : ('b -> 'a -> 'b Lwt.t) -> 'b -> 'a t -> 'b Lwt.t (** lwt version of fold_back *) val lwt_fold_back : ('b -> 'a -> 'b Lwt.t) -> 'b -> 'a t -> 'b Lwt.t (** Move a node from one dlist to another one, without finalizing. If one value is removed from the destination list (because its maximum size is reached), it is returned (after finalisation). *) val move : 'a node -> 'a t -> 'a option (** change the maximum size ; returns the list of removed values, if any. *) val set_maxsize : 'a t -> int -> 'a list (** set a function to be called automatically on a piece of data just before it disappears from the list (either by explicit removal or because the maximum size is exceeded) *) val set_finaliser_before : ('a node -> unit) -> 'a t -> unit val get_finaliser_before : 'a t -> ('a node -> unit) (** set a function to be called automatically on a piece of data just after it disappears from the list (either by explicit removal or because the maximum size is exceeded) *) val set_finaliser_after : ('a node -> unit) -> 'a t -> unit val get_finaliser_after : 'a t -> ('a node -> unit) end ocsigenserver-2.16.0/src/baselib/ocsigen_commandline.ml000066400000000000000000000046161357715257700232270ustar00rootroot00000000000000(* Ocsigen * Copyright (C) 2005 Vincent Balat * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) open Ocsigen_config let cmdline : unit = try Arg.parse_argv Ocsigen_getcommandline.commandline [("-c", Arg.String set_configfile, "Alternate config file (default "^ Ocsigen_config.get_config_file() ^")"); ("--config", Arg.String set_configfile, "Alternate config file (default "^ Ocsigen_config.get_config_file() ^")"); ("-s", Arg.Unit set_silent, "silent mode (no logging to console; does not affect *.log files)"); ("--silent", Arg.Unit set_silent, "silent mode (no logging to console; does not affect *.log files)"); ("-p", Arg.String set_pidfile, "Specify a file where to write the PIDs of servers"); ("--pidfile", Arg.String set_pidfile, "Specify a file where to write the PIDs of servers"); ("-v", Arg.Unit set_verbose, "Verbose mode (notice)"); ("--verbose", Arg.Unit set_verbose, "Verbose mode (notice)"); ("-vv", Arg.Unit set_veryverbose, "Very verbose mode (info)"); ("--veryverbose", Arg.Unit set_veryverbose, "Very verbose mode (info)"); ("-vvv", Arg.Unit set_debug, "Extremely verbose mode (info)"); ("--debug", Arg.Unit set_debug, "Extremely verbose mode (debug)"); ("-d", Arg.Unit set_daemon, "Daemon mode (detach the process)"); ("--daemon", Arg.Unit set_daemon, "Daemon mode (detach the process) (This is the default when there are more than 1 process)"); ("--version", Arg.Unit display_version, "Display version number and exit") ] (fun _ -> ()) "usage: ocsigen [-c configfile]" with | Arg.Help s -> print_string s; exit 0 | Arg.Bad s -> prerr_string s; exit 1 ocsigenserver-2.16.0/src/baselib/ocsigen_config.ml.in000066400000000000000000000153341357715257700226120ustar00rootroot00000000000000(* Warning! ocsigen_config.ml is generated automatically from ocsigen_config.ml.in! Do not modify it manually *) (* Ocsigen * Copyright (C) 2005 Vincent Balat * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) open Ocsigen_lib exception Config_file_error of string (* General config *) let config_file = ref "_CONFIGDIR_/_PROJECTNAME_.conf" let verbose = ref false let silent = ref false let daemon = ref false let veryverbose = ref false let debug = ref false let version_number = (**)"0000000000000000"(**) let pidfile = ref None let server_name = "Ocsigen" let full_server_name = server_name^"/"^version_number let is_native = _ISNATIVE_ let native_ext = if is_native then ".opt" else "" let builtin_packages = List.fold_left (fun a s -> String.Set.add s a) String.Set.empty [_DEPS_] (* Server config: *) let (uploaddir : string option ref) = ref None let logdir = ref (Some ("_LOGDIR_")) let syslog_facility = ref None let default_user = ref "_OCSIGENUSER_" let default_group = ref "_OCSIGENGROUP_" let minthreads = ref 10 let maxthreads = ref 30 let max_number_of_connections = ref 350 let mimefile = ref "_CONFIGDIR_/mime.types" let silent_client_timeout = ref 30 (* without speaking during sending frame *) let silent_server_timeout = ref 30 (* without speaking during sending frame *) (*let keepalive_timeout = ref 30 let keepopen_timeout = ref 300 (* for ocsigen as client *) *) let netbuffersize = ref 8192 let filebuffersize = ref 8192 let maxrequestbodysize = ref (Some (Int64.of_int 8000000)) let maxrequestbodysizeinmemory = ref 8192 let maxuploadfilesize = ref (Some (Int64.of_int 2000000)) let defaultcharset = ref (None : string option) let datadir = ref "_DATADIR_" let bindir = ref "_BINDIR_" let extdir = ref "_EXTDIR_" let user = ref (Some !default_user) let group = ref (Some !default_group) let command_pipe = ref "_COMMANDPIPE_" let debugmode = ref false let disablepartialrequests = ref false let usedefaulthostname = ref false let respectpipeline = ref false let default_port = ref 80 let default_sslport = ref 443 let maxretries = ref 10 let shutdowntimeout = ref (Some 10.) let set_uploaddir u = uploaddir := u let set_logdir s = logdir := Some s let set_syslog_facility f = syslog_facility := f; logdir := None let set_configfile s = config_file := s let set_pidfile s = pidfile := Some s let set_mimefile s = mimefile := s let () = Lwt_log.add_rule "ocsigen:*" Lwt_log.Warning (* without --verbose *) let set_verbose () = verbose := true; Lwt_log.add_rule "ocsigen:*" Lwt_log.Notice let set_silent () = silent := true let set_daemon () = set_silent (); daemon := true let set_veryverbose () = verbose := true; veryverbose := true; Lwt_log.add_rule "ocsigen:*" Lwt_log.Info let set_debug () = verbose := true; veryverbose := true; debug := true; Lwt_log.add_rule "ocsigen:*" Lwt_log.Debug let set_minthreads i = minthreads := i let set_maxthreads i = maxthreads := i let set_max_number_of_threads_queued = Lwt_preemptive.set_max_number_of_threads_queued let set_max_number_of_connections i = max_number_of_connections := i let set_client_timeout i = silent_client_timeout := i let set_server_timeout i = silent_server_timeout := i (* let set_keepalive_timeout i = keepalive_timeout := i let set_keepopen_timeout i = keepopen_timeout := i *) let set_netbuffersize i = netbuffersize := i let set_filebuffersize i = filebuffersize := i let set_maxuploadfilesize i = maxuploadfilesize := i let set_maxrequestbodysize i = maxrequestbodysize := i let set_maxrequestbodysizeinmemory i = maxrequestbodysizeinmemory := i let set_default_charset o = defaultcharset := o let set_datadir o = datadir := o let set_bindir o = bindir := o let set_extdir o = extdir := o let set_user o = user := o let set_group o = group := o let set_command_pipe s = command_pipe := s let set_debugmode s = debugmode := s let set_disablepartialrequests s = disablepartialrequests := s let set_usedefaulthostname s = usedefaulthostname := s let set_respect_pipeline () = respectpipeline := true let set_default_port p = default_port := p let set_default_sslport p = default_sslport := p let set_maxretries i = maxretries := i let set_shutdown_timeout s = shutdowntimeout := s let get_uploaddir () = !uploaddir let get_logdir () = match !logdir with | Some s -> s | None -> raise ( Config_file_error ("Log directory requested, but not set"));; let get_syslog_facility () = !syslog_facility let get_config_file () = !config_file let get_pidfile () = !pidfile let get_mimefile () = !mimefile let get_verbose () = !verbose let get_silent () = !silent let get_daemon () = !daemon let get_veryverbose () = !veryverbose let get_debug () = !debug let get_default_user () = !default_user let get_default_group () = !default_group let get_minthreads () = !minthreads let get_maxthreads () = !maxthreads let get_max_number_of_threads_queued = Lwt_preemptive.get_max_number_of_threads_queued let get_max_number_of_connections () = !max_number_of_connections let get_client_timeout () = !silent_client_timeout let get_server_timeout () = !silent_server_timeout (*let get_keepalive_timeout () = !keepalive_timeout let get_keepopen_timeout () = !keepopen_timeout *) let get_netbuffersize () = !netbuffersize let get_filebuffersize () = !filebuffersize let get_maxuploadfilesize () = !maxuploadfilesize let get_maxrequestbodysize () = !maxrequestbodysize let get_maxrequestbodysizeinmemory () = !maxrequestbodysizeinmemory let get_default_charset () = !defaultcharset let get_datadir () = !datadir let get_bindir () = !bindir let get_extdir () = !extdir let get_user () = !user let get_group () = !group let get_command_pipe () = !command_pipe let get_debugmode () = !debugmode let get_disablepartialrequests () = !disablepartialrequests let get_usedefaulthostname () = !usedefaulthostname let get_respect_pipeline () = !respectpipeline let get_default_port () = !default_port let get_default_sslport () = !default_sslport let get_maxretries () = !maxretries let get_shutdown_timeout () = !shutdowntimeout let display_version () = print_string version_number; print_newline (); exit 0 let init_preempt = Lwt_preemptive.init ocsigenserver-2.16.0/src/baselib/ocsigen_config.mli000066400000000000000000000107211357715257700223510ustar00rootroot00000000000000(* Ocsigen * Copyright (C) 2005 Vincent Balat * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) (** Configuring Ocsigen server *) open Ocsigen_lib exception Config_file_error of string val server_name : string val full_server_name : string val version_number : string val is_native : bool val native_ext : string val builtin_packages : String.Set.t val set_logdir : string -> unit val set_syslog_facility: Lwt_log.syslog_facility option -> unit val set_configfile : string -> unit val set_pidfile : string -> unit val set_mimefile : string -> unit val set_verbose : unit -> unit val set_silent : unit -> unit val set_daemon : unit -> unit val set_veryverbose : unit -> unit val set_debug : unit -> unit val set_minthreads : int -> unit val set_maxthreads : int -> unit val set_max_number_of_threads_queued : int -> unit val set_max_number_of_connections : int -> unit val set_client_timeout : int -> unit val set_server_timeout : int -> unit (* val set_keepalive_timeout : int -> unit val set_keepopen_timeout : int -> unit *) val set_netbuffersize : int -> unit val set_filebuffersize : int -> unit val set_maxrequestbodysize : int64 option -> unit val set_maxrequestbodysizeinmemory : int -> unit val set_default_charset : string option -> unit val set_datadir : string -> unit val set_bindir : string -> unit val set_extdir : string -> unit val set_user : string option -> unit val set_group : string option -> unit val set_command_pipe : string -> unit val set_debugmode : bool -> unit val set_disablepartialrequests : bool -> unit val set_usedefaulthostname : bool -> unit val set_respect_pipeline : unit -> unit val set_default_port : int -> unit val set_default_sslport : int -> unit val set_maxretries : int -> unit val set_shutdown_timeout : float option -> unit val get_logdir : unit -> string val get_syslog_facility: unit -> Lwt_log.syslog_facility option val get_config_file : unit -> string val get_pidfile : unit -> string option val get_mimefile : unit -> string val get_verbose : unit -> bool val get_silent : unit -> bool val get_daemon : unit -> bool val get_veryverbose : unit -> bool val get_debug : unit -> bool val get_default_user : unit -> string val get_default_group : unit -> string val get_minthreads : unit -> int val get_maxthreads : unit -> int val get_max_number_of_threads_queued : unit -> int val get_max_number_of_connections : unit -> int val get_client_timeout : unit -> int val get_server_timeout : unit -> int (*val get_keepalive_timeout : unit -> int val get_keepopen_timeout : unit -> int*) val get_netbuffersize : unit -> int val get_filebuffersize : unit -> int val get_maxrequestbodysize : unit -> int64 option val get_maxrequestbodysizeinmemory : unit -> int val get_default_charset : unit -> string option val get_datadir : unit -> string val get_bindir : unit -> string val get_extdir : unit -> string val get_user : unit -> string option val get_group : unit -> string option val get_command_pipe : unit -> string val get_debugmode : unit -> bool val get_disablepartialrequests : unit -> bool val get_usedefaulthostname : unit -> bool val get_respect_pipeline : unit -> bool val get_default_port : unit -> int val get_default_sslport : unit -> int val get_maxretries : unit -> int val get_shutdown_timeout : unit -> float option val display_version : unit -> 'a val init_preempt : int -> int -> (string -> unit) -> unit (**/**) (* Global setting for upload directory. This can be overwritten on a per-site basis. Thus, use only the value inside the [ri.request_config] field of a request (which can be changed by the extension [Extendconfiguration]) *) val set_uploaddir : string option -> unit val get_uploaddir : unit -> string option (* Same thing for upload size *) val set_maxuploadfilesize : int64 option -> unit val get_maxuploadfilesize : unit -> int64 option ocsigenserver-2.16.0/src/baselib/ocsigen_getcommandline.mli000066400000000000000000000002111357715257700240630ustar00rootroot00000000000000(** Contains the command line that will be parsed by the server when Ocsigen_commandline is linked *) val commandline : string array ocsigenserver-2.16.0/src/baselib/ocsigen_lib.ml000066400000000000000000000246321357715257700215070ustar00rootroot00000000000000(* Ocsigen * Copyright (C) 2005 Vincent Balat * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) include Ocsigen_lib_base module String = String_base (*****************************************************************************) module Ip_address = struct exception No_such_host let get_inet_addr ?(v6=false) host = let rec aux = function | [] -> Lwt.fail No_such_host | {Unix.ai_addr=Unix.ADDR_INET (inet_addr, _)}::_ -> Lwt.return inet_addr | _::l -> aux l in let options = [if v6 then Lwt_unix.AI_FAMILY Lwt_unix.PF_INET6 else Lwt_unix.AI_FAMILY Lwt_unix.PF_INET] in Lwt.bind (Lwt_unix.getaddrinfo host "" options) aux end (*****************************************************************************) module Filename = struct include Filename let basename f = let n = String.length f in let i = try String.rindex f '\\' + 1 with Not_found -> 0 in let j = try String.rindex f '/' + 1 with Not_found -> 0 in let k = max i j in if k < n then String.sub f k (n-k) else "none" let extension_no_directory filename = try let pos = String.rindex filename '.' in String.sub filename (pos+1) ((String.length filename) - pos - 1) with Not_found -> raise Not_found let extension filename = try let pos = String.rindex filename '.' and slash = try String.rindex filename '/' with Not_found -> -1 in if pos > slash then String.sub filename (pos+1) ((String.length filename) - pos - 1) else (* Dot before a directory separator *) raise Not_found with Not_found -> (* No dot in filename *) raise Not_found end (*****************************************************************************) let make_cryptographic_safe_string = let rng = Cryptokit.Random.device_rng "/dev/urandom" in fun () -> let random_part = let random_number = Cryptokit.Random.string rng 20 in let to_b64 = Cryptokit.Base64.encode_compact () in Cryptokit.transform_string to_b64 random_number and sequential_part = (*VVV Use base 64 also here *) Printf.sprintf "%Lx" (Int64.bits_of_float (Unix.gettimeofday ())) in random_part ^ sequential_part (* The string is produced from the concatenation of two components: a 160-bit random sequence obtained from /dev/urandom, and a 64-bit sequential component derived from the system clock. The former is supposed to prevent session spoofing. The assumption is that given the high cryptographic quality of /dev/urandom, it is impossible for an attacker to deduce the sequence of random numbers produced. As for the latter component, it exists to prevent a theoretical (though infinitesimally unlikely) session ID collision if the server were to be restarted. *) module Url = struct include Url_base (* Taken from Neturl version 1.1.2 *) let problem_re1 = Netstring_pcre.regexp "[ <>\"{}|\\\\^\\[\\]`]" let fixup_url_string1 = Netstring_pcre.global_substitute problem_re1 (fun m s -> Printf.sprintf "%%%02x" (Char.code s.[Netstring_pcre.match_beginning m])) (* I add this fixup to handle %uxxxx sent by browsers. Translated to %xx%xx *) let problem_re2 = Netstring_pcre.regexp "\\%u(..)(..)" let fixup_url_string s = fixup_url_string1 (Netstring_pcre.global_substitute problem_re2 (fun m s -> String.concat "" ["%"; Netstring_pcre.matched_group m 1 s; "%"; Netstring_pcre.matched_group m 2 s] ) s) (*VVV This is in Netencoding but we have a problem with ~ (not encoded by browsers). Here is a patch that does not encode '~': *) module MyUrl = struct let percent_encode = let lengths = let l = Array.make 256 3 in String.iter (fun c -> l.(Char.code c) <- 1) (* Unreserved Characters (section 2.3 of RFC 3986) *) "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~"; l in fun s -> let l = String.length s in let l' = ref 0 in for i = 0 to l - 1 do l' := !l' + lengths.(Char.code s.[i]) done; if l = !l' then String.copy s else let s' = Bytes.create !l' in let j = ref 0 in let hex = "0123456789ABCDEF" in for i = 0 to l - 1 do let c = s.[i] in let n = Char.code s.[i] in let d = lengths.(n) in if d = 1 then Bytes.set s' !j c else begin Bytes.set s' !j '%'; Bytes.set s' (!j + 1) hex.[n lsr 4]; Bytes.set s' (!j + 2) hex.[n land 0xf] end; j := !j + d done; Bytes.unsafe_to_string s' let encode_plus = let lengths = let l = Array.make 256 3 in String.iter (fun c -> l.(Char.code c) <- 1) (* Unchanged characters + space (HTML spec) *) "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.* "; l in fun s -> let l = String.length s in let l' = ref 0 in for i = 0 to l - 1 do l' := !l' + lengths.(Char.code s.[i]) done; let s' = Bytes.create !l' in let j = ref 0 in let hex = "0123456789ABCDEF" in for i = 0 to l - 1 do let c = s.[i] in let n = Char.code s.[i] in let d = lengths.(n) in if d = 1 then Bytes.set s' !j (if c = ' ' then '+' else c) else begin Bytes.set s' !j '%'; Bytes.set s' (!j + 1) hex.[n lsr 4]; Bytes.set s' (!j + 2) hex.[n land 0xf] end; j := !j + d done; Bytes.unsafe_to_string s' let encode ?(plus = true) s = if plus then encode_plus s else percent_encode s end let encode = MyUrl.encode let decode ?plus a = Netencoding.Url.decode ?plus a let make_encoded_parameters params = String.concat "&" (List.map (fun (name, value) -> encode name ^ "=" ^ encode value) params) let string_of_url_path ~encode l = if encode then fixup_url_string (String.concat "/" (List.map (*Netencoding.Url.encode*) (MyUrl.encode ~plus:false) l)) (* ' ' are not encoded to '+' in paths *) else String.concat "/" l (* BYXXX : check illicit characters *) let parse = (* We do not accept http://login:pwd@host:port (should we?). *) let url_re = Netstring_pcre.regexp "^([Hh][Tt][Tt][Pp][Ss]?)://([0-9a-zA-Z.-]+|\\[[0-9A-Fa-f:.]+\\])(:([0-9]+))?/([^\\?]*)(\\?(.*))?$" in let short_url_re = Netstring_pcre.regexp "^/([^\\?]*)(\\?(.*))?$" in (* let url_relax_re = Netstring_pcre.regexp "^[Hh][Tt][Tt][Pp][Ss]?://[^/]+" in *) fun url -> let match_re = Netstring_pcre.string_match url_re url 0 in let (https, host, port, pathstring, query) = match match_re with | None -> (match Netstring_pcre.string_match short_url_re url 0 with | None -> raise Ocsigen_Bad_Request | Some m -> let path = fixup_url_string (Netstring_pcre.matched_group m 1 url) in let query = try Some (fixup_url_string (Netstring_pcre.matched_group m 3 url)) with Not_found -> None in (None, None, None, path, query)) | Some m -> let path = fixup_url_string (Netstring_pcre.matched_group m 5 url) in let query = try Some (fixup_url_string (Netstring_pcre.matched_group m 7 url)) with Not_found -> None in let https = try (match Netstring_pcre.matched_group m 1 url with | "http" -> Some false | "https" -> Some true | _ -> None) with Not_found -> None in let host = try Some (Netstring_pcre.matched_group m 2 url) with Not_found -> None in let port = try Some (int_of_string (Netstring_pcre.matched_group m 4 url)) with Not_found -> None in (https, host, port, path, query) in (* Note that the fragment (string after #) is not sent by browsers *) (*20110707 ' ' is encoded to '+' in queries, but not in paths. Warning: if we write the URL manually, we must encode ' ' to '+' manually (not done by the browser). --Vincent *) let get_params = lazy begin let params_string = match query with None -> "" | Some s -> s in try Netencoding.Url.dest_url_encoded_parameters params_string with Failure _ -> raise Ocsigen_Bad_Request end in let path = List.map (Netencoding.Url.decode ~plus:false) (Neturl.split_path pathstring) in let path = remove_dotdot path (* and remove "//" *) (* here we remove .. from paths, as it is dangerous. But in some very particular cases, we may want them? I prefer forbid that. *) in let uri_string = match query with | None -> pathstring | Some s -> String.concat "?" [pathstring; s] in (https, host, port, uri_string, path, query, get_params) let split_path = Neturl.split_path let prefix_and_path_of_t url = let (https, host, port, _, path, _, _) = parse url in let https_str = match https with | None -> "" | Some x -> if x then "https://" else "http://" in let host_str = match host with | None -> "" | Some x -> x in let port_str = match port with | None -> "" | Some x -> string_of_int x in (https_str ^ host_str ^ ":" ^ port_str, path) end ocsigenserver-2.16.0/src/baselib/ocsigen_lib.mli000066400000000000000000000063211357715257700216530ustar00rootroot00000000000000(* Ocsigen * Copyright (C) 2005 Vincent Balat * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) (** This module contains some auxiliaries for the Ocsigenserver. In contrast to {!Ocsigen_lib_base}, the function may also refer to libraries other than the standard library. *) include module type of Ocsigen_lib_base with type poly = Ocsigen_lib_base.poly and type yesnomaybe = Ocsigen_lib_base.yesnomaybe and type ('a, 'b) leftright = ('a, 'b) Ocsigen_lib_base.leftright and type 'a Clist.t = 'a Ocsigen_lib_base.Clist.t and type 'a Clist.node = 'a Ocsigen_lib_base.Clist.node (** Generate an unique and cryptographically safe random string. It is impossible to guess for other people and will never return twice the same value (with very good probabilities). *) val make_cryptographic_safe_string : unit -> string module String : module type of String_base module Ip_address : sig exception No_such_host val get_inet_addr : ?v6:bool -> string -> Unix.inet_addr Lwt.t end module Filename : sig include module type of Filename (* val basename : string -> string *) (* val extension : string -> string *) val extension_no_directory : string -> string end module Url : sig include module type of Url_base with type t = Url_base.t val fixup_url_string : t -> t val encode : ?plus:bool -> string -> string val decode : ?plus:bool -> string -> string val make_encoded_parameters : (string * string) list -> string val string_of_url_path : encode:bool -> path -> uri (** [parse url] returns a tuple containing information about [url] {ul {- If url contains scheme 'https'} {- host of url (ex: http://www.ocsigen.org/ -> www.ocsigen.org)} {- port of url} {- path as [string] without first '/'} {- path as [string list]} {- GET query of url} {- lazy value to decode GET query } } *) val parse : t -> bool option * string option * int option * string (** the path, as a string, without the first / *) * string list * string option * (string * string) list Lazy.t (** alias of (Ocamlnet) [Neturl.split_path] *) val split_path : string -> string list (** [prefix_and_path_of_t url] splits [url] in a couple [(prefix, path)] where [prefix] is ["http(s)://host:port"] and [path] is the path as [string list] Example: [prefix_and_path_of_t "http://ocsigen.org:80/tuto/manual"] returns [("http://ocsigen.org:80", ["tuto", "manual"])]. *) val prefix_and_path_of_t : string -> string * string list end ocsigenserver-2.16.0/src/baselib/ocsigen_lib_base.ml000066400000000000000000000261371357715257700225030ustar00rootroot00000000000000(* Ocsigen * Copyright (C) 2005-2008 Vincent Balat, Stéphane Glondu * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) exception Ocsigen_Internal_Error of string exception Input_is_too_large exception Ocsigen_Bad_Request exception Ocsigen_Request_too_long external id : 'a -> 'a = "%identity" let (>>=) = Lwt.bind let (>|=) = Lwt.(>|=) let (!!) = Lazy.force let (|>) x f = f x let (@@) f x = f x let comp f g x = f (g x) let curry f x y = f (x, y) let uncurry f (x, y) = f x y type poly external to_poly : 'a -> poly = "%identity" external from_poly : poly -> 'a = "%identity" module Tuple3 = struct let fst (a, _, _) = a let snd (_, a, _) = a let thd (_, _, a) = a end type yesnomaybe = Yes | No | Maybe type ('a, 'b) leftright = Left of 'a | Right of 'b let advert = "Page generated by OCaml with Ocsigen. See http://ocsigen.org/ and http://caml.inria.fr/ for information" (*****************************************************************************) module Option = struct type 'a t = 'a option let map f = function | Some x -> Some (f x) | None -> None let get f = function | Some x -> x | None -> f () let get' a = function | Some x -> x | None -> a let iter f = function | Some x -> f x | None -> () let return x = Some x let bind opt k = match opt with | Some x -> k x | None -> None let to_list = function | None -> [] | Some v -> [v] module Lwt = struct let map f = function | Some x -> f x >>= fun v -> Lwt.return (Some v) | None -> Lwt.return None let get f = function | Some x -> Lwt.return x | None -> f () let get' a = function | Some x -> Lwt.return x | None -> a let iter f = function | Some x -> f x | None -> Lwt.return () let bind opt k = match opt with | Some x -> k x | None -> Lwt.return None end end module List = struct include List let map_filter f l = let rec aux acc = function | [] -> acc | t::q -> match f t with | None -> aux acc q | Some r -> aux (r::acc) q in List.rev (aux [] l) let rec remove_first_if_any a = function | [] -> [] | b::l when a = b -> l | b::l -> b::(remove_first_if_any a l) let rec remove_first_if_any_q a = function | [] -> [] | b::l when a == b -> l | b::l -> b::(remove_first_if_any_q a l) let rec remove_first a = function | [] -> raise Not_found | b::l when a = b -> l | b::l -> b::(remove_first a l) let rec remove_first_q a = function | [] -> raise Not_found | b::l when a == b -> l | b::l -> b::(remove_first_q a l) let rec remove_all a = function | [] -> [] | b::l when a = b -> remove_all a l | b::l -> b::(remove_all a l) let rec remove_all_q a = function | [] -> [] | b::l when a == b -> remove_all_q a l | b::l -> b::(remove_all_q a l) let rec remove_all_assoc a = function | [] -> [] | (b, _)::l when a = b -> remove_all_assoc a l | b::l -> b::(remove_all_assoc a l) let rec remove_all_assoc_q a = function | [] -> [] | (b,_)::l when a == b -> remove_all_assoc_q a l | b::l -> b::(remove_all_assoc_q a l) let rec last = function | [] -> raise Not_found | [b] -> b | _::l -> last l let rec assoc_remove a = function | [] -> raise Not_found | (b, c)::l when a = b -> c, l | b::l -> let v, ll = assoc_remove a l in (v, b::ll) let rec is_prefix l1 l2 = match (l1, l2) with | [], _ -> true | a::ll1, b::ll2 when a=b -> is_prefix ll1 ll2 | _ -> false let rec chop n xs = if n <= 0 then xs else match xs with | [] -> [] | x :: xs -> chop (n-1) xs let rec split_at n xs = if n <= 0 then [], xs else match xs with | [] -> [], [] | x::xs -> let l,r = split_at (n-1) xs in x::l, r end (*****************************************************************************) (* circular lists *) module Clist : sig type 'a t type 'a node val make : 'a -> 'a node val create : unit -> 'a t val insert : 'a t -> 'a node -> unit val remove : 'a node -> unit val value : 'a node -> 'a val in_list : 'a node -> bool val is_empty : 'a t -> bool val iter : ('a -> unit) -> 'a t -> unit val fold_left : ('a -> 'b -> 'a) -> 'a -> 'b t -> 'a end = struct type 'a node = { content : 'a option; mutable prev : 'a node; mutable next : 'a node } type 'a t = 'a node let make' c = let rec x = { content = c; prev = x; next = x } in x let make c = make' (Some c) let create () = make' None let insert p x = let n = p.next in p.next <- x; x.prev <- p; x.next <- n; n.prev <- x let remove x = let p = x.prev in let n = x.next in p.next <- n; n.prev <- p; x.next <- x; x.prev <- x let in_list x = x.next != x let is_empty set = set.next == set let value c = match c.content with | None -> failwith "Clist.value" | Some c -> c let rec iter f (node : 'a t) = match node.next.content with | Some c -> f c; iter f node.next | None -> () let rec fold_left f a (node : 'a t) = match node.next.content with | Some c -> fold_left f (f a c) node.next | None -> a end (*****************************************************************************) module Int = struct module Table = Map.Make(struct type t = int let compare = compare end) end (*****************************************************************************) module String_base = struct include String (* Returns a copy of the string from beg to endd, removing spaces at the beginning and at the end *) let remove_spaces s beg endd = let rec find_not_space s i step = if (i > endd) || (beg > i) then i else if s.[i] = ' ' then find_not_space s (i+step) step else i in let first = find_not_space s beg 1 in let last = find_not_space s endd (-1) in if last >= first then String.sub s first (1+ last - first) else "" (* Cut a string to the next separator *) let basic_sep char s = try let seppos = String.index s char in ((String.sub s 0 seppos), (String.sub s (seppos+1) ((String.length s) - seppos - 1))) with Invalid_argument _ -> raise Not_found (* Cut a string to the next separator, removing spaces. Raises Not_found if the separator cannot be found. *) let sep char s = let len = String.length s in let seppos = String.index s char in ((remove_spaces s 0 (seppos-1)), (remove_spaces s (seppos+1) (len-1))) (* splits a string, for ex "azert, sdfmlskdf, dfdsfs" *) let rec split ?(multisep=false) char s = let longueur = String.length s in let rec aux deb = if deb >= longueur then [] else try let firstsep = String.index_from s deb char in if multisep && firstsep = deb then aux (deb + 1) else (remove_spaces s deb (firstsep-1)):: (aux (firstsep+1)) with Not_found -> [remove_spaces s deb (longueur-1)] in aux 0 let may_append s1 ~sep = function | "" -> s1 | s2 -> s1^sep^s2 let may_concat s1 ~sep s2 = match s1, s2 with | _, "" -> s1 | "", _ -> s2 | _ -> String.concat sep [s1;s2] (* returns the index of the first difference between s1 and s2, starting from n and ending at last. returns (last + 1) if no difference is found. *) let rec first_diff s1 s2 n last = try if s1.[n] = s2.[n] then if n = last then last+1 else first_diff s1 s2 (n+1) last else n with Invalid_argument _ -> n module Table = Map.Make(String) module Set = Set.Make(String) module Map = Map.Make(String) end (*****************************************************************************) module Url_base = struct type t = string type uri = string type path = string list let make_absolute_url ~https ~host ~port uri = (if https then "https://" else "http://" )^ host^ (if (port = 80 && not https) || (https && port = 443) then "" else ":"^string_of_int port)^ uri let remove_dotdot = (* removes "../" *) let rec aux = function | [] -> [] | [""] as l -> l (* | ""::l -> aux l *) (* we do not remove "//" any more, because of optional suffixes in Eliom *) | ".."::l -> aux l | a::l -> a::(aux l) in function | [] -> [] | ""::l -> ""::(aux l) | l -> aux l let remove_end_slash s = try if s.[(String.length s) - 1] = '/' then String.sub s 0 ((String.length s) - 1) else s with Invalid_argument _ -> s let remove_internal_slash u = let rec aux = function | [] -> [] | [a] -> [a] | ""::l -> aux l | a::l -> a::(aux l) in match u with | [] -> [] | a::l -> a::(aux l) let change_empty_list = function | [] -> [""] (* It is not possible to register an empty URL *) | l -> l let rec add_end_slash_if_missing = function | [] -> [""] | [""] as a -> a | a::l -> a::(add_end_slash_if_missing l) let rec remove_slash_at_end = function | [] | [""] -> [] | a::l -> a::(remove_slash_at_end l) let remove_slash_at_beginning = function | [] -> [] | [""] -> [""] | ""::l -> l | l -> l let rec recursively_remove_slash_at_beginning = function | [] -> [] | [""] -> [""] | ""::l -> recursively_remove_slash_at_beginning l | l -> l let rec is_prefix_skip_end_slash l1 l2 = match (l1, l2) with | [""], _ | [], _ -> true | a::ll1, b::ll2 when a=b -> is_prefix_skip_end_slash ll1 ll2 | _ -> false let split_fragment s = try let pos = String.index s '#' in String.sub s 0 pos, Some (String.sub s (pos+1) (String.length s - 1 - pos)) with Not_found -> s, None end (************************************************************************) module Printexc = struct include Printexc let exc_printer = ref (fun _ e -> Printexc.to_string e) let rec to_string e = !exc_printer to_string e let register_exn_printer p = let printer = let old = !exc_printer in (fun f_rec s -> try p f_rec s with e -> old f_rec s) in exc_printer := printer end (*****************************************************************************) let debug = prerr_endline ocsigenserver-2.16.0/src/baselib/ocsigen_lib_base.mli000066400000000000000000000143421357715257700226470ustar00rootroot00000000000000(* Ocsigen * Copyright (C) 2005 Vincent Balat * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) (** This module just contains only extensions of the standard library and very basic Ocsigen values and exceptions. Cf. {!Ocsigen_lib} for functionality which depends on specific external libraries. *) exception Ocsigen_Internal_Error of string exception Input_is_too_large exception Ocsigen_Bad_Request exception Ocsigen_Request_too_long val (>>=) : 'a Lwt.t -> ('a -> 'b Lwt.t) -> 'b Lwt.t val (>|=) : 'a Lwt.t -> ('a -> 'b) -> 'b Lwt.t val (!!) : 'a Lazy.t -> 'a val (|>) : 'a -> ('a -> 'b) -> 'b val (@@) : ('a -> 'b) -> 'a -> 'b external id : 'a -> 'a = "%identity" val comp : ('a -> 'b) -> ('c -> 'a) -> 'c -> 'b val curry : ('a * 'b -> 'c) -> 'a -> 'b -> 'c val uncurry : ('a -> 'b -> 'c) -> ('a * 'b) -> 'c module Tuple3 : sig val fst : 'a * 'b * 'c -> 'a val snd : 'a * 'b * 'c -> 'b val thd : 'a * 'b * 'c -> 'c end type poly val to_poly: 'a -> poly val from_poly: poly -> 'a type yesnomaybe = Yes | No | Maybe type ('a, 'b) leftright = Left of 'a | Right of 'b val advert: string (** Module Option to compute type ['a option] *) module Option : sig type 'a t = 'a option val map : ('a -> 'b) -> 'a t -> 'b t val get : (unit -> 'a) -> 'a t -> 'a val get' : 'a -> 'a t -> 'a val iter : ('a -> unit) -> 'a t -> unit val return : 'a -> 'a t val bind : 'a t -> ('a -> 'b t) -> 'b t val to_list : 'a t -> 'a list module Lwt : sig val map : ('a -> 'b Lwt.t) -> 'a t -> 'b t Lwt.t val get : (unit -> 'a Lwt.t) -> 'a t -> 'a Lwt.t val get' : 'a Lwt.t -> 'a t -> 'a Lwt.t val iter : ('a -> unit Lwt.t) -> 'a t -> unit Lwt.t val bind : 'a t -> ('a -> 'b t Lwt.t) -> 'b t Lwt.t end end (** Improvement of module List *) module List : sig include module type of List val map_filter : ('a -> 'b option) -> 'a list -> 'b list val last : 'a list -> 'a val assoc_remove : 'a -> ('a * 'b) list -> 'b * ('a * 'b) list val remove_first_if_any : 'a -> 'a list -> 'a list val remove_first_if_any_q : 'a -> 'a list -> 'a list val remove_first : 'a -> 'a list -> 'a list val remove_first_q : 'a -> 'a list -> 'a list val remove_all : 'a -> 'a list -> 'a list val remove_all_q : 'a -> 'a list -> 'a list val remove_all_assoc : 'a -> ('a * 'b) list -> ('a * 'b) list val remove_all_assoc_q : 'a -> ('a * 'b) list -> ('a * 'b) list val is_prefix : 'a list -> 'a list -> bool val chop : int -> 'a list -> 'a list val split_at : int -> 'a list -> 'a list * 'a list end (** Circular lists *) module Clist : sig type 'a t type 'a node val make : 'a -> 'a node val create : unit -> 'a t val insert : 'a t -> 'a node -> unit val remove : 'a node -> unit val value : 'a node -> 'a val in_list : 'a node -> bool val is_empty : 'a t -> bool (** Infinite iteration on circular lists *) val iter : ('a -> unit) -> 'a t -> unit (** Infinite fold on circular lists (use with care!) *) val fold_left : ('a -> 'b -> 'a) -> 'a -> 'b t -> 'a end module Int : sig module Table : Map.S with type key = int end (** Improvement of module String *) module String_base : sig include module type of String (** [remove_spaces s beg endd] returns a copy of the string from beg to endd, removing spaces at the beginning and at the end *) val remove_spaces : string -> int -> int -> string (** Cuts a string to the next separator *) val basic_sep : char -> string -> string * string (** Cuts a string to the next separator, removing spaces. Raises [Not_found] if the separator cannot be found. *) val sep : char -> string -> string * string (** Splits a string for words with separator, removing spaces. For ex "azert, sdfmlskdf, dfdsfs". *) val split : ?multisep:bool -> char -> string -> string list val may_append : string -> sep:string -> string -> string (* WAS add_to_string *) val may_concat : string -> sep:string -> string -> string (* WAS concat_strings *) (** [first_diff s1 s2 n last] returns the index of the first difference between s1 and s2, starting from n and ending at last. returns (last + 1) if no difference is found. *) val first_diff : string -> string -> int -> int -> int module Table : Map.S with type key = string module Set : Set.S with type elt = string module Map : Map.S with type key = string end module Url_base : sig type t = string type uri = string (** [make_absolute_url https host port path] generates a new absolute url *) val make_absolute_url : https:bool -> host:string -> port:int -> uri -> t type path = string list (** [remove_dotdot path] cleans the path of [..] *) val remove_dotdot : path -> path (** [remove_end_slash str] removes last [/] *) val remove_end_slash : string -> string (** [remove_internal_slash path] cleans the path of empty string *) val remove_internal_slash : path -> path val change_empty_list : path -> path val add_end_slash_if_missing : path -> path val remove_slash_at_end : path -> path val remove_slash_at_beginning : path -> path (* val recursively_remove_slash_at_beginning : path -> path *) (** [is_prefix_skip_end_slash path1 path2] returns [true] if [path1] is the same as [path2] before a first slash *) val is_prefix_skip_end_slash : string list -> string list -> bool (** [split_fragment str] splits [str] at first '#' *) val split_fragment : string -> string * string option end module Printexc : sig include module type of Printexc val register_exn_printer : ((exn -> string) -> exn -> string) -> unit end val debug : string -> unit ocsigenserver-2.16.0/src/baselib/ocsigen_loader.ml000066400000000000000000000153131357715257700222030ustar00rootroot00000000000000(* Ocsigen * http://www.ocsigen.org * File ocsigen_loader.ml * Copyright (C) 2008 Stéphane Glondu * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) open Ocsigen_lib exception Dynlink_error of string * exn exception Findlib_error of string * exn let section = Lwt_log.Section.make "ocsigen:dynlink" (************************************************************************) (* Translate .cmo/.cma extensions to .cmxs in native mode, and .cmxs to .cmo (.cma if the file exists) in bytecode mode. *) let translate = if Ocsigen_config.is_native then fun filename -> if Filename.check_suffix filename ".cmo" || Filename.check_suffix filename ".cma" then (Filename.chop_extension filename) ^ ".cmxs" else filename else fun filename -> if Filename.check_suffix filename ".cmxs" then let filename = Filename.chop_extension filename in let cma = filename ^ ".cma" in if Sys.file_exists cma then cma else filename ^ ".cmo" else filename (************************************************************************) (* Loading files *) let isloaded, addloaded = let set = ref String.Set.empty in ((fun s -> String.Set.mem s !set), (fun s -> set := String.Set.add s !set)) module M = Map.Make(String) let init_functions = ref M.empty let get_init_on_load, set_init_on_load = let init_on_load = ref false in ((fun () -> !init_on_load), (fun b -> init_on_load := b)) let loadfile pre post force file = let file = translate file in try if force then begin pre (); Lwt_log.ign_info_f ~section "Loading %s (will be reloaded every times)" file; begin try Dynlink_wrapper.loadfile file; post () with e -> post (); raise e end end else if not (isloaded file) then begin pre (); Lwt_log.ign_info_f ~section "Loading extension %s" file; begin try Dynlink_wrapper.loadfile file; post () with e -> post (); raise e end; addloaded file; end else Lwt_log.ign_info_f ~section "Extension %s already loaded" file with | e -> raise (Dynlink_error (file, e)) let id () = () let loadfiles pre post force modules = let rec aux = function | [] -> () | [m] -> loadfile pre post force m | m::q -> loadfile id id false m; aux q in aux modules let set_module_init_function name f = init_functions := M.add name f !init_functions; (* print_endline ("Added init_function for " ^ name); *) (* print_endline ("get_init_on_load: " ^ string_of_bool (get_init_on_load ())); *) if get_init_on_load () then f () let init_module pre post force name = let f = try M.find name !init_functions with Not_found as e -> raise (Dynlink_error ("named module " ^ name, e)) in try if force then begin pre (); Lwt_log.ign_info_f ~section "Initializing %s (will be initialized every time)" name; begin try f (); post () with e -> post (); raise e end end else if not (isloaded name) then begin pre (); Lwt_log.ign_info_f ~section "Initializing module %s " name; begin try f (); post () with e -> post (); raise e end; addloaded name; end else Lwt_log.ign_info_f ~section "Module %s already initialized." name with | e -> raise (Dynlink_error (name, e)) (************************************************************************) (* Manipulating Findlib's search path *) let () = Findlib.init () let ocsigen_search_path = ref [] let update_search_path () = match !ocsigen_search_path with | [] -> Findlib.init () | x -> Findlib.init ~env_ocamlpath:(String.concat ":" x) () let get_ocamlpath = Findlib.search_path let set_ocamlpath lp = ocsigen_search_path := lp; update_search_path () let add_ocamlpath p = ocsigen_search_path := p :: !ocsigen_search_path; update_search_path () (************************************************************************) (* Using Findlib to locate files *) let findfiles = let cmx = Netstring_pcre.regexp_case_fold "\\.cmx($| |a)" in fun package -> try let preds = [(if Ocsigen_config.is_native then "native" else "byte"); "plugin"; "mt"] in let deps = Findlib.package_deep_ancestors preds [package] in let deps = List.filter (fun a -> not (String.Set.mem a Ocsigen_config.builtin_packages)) deps in Lwt_log.ign_info_f ~section "Dependencies of %s: %s" package (String.concat ", " deps); let rec aux = function | [] -> [] | a::q -> let mods = try let raw = Findlib.package_property preds a "archive" in (* Replacing .cmx/.cmxa by .cmxs *) let raw = Netstring_pcre.global_replace cmx ".cmxs " raw in List.filter ((<>) "") (String.split ~multisep:true ' ' raw) with | Not_found -> [] in let base = Findlib.package_directory a in (List.map (Findlib.resolve_path ~base) mods) @ (aux q) in let res = aux deps in Lwt_log.ign_info_f ~section "Needed: %s" (String.concat ", " res); res with | e -> raise (Findlib_error (package, e)) (************************************************************************) (* Error formatting *) open Printf let () = Printexc.register_exn_printer (fun f_rec -> function | Dynlink_wrapper.Error e -> Dynlink_wrapper.error_message e | Dynlink_error (s, e) -> sprintf "Dynlink error while loading %s: %s" s (f_rec e) | Findlib_error (s, Fl_package_base.No_such_package (s', msg)) -> let pkg = if s = s' then s else sprintf "%s [while trying to load %s]" s' s in let additional = if msg = "" then "" else sprintf " (%s)" msg in sprintf "Findlib package %s not found%s: maybe you forgot ?" pkg additional | Findlib_error (s, e) -> sprintf "Findlib error while handling %s: %s" s (f_rec e) | e -> raise e) ocsigenserver-2.16.0/src/baselib/ocsigen_loader.mli000066400000000000000000000072271357715257700223610ustar00rootroot00000000000000(* Ocsigen * http://www.ocsigen.org * File ocsigen_loader.mli * Copyright (C) 2008 Stéphane Glondu * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) (** Module [Ocsigen_loader]: Dynamic loading for Ocsigen. *) (** Notes about Findlib usage: - Findlib is called with predicates "plugin", "mt". Moreover, the predicate "native" or "byte" is added, depending on whether Ocsigen is running in native or bytecode mode. - In native mode, .cmx/.cmxa extensions provided by META files are replaced by .cmxs. - The OCAMLPATH environment variable is ignored altogether. *) exception Dynlink_error of string * exn exception Findlib_error of string * exn val translate : string -> string (** [translate filename] translate .cmo/.cma extensions to .cmxs in native mode, and .cmxs to .cmo (.cma if it exists) in bytecode mode. *) val set_init_on_load: bool -> unit (** If set to [true], the module initialization functions passed to [set_module_init_function] will be executed directly. Otherwise, they will have to be invoked using [init_module] at some later stage. *) val loadfile: (unit -> unit) -> (unit -> unit) -> bool -> string -> unit (** [loadfile pre post force file] (dynamically) loads [file]. If [force] is [false], remember [file] so that it isn't loaded twice. If the loading effectively occurs, [pre] (resp. [post]) is called before (resp. after) the loading. [post] will be called even if the loading fails. *) val loadfiles: (unit -> unit) -> (unit -> unit) -> bool -> string list -> unit (** [loadfiles pre post force file] loads all the [files], using [loadfile (fun () -> ()) (fun () -> ()) false] for all the files but the last one, and [loadfile pre post force] for the last one (if any). *) val set_module_init_function : string -> (unit -> unit) -> unit (** [set_module_init_function name f] registers the function [f], which will be used to initialize the module when [init_module name] is called. *) val init_module : (unit -> unit) -> (unit -> unit) -> bool -> string -> unit (** [init_module pre post force name] runs the init function for the module [name]. If [force] is [false], remember [name] so that the init function isn't executed twice. If the function is executed, [pre] (resp. [post]) is called before (resp. after) the loading. [post] will be called even if the loading fails. *) val get_ocamlpath: unit -> string list (** Returns the current Findlib library search path. *) val set_ocamlpath: string list -> unit (** Sets the current Findlib library search path. The OCaml standard library path and some site-specific paths are always implicitly added. *) val add_ocamlpath: string -> unit (** Adds a path to the Findlib library search path. *) val findfiles: string -> string list (** [findfiles pkg] returns the list of files needed to load Findlib package [pkg], including dependencies. The archive files of [pkg] will appear last in the returned result. *) ocsigenserver-2.16.0/src/baselib/ocsigen_messages.ml000066400000000000000000000216161357715257700225470ustar00rootroot00000000000000(* Ocsigen * Copyright (C) 2005 Vincent Balat * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) (** Writing messages in the logs *) let (>>=) = Lwt.bind let access_file = "access.log" let warning_file = "warnings.log" let error_file = "errors.log" let access_sect = Lwt_log.Section.make "ocsigen:access" let full_path f = Filename.concat (Ocsigen_config.get_logdir ()) f let error_log_path () = full_path error_file let stderr = Lwt_log.channel `Keep Lwt_io.stderr () let stdout = Lwt_log.channel `Keep Lwt_io.stdout () let loggers = ref [] let access_logger = ref Lwt_log_core.null let open_files ?(user = Ocsigen_config.get_user ()) ?(group = Ocsigen_config.get_group ()) () = (* CHECK: we are closing asynchronously! That should be ok, though. *) List.iter (fun l -> ignore (Lwt_log.close l : unit Lwt.t)) !loggers; match Ocsigen_config.get_syslog_facility () with | Some facility -> (* log to syslog *) let syslog = Lwt_log.syslog ~facility () in loggers := [syslog]; Lwt_log.default := Lwt_log.broadcast [syslog; stderr]; Lwt.return () | None -> (* log to files *) let open_log path = let path = full_path path in Lwt.catch (fun () -> Lwt_log.file path ()) (function | Unix.Unix_error (error, _, _) -> Lwt.fail (Ocsigen_config.Config_file_error (Printf.sprintf "can't open log file %s: %s" path (Unix.error_message error))) | exn -> Lwt.fail exn) in open_log access_file >>= fun acc -> access_logger := acc; open_log warning_file >>= fun war -> open_log error_file >>= fun err -> loggers := [acc; war; err]; Lwt_log.default := Lwt_log.broadcast [Lwt_log.dispatch (fun sect lev -> match lev with | Lwt_log.Error | Lwt_log.Fatal -> err | Lwt_log.Warning -> war | _ -> Lwt_log.null); Lwt_log.dispatch (fun sect lev -> if Ocsigen_config.get_silent () then Lwt_log.null else match lev with | Lwt_log.Warning | Lwt_log.Error | Lwt_log.Fatal -> stderr | _ -> stdout)]; let gid = match group with | None -> Unix.getgid () | Some group -> (try (Unix.getgrnam group).Unix.gr_gid with Not_found as e -> ignore (Lwt_log.error "Error: Wrong group"); raise e) in let uid = match user with | None -> Unix.getuid () | Some user -> (try (Unix.getpwnam user).Unix.pw_uid with Not_found as e -> ignore (Lwt_log.error "Error: Wrong user"); raise e) in Lwt.catch (fun () -> Lwt_unix.chown (full_path access_file) uid gid >>= fun () -> Lwt_unix.chown (full_path warning_file) uid gid >>= fun () -> Lwt_unix.chown (full_path error_file) uid gid) (fun e -> match e with | Unix.Unix_error (Unix.EPERM, _, _) -> (* to allow for symlinks to /dev/null *) Lwt.return_unit | _ -> Lwt.fail e) (****) let accesslog s = (* not really fatal, but log in all cases; does not affect console *) Lwt_log.ign_fatal ~section:access_sect ~logger:!access_logger s; Lwt_log.ign_notice ~section:access_sect s let errlog ?section s = Lwt_log.ign_error ?section s let warning ?section s = Lwt_log.ign_warning ?section s let unexpected_exception e s = Lwt_log.ign_warning_f ~exn:e "Unexpected exception in %s" s (****) let console = if (not (Ocsigen_config.get_silent ())) then (fun s -> print_endline (s ())) else (fun s -> ()) let level_of_string = function | "debug" -> Some Lwt_log.Debug | "info" -> Some Lwt_log.Info | "notice" -> Some Lwt_log.Notice | "warning"-> Some Lwt_log.Warning | "error" -> Some Lwt_log.Error | "fatal" -> Some Lwt_log.Fatal | _ -> None let command_f exc _ = function | [sect_name] -> (* Lwt_log.Section.make : if a section with the same name already exists, it is returned. *) let sect = Lwt_log.Section.make sect_name in Lwt_log.Section.reset_level sect; Lwt.return_unit | [sect_name; level_name] -> (* Lwt_log.Section.make : if a section with the same name already exists, it is returned. *) let sect = Lwt_log.Section.make sect_name in (match level_of_string (String.lowercase level_name) with | None -> Lwt_log.Section.reset_level sect | Some l -> Lwt_log.Section.set_level sect l); Lwt.return () | _ -> Lwt.fail exc (* Re: [Caml-list] log function without evaluate arguments From: tmp123 To: caml-list@inria.fr Date: Nov 7 2007, 10:37 am Hello, Thanks a lot to everybody for your help. I've been testing the different proposals. I must recognize I've not yet reviewed the proposed library, it is next step. The four methods tested are: lazy, fun, ifprint, and fun moving the "if" to the caller (see full listing and results at the end of the post). Two test has been done for each one: when parameter is an integer constant and when parameter is the result of a function call who mades an addition. The conclusion seems: defining that "lazy" method needs 1 unit of time, proposal using "fun" instead of lazy needs 0.8, and the version "ifprintf" needs 16. Proposal moving the "if" needs 0.7. Thus, if no error has been done, fun is the fastest option, lazy is near. Another point is the possibility of, using a camlp4 syntax extension, to introduce a few of sugar. Something like expand: from: log "some=%d\n" 14; to: logint ( fun () -> Printf.printf "some=%d\n" 14); or to: if log_active.val then logint ( fun() -> Printf.printf "some=%d\n" 14) else (); Thanks again to everybody. Full listing and results: value log_active = ref False; value log1 exp = if log_active.val then Lazy.force exp else (); value log2 exp = if log_active.val then exp() else (); value log3 fmt = if log_active.val then Printf.printf fmt else Printf.ifprintf stderr fmt; value log4 exp = exp (); value suma a b = ( a+b; ); value some = ref 14; value test1 () = log1 (lazy (Printf.printf "%d" (suma some.val 3))); value test2 () = log2 ( fun () -> Printf.printf "%d" (suma some.val 3)); value test3 () = log3 "%d" (suma some.val 3); value test4 () = if log_active.val then log4 ( fun () -> Printf.printf "%d" (suma some.val 3)) else (); value testb1 () = log1 (lazy (Printf.printf "%d" 3)); value testb2 () = log2 ( fun () -> Printf.printf "%d" 3); value testb3 () = log3 "%d" 3; value testb4 () = if log_active.val then log4 ( fun () -> Printf.printf "%d" 3) else (); value loop f = ( let t=Unix.times() in Printf.printf "%f %f %f\n" (Unix.gettimeofday()) t.Unix.tms_utime t.Unix.tms_stime; for i = 0 to 1000 do for j = 0 to 1000000 do f (); done; done; let t=Unix.times() in Printf.printf "%f %f %f\n" (Unix.gettimeofday()) t.Unix.tms_utime t.Unix.tms_stime; ); value main () = ( Printf.printf "test1\n"; loop test1; Printf.printf "test2\n"; loop test2; Printf.printf "test3\n"; loop test3; Printf.printf "test4\n"; loop test4; Printf.printf "\n"; Printf.printf "testb1\n"; loop testb1; Printf.printf "testb2\n"; loop testb2; Printf.printf "testb3\n"; loop testb3; Printf.printf "testb4\n"; loop testb4; ); main(); Results: test1 1194426404.657406 0.015000 0.000000 1194426414.136406 9.453000 0.000000 test2 1194426414.137406 9.468000 0.000000 1194426422.147406 17.453000 0.000000 test3 1194426422.147406 17.453000 0.000000 1194426593.308406 188.515000 0.000000 test4 1194426593.308406 188.515000 0.000000 1194426599.964406 195.156000 0.000000 testb1 1194426599.964406 195.156000 0.000000 1194426609.408406 204.609000 0.000000 testb2 1194426609.408406 204.609000 0.000000 1194426617.378406 212.578000 0.000000 testb3 1194426617.378406 212.578000 0.000000 1194426790.412406 385.484000 0.000000 testb4 1194426790.412406 385.484000 0.000000 1194426797.060406 392.125000 0.000000 ------------- _______________________________________________ Caml-list mailing list. *) ocsigenserver-2.16.0/src/baselib/ocsigen_messages.mli000066400000000000000000000034321357715257700227140ustar00rootroot00000000000000(* Ocsigen * Copyright (C) 2005 Vincent Balat * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) (** Writing messages in the logs *) (** Write a message in access.log *) val accesslog : string -> unit (** Write a message in errors.log *) val errlog : ?section:Lwt_log.section -> string -> unit (** Write a message in warnings.log *) val warning : ?section:Lwt_log.section -> string -> unit (** Write a message in the console (if not called in silent mode) *) val console : (unit -> string) -> unit (** Use that function for all impossible cases in exception handlers ([try ... with ... | e -> unexpected_exception ...] or [Lwt.catch ...]). A message will be written in [warnings.log]. Put something in the string to help locating the problem (usually the name of the function where is has been called). *) val unexpected_exception : exn -> string -> unit (** Path to the error log file *) val error_log_path : unit -> string (**/**) val open_files : ?user:string option -> ?group:string option -> unit -> unit Lwt.t val command_f : exn -> string -> string list -> unit Lwt.t ocsigenserver-2.16.0/src/baselib/ocsigen_stream.ml000066400000000000000000000203051357715257700222250ustar00rootroot00000000000000(* Ocsigen * ocsigen_stream.ml Copyright (C) 2005 Vincent Balat * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) open Ocsigen_lib exception Interrupted of exn exception Cancelled exception Already_read exception Finalized type 'a stream = 'a step Lwt.t Lazy.t and 'a step = | Finished of 'a stream option (* If there is another stream following (useful for substreams) *) | Cont of 'a * 'a stream (* Current buffer, what follows *) type outcome = [`Success | `Failure] type 'a t = { mutable stream : 'a stream; mutable in_use : bool; mutable finalizer : outcome -> unit Lwt.t } let empty follow = match follow with None -> Lwt.return (Finished None) | Some st -> Lwt.return (Finished (Some (Lazy.from_fun st))) let cont stri f = Lwt.return (Cont (stri, Lazy.from_fun f)) let make ?finalize:(g = fun _ -> Lwt.return ()) f = { stream = Lazy.from_fun f; in_use = false; finalizer = g } let next = Lazy.force let rec get_aux st = lazy (Lwt.try_bind (fun () -> Lazy.force st.stream) (fun e -> Lwt.return (match e with Cont (s, rem) -> st.stream <- rem; Cont (s, get_aux st) | _ -> e)) (fun e -> st.stream <- lazy (Lwt.fail e); Lwt.fail (Interrupted e))) let get st = if st.in_use then raise Already_read; st.in_use <- true; get_aux st (** read the stream until the end, without decoding *) let rec consume_aux st = next st >>= fun e -> match e with | Cont (_, f) -> consume_aux f | Finished (Some ss) -> consume_aux ss | Finished None -> Lwt.return () let cancel st = let st' = st.stream in st.stream <- lazy (Lwt.fail Cancelled); consume_aux st' let consume st = consume_aux st.stream let finalize st status = let f = st.finalizer in st.finalizer <- (fun _ -> Lwt.return ()); f status >>= fun () -> st.stream <- lazy (Lwt.fail Finalized); Lwt.return () let add_finalizer st g = let f = st.finalizer in st.finalizer <- fun status -> f status >>= fun () -> g status (****) (** String streams *) exception Stream_too_small exception Stream_error of string exception String_too_large let string_of_stream m s = let buff = Buffer.create (m/4) in let rec aux i s = next s >>= function | Finished _ -> Lwt.return buff | Cont (s, f) -> let i = i + String.length s in if i > m then Lwt.fail String_too_large else (Buffer.add_string buff s; aux i f) in aux 0 s >|= Buffer.contents (* (*XXX Quadratic!!! *) let string_of_streams = let rec aux l = function | Finished None -> return "" | Finished (Some s) -> next s >>= fun r -> aux l r | Cont (s, f) -> let l2 = l+String.length s in if l2 > Ocsigen_config.get_netbuffersize () then Lwt.fail String_too_large else next f >>= fun r -> aux l2 r >>= fun r -> return (s^r) in aux 0 *) let enlarge_stream = function | Finished a -> Lwt.fail Stream_too_small | Cont (s, f) -> let long = String.length s in let max = Ocsigen_config.get_netbuffersize () in if long >= max then Lwt.fail Input_is_too_large else next f >>= fun e -> match e with | Finished _ -> Lwt.fail Stream_too_small | Cont (r, ff) -> let long2 = String.length r in let long3=long+long2 in let new_s = s^r in if long3 <= max then Lwt.return (Cont (new_s, ff)) else let long4 = long3 - max in cont (String.sub new_s 0 max) (fun () -> Lwt.return (Cont (String.sub new_s max long4, ff))) let rec stream_want s len = (* returns a stream with at least len bytes read if possible *) match s with | Finished _ -> Lwt.return s | Cont (stri, f) -> if String.length stri >= len then Lwt.return s else Lwt.catch (fun () -> enlarge_stream s >>= fun r -> Lwt.return (`OK r)) (function | Stream_too_small -> Lwt.return `Too_small | e -> Lwt.fail e) >>= function | `OK r -> stream_want r len | `Too_small -> Lwt.return s let current_buffer = function | Finished _ -> raise Stream_too_small | Cont (s, _) -> s let rec skip s k = match s with | Finished _ -> raise Stream_too_small | Cont (s, f) -> let len = String.length s in let len64 = Int64.of_int len in if Int64.compare k len64 <= 0 then let k = Int64.to_int k in Lwt.return (Cont (String.sub s k (len - k), f)) else (enlarge_stream (Cont ("", f)) >>= (fun s -> skip s (Int64.sub k len64))) let substream delim s = let ldelim = String.length delim in if ldelim = 0 then Lwt.fail (Stream_error "Empty delimiter") else let rdelim = Netstring_pcre.regexp_string delim in let rec aux = function | Finished _ -> Lwt.fail Stream_too_small | Cont (s, f) as stre -> let len = String.length s in if len < ldelim then enlarge_stream stre >>= aux else try let p,_ = Netstring_pcre.search_forward rdelim s 0 in cont (String.sub s 0 p) (fun () -> empty (Some (fun () -> Lwt.return (Cont (String.sub s p (len - p), f))))) with Not_found -> let pos = (len + 1 - ldelim) in cont (String.sub s 0 pos) (fun () -> next f >>= function | Finished _ -> Lwt.fail Stream_too_small | Cont (s', f') -> aux (Cont (String.sub s pos (len - pos) ^ s', f')) ) in aux s (*****************************************************************************) (*VVV Is it the good place for this? *) let of_file filename = let fd = Lwt_unix.of_unix_file_descr (Unix.openfile filename [Unix.O_RDONLY;Unix.O_NONBLOCK] 0o666) in let ch = Lwt_io.of_fd ~mode:Lwt_io.input fd in let buf = Bytes.create 1024 in let rec aux () = Lwt_io.read_into ch buf 0 1024 >>= fun n -> if n = 0 then empty None else (* Streams should be immutable, thus we always make a copy of the buffer *) cont (Bytes.sub_string buf 0 n) aux in make ~finalize:(fun _ -> Lwt_unix.close fd) aux let of_string s = make (fun () -> cont s (fun () -> empty None)) (** Convert a {!Lwt_stream.t} to an {!Ocsigen_stream.t}. *) let of_lwt_stream stream = let rec aux () = Lwt_stream.get stream >>= function | Some e -> cont e aux | None -> empty None in make aux (** Convert an {!Ocsigen_stream.t} into a {!Lwt_stream.t}. @param is_empty function to skip empty chunk. *) let to_lwt_stream o_stream = let stream = ref (get o_stream) in let rec wrap () = next !stream >>= function | Finished None -> o_stream.finalizer `Success >>= fun () -> Lwt.return None | Finished (Some next) -> stream := next; wrap () | Cont (value, next) -> stream := next; Lwt.return (Some value) in Lwt_stream.from wrap module StringStream = struct type out = string t type m = (string stream -> string step Lwt.t) Lazy.t let empty : m = lazy (fun c -> Lazy.force c) let concat (m: m) (f: m) : m = lazy (fun c -> Lazy.force m (lazy (Lazy.force f c))) let put (s : string) : m = lazy (fun c -> Lwt.return (Cont (s, c))) let make_stream (m: m) : string stream = lazy (Lazy.force m (lazy (Lwt.return (Finished None)))) let make (m: m) : out = make (fun () -> Lazy.force (make_stream m)) end ocsigenserver-2.16.0/src/baselib/ocsigen_stream.mli000066400000000000000000000106261357715257700224030ustar00rootroot00000000000000(* Ocsigen * ocsigen_stream.ml Copyright (C) 2005 Vincent Balat * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) exception Interrupted of exn exception Cancelled exception Already_read exception Finalized (** Streams are a means to read data block by block *) type 'a stream (** A stream may be composed by several substreams. Thus a stream is either something that contains the current buffer and a function to retrieve the following data, or a finished stream with possibly another stream following. *) type 'a step = private | Finished of 'a stream option | Cont of 'a * 'a stream type 'a t type outcome = [`Success | `Failure] (** creates a new stream *) val make : ?finalize:(outcome -> unit Lwt.t) -> (unit -> 'a step Lwt.t) -> 'a t (** call this function if you decide to start reading a stream. @raise Already_read if the stream has already been read. *) val get : 'a t -> 'a stream (** get the next step of a stream. Fails with [Interrupted e] if reading the thread failed with exception [e], and with [Cancelled] if the thread has been cancelled. *) val next : 'a stream -> 'a step Lwt.t (** creates an empty step. The parameter is the following substream, if any. *) val empty : (unit -> 'a step Lwt.t) option -> 'a step Lwt.t (** creates a non empty step. *) val cont : 'a -> (unit -> 'a step Lwt.t) -> 'a step Lwt.t (** Add a finalizer function. In the current version, finalizers must be called manually. *) val add_finalizer : 'a t -> (outcome -> unit Lwt.t) -> unit (** Finalize the stream. This function must be called explicitly after reading the stream, otherwise finalizers won't be called. *) val finalize : 'a t -> outcome -> unit Lwt.t (** Cancel the stream, i.e. read the stream until the end, without decoding. Further tries to read on the stream will fail with exception {!Ocsigen_stream.Cancelled} *) val cancel : 'a t -> unit Lwt.t (** Consume without cancelling. Read the stream until the end, without decoding. *) val consume : 'a t -> unit Lwt.t exception Stream_too_small (** possibly with the size of the stream *) exception Stream_error of string exception String_too_large (** Creates a string from a stream. The first argument is the upper limit of the string length *) val string_of_stream : int -> string stream -> string Lwt.t (** Read more data in the buffer *) val enlarge_stream : string step -> string step Lwt.t (** [stream_want s len] Returns a stream with at least len bytes in the buffer if possible *) val stream_want : string step -> int -> string step Lwt.t (** Returns the value of the current buffer *) val current_buffer : string step -> string (** Skips data. Raises [Stream_too_small (Some size)] if the stream is too small, where [size] is the size of the stream. *) val skip : string step -> int64 -> string step Lwt.t (** Cut the stream at the position given by a string delimiter *) val substream : string -> string step -> string step Lwt.t (** returns a stream reading from a file. Do not forget to finalize the stream to close the file. *) val of_file : string -> string t (** returns a stream containing a string. *) val of_string : string -> string t (** Convert a {!Lwt_stream.t} to an {!Ocsigen_stream.t}. *) val of_lwt_stream : 'a Lwt_stream.t -> 'a t (** Convert an {!Ocsigen_stream.t} into a {!Lwt_stream.t}. @param is_empty function to skip empty chunk. *) val to_lwt_stream : 'a t -> 'a Lwt_stream.t module StringStream : sig (** Interface for stream creation (for tyxml) *) type out = string t type m val make: m -> out (** Create an empty stream *) val empty: m (** Create a stream with one element *) val put: string -> m (** Concatenate two stream *) val concat: m -> m -> m end ocsigenserver-2.16.0/src/baselib/polytables.ml000066400000000000000000000027301357715257700214030ustar00rootroot00000000000000(* Ocsigen * Copyright (C) 2009 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) (** @author Vincent Balat @author Jérôme Vouillon *) type 'a key = int * 'a option ref module T = Map.Make(struct type t = int let compare = compare end) type t = (unit -> unit) T.t ref let create () = ref T.empty let c = ref (-1) let make_key () = c := !c + 1; (!c, ref None) let set ~(table : t) ~key:((k, r) : 'a key) ~(value : 'a) = table := T.add k (fun () -> r := Some value) !table let get ~(table : t) ~key:((k, r) : 'a key) = (T.find k !table) (); match !r with | Some v -> r:= None; v | None -> failwith "Polytables.get" let remove ~(table : t) ~key:((k, r) : 'a key) = table := T.remove k !table let clear ~(table : t) = table := T.empty ocsigenserver-2.16.0/src/baselib/polytables.mli000066400000000000000000000030711357715257700215530ustar00rootroot00000000000000(* Ocsigen * Copyright (C) 2009 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) (** Polymorphic tables (using Map) @author Vincent Balat @author Jérôme Vouillon *) (** Warning: this module is not thread safe! *) (** The type of key for a piece of data of type 'a *) type 'a key (** The type of tables *) type t (** creates a new table *) val create : unit -> t (** create a new key for each data you want to save *) val make_key : unit -> 'a key (** [set t k v] associates [v] to [k] in [t] *) val set : table:t -> key:'a key -> value:'a -> unit (** [get t k] returns the current binding of [k] in [t] or raises [Not_found] *) val get : table:t -> key:'a key -> 'a (** [remove t k] remove the current binding of [k] in [t] if it exists *) val remove : table:t -> key:'a key -> unit (** [clear t] remove all data from t *) val clear : table:t -> unit ocsigenserver-2.16.0/src/baselib/tests/000077500000000000000000000000001357715257700200335ustar00rootroot00000000000000ocsigenserver-2.16.0/src/baselib/tests/test_wrapping.ml000066400000000000000000000103021357715257700232470ustar00rootroot00000000000000(* ocamlfind ocamlopt -linkpkg -package react -g -I ../ ../wrapping.cmxa test_wrapping.ml -o test_wrapping *) let _ = Printexc.record_backtrace true (*** simple wrap test ***) type a = { a : float; a_wrap : a Ocsigen_wrap.wrapper; } let a_wrap () = Ocsigen_wrap.create_wrapper (fun t -> { a = t.a +. 1.; a_wrap = Ocsigen_wrap.empty_wrapper }) let a i = { a = i; a_wrap = a_wrap () } let va = a 3.14 let _,v = Ocsigen_wrap.wrap va let () = assert (v.a -. 4.14 < 0.0001) (*** deep wrap test ***) let va = [[[[[[1,[3.1,a 3.14]],a 35.1]]]]] let _,_ = Ocsigen_wrap.wrap va (*** multiple wrap test ***) type b = { b : string; ba : a; b' : string; b_wrap : b Ocsigen_wrap.wrapper; } let b_wrap () = Ocsigen_wrap.create_wrapper (fun t -> t.b, t.ba ) let b s f = { b = s; b' = s; b_wrap = b_wrap (); ba = a f } let tst_string = "test" let vb = b tst_string 3.14 let _,vb' = Ocsigen_wrap.wrap vb let _ = assert ( let (t,f) = Obj.magic (vb') in t == tst_string && ( f -. 4.14 < 0.0001 )) let _,vb'' = Ocsigen_wrap.wrap [2,[1.,[vb,4,ref 0],ref 42]] let () = match vb'' with | [2,[1.,[vb',4,{ contents = 0}],{ contents = 42}]] -> assert ( let (t,f) = Obj.magic (vb') in t == tst_string && ( f -. 4.14 < 0.0001 )) | _ -> assert false (*** create wrap during wrap test ***) let b'_wrap () = Ocsigen_wrap.create_wrapper (fun t -> t.b, a 1.2 ) let b' s f = { b = s; b' = s; b_wrap = b'_wrap (); ba = a f } let vb' = b' "test" 3.14 let _,vb'' = Ocsigen_wrap.wrap vb' let () = match (Obj.magic vb'') with | (x,y) -> assert (x == vb'.b); assert (y.a -. 4.14 < 0.0001 ) (*** big value copy ***) let ( -- ) x y = let rec aux y x acc = if x > y then acc else aux (y-1) x (y::acc) in aux y x [] (* type l = | A | L of l * int let ( -- ) x y = let rec aux y x acc = if x > y then acc else aux (y-1) x (L (acc,y)) in aux y x A let _ = Marshal.to_string (0--50000000) *) let v = (0--80000) (* it cannot grow much bigger than that, On systems with a small stack, it could die with stack overflow *) let _,v' = Ocsigen_wrap.wrap v let () = assert (v' = v) (*** simple wrap weak test ***) let d = Weak.create 1 let d_val = (Some (ref 0)) let _ = Weak.set d 0 d_val type d = { da : int; dw : int Weak.t; dm : d Ocsigen_wrap.wrapper; } let d_wrap () = Ocsigen_wrap.create_wrapper (fun {da;dw} -> Weak.get dw 0,da) let _,d' = Ocsigen_wrap.wrap (1,d,d_wrap ()) let () = assert (Obj.magic d' = (d_val,1)) (*** simple wrap react test ***) let r',push = React.E.create () let r = React.E.map (fun i -> i+1) r' let c a r w = (a,r,w) let c_wrap () = Ocsigen_wrap.create_wrapper (fun (a,r,w) -> a) let _,c' = Ocsigen_wrap.wrap (c 1 r (c_wrap ())) let () = assert (Obj.magic c' = 1) (*** Eliom_react like test ***) let r',push = React.E.create () let r = React.E.map (fun i -> i+1) r' type toto = { a : float; mtoto : toto Ocsigen_wrap.wrapper; } type t = { v1 : int; v2 : toto; v3 : int React.event; mt : t Ocsigen_wrap.wrapper; } let i = ref 0 let mtoto () = Ocsigen_wrap.create_wrapper (fun t -> incr i; string_of_float t.a, !i) let mt () = Ocsigen_wrap.create_wrapper (fun t -> incr i; t.v2) let toto i = { a = i; mtoto = mtoto () } let t i b = { v1 = i; v2 = b; v3 = r; mt = mt () } let vtoto = toto 3.14 let vt = t 42 vtoto let _,v' = Ocsigen_wrap.wrap vtoto let _,v' = Ocsigen_wrap.wrap vt (*** closure copy test ***) type t1 = { t1a : float; t1mark : t1 Ocsigen_wrap.wrapper; } type t2 = { t2t1 : t1; t2f : (int ref -> unit) option; t2mark : t2 Ocsigen_wrap.wrapper; } let r1 = ref 13 let r2 = ref 42 let r3 = ref 88 let t1mark () = Ocsigen_wrap.create_wrapper (fun t -> incr r1; { t1a = 3.14; t1mark = Ocsigen_wrap.empty_wrapper } ) let t2mark () = Ocsigen_wrap.create_wrapper (fun t -> (match t.t2f with | Some f -> f r2; | None -> assert false); { t with t2f = None; t2mark = Ocsigen_wrap.empty_wrapper } ) let t1 = { t1a = 1.1; t1mark = t1mark () } let t2 = { t2t1 = t1; t2f = Some (fun r -> incr r; incr r3); t2mark = t2mark () } let _,t2' = Ocsigen_wrap.wrap (Obj.repr t2) let _,t1' = Ocsigen_wrap.wrap (Obj.repr t1) let _ = assert (!r1 = 15); assert (!r2 = 43); assert (!r3 = 89) ocsigenserver-2.16.0/src/extensions/000077500000000000000000000000001357715257700174675ustar00rootroot00000000000000ocsigenserver-2.16.0/src/extensions/.depend000066400000000000000000000133711357715257700207340ustar00rootroot00000000000000accesscontrol.cmo : ../server/ocsigen_request_info.cmi \ ../baselib/ocsigen_lib.cmi ../http/ocsigen_http_frame.cmi \ ../server/ocsigen_extensions.cmi ../http/ocsigen_cookies.cmi \ ../http/http_headers.cmi ../http/framepp.cmi accesscontrol.cmi accesscontrol.cmx : ../server/ocsigen_request_info.cmx \ ../baselib/ocsigen_lib.cmx ../http/ocsigen_http_frame.cmx \ ../server/ocsigen_extensions.cmx ../http/ocsigen_cookies.cmx \ ../http/http_headers.cmx ../http/framepp.cmx accesscontrol.cmi accesscontrol.cmi : ../server/ocsigen_extensions.cmi authbasic.cmo : ../server/ocsigen_request_info.cmi \ ../http/ocsigen_http_frame.cmi ../server/ocsigen_extensions.cmi \ ../http/ocsigen_cookies.cmi ../http/http_headers.cmi authbasic.cmi authbasic.cmx : ../server/ocsigen_request_info.cmx \ ../http/ocsigen_http_frame.cmx ../server/ocsigen_extensions.cmx \ ../http/ocsigen_cookies.cmx ../http/http_headers.cmx authbasic.cmi authbasic.cmi : cgimod.cmo : ../baselib/ocsigen_stream.cmi ../http/ocsigen_senders.cmi \ ../server/ocsigen_request_info.cmi ../baselib/ocsigen_lib.cmi \ ../http/ocsigen_http_frame.cmi ../http/ocsigen_http_com.cmi \ ../server/ocsigen_extensions.cmi ../http/ocsigen_cookies.cmi \ ../baselib/ocsigen_config.cmi ../http/http_headers.cmi \ ../http/framepp.cmi cgimod.cmx : ../baselib/ocsigen_stream.cmx ../http/ocsigen_senders.cmx \ ../server/ocsigen_request_info.cmx ../baselib/ocsigen_lib.cmx \ ../http/ocsigen_http_frame.cmx ../http/ocsigen_http_com.cmx \ ../server/ocsigen_extensions.cmx ../http/ocsigen_cookies.cmx \ ../baselib/ocsigen_config.cmx ../http/http_headers.cmx \ ../http/framepp.cmx cors.cmo : ../server/ocsigen_request_info.cmi ../baselib/ocsigen_lib.cmi \ ../http/ocsigen_http_frame.cmi ../server/ocsigen_extensions.cmi \ ../http/http_headers.cmi ../http/framepp.cmi cors.cmx : ../server/ocsigen_request_info.cmx ../baselib/ocsigen_lib.cmx \ ../http/ocsigen_http_frame.cmx ../server/ocsigen_extensions.cmx \ ../http/http_headers.cmx ../http/framepp.cmx deflatemod.cmo : ../baselib/ocsigen_stream.cmi \ ../server/ocsigen_request_info.cmi ../baselib/ocsigen_lib.cmi \ ../http/ocsigen_http_frame.cmi ../http/ocsigen_headers.cmi \ ../server/ocsigen_extensions.cmi ../http/http_headers.cmi deflatemod.cmx : ../baselib/ocsigen_stream.cmx \ ../server/ocsigen_request_info.cmx ../baselib/ocsigen_lib.cmx \ ../http/ocsigen_http_frame.cmx ../http/ocsigen_headers.cmx \ ../server/ocsigen_extensions.cmx ../http/http_headers.cmx extendconfiguration.cmo : ../server/ocsigen_parseconfig.cmi \ ../server/ocsigen_extensions.cmi ../http/ocsigen_cookies.cmi \ ../baselib/ocsigen_config.cmi ../http/ocsigen_charset_mime.cmi extendconfiguration.cmx : ../server/ocsigen_parseconfig.cmx \ ../server/ocsigen_extensions.cmx ../http/ocsigen_cookies.cmx \ ../baselib/ocsigen_config.cmx ../http/ocsigen_charset_mime.cmx extensiontemplate.cmo : ../http/ocsigen_senders.cmi \ ../server/ocsigen_extensions.cmi extensiontemplate.cmx : ../http/ocsigen_senders.cmx \ ../server/ocsigen_extensions.cmx ocsigen_comet.cmo : ../baselib/ocsigen_stream.cmi \ ../server/ocsigen_request_info.cmi ../baselib/ocsigen_lib.cmi \ ../http/ocsigen_http_frame.cmi ../server/ocsigen_extensions.cmi \ ../baselib/ocsigen_config.cmi ocsigen_comet.cmi ocsigen_comet.cmx : ../baselib/ocsigen_stream.cmx \ ../server/ocsigen_request_info.cmx ../baselib/ocsigen_lib.cmx \ ../http/ocsigen_http_frame.cmx ../server/ocsigen_extensions.cmx \ ../baselib/ocsigen_config.cmx ocsigen_comet.cmi ocsigen_comet.cmi : ../baselib/ocsigen_stream.cmi ocsipersist.cmi : outputfilter.cmo : ../http/ocsigen_http_frame.cmi \ ../http/ocsigen_headers.cmi ../server/ocsigen_extensions.cmi \ ../http/http_headers.cmi outputfilter.cmx : ../http/ocsigen_http_frame.cmx \ ../http/ocsigen_headers.cmx ../server/ocsigen_extensions.cmx \ ../http/http_headers.cmx redirectmod.cmo : ../server/ocsigen_request_info.cmi \ ../baselib/ocsigen_lib.cmi ../http/ocsigen_http_frame.cmi \ ../server/ocsigen_extensions.cmi redirectmod.cmx : ../server/ocsigen_request_info.cmx \ ../baselib/ocsigen_lib.cmx ../http/ocsigen_http_frame.cmx \ ../server/ocsigen_extensions.cmx revproxy.cmo : ../baselib/ocsigen_stream.cmi \ ../server/ocsigen_request_info.cmi ../baselib/ocsigen_lib.cmi \ ../http/ocsigen_http_frame.cmi ../server/ocsigen_http_client.cmi \ ../http/ocsigen_headers.cmi ../server/ocsigen_extensions.cmi \ ../http/http_headers.cmi revproxy.cmx : ../baselib/ocsigen_stream.cmx \ ../server/ocsigen_request_info.cmx ../baselib/ocsigen_lib.cmx \ ../http/ocsigen_http_frame.cmx ../server/ocsigen_http_client.cmx \ ../http/ocsigen_headers.cmx ../server/ocsigen_extensions.cmx \ ../http/http_headers.cmx rewritemod.cmo : ../server/ocsigen_request_info.cmi \ ../server/ocsigen_extensions.cmi ../http/ocsigen_cookies.cmi rewritemod.cmx : ../server/ocsigen_request_info.cmx \ ../server/ocsigen_extensions.cmx ../http/ocsigen_cookies.cmx staticmod.cmo : ../server/ocsigen_request_info.cmi \ ../server/ocsigen_local_files.cmi ../baselib/ocsigen_lib.cmi \ ../http/ocsigen_http_frame.cmi ../http/ocsigen_http_com.cmi \ ../server/ocsigen_extensions.cmi ../http/http_headers.cmi staticmod.cmx : ../server/ocsigen_request_info.cmx \ ../server/ocsigen_local_files.cmx ../baselib/ocsigen_lib.cmx \ ../http/ocsigen_http_frame.cmx ../http/ocsigen_http_com.cmx \ ../server/ocsigen_extensions.cmx ../http/http_headers.cmx userconf.cmo : ../server/ocsigen_request_info.cmi ../baselib/ocsigen_lib.cmi \ ../server/ocsigen_extensions.cmi ../http/ocsigen_cookies.cmi userconf.cmx : ../server/ocsigen_request_info.cmx ../baselib/ocsigen_lib.cmx \ ../server/ocsigen_extensions.cmx ../http/ocsigen_cookies.cmx ocsigenserver-2.16.0/src/extensions/Makefile000066400000000000000000000043071357715257700211330ustar00rootroot00000000000000include ../../Makefile.config PACKAGE := \ bytes \ lwt.unix \ lwt_log \ ipaddr \ lwt_ssl \ lwt_react \ netstring \ netstring-pcre \ xml-light LIBS := -I ../baselib -I ../http -I ../server ${addprefix -package ,${PACKAGE}} OCAMLC := $(OCAMLFIND) ocamlc ${BYTEDBG} ${THREAD} OCAMLOPT := $(OCAMLFIND) ocamlopt ${OPTDBG} ${THREAD} OCAMLDOC := $(OCAMLFIND) ocamldoc OCAMLDEP := $(OCAMLFIND) ocamldep all: byte opt ### Extensions ### FILES := staticmod.ml \ cgimod.ml \ redirectmod.ml \ revproxy.ml \ extensiontemplate.ml \ accesscontrol.ml \ userconf.ml \ outputfilter.ml \ authbasic.ml \ rewritemod.ml \ extendconfiguration.ml \ ocsigen_comet.ml \ cors.ml \ ifeq "$(CAMLZIP)" "YES" FILES += deflatemod.ml deflatemod.cmo deflatemod.cmx: LIBS+=-package ${CAMLZIPNAME} endif byte:: ${FILES:.ml=.cmo} opt:: ${FILES:.ml=.cmx} ifeq "$(NATDYNLINK)" "YES" opt:: ${FILES:.ml=.cmxs} endif ### PostgreSQL ### ifeq "$(OCSIPERSISTPGSQL)" "YES" byte:: $(MAKE) -C ocsipersist-pgsql byte opt:: $(MAKE) -C ocsipersist-pgsql opt endif ### SQLite ### ifeq "$(OCSIPERSISTSQLITE)" "YES" byte:: $(MAKE) -C ocsipersist-sqlite byte opt:: $(MAKE) -C ocsipersist-sqlite opt endif ### DBM #### ifeq "$(OCSIPERSISTDBM)" "YES" byte:: $(MAKE) -C ocsipersist-dbm byte opt:: $(MAKE) -C ocsipersist-dbm opt endif ########## %.cmi: %.mli $(OCAMLC) ${LIBS} -c $< %.cmo: %.ml $(OCAMLC) ${LIBS} -c $< %.cmx: %.ml $(OCAMLOPT) ${LIBS} -c $< %.cmxs: %.cmx $(OCAMLOPT) -shared -linkall -o $@ $< ## Clean up clean: clean.local ${MAKE} -C ocsipersist-dbm clean ${MAKE} -C ocsipersist-sqlite clean ${MAKE} -C ocsipersist-pgsql clean clean.local: -rm -f *.cm* *.o *.a *.annot -rm -f ${PREDEP} distclean: clean.local -rm -f *~ \#* .\#* ${MAKE} -C ocsipersist-dbm distclean ${MAKE} -C ocsipersist-sqlite distclean ${MAKE} -C ocsipersist-pgsql distclean -rm -f .depend ## Dependencies depend: ${PREDEP} $(OCAMLDEP) ${LIBS} *.mli *.ml > .depend ${MAKE} -C ocsipersist-dbm depend ${MAKE} -C ocsipersist-sqlite depend ${MAKE} -C ocsipersist-pgsql depend FORCE: -include .depend ocsigenserver-2.16.0/src/extensions/accesscontrol.ml000066400000000000000000000375251357715257700226770ustar00rootroot00000000000000(* Ocsigen * http://www.ocsigen.org * Module accesscontrol.ml * Copyright (C) 2007 Vincent Balat, Stéphane Glondu * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) (** Filtering requests in the configuration file *) (* Then load it dynamically from Ocsigen's config file: *) open Ocsigen_lib open Printf open Lwt open Ocsigen_extensions open Ocsigen_http_frame let section = Lwt_log.Section.make "ocsigen:ext:access-control" (*****************************************************************************) (* Parsing a condition *) let rec parse_condition = function | Xml.Element ("ip", ["value", s], []) -> let prefix = try Ipaddr.Prefix.of_string_exn s with Ipaddr.Parse_error _ -> try let ip = Ipaddr.of_string_exn s in Ipaddr.Prefix.of_addr ip with _ -> badconfig "Bad ip/netmask [%s] in condition" s in (fun ri -> let r = Ipaddr.Prefix.mem (Lazy.force (Ocsigen_request_info.remote_ip_parsed ri)) prefix in if r then Lwt_log.ign_info_f ~section "IP: %a matches %s" (fun () -> Ocsigen_request_info.remote_ip) ri s else Lwt_log.ign_info_f ~section "IP: %a does not match %s" (fun () -> Ocsigen_request_info.remote_ip) ri s; r) | Xml.Element ("ip" as s, _, _) -> badconfig "Bad syntax for tag %s" s | Xml.Element ("port", ["value", s], []) -> let port = try int_of_string s with Failure _ -> badconfig "Bad port [%s] in condition" s in (fun ri -> let r = Ocsigen_request_info.server_port ri = port in if r then Lwt_log.ign_info_f ~section "PORT: %d accepted" port else Lwt_log.ign_info_f ~section "PORT: %a not accepted (%d expected)" (fun () ri -> string_of_int (Ocsigen_request_info.server_port ri)) ri port; r) | Xml.Element ("port" as s, _, _) -> badconfig "Bad syntax for tag %s" s | Xml.Element ("ssl", [], []) -> (fun ri -> let r = Ocsigen_request_info.ssl ri in if r then Lwt_log.ign_info ~section "SSL: accepted" else Lwt_log.ign_info ~section "SSL: not accepted"; r) | Xml.Element ("ssl" as s, _, _) -> badconfig "Bad syntax for tag %s" s | Xml.Element ("header", ["name", name; "regexp", reg], []) -> let regexp = try Netstring_pcre.regexp ("^"^reg^"$") with Failure _ -> badconfig "Bad regular expression [%s] in

    condition" reg in (fun ri -> let r = List.exists (fun a -> let r = Netstring_pcre.string_match regexp a 0 <> None in if r then Lwt_log.ign_info_f "HEADER: header %s matches %S" name reg; r) (try (Http_headers.find_all (Http_headers.name name) (Ocsigen_request_info.http_frame ri) .Ocsigen_http_frame.frame_header .Ocsigen_http_frame.Http_header.headers) with | Not_found -> []) in if not r then Lwt_log.ign_info_f "HEADER: header %s does not match %S" name reg; r) | Xml.Element ("header" as s, _, _) -> badconfig "Bad syntax for tag %s" s | Xml.Element ("method", ["value", s], []) -> let meth = try Framepp.method_of_string s with Failure _ -> badconfig "Bad method [%s] in condition" s in (fun ri -> let r = meth = Ocsigen_request_info.meth ri in if r then Lwt_log.ign_info_f ~section "METHOD: %a matches %s" (fun () ri -> Framepp.string_of_method (Ocsigen_request_info.meth ri)) ri s else Lwt_log.ign_info_f ~section "METHOD: %a does not match %s" (fun () ri -> Framepp.string_of_method (Ocsigen_request_info.meth ri)) ri s; r) | Xml.Element ("method" as s, _, _) -> badconfig "Bad syntax for tag %s" s | Xml.Element ("protocol", ["value", s], []) -> let pr = try Framepp.proto_of_string s with Failure _ -> badconfig "Bad protocol [%s] in condition" s in (fun ri -> let r = pr = Ocsigen_request_info.protocol ri in if r then Lwt_log.ign_info_f ~section "PROTOCOL: %a matches %s" (fun () ri -> Framepp.string_of_proto (Ocsigen_request_info.protocol ri)) ri s else Lwt_log.ign_info_f ~section "PROTOCOL: %a does not match %s" (fun () ri -> Framepp.string_of_proto (Ocsigen_request_info.protocol ri)) ri s; r) | Xml.Element ("protocol" as s, _, _) -> badconfig "Bad syntax for tag %s" s | Xml.Element ("path", ["regexp", s], []) -> let regexp = try Netstring_pcre.regexp ("^"^s^"$") with Failure _ -> badconfig "Bad regular expression [%s] in condition" s in (fun ri -> let r = Netstring_pcre.string_match regexp (Ocsigen_request_info.sub_path_string ri) 0 <> None in if r then Lwt_log.ign_info_f ~section "PATH: \"%a\" matches %S" (fun () ri -> Ocsigen_request_info.sub_path_string ri) ri s else Lwt_log.ign_info_f ~section "PATH: \"%a\" does not match %S" (fun () ri -> Ocsigen_request_info.sub_path_string ri) ri s; r) | Xml.Element ("path" as s, _, _) -> badconfig "Bad syntax for tag %s" s | Xml.Element ("and", [], sub) -> let sub = List.map parse_condition sub in (fun ri -> List.for_all (fun cond -> cond ri) sub) | Xml.Element ("and" as s, _, _) -> badconfig "Bad syntax for tag %s" s | Xml.Element ("or", [], sub) -> let sub = List.map parse_condition sub in (fun ri -> List.exists (fun cond -> cond ri) sub) | Xml.Element ("or" as s, _, _) -> badconfig "Bad syntax for tag %s" s | Xml.Element ("not", [], [sub]) -> let sub = parse_condition sub in (fun ri -> not (sub ri)) | Xml.Element ("not" as s, _, _) -> badconfig "Bad syntax for tag %s" s | _ -> badconfig "Bad syntax for condition" (*****************************************************************************) (* Parsing filters *) let comma_space_regexp = Netstring_pcre.regexp "\ *,\ *" let parse_config parse_fun = function | Xml.Element ("if", [], sub) -> let (condition, sub) = match sub with | cond::q -> (parse_condition cond, q) | _ -> badconfig "Bad condition in " in let (ithen, sub) = match sub with | Xml.Element("then", [], ithen)::q -> (parse_fun ithen, q) | _ -> badconfig "Bad branch in " in let (ielse, sub) = match sub with | Xml.Element ("else", [], ielse)::([] as q) -> (parse_fun ielse, q) | [] -> (parse_fun [], []) | _ -> badconfig "Bad branch in " in (function | Ocsigen_extensions.Req_found (ri, _) | Ocsigen_extensions.Req_not_found (_, ri) -> Lwt.return (if condition ri.request_info then begin Lwt_log.ign_info ~section "COND: going into branch"; Ocsigen_extensions.Ext_sub_result ithen end else begin Lwt_log.ign_info ~section "COND: going into branch, if any"; Ocsigen_extensions.Ext_sub_result ielse end)) | Xml.Element ("if" as s, _, _) -> badconfig "Bad syntax for tag %s" s | Xml.Element ("notfound", [], []) -> (fun rs -> Lwt_log.ign_info ~section "NOT_FOUND: taking in charge 404"; Lwt.return (Ocsigen_extensions.Ext_stop_all (Ocsigen_cookies.Cookies.empty, 404))) | Xml.Element ("notfound" as s, _, _) -> badconfig "Bad syntax for tag %s" s | Xml.Element ("nextsite", [], []) -> (function | Ocsigen_extensions.Req_found (_, r) -> Lwt.return (Ocsigen_extensions.Ext_found_stop (fun () -> Lwt.return r)) | Ocsigen_extensions.Req_not_found (err, ri) -> Lwt.return (Ocsigen_extensions.Ext_stop_site (Ocsigen_cookies.Cookies.empty, 404))) | Xml.Element ("nexthost", [], []) -> (function | Ocsigen_extensions.Req_found (_, r) -> Lwt.return (Ocsigen_extensions.Ext_found_stop (fun () -> Lwt.return r)) | Ocsigen_extensions.Req_not_found (err, ri) -> Lwt.return (Ocsigen_extensions.Ext_stop_host (Ocsigen_cookies.Cookies.empty, 404))) | Xml.Element ("nextsite" as s, _, _) -> badconfig "Bad syntax for tag %s" s | Xml.Element ("stop", [], []) -> (function | Ocsigen_extensions.Req_found (_, r) -> Lwt.return (Ocsigen_extensions.Ext_found_stop (fun () -> Lwt.return r)) | Ocsigen_extensions.Req_not_found (err, ri) -> Lwt.return (Ocsigen_extensions.Ext_stop_all (Ocsigen_cookies.Cookies.empty, 404))) | Xml.Element ("stop" as s, _, _) -> badconfig "Bad syntax for tag %s" s | Xml.Element ("forbidden", [], []) -> (fun rs -> Lwt_log.ign_info ~section "FORBIDDEN: taking in charge 403"; Lwt.return (Ocsigen_extensions.Ext_stop_all (Ocsigen_cookies.Cookies.empty, 403))) | Xml.Element ("forbidden" as s, _, _) -> badconfig "Bad syntax for tag %s" s | Xml.Element ("iffound", [], sub) -> let ext = parse_fun sub in (function | Ocsigen_extensions.Req_found (_, _) -> Lwt.return (Ext_sub_result ext) | Ocsigen_extensions.Req_not_found (err, ri) -> Lwt.return (Ocsigen_extensions.Ext_next err)) | Xml.Element ("iffound" as s, _, _) -> badconfig "Bad syntax for tag %s" s | Xml.Element ("ifnotfound", [], sub) -> let ext = parse_fun sub in (function | Ocsigen_extensions.Req_found (_, r) -> Lwt.return (Ocsigen_extensions.Ext_found (fun () -> Lwt.return r)) | Ocsigen_extensions.Req_not_found (err, ri) -> Lwt.return (Ext_sub_result ext)) | Xml.Element ("ifnotfound", [("code", s)], sub) -> let ext = parse_fun sub in let r = Netstring_pcre.regexp ("^"^s^"$") in (function | Ocsigen_extensions.Req_found (_, r) -> Lwt.return (Ocsigen_extensions.Ext_found (fun () -> Lwt.return r)) | Ocsigen_extensions.Req_not_found (err, ri) -> if Netstring_pcre.string_match r (string_of_int err) 0 <> None then Lwt.return (Ext_sub_result ext) else Lwt.return (Ocsigen_extensions.Ext_next err)) | Xml.Element ("ifnotfound" as s, _, _) -> badconfig "Bad syntax for tag %s" s | Xml.Element ("allow-forward-for", param, _) -> let apply request code = Lwt_log.ign_info ~section "Allowed proxy"; let request = try let header = Http_headers.find Http_headers.x_forwarded_for (Ocsigen_request_info.http_frame request.request_info).frame_header.Http_header.headers in match Netstring_pcre.split comma_space_regexp header with | [] | [_] -> Lwt_log.ign_info_f ~section "Malformed X-Forwarded-For field: %s" header; request | original_ip::proxies -> let last_proxy = List.last proxies in let proxy_ip = Ipaddr.of_string_exn last_proxy in let equal_ip = proxy_ip = Lazy.force (Ocsigen_request_info.remote_ip_parsed request.request_info) in let need_equal_ip = match param with | [] -> false | ["check-equal-ip",b] -> ( try bool_of_string b with Invalid_argument _ -> badconfig "Bad syntax for argument of tag allow-forward-for" ) | _ -> badconfig "Bad syntax for argument of tag allow-forward-for" in if equal_ip || (not need_equal_ip) then { request with request_info = (Ocsigen_request_info.update request.request_info ~remote_ip:original_ip ~remote_ip_parsed:(lazy (Ipaddr.of_string_exn original_ip)) ~forward_ip:proxies ()) } else (* the announced ip of the proxy is not its real ip *) ( Lwt_log.ign_warning_f ~section "X-Forwarded-For: host ip ( %a ) does not match the header ( %s )" (fun () -> Ocsigen_request_info.remote_ip) request.request_info header; request ) with | Not_found -> request in Lwt.return (Ocsigen_extensions.Ext_continue_with ( request, Ocsigen_cookies.Cookies.empty, code )) in (function | Ocsigen_extensions.Req_found (request, resp) -> apply request (Ocsigen_http_frame.Result.code resp) | Ocsigen_extensions.Req_not_found (code, request) -> apply request code) | Xml.Element ("allow-forward-proto", _, _) -> let apply request code = Lwt_log.ign_info ~section "Allowed proxy for ssl"; let request = try let header = Http_headers.find Http_headers.x_forwarded_proto (Ocsigen_request_info.http_frame request.request_info) .frame_header.Http_header.headers in match String.lowercase header with | "http" -> { request with request_info = (Ocsigen_request_info.update request.request_info ~ssl:false ()) } | "https" -> { request with request_info = (Ocsigen_request_info.update request.request_info ~ssl:true ()) } | _ -> Lwt_log.ign_info_f ~section "Malformed X-Forwarded-Proto field: %s" header; request with | Not_found -> request in Lwt.return (Ocsigen_extensions.Ext_continue_with ( request, Ocsigen_cookies.Cookies.empty, code )) in (function | Ocsigen_extensions.Req_found (request, resp) -> apply request (Ocsigen_http_frame.Result.code resp) | Ocsigen_extensions.Req_not_found (code, request) -> apply request code) | Xml.Element (t, _, _) -> raise (Bad_config_tag_for_extension t) | _ -> badconfig "(accesscontrol extension) Bad data" (*****************************************************************************) (** Registration of the extension *) let () = register_extension ~name:"accesscontrol" ~fun_site:(fun _ _ _ _ -> parse_config) ~user_fun_site:(fun _ _ _ _ _ -> parse_config) () ocsigenserver-2.16.0/src/extensions/accesscontrol.mli000066400000000000000000000017021357715257700230340ustar00rootroot00000000000000(* Ocsigen * http://www.ocsigen.org * Module accesscontrol.ml * Copyright (C) 2007 Vincent Balat, Stéphane Glondu * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) val parse_condition : Xml.xml -> Ocsigen_extensions.request_info -> bool ocsigenserver-2.16.0/src/extensions/authbasic.ml000066400000000000000000000120601357715257700217630ustar00rootroot00000000000000(* Ocsigen * http://www.ocsigen.org * Module authbasic.ml * Copyright (C) 2008 Stéphane Glondu * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) open Printf open Lwt open Ocsigen_extensions open Ocsigen_http_frame let section = Lwt_log.Section.make "ocsigen:ext:access-control" (*****************************************************************************) (* Management of basic authentication methods *) exception Bad_config_tag_for_auth of string let register_basic_authentication_method, get_basic_authentication_method = let fun_auth = ref (fun config -> raise (Bad_config_tag_for_auth "")) in (********* register_basic_authentication_method *********) (fun new_fun_auth -> let old_fun_auth = !fun_auth in fun_auth := (fun config -> try old_fun_auth config with | Bad_config_tag_for_auth c -> new_fun_auth config)), (********* get_basic_authentication_method *********) (fun config -> !fun_auth config) (*****************************************************************************) (* Basic authentication with a predefined login/password (example) *) let _ = let open Xml in register_basic_authentication_method (function | Element ("plain", ["login", login; "password", password], _) -> (fun l p -> Lwt.return (login = l && password = p)) | _ -> raise (Bad_config_tag_for_extension "not for htpasswd")) (*****************************************************************************) let gen ~realm ~auth rs = match rs with | Ocsigen_extensions.Req_not_found (err, ri) -> let reject () = let h = Http_headers.add (Http_headers.name "WWW-Authenticate") (sprintf "Basic realm=\"%s\"" realm) Http_headers.empty in Lwt_log.ign_info ~section "AUTH: invalid credentials!"; fail (Http_error.Http_exception (401, None, Some h)) in begin try let (login, password) = let credentials = Http_headers.find (Http_headers.name "Authorization") (Ocsigen_request_info.http_frame ri.request_info) .Ocsigen_http_frame.frame_header .Ocsigen_http_frame.Http_header.headers in let encoded = let n = String.length credentials in if n > 6 && String.sub credentials 0 6 = "Basic " then String.sub credentials 6 (n-6) else failwith "credentials" in let decoded = Netencoding.Base64.decode encoded in let i = String.index decoded ':' in (String.sub decoded 0 i, String.sub decoded (i+1) (String.length decoded - (i+1))) in auth login password >>= (fun r -> if r then begin Lwt_log.ign_info ~section "AUTH: invalid credentials!"; Lwt.return (Ocsigen_extensions.Ext_next err) end else reject ()) with | Not_found -> reject () | exn -> Lwt_log.ign_info ~exn ~section "AUTH: Invalid Authorization header"; fail (Ocsigen_http_error (Ocsigen_cookies.Cookies.empty, 400)) end | Ocsigen_extensions.Req_found (ri, r) -> Lwt.return Ocsigen_extensions.Ext_do_nothing let parse_config element = let realm_ref = ref "" in let rest_ref = ref [] in Ocsigen_extensions.( Configuration.process_element ~in_tag:"host" ~other_elements:(fun t _ _ -> raise (Bad_config_tag_for_extension t)) ~elements:[ Configuration.element ~name:"authbasic" ~attributes:[ Configuration.attribute ~name:"realm" ~obligatory:true (fun s -> realm_ref := s) ] ~other_elements:(fun name attrs content -> rest_ref := Xml.Element (name, attrs, content) :: !rest_ref) ()] element ); let realm = !realm_ref in let auth = match !rest_ref with | [ x ] -> get_basic_authentication_method x | _ -> badconfig "Bad syntax for tag authbasic" in gen ~realm ~auth (*****************************************************************************) (** Registration of the extension *) let () = register_extension ~name:"authbasic" ~fun_site:(fun _ _ _ _ _ -> parse_config) ~user_fun_site:(fun _ _ _ _ _ _ -> parse_config) () ocsigenserver-2.16.0/src/extensions/authbasic.mli000066400000000000000000000053641357715257700221450ustar00rootroot00000000000000(* Ocsigen * http://www.ocsigen.org * Module authbasic.mli * Copyright (C) 2008 Stéphane Glondu * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) (** Module [Authbasic]: Basic HTTP Authentication. *) (** This module implements Basic HTTP Authentication as described in {{:http://www.ietf.org/rfc/rfc2617.txt}RFC 2617}. It can be used to add an authentication layer to sites with no built-in authentication (e.g. static files). Beware, passwords are transmitted in cleartext with this scheme, so the medium should be secured somehow (by e.g. SSL). This module implements only the HTTP-related part of the protocol, and is meant to be extended with various authentication schemes. A very naive one (authentication with a single user/password, given in the configuration file) is provided. *) val register_basic_authentication_method : (Xml.xml -> string -> string -> bool Lwt.t) -> unit (** This function registers an authentication plugin: it adds a new parser to the list of available authentication schemes. A parser takes as argument an XML tree (corresponding to the first son of an element in the configuration file) and returns an authentication function [f]. [f] will be called for each request with the supplied user and password and should return (cooperatively) a boolean telling whether access is granted or not. Exceptions are handled the same way as for extension parsers. The element must have a {i realm} attribute, giving some identifier to the resource which is protected (several resources on the same hostname can share the same realm). This gives a general customization scheme "for free" from the point of view of plugin developers and is totally transparent to the plugin. *) val get_basic_authentication_method : Xml.xml -> string -> string -> bool Lwt.t (** This function combines all the parsers registered with [register_basic_authentication_method]. It might be useful for other extensions. Not for the casual user. *) ocsigenserver-2.16.0/src/extensions/cgimod.ml000066400000000000000000000542441357715257700212740ustar00rootroot00000000000000(* Ocsigen * http://www.ocsigen.org * Module cgimod.ml * Copyright (C) 2007 Jérôme Velleine - Gabriel Kerneis * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) (** Module CGI for Ocsigen *) (* TODO - nph- scripts *) open Ocsigen_lib open Lwt open Ocsigen_extensions open Ocsigen_http_frame open Ocsigen_http_com open Ocsigen_senders let section = Lwt_log.Section.make "ocsigen:ext:cgimod" module Regexp = Netstring_pcre exception Failed_403 exception Failed_404 exception Not_concerned exception CGI_Timeout exception CGI_Error of exn let cgitimeout = ref 30 (*****************************************************************************) (* The table of cgi pages for each virtual server *) type reg = { regexp:Regexp.regexp; (** regexp of the script url *) doc_root: Ocsigen_extensions.ud_string; (** physical directory of the script (regexp) *) script: Ocsigen_extensions.ud_string; (** physical name of the script (regexp) *) path: string; (** path of the script *) path_info: string; (** path_info environment variable *) exec:string option; (** binary to execute the script with (optional) *) env:(string * string) list (** environment variables *) } (*****************************************************************************) let environment= ["CONTENT_LENGTH=%d"; "CONTENT_TYPE"; "DOCUMENT_ROOT"; "GATEWAY_INTERFACE"; "HTTP_COOKIE"; "HTTP_HOST"; "HTTP_REFERER"; "HTTP_USER_AGENT"; "PATH_INFO"; "PATH_TRANSLATED"; "QUERY_STRING"; "REMOTE_PORT"; "REMOTE_ADDR"; "REQUEST_METHOD%s"; "SCRIPT_NAME"; "SCRIPT_FILENAME"; "SERVER_NAME"; "SERVER_PORT"; "SERVER_PROTOCOL"; "SERVER_SOFTWARE"] (*****************************************************************************) let string_conform s = match String.length s with | 0 -> "/" | n -> match s.[0], s.[n - 1] with | '/' ,'/' -> s | _, '/' -> "/"^s | '/', _ -> s^"/" | _, _ -> "/"^s^"/" let string_conform0 s = try match s.[0] with | '/' -> s | _ -> "/"^s with Invalid_argument _ -> "/" let string_conform1 s = try match s.[String.length s - 1] with | '/' -> s | _ -> s^"/" with Invalid_argument _ -> "/" let string_conform2 s = match String.length s with | 0 | 1 when s = "/" -> "" | n -> match s.[0], s.[n - 1] with | '/', '/' -> String.sub s 1 (n-1) | _, '/' -> s | '/', _ -> (String.sub s 1 (n-1))^"/" | _, _ -> s^"/" (* split a string in two parts, according to a regexp *) let split_regexp r s = match Regexp.string_match r s 0 with | None -> None (* the beginning of the string doesn't match the regexp *) | Some result -> let (split,l) = Regexp.match_end result, String.length s in let s' = Regexp.first_chars s split in let s'' = Regexp.last_chars s (l - split) in Some (s',s'') (** permet de recuperer le fichier correspondant a l url *) let find_cgi_page request reg sub_path = let find_file (filename, re, doc_root) = (* See also module Files in eliom.ml *) Lwt_log.ign_info_f ~section "Testing %S." filename; try let stat = Unix.LargeFile.stat filename in let filename = if (stat.Unix.LargeFile.st_kind = Unix.S_DIR) then (Lwt_log.ign_info ~section "Cigmod: this is a directory."; raise (Ocsigen_Is_a_directory (new_url_of_directory_request request))) else filename in Lwt_log.ign_info_f ~section "Looking for %S." filename; if (stat.Unix.LargeFile.st_kind = Unix.S_REG) then begin match re.exec with | None -> Unix.access filename [Unix.X_OK]; (filename, re, doc_root) | Some exec -> Unix.access filename [Unix.R_OK]; (filename, re, doc_root) end else raise Failed_403 with | Unix.Unix_error (Unix.ENOENT, _, _) -> raise Failed_404 in let sub_path = (Url.string_of_url_path ~encode:true sub_path) in match split_regexp reg.regexp sub_path with | None -> raise Failed_404 | Some (path', path_info) -> let path'' = reg.path^path' in try let dr = Ocsigen_extensions.replace_user_dir reg.regexp reg.doc_root path' in let sc = Ocsigen_extensions.replace_user_dir reg.regexp reg.script path' in let reg = {reg with path = path''; path_info= string_conform0 path_info} in find_file (dr^sc, reg, dr) with Ocsigen_extensions.NoSuchUser -> raise Failed_404 (*****************************************************************************) (** permet de creer le tableau des variables d environnement *) (*XXX Is this documented anywhere?*) let suitable_header = Regexp.regexp "[a-zA-Z-]+" let hyphen = Regexp.regexp_string "-" (* Headers processed separately when setting CGI's variable *) let exclude_headers = Http_headers.NameHtbl.create 10 let _ = List.iter (fun x -> Http_headers.NameHtbl.add exclude_headers (Http_headers.name x) ()) ["Content-type"; "Authorization"; "Content-length"; (*"Referer"; "Host"; "Cookie"*) ] let array_environment filename re doc_root ri hostname = let header = (Ocsigen_request_info.http_frame ri).Ocsigen_http_frame.frame_header in let opt = function | None -> "" | Some a -> a and opt_int = function | None -> "0" | Some a -> Int64.to_string a in let meth = match Http_header.get_firstline header with | Http_header.Query (meth, _) -> Framepp.string_of_method meth | _ -> raise Ocsigen_Bad_Request in (* Rule : the header lines received from the client, if any, are placed into the * environment with the prefix HTTP_ followed by the header name. Any - characters * in the header name are changed to _ characters. The server may exclude any * headers which it has already processed, such as Authorization, Content-type, and * Content-length. If necessary, the server may choose to exclude any or all of * these headers if including them would exceed any system environment limits. *) let additionnal_headers = let headers = List.filter (fun (h,_) -> Regexp.string_match suitable_header (Http_headers.name_to_string h) 0 <> None && not (Http_headers.NameHtbl.mem exclude_headers h)) (Http_headers.fold (fun n vl rem -> (n, String.concat "," vl) :: rem) (Http_header.get_headers header) []) in let transform (h,v) = let h' = Regexp.global_replace hyphen "_" (Http_headers.name_to_string h) in Printf.sprintf "HTTP_%s=%s" (String.uppercase h') v in List.map transform headers in List.concat [ (* Let's follow CGI spec : http://hoohoo.ncsa.uiuc.edu/cgi/env.html *) (* Not request-specific variables *) [Printf.sprintf "SERVER_NAME=%s" hostname; Printf.sprintf "SERVER_SOFTWARE=%s" Ocsigen_config.full_server_name ; "GATEWAY_INTERFACE=CGI/1.1"] ; (* Request-specific variables *) ["SERVER_PROTOCOL=HTTP/1.1"; Printf.sprintf "SERVER_PORT=%s" (string_of_int (Ocsigen_request_info.server_port ri)); Printf.sprintf "REQUEST_METHOD=%s" meth; Printf.sprintf "PATH_INFO=%s" re.path_info; Printf.sprintf "PATH_TRANSLATED=" ; (* PATH_INFO virtual -> physical; unclear, so don't set *) Printf.sprintf "SCRIPT_NAME=%s" re.path; Printf.sprintf "QUERY_STRING=%s" (opt (Ocsigen_request_info.get_params_string ri)); Printf.sprintf "REMOTE_ADDR=%s" (Ocsigen_request_info.remote_ip ri); (* no REMOTE_HOST: implies reverse DNS resolution *) (* neither AUTH_TYPE, REMOTE_USER nor REMOTE_IDENT: implies authentication *) Printf.sprintf "CONTENT_LENGTH=%s" (opt_int (Ocsigen_request_info.content_length ri)); Printf.sprintf "CONTENT_TYPE=%s" (opt (Ocsigen_request_info.content_type_string ri))] ; (* Additional headers, coming from the client *) [(* Document_root is defined by Apache but not in the CGI's spec *) Printf.sprintf "DOCUMENT_ROOT=%s" doc_root; (* Should be retrieved from additionnal_headers Printf.sprintf "HTTP_COOKIE=%s" (opt (Lazy.force ri.ri_cookies_string)); Printf.sprintf "HTTP_HOST=%s" (opt ri.ri_host); Printf.sprintf "HTTP_REFERER=%s" (opt (Lazy.force ri.ri_referer)); *) (* Neither in the CGI's spec nor in the HTTP headers but used, e.g., by PHP *) Printf.sprintf "REMOTE_PORT=%d" (Ocsigen_request_info.remote_port ri); Printf.sprintf "REQUEST_URI=%s" (Ocsigen_request_info.url_string ri); (* FIXME: URI instead of URL ? *) Printf.sprintf "SCRIPT_FILENAME=%s" filename ] ; additionnal_headers ] (*****************************************************************************) let rec set_env_list=function | [] -> [] | (vr, vl) :: l -> (vr^"="^vl) :: set_env_list l (** launch the process *) let create_process_cgi filename ri post_out cgi_in err_in re doc_root hostname = let envir = Array.of_list ( (array_environment filename re doc_root ri hostname)@(set_env_list re.env)) in match re.exec with | None -> Unix.create_process_env "/bin/sh" [|"/bin/sh"; "-c"; filename|] envir post_out cgi_in err_in | Some r -> Unix.create_process_env "/bin/sh" [|"/bin/sh"; "-c"; (r^" "^filename)|] envir post_out cgi_in err_in (* Copied from deprecated [Lwt_chan]. *) let lwt_chan_input_line ic = let rec loop buf = Lwt_io.read_char_opt ic >>= function | None | Some '\n' -> Lwt.return (Buffer.contents buf) | Some char -> Buffer.add_char buf char; loop buf in Lwt_io.read_char_opt ic >>= function | Some '\n' -> Lwt.return "" | Some char -> let buf = Buffer.create 128 in Buffer.add_char buf char; loop buf | None -> Lwt.fail End_of_file (** This function makes it possible to launch a cgi script *) let recupere_cgi head re doc_root filename ri hostname = try (* Create the three pipes to communicate with the CGI script: *) let (post_out, post_in) = Lwt_unix.pipe_out () in let (cgi_out, cgi_in) = Lwt_unix.pipe_in () in let (err_out, err_in) = Lwt_unix.pipe_in () in (* I don't want to give them to the script: *) Lwt_unix.set_close_on_exec cgi_out; Lwt_unix.set_close_on_exec post_in; Lwt_unix.set_close_on_exec err_out; (* Launch the CGI script *) let pid = create_process_cgi filename ri post_out cgi_in err_in re doc_root hostname in Unix.close cgi_in; Unix.close post_out; Unix.close err_in; let is_running = ref true in (* A timeout for CGI scripts *) (* For now a timeout for the whole process. We may want to reset the timeout each time the CGI writes something. *) let timeout = Lwt_timeout.create !cgitimeout (fun () -> try Lwt_unix.abort cgi_out CGI_Timeout; Lwt_unix.abort post_in CGI_Timeout; Lwt_unix.abort err_out CGI_Timeout; if !is_running then begin Unix.kill Sys.sigterm pid; ignore (Lwt_unix.sleep 1. >>= fun () -> if !is_running then Unix.kill Sys.sigkill pid; return ()) end with Unix.Unix_error (Unix.ESRCH, _, _) -> () ) in Lwt_timeout.start timeout; (* A thread giving POST data to the CGI script: *) let post_in_ch = Lwt_io.of_fd ~mode:Lwt_io.output post_in in ignore (catch (fun () -> (match (Ocsigen_request_info.http_frame ri).Ocsigen_http_frame.frame_content with | None -> Lwt_unix.close post_in | Some content_post -> Ocsigen_http_com.write_stream post_in_ch content_post >>= fun () -> Lwt_io.flush post_in_ch >>= fun () -> Lwt_unix.close post_in )) (*XXX Check possible errors! *) (function | Unix.Unix_error (Unix.EPIPE, _, _) -> Lwt_unix.close post_in | exn -> Lwt_log.ign_warning ~exn "Unexpected at Cgimod.recupere_cgi (1)"; Lwt_unix.close post_in )); (* A thread listening the error output of the CGI script and writing them in warnings.log *) let err_channel = Lwt_io.of_fd ~mode:Lwt_io.input err_out in let rec get_errors () = lwt_chan_input_line err_channel >>= fun err -> Lwt_log.ign_warning ~section err; get_errors () in ignore (catch get_errors (function | End_of_file -> Lwt_unix.close err_out | exn -> Lwt_log.ign_warning ~exn "Unexpected at Cgimod.recupere_cgi (2)"; Lwt_unix.close err_out)); (* This threads terminates, as you can see by doing: in ignore (catch get_errors (fun _ -> print_endline "the end"; Lwt_unix.close err_out; return ())); *) (* A thread waiting the end of the process. if the process terminates with an error, we raise CGI_Error *) ignore (Lwt_unix.waitpid [] pid >>= fun (_, status) -> is_running := false; Lwt_timeout.stop timeout; (* All "read" will return 0, and "write" will raise "Broken Pipe" *) (match status with | Unix.WEXITED 0 -> () | Unix.WEXITED i -> Lwt_log.ign_warning_f ~section "Exited with code %d" i | Unix.WSIGNALED i -> Lwt_log.ign_warning_f ~section "Killed by signal %d" i | Unix.WSTOPPED i -> (* Cannot occur without Unix.WUNTRACED wait_flag *) assert false ); Lwt.return ()); (* A thread getting the result of the CGI script *) let receiver = Ocsigen_http_com.create_receiver (Ocsigen_config.get_server_timeout ()) Ocsigen_http_com.Nofirstline (Lwt_ssl.plain cgi_out) in catch (fun () -> Ocsigen_http_com.get_http_frame ~head receiver >>= fun http_frame -> return (http_frame, fun _ -> Lwt_unix.close cgi_out)) (fun e -> Lwt_unix.close cgi_out >>= fun () -> fail e); with e -> fail e (** return the content of the frame *) let get_content str = match str.Ocsigen_http_frame.frame_content with | None -> Ocsigen_stream.make (fun () -> Ocsigen_stream.empty None) | Some c -> c (*****************************************************************************) let rec parse_global_config = function | [] -> () | (Xml.Element ("cgitimeout", [("value", s)], []))::[] -> cgitimeout := int_of_string s | _ -> raise (Error_in_config_file ("Unexpected content inside cgimod config")) (*****************************************************************************) let gen reg = function | Ocsigen_extensions.Req_found _ -> Lwt.return Ocsigen_extensions.Ext_do_nothing | Ocsigen_extensions.Req_not_found (err, ri) -> catch (* Is it a cgi page? *) (fun () -> Lwt_log.ign_info ~section "Is it a cgi file?"; let (filename, re, doc_root) = find_cgi_page ri reg (Ocsigen_request_info.sub_path ri.request_info) in recupere_cgi (Ocsigen_request_info.meth ri.request_info = Http_header.HEAD) re doc_root filename ri.request_info (Ocsigen_extensions.get_hostname ri) >>= fun (frame, finalizer) -> let header = frame.Ocsigen_http_frame.frame_header in let content = get_content frame in Ocsigen_stream.add_finalizer content finalizer; Lwt.catch (fun () -> let code = try let status = Ocsigen_http_frame.Http_header.get_headers_value header Http_headers.status in if String.length status < 3 then raise (Failure "Cgimod.gen"); Some (int_of_string (String.sub status 0 3)) with | Not_found -> None | Failure _ -> raise (CGI_Error (Failure "Bad Status line in header")) in let loc = try Some (Ocsigen_http_frame.Http_header.get_headers_value header Http_headers.location) with Not_found -> None in match code, loc with | None, Some loc -> Ocsigen_stream.finalize content `Success >>= fun () -> if loc <> "" && loc.[0] = '/' then Lwt.return (Ext_retry_with ({ ri with request_info = ri_of_url loc ri.request_info }, Ocsigen_cookies.Cookies.empty)) else let default_result = Ocsigen_http_frame.Result.default () in Lwt.return (Ext_found (fun () -> Lwt.return (Ocsigen_http_frame.Result.update default_result ~code:301 ~location:(Some loc) ()))) | _, _ -> let code = match code with | None -> 200 | Some c -> c in let default_result = Ocsigen_http_frame.Result.default () in (*VVV Warning: this is really late to make the return Ext_found ... *) (*VVV But the extension may also answer Ext_retry_with ... *) (*VVV and the other extensions may receive requests in wrong order ... *) Lwt.return (Ext_found (fun () -> (*VVV NO! If sending is interrupted, we probably must do something else! *) Ocsigen_stream.add_finalizer content (fun outcome -> match outcome with `Failure -> frame.Ocsigen_http_frame.frame_abort () | `Success -> Lwt.return ()); Lwt.return (Ocsigen_http_frame.Result.update default_result ~content_length:None ~stream:(content, None) ~location:loc ~headers: (Http_headers.replace_opt Http_headers.status None header.Http_header.headers) ~code:code ())))) (fun e -> Ocsigen_stream.finalize content `Failure >>= fun () -> Lwt.fail e)) (function | Unix.Unix_error (Unix.EACCES,_,_) | Lost_connection _ as e -> fail e | Unix.Unix_error (Unix.ENOENT,_,_) -> return (Ext_next 404) | Failed_403 -> return (Ext_next 403) | Failed_404 -> return (Ext_next 404) | Not_concerned -> return (Ext_next err) | e -> fail e) (*****************************************************************************) (** Parsing of config file *) let rec set_env = function | [] -> [] | (Xml.Element("setenv", [("var",vr);("val",vl)], []))::l -> if List.mem vr environment then (Lwt_log.ign_info_f ~section "Variable no set %s" vr; set_env l) else (vr,vl)::set_env l | _ :: l -> raise (Error_in_config_file "Bad config tag for ") let parse_config _ path _ _ = function | Xml.Element ("cgi", atts, l) -> let good_root r = Regexp.quote (string_conform2 r) in let dir = match atts with | [] -> raise (Error_in_config_file "attributes expected for ") | [("root",r);("dir", s)] -> { regexp= Regexp.regexp ("^"^(good_root r)^"([^/]*)"); doc_root= Ocsigen_extensions.parse_user_dir (string_conform1 s); script= Ocsigen_extensions.parse_user_dir "\\1"; path= string_conform (Url.string_of_url_path ~encode:true path); path_info=""; exec=None; env=set_env l} | ("regexp", s)::("dir",d)::("script",t)::q -> { regexp=Regexp.regexp ("^"^s); doc_root= Ocsigen_extensions.parse_user_dir (string_conform1 d); script= Ocsigen_extensions.parse_user_dir t; path= string_conform (Url.string_of_url_path ~encode:true path); path_info=""; (* unknown for the moment *) exec= (match q with |[] -> None |[("exec",x)] -> Some(x) |_ -> raise (Error_in_config_file "Wrong attributes for ")) ; env=set_env l} | _ -> raise (Error_in_config_file "Wrong attributes for ") in gen dir | Element (t, _, _) -> raise (Bad_config_tag_for_extension t) | _ -> raise (Error_in_config_file "Unexpected data in config file") (*****************************************************************************) (** Registration of the extension *) let () = register_extension ~name:"cgimod" ~fun_site:(fun _ -> parse_config) ~init_fun:parse_global_config () ocsigenserver-2.16.0/src/extensions/cors.ml000066400000000000000000000146031357715257700207730ustar00rootroot00000000000000(* Ocsigen * http://www.ocsigen.org * Module accesscontrol.ml * Copyright (C) 2011 Pierre Chambart * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) (** Handling Cross-Origin Resource Sharing headers *) open Ocsigen_lib open Lwt let section = Lwt_log.Section.make "ocsigen:ext:cors" (*** MAIN FUNCTION ***) let default_frame () = (Ocsigen_http_frame.Result.update (Ocsigen_http_frame.Result.default ()) ~code:200 ~content_length:(Some 0L) ()) type config = { allowed_method : Ocsigen_http_frame.Http_header.http_method list option; (* None means: all method are accepted *) allowed_credentials : bool; max_age : int option; exposed_headers : string list } exception Refused let add_headers config rq response = match Lazy.force (Ocsigen_request_info .origin rq.Ocsigen_extensions.request_info) with | None -> return Ocsigen_extensions.Ext_do_nothing | Some origin -> Lwt_log.ign_info_f ~section "request with origin: %s" origin; let res_headers = (Ocsigen_http_frame.Result.headers response) in let res_headers = Http_headers.add Http_headers.access_control_allow_origin origin res_headers in let res_headers = if config.allowed_credentials then Http_headers.add Http_headers.access_control_allow_credentials "true" res_headers else res_headers in let res_headers = let req_method = Lazy.force (Ocsigen_request_info .access_control_request_method rq.Ocsigen_extensions.request_info) in match req_method with None -> res_headers | Some request_method -> let allowed_method = match config.allowed_method with | None -> true | Some l -> try List.mem (Framepp.method_of_string request_method) l with | _ -> false in if allowed_method then Http_headers.add Http_headers.access_control_allow_methods request_method res_headers else (Lwt_log.ign_info ~section "Method refused"; raise Refused) in let res_headers = let req_headers = Lazy.force (Ocsigen_request_info .access_control_request_headers rq.Ocsigen_extensions.request_info) in match req_headers with None -> res_headers | Some request_headers -> Http_headers.add Http_headers.access_control_allow_headers (String.concat ", " request_headers) res_headers in let res_headers = match config.max_age with | None -> res_headers | Some max_age -> Http_headers.add Http_headers.access_control_max_age (string_of_int max_age) res_headers in let res_headers = match config.exposed_headers with | [] -> res_headers | _ -> Http_headers.add Http_headers.access_control_expose_headers (String.concat ", " config.exposed_headers) res_headers in return (Ocsigen_extensions.Ext_found (fun () -> return (Ocsigen_http_frame.Result.update response ~headers:res_headers ()))) let main config = function | Ocsigen_extensions.Req_not_found (_, rq) -> begin match (Ocsigen_request_info.meth rq.Ocsigen_extensions.request_info) with | Ocsigen_http_frame.Http_header.OPTIONS -> Lwt_log.ign_info ~section "OPTIONS request"; begin try add_headers config rq (default_frame ()) with | Refused -> Lwt_log.ign_info ~section "Refused request"; Lwt.return Ocsigen_extensions.Ext_do_nothing end | _ -> Lwt.return Ocsigen_extensions.Ext_do_nothing end | Ocsigen_extensions.Req_found (rq,response) -> Lwt_log.ign_info ~section "answered request"; add_headers config rq response (*** EPILOGUE ***) (* registering extension *) let comma_space_regexp = Netstring_pcre.regexp "[[:blank:]\n]*,[[:blank:]\n]*" let parse_config _ _ parse_fun config_elem = let config = ref { allowed_method = None; allowed_credentials = false; max_age = None; exposed_headers = [] } in Ocsigen_extensions.( Configuration.process_element ~in_tag:"host" ~other_elements:(fun t _ _ -> raise (Bad_config_tag_for_extension t)) ~elements:[ Configuration.element ~name:"cors" ~attributes:[ Configuration.attribute ~name:"credentials" (fun s -> let s = bool_of_string s in config := { !config with allowed_credentials = s }); Configuration.attribute ~name:"max_age" (fun s -> let s = Some (int_of_string s) in config := { !config with max_age = s }); Configuration.attribute ~name:"exposed_headers" (fun s -> let s = Netstring_pcre.split comma_space_regexp s in config := { !config with exposed_headers = s }); Configuration.attribute ~name:"methods" (fun s -> let s = Netstring_pcre.split comma_space_regexp s in let s = Some (List.map Framepp.method_of_string s) in config := { !config with allowed_method = s }); ] ()] config_elem ); main !config let site_creator (_ : Ocsigen_extensions.virtual_hosts) _ = parse_config let user_site_creator (_ : Ocsigen_extensions.userconf_info) = site_creator let () = Ocsigen_extensions.register_extension ~name:"CORS" ~fun_site:site_creator ~user_fun_site:user_site_creator () ocsigenserver-2.16.0/src/extensions/deflatemod.ml000066400000000000000000000323331357715257700221310ustar00rootroot00000000000000(* Ocsigen * http://www.ocsigen.org * Module deflatemod.ml * Copyright (C) 2007 Gabriel Kerneis * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) (*****************************************************************************) (*****************************************************************************) (* This module allows to compress output sent by the server *) (*****************************************************************************) (*****************************************************************************) open Ocsigen_lib open Lwt open Ocsigen_extensions open Ocsigen_headers let section = Lwt_log.Section.make "ocsigen:ext:deflate" (* Content-type *) type filter = Type of string option * string option | Extension of string type compress_choice = All_but of filter list | Compress_only of filter list let should_compress (t, t') url choice_list = let check = function |Type (None, None) -> true |Type (None, Some x') -> x' = t' |Type (Some x, None) -> x = t |Type (Some x, Some x') -> x = t && x' = t' |Extension suff -> Filename.check_suffix url suff in match choice_list with |Compress_only l -> List.exists check l |All_but l -> List.for_all (fun c -> not (check c)) l (* Pas de filtre global pour l'instant let choice_list = ref (All_but []) *) (** Compression *) let buffer_size = ref 8192 (* 0 = no compression ; 1 = best speed ; 9 = best compression *) let compress_level = ref 6 (* Minimal header, by X. Leroy *) let gzip_header_length = 10 let gzip_header = let gzip_header = Bytes.make gzip_header_length (Char.chr 0) in Bytes.set gzip_header 0 @@ Char.chr 0x1F; Bytes.set gzip_header 1 @@ Char.chr 0x8B; Bytes.set gzip_header 2 @@ Char.chr 8; Bytes.set gzip_header 9 @@ Char.chr 0xFF; Bytes.unsafe_to_string gzip_header (* inspired by an auxiliary function from camlzip, by Xavier Leroy *) type output_buffer = { stream: Zlib.stream; buf: bytes; mutable pos: int; mutable avail: int; mutable size : int32; mutable crc : int32; mutable add_trailer : bool } let write_int32 oz n = for i = 0 to 3 do Bytes.set oz.buf (oz.pos + i) (Char.chr (Int32.to_int (Int32.shift_right_logical n (8 * i)) land 0xff)) done; oz.pos <- oz.pos + 4; oz.avail <- oz.avail - 4; assert (oz.avail >= 0) (* puts in oz the content of buf, from pos to pos + len ; * f is the continuation of the current stream *) let rec output oz f buf pos len = assert (pos >= 0 && len >= 0 && pos + len <= String.length buf); if oz.avail = 0 then begin let cont () = output oz f buf pos len in Lwt_log.ign_info ~section "Flushing because output buffer is full"; flush oz cont end else if len = 0 then next_cont oz f else begin let (_, used_in, used_out) = try Zlib.deflate oz.stream (Bytes.unsafe_of_string buf) pos len oz.buf oz.pos oz.avail Zlib.Z_NO_FLUSH with Zlib.Error(s, s') -> raise (Ocsigen_stream.Stream_error("Error during compression: "^s^" "^s')) in oz.pos <- oz.pos + used_out; oz.avail <- oz.avail - used_out; oz.size <- Int32.add oz.size (Int32.of_int used_in); oz.crc <- Zlib.update_crc_string oz.crc buf pos used_in; output oz f buf (pos + used_in) (len - used_in) end (* Flush oz, ie. produces a new_stream with the content of oz, cleans it * and returns the continuation of the stream *) and flush oz cont = let len = oz.pos in if len = 0 then cont () else begin let buf_len = Bytes.length oz.buf in let s = if len = buf_len then Bytes.to_string oz.buf else Bytes.sub_string oz.buf 0 len in Lwt_log.ign_info ~section "Flushing!"; oz.pos <- 0 ; oz.avail <- buf_len; Ocsigen_stream.cont s cont end and next_cont oz stream = Ocsigen_stream.next (stream : string Ocsigen_stream.stream) >>= fun e -> match e with | Ocsigen_stream.Finished None -> Lwt_log.ign_info ~section "End of stream: big cleaning for zlib" ; (* loop until there is nothing left to compress and flush *) let rec finish () = (* buffer full *) if oz.avail = 0 then flush oz finish else ( (* no more input, deflates only what were left because output buffer * was full *) let (finished, _, used_out) = Zlib.deflate oz.stream oz.buf 0 0 oz.buf oz.pos oz.avail Zlib.Z_FINISH in oz.pos <- oz.pos + used_out; oz.avail <- oz.avail - used_out; if not finished then finish () else write_trailer ()) and write_trailer () = if oz.add_trailer && oz.avail < 8 then flush oz write_trailer else begin if oz.add_trailer then begin write_int32 oz oz.crc; write_int32 oz oz.size end; Lwt_log.ign_info ~section "Zlib.deflate finished, last flush"; flush oz (fun () -> Ocsigen_stream.empty None) end in finish () | Ocsigen_stream.Finished (Some s) -> next_cont oz s | Ocsigen_stream.Cont(s,f) -> output oz f s 0 (String.length s) (* deflate param : true = deflate ; false = gzip (no header in this case) *) let compress deflate stream = let zstream = Zlib.deflate_init !compress_level deflate in let finalize status = Ocsigen_stream.finalize stream status >>= fun e -> (try Zlib.deflate_end zstream with (* ignore errors, deflate_end cleans everything anyway *) Zlib.Error _ -> ()); return (Lwt_log.ign_info ~section "Zlib stream closed") in let oz = { stream = zstream ; buf = Bytes.create !buffer_size; pos = 0; avail = !buffer_size; size = 0l; crc = 0l; add_trailer = not deflate } in let new_stream () = next_cont oz (Ocsigen_stream.get stream) in Lwt_log.ign_info ~section "Zlib stream initialized" ; if deflate then Ocsigen_stream.make ~finalize new_stream else Ocsigen_stream.make ~finalize (fun () -> Ocsigen_stream.cont gzip_header new_stream) (*****************************************************************************) (** The filter function *) (* We implement Content-Encoding, not Transfer-Encoding *) type encoding = Deflate | Gzip | Id | Star | Not_acceptable let qvalue = function Some x -> x |None -> 1.0 let enc_compare e e' = match e,e' with |(Star,_),(_,_) -> -1 (* star should be at the very end *) |(_,_),(Star,_) -> 1 |(_,v),(_,v') when v 1 (* then, sort by qvalue *) |(_,v),(_,v') when v>v' -> -1 |(x,_),(x',_) when x=x' -> 0 |(Deflate,_),(_,_) -> 1 (* and subsort by encoding *) |(_,_),(Deflate,_) -> -1 |(Gzip,_),(_,_) -> 1 |(_,_),(Gzip,_) -> -1 |(Id,_),(_,_) -> 1 |(_,_),(Id,_) -> -1 |_ -> assert false let rec filtermap f = function |[] -> [] |t::q -> match f t with |Some s -> s::(filtermap f q) |None -> filtermap f q let convert = function |(Some "deflate",v) -> Some (Deflate, qvalue v) |(Some "gzip",v)|(Some "x-gzip",v) -> Some (Gzip, qvalue v) |(Some "identity",v) -> Some (Id, qvalue v) |(None,v) -> Some (Star, qvalue v) |_ -> None (* Follow http's RFC to select the transfer encoding *) let select_encoding accept_header = let h = List.sort enc_compare (filtermap convert accept_header) in let (exclude,accept) = let (e,a) = List.partition (fun x -> snd x = 0.) h in (List.map fst e, List.map fst a) in let rec aux = function |[] -> if ((List.mem Star exclude) || (List.mem Id exclude)) then Not_acceptable else Id |t::q -> if (List.mem t exclude) then aux q else t in aux accept exception No_compress (* deflate = true -> mode deflate * deflate = false -> mode gzip *) let stream_filter contentencoding url deflate choice res = return (Ext_found (fun () -> try ( match Ocsigen_http_frame.Result.content_type res with | None -> raise No_compress (* il faudrait défaut ? *) | Some contenttype -> match Ocsigen_headers.parse_mime_type contenttype with | None, _ | _, None -> raise No_compress (* should never happen? *) | (Some a, Some b) when should_compress (a, b) url choice -> return (Ocsigen_http_frame.Result.update res ~content_length:None ~etag: (match Ocsigen_http_frame.Result.etag res with | Some e -> Some ((if deflate then "Ddeflatemod" else "Gdeflatemod")^e) | None -> None) ~stream: (compress deflate (fst (Ocsigen_http_frame.Result.stream res)), None) ~headers: (Http_headers.replace Http_headers.content_encoding contentencoding (Ocsigen_http_frame.Result.headers res)) ()) | _ -> raise No_compress) with Not_found | No_compress -> return res)) let filter choice_list = function | Req_not_found (code,_) -> return (Ext_next code) | Req_found ({ request_info = ri }, res) -> match select_encoding (Lazy.force(Ocsigen_request_info.accept_encoding ri)) with | Deflate -> stream_filter "deflate" (Ocsigen_request_info.sub_path_string ri) true choice_list res | Gzip -> stream_filter "gzip" (Ocsigen_request_info.sub_path_string ri) false choice_list res | Id | Star -> return (Ext_found (fun () -> return res)) | Not_acceptable -> return (Ext_stop_all (Ocsigen_http_frame.Result.cookies res,406)) (*****************************************************************************) let rec parse_global_config = function | [] -> () | (Xml.Element ("compress", [("level", l)], []))::ll -> let l = try int_of_string l with Failure _ -> raise (Error_in_config_file "Compress level should be an integer between 0 and 9") in compress_level := if (l <= 9 && l >= 0) then l else 6 ; parse_global_config ll | (Xml.Element ("buffer", [("size", s)], []))::ll -> let s = (try int_of_string s with Failure _ -> raise (Error_in_config_file "Buffer size should be a positive integer")) in buffer_size := if s > 0 then s else 8192 ; parse_global_config ll (* TODO: Pas de filtre global pour l'instant * le nom de balise contenttype est mauvais, au passage | (Xml.Element ("contenttype", [("compress", b)], choices))::ll -> let l = (try parse_filter choices with Not_found -> raise (Error_in_config_file "Can't parse mime-type content")) in (match b with |"only" -> choice_list := Compress_only l |"allbut" -> choice_list := All_but l | _ -> raise (Error_in_config_file "Attribute \"compress\" should be \"allbut\" or \"only\"")); parse_global_config ll *) | _ -> raise (Error_in_config_file "Unexpected content inside deflatemod config") (*****************************************************************************) let parse_config config_elem = let mode = ref (Compress_only []) in let pages = ref [] in Ocsigen_extensions.( Configuration.process_element ~in_tag:"host" ~other_elements:(fun t _ _ -> raise (Bad_config_tag_for_extension t)) ~elements:[ Configuration.element ~name:"deflate" ~attributes:[ Configuration.attribute ~name:"compress" ~obligatory:true (function | "only" -> mode := Compress_only [] | "allbut" -> mode := All_but [] | _ -> badconfig "Attribute 'compress' should be 'allbut' or 'only'" ); ] ~elements:[ Configuration.element ~name:"type" ~pcdata:(fun s -> let (a, b) = Ocsigen_headers.parse_mime_type s in pages := Type (a, b) :: !pages) (); Configuration.element ~name:"extension" ~pcdata:(fun s -> pages := Extension s :: !pages) (); ] ()] config_elem ); match !pages with | [] -> badconfig "Unexpected element inside contenttype (should be or )" | l -> let mode = match !mode with | Compress_only __ -> Compress_only l | All_but _ -> All_but l in filter mode (*****************************************************************************) (** Registration of the extension *) let () = Ocsigen_extensions.register_extension ~name:"deflatemod" ~fun_site:(fun _ _ _ _ _ -> parse_config) ~init_fun:parse_global_config () ocsigenserver-2.16.0/src/extensions/extendconfiguration.ml000066400000000000000000000204171357715257700241040ustar00rootroot00000000000000(* Ocsigen * http://www.ocsigen.org * Module extendconfiguration.ml * Copyright (C) 2008 Boris Yakobowski * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) open Lwt open Ocsigen_extensions open Ocsigen_charset_mime let bad_config s = raise (Error_in_config_file s) let gen configfun = function | Ocsigen_extensions.Req_found _ -> Lwt.return Ocsigen_extensions.Ext_do_nothing | Ocsigen_extensions.Req_not_found (err, request) -> Lwt_log.ign_info "Updating configuration"; let updated_request = { request with request_config = configfun request.request_config } in Lwt.return (Ocsigen_extensions.Ext_continue_with (updated_request, Ocsigen_cookies.Cookies.empty, err )) let gather_do_not_serve_files tag = let rec aux (regexps, files, extensions) = function | [] -> { Ocsigen_extensions.do_not_serve_regexps = regexps; do_not_serve_files = files; do_not_serve_extensions = extensions } | Xml.Element ("regexp", ["regexp", f], []) :: q -> aux (f :: regexps, files, extensions) q | Xml.Element ("file", ["file", f], []) :: q -> aux (regexps, f :: files, extensions) q | Xml.Element ("extension", ["ext", f], []) :: q -> aux (regexps, files, f :: extensions) q | _ :: q -> bad_config ("invalid options in tag " ^ tag) in aux ([], [], []) exception Bad_regexp of string let check_regexp_list = let hashtbl = Hashtbl.create 17 in let aux r = try Hashtbl.find hashtbl r with Not_found -> try ignore (Netstring_pcre.regexp r); Hashtbl.add hashtbl r () with _ -> raise (Bad_regexp r) in (fun l -> List.iter aux l) let update_config usermode = function | Xml.Element ("listdirs", ["value", "true"], []) -> gen (fun config -> { config with list_directory_content = true }) | Xml.Element ("listdirs", ["value", "false"], []) -> gen (fun config -> { config with list_directory_content = false }) | Xml.Element ("listdirs" as s, _, _) -> badconfig "Bad syntax for tag %s" s | Xml.Element ("followsymlinks", ["value", s], []) -> let v = match s with | "never" -> DoNotFollowSymlinks | "always" -> if usermode = false then AlwaysFollowSymlinks else raise (Error_in_user_config_file "Cannot specify value 'always' for option \ 'followsymlinks' in userconf files") | "ownermatch" -> FollowSymlinksIfOwnerMatch | _ -> bad_config ("Wrong value \""^s^"\" for option \"followsymlinks\"") in gen (fun config -> { config with follow_symlinks = v }) | Xml.Element ("followsymlinks" as s, _, _) -> badconfig "Bad syntax for tag %s" s | Xml.Element ("charset", attrs, exts) -> let rec aux charset_assoc = function | [] -> charset_assoc | Xml.Element ("extension", ["ext", extension; "value", charset], []) :: q -> aux (update_charset_ext charset_assoc extension charset) q | Xml.Element ("file", ["file", file; "value", charset], []) :: q -> aux (update_charset_file charset_assoc file charset) q | Xml.Element ("regexp", ["regexp", regexp; "value", charset], []) :: q -> (try let r = Netstring_pcre.regexp regexp in aux (update_charset_regexp charset_assoc r charset) q with _ -> bad_config "invalid regexp '%s' in ") | _ :: q -> bad_config "invalid subtag in option charset" in gen (fun config -> let config = match attrs with | ["default", s] -> { config with charset_assoc = set_default_charset config.charset_assoc s } | [] -> config | _ -> bad_config "Only attribute \"default\" is permitted \ for option \"charset\"" in { config with charset_assoc = aux config.charset_assoc exts }) | Xml.Element ("contenttype", attrs, exts) -> let rec aux mime_assoc = function | [] -> mime_assoc | Xml.Element ("extension", ["ext", extension; "value", mime], []) :: q -> aux (update_mime_ext mime_assoc extension mime) q | Xml.Element ("file", ["file", file; "value", mime], []) :: q -> aux (update_mime_file mime_assoc file mime) q | Xml.Element ("regexp", ["regexp", regexp; "value", mime], []) :: q -> (try let r = Netstring_pcre.regexp regexp in aux (update_mime_regexp mime_assoc r mime) q with _ -> bad_config "invalid regexp '%s' in ") | _ :: q -> bad_config "invalid subtag in option mime" in gen (fun config -> let config = match attrs with | ["default", s] -> { config with mime_assoc = set_default_mime config.mime_assoc s } | [] -> config | _ -> bad_config "Only attribute \"default\" is permitted \ for option \"contenttype\"" in { config with mime_assoc = aux config.mime_assoc exts }) | Xml.Element ("defaultindex", [], l) -> let rec aux indexes = function | [] -> List.rev indexes | Xml.Element ("index", [], [PCData f]) :: q -> aux (f :: indexes) q | _ :: q -> bad_config "subtags must be of the form \ ... \ in option defaultindex" in gen (fun config -> { config with default_directory_index = aux [] l }) | Xml.Element ("defaultindex" as s, _, _) -> badconfig "Bad syntax for tag %s" s | Xml.Element ("hidefile", [], l) -> let do_not_serve = gather_do_not_serve_files "hidefile" l in (try check_regexp_list do_not_serve.do_not_serve_regexps; gen (fun config -> { config with do_not_serve_404 = join_do_not_serve do_not_serve config.do_not_serve_404 }) with Bad_regexp r -> badconfig "Invalid regexp %s in %s" r "hidefile") | Xml.Element ("hidefile" as s, _, _) -> badconfig "Bad syntax for tag %s" s | Xml.Element ("forbidfile", [], l) -> let do_not_serve = gather_do_not_serve_files "forbidfile" l in (try check_regexp_list do_not_serve.do_not_serve_regexps; gen (fun config -> { config with do_not_serve_403 = join_do_not_serve do_not_serve config.do_not_serve_403 }) with Bad_regexp r -> badconfig "Invalid regexp %s in %s" r "forbidfile") | Xml.Element ("forbidfile" as s, _, _) -> badconfig "Bad syntax for tag %s" s | Xml.Element ("uploaddir", [], [PCData s]) -> if s = "" then gen (fun config -> { config with uploaddir = None }) else gen (fun config -> { config with uploaddir = Some s }) | Xml.Element ("uploaddir" as s, _, _) -> badconfig "Bad syntax for tag %s" s | Xml.Element ("maxuploadfilesize" as tag, [], [PCData s]) -> let s = try Ocsigen_parseconfig.parse_size_tag "uploaddir" s with Ocsigen_config.Config_file_error _ -> badconfig "Bad syntax for tag %s" tag in gen (fun config -> { config with maxuploadfilesize = s }) | Xml.Element ("maxuploadfilesize" as s, _, _) -> badconfig "Bad syntax for tag %s" s | Xml.Element (t, _, _) -> raise (Bad_config_tag_for_extension t) | _ -> raise (Error_in_config_file "Unexpected data in config file") let parse_config usermode : parse_config_aux = fun _ _ _ xml -> update_config usermode xml let () = register_extension ~name:"extendconfiguration" ~fun_site:(fun _ _ -> parse_config false) ~user_fun_site:(fun path _ _ -> parse_config true) () ocsigenserver-2.16.0/src/extensions/extensiontemplate.ml000066400000000000000000000167251357715257700236040ustar00rootroot00000000000000(* Ocsigen * http://www.ocsigen.org * Module extensiontemplate.ml * Copyright (C) 2007 Vincent Balat * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) (*****************************************************************************) (*****************************************************************************) (* This is an example of extension for Ocsigen *) (* Take this as a template for writing your own extensions to the Web server *) (*****************************************************************************) (*****************************************************************************) (* If you want to create an extension to filter the output of the server (for ex: compression), have a look at deflatemod.ml as an example. It is very similar to this example, but using Ocsigen_extensions.register_output_filter instead of Ocsigen_extensions.register_extension. *) (* To compile it: ocamlfind ocamlc -thread -package netstring-pcre,ocsigen -c extensiontemplate.ml Then load it dynamically from Ocsigen's config file: *) open Lwt open Ocsigen_extensions (*****************************************************************************) (** Extensions may take some options from the config file. These options are written in xml inside the tag. For example: ... *) let rec parse_global_config = function | [] -> () | (Xml.Element ("myoption", [("myattr", s)], []))::ll -> () | _ -> raise (Error_in_config_file ("Unexpected content inside extensiontemplate config")) (*****************************************************************************) (** The function that will generate the pages from the request, or modify a result generated by another extension. - a value of type [Ocsigen_extensions.conf_info] containing the current configuration options - [Ocsigen_extensions.req_state] is the request, possibly modified by previous extensions, or already found *) let gen = function | Ocsigen_extensions.Req_found _ -> (* If previous extension already found the page, you can modify the result (if you write a filter) or return it without modification like this: *) Lwt.return Ocsigen_extensions.Ext_do_nothing | Ocsigen_extensions.Req_not_found (err, ri) -> (* If previous extensions did not find the result, I decide here to answer with a default page (for the example): *) return (Ext_found (fun () -> let content = "Extensiontemplate page" in Ocsigen_senders.Text_content.result_of_content (content, "text/plain"))) (*****************************************************************************) (** Extensions may define new tags for configuring each site. These tags are inside ... in the config file. For example: Each extension will set its own configuration options, for example: Here parse_site is the function used to parse the config file inside this site. Use this if you want to put extensions config options inside your own option. For example: {[ | Element ("iffound", [], sub) -> let ext = parse_fun sub in (* DANGER: parse_fun MUST be called BEFORE the function! *) (fun charset -> function | Ocsigen_extensions.Req_found (_, _) -> Lwt.return (Ext_sub_result ext) | Ocsigen_extensions.Req_not_found (err, ri) -> Lwt.return (Ocsigen_extensions.Ext_not_found err)) ]} *) let parse_config path _ parse_site = function | Xml.Element ("extensiontemplate", atts, []) -> gen | Xml.Element (t, _, _) -> raise (Bad_config_tag_for_extension t) | _ -> raise (Error_in_config_file "Unexpected data in config file") (*****************************************************************************) (** Function to be called at the beginning of the initialisation phase of the server (actually each time the config file is reloaded) *) let begin_init () = () (** Function to be called at the end of the initialisation phase *) let end_init () = () (*****************************************************************************) (** A function that will create an error message from the exceptions that may be raised during the initialisation phase, and raise again all other exceptions. That function has type exn -> string. Use the raise function if you don't need any. *) let exn_handler = raise (*****************************************************************************) (* a function taking {ul {- the name of the virtual }} that will be called for each , and that will generate a function taking: {ul {- the path attribute of a tag that will be called for each , and that will generate a function taking:}} {ul {- an item of the config file that will be called on each tag inside and:} {ul {- raise [Bad_config_tag_for_extension] if it does not recognize that tag} {- return something of type [extension] (filter or page generator)}} *) let site_creator (hostpattern : Ocsigen_extensions.virtual_hosts) (config_info : Ocsigen_extensions.config_info) = parse_config (* hostpattern has type Ocsigen_extensions.virtual_hosts and represents the name of the virtual host. The path and the charset are declared in *) (* Same thing if the extension is loaded inside a local config file (using the userconf extension). However, we receive one additional argument, the root of the files the user can locally serve. See staticmod and userconf for details *) let user_site_creator (path : Ocsigen_extensions.userconf_info) = site_creator (*****************************************************************************) (** Registration of the extension *) let () = register_extension ~name:"extensionname" ~fun_site:site_creator (* If your extension is safe for users and if you want to allow exactly the same options as for global configuration, use the same [site_creator] function for [user_fun_site] as for [fun_site]. If you don't want to allow users to use that extension in their configuration files, you can omit user_fun_site. *) ~user_fun_site:user_site_creator ~init_fun: parse_global_config ~begin_init ~end_init ~exn_handler () ocsigenserver-2.16.0/src/extensions/ocsigen_comet.ml000066400000000000000000000434431357715257700226470ustar00rootroot00000000000000(* Ocsigen * http://www.ocsigen.org * Copyright (C) 2010 * Raphaël Proust * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) (* Comet extension for Ocsigen server * ``Comet'' is a set of hacks techniques providing basic * server-to-client communication. Using HTTP, it is not possible for the server * to send a message to the client, it is only possible to answer a client's * request. * * This implementation is to evolve and will change a lot with HTML5's * WebSockets support. *) open Ocsigen_lib let section = Lwt_log.Section.make "ocsigen:ext:comet" (*** PREAMBLE ***) (* small addition to the standard library *) let map_rev_accu_split func lst accu1 accu2 = let rec aux accu1 accu2 = function | [] -> (accu1, accu2) | x :: xs -> match func x with | Left y -> aux (y :: accu1) accu2 xs | Right y -> aux accu1 (y :: accu2) xs in aux accu1 accu2 lst (*** EXTENSION OPTIONS ***) (* timeout for comet connections : if no value has been written in the elapsed * time, connection will be closed. Should be equal to client timeout. *) let timeout_ref = ref 20. let get_timeout () = !timeout_ref (* the size initialization for the channel hashtable *) let tbl_initial_size = 16 let max_virtual_channels_ref = ref None let get_max_virtual_channels () = !max_virtual_channels_ref (*** CORE ***) module Channels : sig exception Too_many_virtual_channels (* raised when calling [create] while [max_virtual_channels] is [Some x] and * creating a new channel would make the virtual channel count greater than * [x]. *) exception Non_unique_channel_name (* raised when creating a channel with a name already associated. *) type t (* the type of channels : * channels can be written on or read from using the following functions *) type chan_id = string val create : ?name:string -> unit -> t val read : t -> (string * Ocsigen_stream.outcome Lwt.u option) Lwt.t val write : t -> (string * Ocsigen_stream.outcome Lwt.u option) -> unit val listeners : t -> int (* The up-to-date count of registered clients *) val send_listeners : t -> int -> unit (* [send_listeners c i] adds [i] to [listeners c]. [i] may be negative. *) val find_channel : chan_id -> t (* may raise Not_found if the channel was collected or never created. * Basically ids are meant for clients to tell a server to start listening * to it. *) val get_id : t -> chan_id (* [find_channel (get_id ch)] returns [ch] if the channel wasn't destroyed * that is. *) end = struct exception Too_many_virtual_channels exception Non_unique_channel_name type chan_id = string type t = { ch_id : chan_id ; mutable ch_read : (string * Ocsigen_stream.outcome Lwt.u option) Lwt.t ; mutable ch_write : (string * Ocsigen_stream.outcome Lwt.u option) Lwt.u; mutable ch_listeners : int ; } module Dummy = struct (*module added to avoid Ctbl.t cyclicity*) type tt = t end let get_id ch = ch.ch_id (* In order to being able to retrieve channels by there IDs, let's have a map * *) module CTbl = Weak.Make (struct type t = Dummy.tt let equal { ch_id = i } { ch_id = j } = i = j let hash { ch_id = c } = Hashtbl.hash c end) (* storage and ID manipulation *) let ctbl = CTbl.create tbl_initial_size let new_id = Ocsigen_lib.make_cryptographic_safe_string (* because Hashtables allow search for elements with a corresponding hash, we * have to create a dummy channel in order to retrieve the original channel. * Is there a KISSer way to do that ? *) let (dummy1, dummy2) = Lwt.task () let dummy_chan i = { ch_id = i ; ch_read = dummy1 ; ch_write = dummy2 ; ch_listeners = 0 ; } (* May raise Not_found *) let find_channel i = CTbl.find ctbl (dummy_chan i) (* virtual channel count *) let (chan_count, incr_chan_count, decr_chan_count) = let cc = ref 0 in ((fun () -> !cc), (fun () -> incr cc), (fun _ -> decr cc)) let maxed_out_virtual_channels () = match get_max_virtual_channels () with | None -> false | Some y -> chan_count () >= y (* creation : newly created channel is stored in the map as a side effect *) let do_create name = if maxed_out_virtual_channels () then begin Lwt_log.ign_warning ~section "Too many virtual channels, associated exception raised"; raise Too_many_virtual_channels end else let (read , write) = Lwt.task () in let ch = { ch_id = name ; ch_read = read ; ch_write = write ; ch_listeners = 0 ; } in incr_chan_count (); CTbl.add ctbl ch; Gc.finalise decr_chan_count ch; ch let write ch x = let (read, write) = Lwt.task () in let old_write = ch.ch_write in ch.ch_write <- write ; ch.ch_read <- read ; Lwt.wakeup old_write x let create ?name () = match name with | None -> do_create (new_id ()) | Some n -> try ignore (find_channel n) ; raise Non_unique_channel_name with Not_found -> do_create n (* reading a channel : just getting a hang on the reader thread *) let read ch = ch.ch_read (* listeners *) let listeners ch = ch.ch_listeners let send_listeners ch x = ch.ch_listeners <- ch.ch_listeners + x end module Messages : (* All about messages from between clients and server *) (* * The client sends a POST request with a "registration" parameter containing * a list of channel ids. Separator for the list are semi-colon : ';'. * * The server sends result to the client in the form of a list of : * channel_id ^ ":" ^ value ^ { ";" ^ channel_id ^ " " ^ value }* * where channel_id is the id of a channel that the client registered upon and * value is the string that was written upon the associated channel. * *) sig val decode_upcomming : Ocsigen_extensions.request -> (Channels.t list * Channels.chan_id list) Lwt.t (* decode incoming message : the result is the list of channels to listen to (on the left) or to signal non existence (on the right). *) val encode_downgoing : Channels.chan_id list -> (Channels.t * string * Ocsigen_stream.outcome Lwt.u option) list option -> string Ocsigen_stream.t (* Encode outgoing messages : the first argument is the list of channels * that have already been collected. * The results is the stream to send to the client*) val encode_ended : Channels.chan_id list -> string end = struct (* constants *) let channel_separator = "\n" let field_separator = ":" let ended_message = "ENDED_CHANNEL" let channel_separator_regexp = Netstring_pcre.regexp channel_separator let url_encode x = Url.encode ~plus:false x let decode_string s accu1 accu2 = map_rev_accu_split (fun s -> try Left (Channels.find_channel s) with | Not_found -> Right s ) (Netstring_pcre.split channel_separator_regexp s) accu1 accu2 let decode_param_list params = let rec aux ((tmp_reg, tmp_end) as tmp) = function | [] -> (tmp_reg, tmp_end) | ("registration", s) :: tl -> aux (decode_string s tmp_reg tmp_end) tl | _ :: tl -> aux tmp tl in aux ([], []) params let decode_upcomming r = (* RRR This next line makes it fail with Ocsigen_unsupported_media, hence * the http_frame low level version *) (* r.Ocsigen_extensions.request_info * .Ocsigen_extensions * .ri_post_params r.Ocsigen_extensions.request_config *) Lwt.catch (fun () -> match (Ocsigen_request_info.http_frame r.Ocsigen_extensions.request_info) .Ocsigen_http_frame.frame_content with | None -> Lwt.return [] | Some body -> Lwt.return (Ocsigen_stream.get body) >>= Ocsigen_stream.string_of_stream (Ocsigen_config.get_maxrequestbodysizeinmemory ()) >|= Url.fixup_url_string >|= Netencoding.Url.dest_url_encoded_parameters ) (function | Ocsigen_stream.String_too_large -> Lwt.fail Input_is_too_large | e -> Lwt.fail e ) >|= decode_param_list let encode1 (c, s, _) = Channels.get_id c ^ field_separator ^ url_encode s let encode l = String.concat channel_separator (List.map encode1 l) let encode_ended l = String.concat channel_separator (List.map (fun c -> c ^ field_separator ^ ended_message) l) let stream_result_notification s outcome = Lwt_list.iter_p (function (*when write has been made with outcome notifier*) | (c, _, Some x) -> (Lwt.wakeup x outcome ; Lwt.return ()) (*when it hasn't*) | (_, _, None) -> Lwt.return () ) s let encode_downgoing e = function | None -> Ocsigen_stream.of_string (encode_ended e) | Some s -> let stream = Ocsigen_stream.of_string (match e with | [] -> encode s | e -> encode_ended e ^ field_separator ^ encode s ) in Ocsigen_stream.add_finalizer stream (stream_result_notification s) ; stream end module Security : sig val set_timeout : ?reset:bool -> float -> unit (* Set the [timeout] constant for new connections. Existing connections are * not affected unless [?reset] is [Some true] *) val deactivate : unit -> unit (* Stop serving comet connections and kill all current connections. *) val activate : unit -> unit (* (Re)start serving connections *) val activated : unit -> bool (* activation state *) val kill : unit React.E.t (* The event reflecting willingness to kill connections *) val command_function : string -> string list -> unit Lwt.t (* To be registered with Ocsigen_extension.register_command_function *) end = struct let (kill, kill_all_connections) = React.E.create () let activated, activate, deactivate = let activated = ref true in ((fun () -> !activated), (fun () -> if !activated then () else begin Lwt_log.ign_warning ~section "Comet is being activated"; activated := true end ), (fun () -> if !activated then begin Lwt_log.ign_warning ~section "Comet is being deactivated"; activated := false; kill_all_connections () end else () ) ) let warn_kill = React.E.map (fun () -> Lwt_log.ign_warning "Comet connections kill notice is being sent.") kill let `R _ = React.E.retain kill (fun () -> ignore warn_kill) let set_timeout ?(reset=false) f = timeout_ref := f ; if reset then kill_all_connections () else () let command_function_ _ = function | ["deactivate"] -> deactivate () | ["activate"] -> activate () | "set_timeout" :: f :: tl -> (try set_timeout ~reset:(match tl with | ["KILL"] -> true | [] -> false | _ -> raise Ocsigen_extensions.Unknown_command ) (float_of_string f) with Failure _ -> raise Ocsigen_extensions.Unknown_command) | _ -> raise Ocsigen_extensions.Unknown_command let command_function x y = command_function_ x y; Lwt.return () end module Main : (* a client can wait for all the channels on which it * is registered and return with the first result. *) sig val main : Ocsigen_extensions.request -> unit -> Ocsigen_http_frame.result Lwt.t (* treat an incoming request from a client. The unit part is for partial * application in Ext_found parameter. *) end = struct let frame_503 () = Lwt.return (Ocsigen_http_frame.Result.update (Ocsigen_http_frame.Result.default ()) ~stream:(Ocsigen_stream.of_string "", None) ~code:503 (*Service Unavailable*) ~content_length:None ~content_type:(Some "text/plain") ()) exception Kill (* Once channel list is obtain, use this function to return a thread that * terminates when one of the channel is written upon. *) let treat_decoded = function | [], [] -> (* error : empty request *) Lwt_log.ign_info ~section "Incorrect or empty Comet request"; Lwt.return (Ocsigen_http_frame.Result.update (Ocsigen_http_frame.Result.default ()) ~stream: (Ocsigen_stream.of_string "Empty or incorrect registration", None) ~code:400(* BAD REQUEST *) ~content_type:(Some "text/plain") ~content_length:None ()) | [], (_::_ as ended) -> (* All channels are closed *) let end_notice = Messages.encode_ended ended in Lwt_log.ign_info ~section "Comet request served"; Lwt.return (Ocsigen_http_frame.Result.update (Ocsigen_http_frame.Result.default ()) ~stream:(Ocsigen_stream.of_string end_notice, None) ~content_length:None ~content_type:(Some "text/plain") ()) | (_::_ as active), ended -> (* generic case *) let choosed = let readings = (List.map (fun c -> Channels.read c >|= fun (v,x) -> (c, v, x)) active ) in (*wait for one thread to terminate and get all terminated threads *) Lwt.choose readings >>= fun _ -> Lwt.nchoose readings in List.iter (fun c -> Channels.send_listeners c 1) active ; Lwt.catch (fun () -> Lwt.choose [ (choosed >|= fun x -> Some x); (Lwt_unix.sleep (get_timeout ()) >|= fun () -> None); (Lwt_react.E.next Security.kill >>= fun () -> Lwt.fail Kill); ] >|= fun x -> List.iter (fun c -> Channels.send_listeners c (-1)) active ; let s = Messages.encode_downgoing ended x in Lwt_log.ign_info ~section "Comet request served"; (Ocsigen_http_frame.Result.update (Ocsigen_http_frame.Result.default ()) ~stream:(s, None) ~content_length:None ~content_type:(Some "text/plain") ()) ) (function | Kill -> (* Comet stopped for security *) List.iter (fun c -> Channels.send_listeners c (-1)) active ; Lwt_log.ign_info ~section "Killed Comet request handling"; frame_503 () | e -> Lwt.fail e ) (* This is just a mashup of the other functions in the module. *) let main r () = if Security.activated () then (Lwt_log.ign_info ~section "Serving Comet request"; Messages.decode_upcomming r >>= treat_decoded) else (Lwt_log.ign_info ~section "Refusing Comet request (Comet deactivated)"; frame_503 ()) end let rec has_comet_content_type = function | [] -> false | ("application", "x-ocsigen-comet") :: _ -> true | _ :: tl -> has_comet_content_type tl (*Only for debugging purpose*) let rec debug_content_type = function | [] -> "" | (s1,s2) :: tl -> s1 ^ "/" ^ s2 ^ "\n" ^ debug_content_type tl (*** MAIN FUNCTION ***) let main = function | Ocsigen_extensions.Req_not_found (_, rq) -> (* Else check for content type *) begin match (Ocsigen_request_info.content_type rq.Ocsigen_extensions.request_info) with | Some (hd, tl) when has_comet_content_type (hd :: tl) -> Lwt_log.ign_info_f ~section "Comet message: %a" (fun () -> debug_content_type) (hd :: tl); Lwt.return (Ocsigen_extensions.Ext_found (Main.main rq)) | Some (hd, tl) -> Lwt_log.ign_info_f ~section "Non comet message: %a" (fun () -> debug_content_type) (hd :: tl); Lwt.return Ocsigen_extensions.Ext_do_nothing | None -> Lwt_log.ign_info ~section "Non comet message: no content type"; Lwt.return Ocsigen_extensions.Ext_do_nothing end | Ocsigen_extensions.Req_found _ -> (* If recognized by some other extension... *) Lwt.return Ocsigen_extensions.Ext_do_nothing (* ...do nothing *) (*** EPILOGUE ***) let parse_config _ _ _ config_elem = max_virtual_channels_ref := None; Ocsigen_extensions.( Configuration.process_element ~in_tag:"host" ~other_elements:(fun t _ _ -> raise (Bad_config_tag_for_extension t)) ~elements:[ Configuration.element ~name:"comet" ~attributes:[ Configuration.attribute ~name:"max_virtual_channels" (function | "" -> max_virtual_channels_ref := None | s -> try max_virtual_channels_ref := Some (int_of_string s) with _ -> badconfig "Wrong value for attribute max_virtual_channels\ of : %s. It should be \"\" or an integer" s ) ] ()] config_elem ); main let site_creator (_ : Ocsigen_extensions.virtual_hosts) _ = parse_config let user_site_creator (_ : Ocsigen_extensions.userconf_info) = site_creator (* registering extension *) let () = Ocsigen_extensions.register_extension ~name:"comet" ~fun_site:site_creator ~user_fun_site:user_site_creator () let () = Ocsigen_extensions.register_command_function ~prefix:"comet" Security.command_function ocsigenserver-2.16.0/src/extensions/ocsigen_comet.mli000066400000000000000000000206761357715257700230230ustar00rootroot00000000000000(* Ocsigen * http://www.ocsigen.org * Copyright (C) 2010 * Raphaël Proust * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) (** Ocsigen_comet server extension : provides low-level server to client communication scheme. *) module Channels : (** A module with all the base primitive needed for server push. *) sig exception Too_many_virtual_channels (** An exception that may be raised when trying to create a new channel while the channel count exceed [max_virtual_channels]. Note that by default [max_virtual_channels] is set to [None] so that the exception is never raised. *) exception Non_unique_channel_name (** An exception raised when creating a channel with a name already associated to another channel. It is strictly forbidden to name several channels with the same string. *) type t (** The abstract type of server-to-client communication channels. *) type chan_id = string (** The type of channel identifier. Channels are uniquely identified by there chan_id value. *) val create : ?name:string -> unit -> t (** [create ()] returns a channel with a freshly baked identifier while [create ~name ()] returns a channel with the identifier [name] after checking for uniqueness. If [name] is the identifier of an existing channel, the exception [Non_unique_channel_name] is raised. *) val write : t -> (string * Ocsigen_stream.outcome Lwt.u option) -> unit (** [write c (s, u)] sends the string [s] on the channel [c]. The argument [u] allow one to observe the result of the operation. If [u] is [None], there is no way to tell if the sending worked as expected. However if [u] is [Some u'] then [u'] will be woken up with the outcome (either [`Falure] or [`Success]) of the stream writing process. *) val listeners : t -> int (** [listeners c] returns the number of clients currently registered on [c] A client is "currently registered" on a channel if an actual connection is open for the server to push a message onto. Note that this information is server-based only, and that because it is so, some clients may still be registered as active while they have in fact closed the connection. In such a case, the outcome mechanism in [write] will report the failure. *) val get_id : t -> chan_id (** [get_id c] returns the unique identifier associated to [c]. The client can register to [c] using the returned identifier. *) end module Security : (** This module is to be used carefully, it provides functions to interrupt and restart Comet related connections. It is however useful to prevent Comet based DOS attacks. These functions can also be called from the Ocsigen command pipe. *) sig val set_timeout : ?reset:bool -> float -> unit (** [set_timeout ?reset f] sets the timeout value for future Comet connections to [f]. If [reset] is [true] then current connections are closed and the new timeout value will apply to the reopened connections. Default value for [reset] is false. *) val deactivate : unit -> unit (** [deactivate ()] ceases all Comet related activity. Each opened connection is closed. Further attempts to connect to the server with a Comet specific content type will result in a HTTP status code 503 (Unavailable). If called when Comet is not activated it does nothing (not even logging the deactivation attempt. *) val activate : unit -> unit (** [activate ()] starts serving Comet requests. It is the client's own responsibility to reopen a connection. If Comet was already activated it keeps going and nothing happens. *) val activated : unit -> bool (** [activated ()] reflects the activation state of the Comet module. If [false] it indicates that Comet connections are answered with a HTTP status code 503. If [true] it indicates that Comet connections are handled in a standard fashion by the server. *) end (** Usage: On the server side : 1) create needed channels 2) transmit their identifiers to clients 3) write when appropriate (using the outcome mechanism if necessary On the client : 1) make a XmlHttpRequest (XHR) with a list of channel identifiers. 2) wait for the reply 3) GOTO 1 Encoding for client-to-server requests: * The content type header should be set to [application/x-ocsigen-comet] (without quotes) * A POST parameter is required. Its name should be [registration] and its content should be a list of channel identifiers separated by [\n] (newline) characters. * Name and content of the said POST parameter should be encoded according to the [escape] JavaScript primitive Encoding for server-to-client answer: * The server answer is either empty (when no channel was written upon before timeout) or a list of pairs of channel identifiers and message content. The pairs are separated by [:] (colon) while the list elements are separated by [\n] (newline) characters. * In the list, channels that no longer exists on the server side are marked as pairs of channel identifier and the special string [ENDED_CHANNEL]. When receiving such a message, the client should lose hope of ever connecting to that particular channel ever again. *) (** Conf-file options: One can use the configuration file to tweak Ocsigen_comet settings. The supported options are: * max_virtual_channels: * default: [None] * syntax: "" is for [None], "i" is for [Some (int_of_string i)] * [max_virtual_channels] is an upper limit to the number of active channels. It does not limit the number of connections but the number of values of type [Ocsigen_comet.Channels.t] that can be used simultaneously. If one calls [Ocsigen_comet.Channels.create] while the number of channels is already maxed out, the exception [Ocsigen_comet.Channels.Too_many_virtual_channels] is raised. *) (** Commands: Comet provides commands (to be piped into Ocsigen's command pipe). The complete list of commands is described here. Don't forget to use the Comet prefix: each command is to be prefixed by "comet:" (without quotes). * deactivate: * deactivate is a command that stops all Comet activity. It is equivalent to a call to [Ocsigen_comet.Security.deactivate]. * activate: * activate is the dual command to deactivate. It resumes Comet activity (or do nothing is Comet is already activated) with exactly the same effect as a call to [Ocsigen_comet.Security.activate] would have. * set_timeout: * parameter: f (float) * optional parameter: s ("KILL") * set_timeout allows one to dynamically change the value of Comet connections timeout to [f]. Previously activated connections are closed if the second optional parameter is used. If not, connections are carried out with their old timeout unchanged. *) (** Note to Eliom users: Although it is possible to use Ocsigen_comet as an extension to the Ocsigen Server, it is recommended to use the higher level Eliom modules, namely Eliom_comet (for server side) and Eliom_client_comet (for client side). The former provides typed channels (with automatic marshaling) and channel wrapping, the later automates decoding and demarshaling and manages channel registration and deregistration. The low level Ocisgen server extension can however be used with classic Javascript clients (whereas the high level Eliom module requires Ocaml compatible unmarshalling which may be difficult to find in a non js_of_ocaml/O'browser based client). It may also be used to add your own high level wrapper with a custom communication protocol. *) ocsigenserver-2.16.0/src/extensions/ocsipersist-dbm/000077500000000000000000000000001357715257700225765ustar00rootroot00000000000000ocsigenserver-2.16.0/src/extensions/ocsipersist-dbm/.depend000066400000000000000000000006611357715257700240410ustar00rootroot00000000000000ocsidbm.cmo : ocsidbmtypes.cmi ocsidbm.cmx : ocsidbmtypes.cmi ocsidbmtypes.cmi : ocsipersist.cmo : ../../baselib/ocsigen_messages.cmi \ ../../server/ocsigen_extensions.cmi ../../baselib/ocsigen_config.cmi \ ocsidbmtypes.cmi ocsipersist.cmi ocsipersist.cmx : ../../baselib/ocsigen_messages.cmx \ ../../server/ocsigen_extensions.cmx ../../baselib/ocsigen_config.cmx \ ocsidbmtypes.cmi ocsipersist.cmi ocsipersist.cmi : ocsigenserver-2.16.0/src/extensions/ocsipersist-dbm/Makefile000066400000000000000000000027211357715257700242400ustar00rootroot00000000000000include ../../../Makefile.config PACKAGE := lwt.unix lwt_log xml-light dbm LIBS := -I ../../baselib -I ../../http -I ../../server \ ${addprefix -package ,${PACKAGE}} OCAMLC := $(OCAMLFIND) ocamlc${BYTEDBG} ${THREAD} OCAMLOPT := $(OCAMLFIND) ocamlopt ${OPTDBG} ${THREAD} OCAMLDOC := $(OCAMLFIND) ocamldoc OCAMLDEP := $(OCAMLFIND) ocamldep all: byte opt ### byte:: ocsipersist-dbm.cma opt:: ocsipersist-dbm.cmxa ifeq "$(NATDYNLINK)" "YES" opt:: ocsipersist-dbm.cmxs endif PREDEP := ocsipersist.mli ocsipersist-dbm.cma: ocsipersist.cmo $(OCAMLC) -a -o $@ $^ cp ocsipersist.cmi .. cp $@ .. ocsipersist-dbm.cmxa: ocsipersist.cmx $(OCAMLOPT) -a -o $@ $^ cp ocsipersist.cmi .. cp $@ ${patsubst %.cmxa,%.a,$@} .. ocsipersist-dbm.cmxs: ocsipersist-dbm.cmxa $(OCAMLOPT) -shared -linkall -o $@ $^ cp $@ .. ocsipersist.mli: ln -s -f ../ocsipersist.mli . ### byte:: ocsidbm opt:: ocsidbm.opt ocsidbm: ocsidbm.cmo $(OCAMLC) -linkpkg -o $@ ${LIBS} $^ ocsidbm.opt: ocsidbm.cmx $(OCAMLOPT) -linkpkg -o $@ ${LIBS} $^ ########## %.cmi: %.mli $(OCAMLC) ${LIBS} -c $< %.cmo: %.ml $(OCAMLC) ${LIBS} -c $< %.cmx: %.ml $(OCAMLOPT) ${LIBS} -c $< %.cmxs: %.cmxa $(OCAMLOPT) -shared -linkall -o $@ $< ## Clean up clean: -rm -f *.cm[ioax] *.cmxa *.cmxs *.o *.a *.annot -rm -f ${PREDEP} -rm -f ocsidbm ocsidbm.opt distclean: clean -rm -f *~ \#* .\#* -rm -f .depend ## Dependencies depend: ${PREDEP} $(OCAMLDEP) ${LIBS} *.mli *.ml > .depend FORCE: -include .depend ocsigenserver-2.16.0/src/extensions/ocsipersist-dbm/ocsidbm.ml000066400000000000000000000232071357715257700245540ustar00rootroot00000000000000(* Ocsigen * http://www.ocsigen.org * Module ocsidbm.ml * Copyright (C) 2007 Vincent Balat * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) (** Module Ocsidbm: persistent data server for Ocsigen *) open Dbm open Ocsidbmtypes open Lwt let directory = Sys.argv.(1) exception Ocsidbm_error let socketname = "socket" let suffix = ".otbl" (*****************************************************************************) (* error messages *) let errlog s = let date = let t = Unix.localtime (Unix.time ()) in Printf.sprintf "%02d-%02d-%04d %02d:%02d:%02d" t.Unix.tm_mday (t.Unix.tm_mon + 1) (1900 + t.Unix.tm_year) t.Unix.tm_hour t.Unix.tm_min t.Unix.tm_sec in let s = date^" Ocsidbm - "^s^"\n" in prerr_endline s (*****************************************************************************) (** Internal functions: storage in files using DBM *) module Tableoftables = Map.Make(struct type t = string let compare = compare end) let tableoftables = ref Tableoftables.empty let list_tables () = let d = try Unix.opendir directory with | Unix.Unix_error(error,_,_) -> failwith ( Printf.sprintf "Ocsidbm: can't open directory %s: %s" directory (Unix.error_message error)) in let rec aux () = try let n = Unix.readdir d in if Filename.check_suffix n suffix then (Filename.chop_extension n)::(aux ()) else if Filename.check_suffix n (suffix^".pag") (* depending on the version of dbm, there may be a .pag suffix *) then (Filename.chop_extension (Filename.chop_extension n))::(aux ()) else aux () with End_of_file -> Unix.closedir d; [] in aux () (* try to create the directory if it does not exist *) let _ = try Unix.access directory [Unix.R_OK; Unix.W_OK; Unix.X_OK; Unix.F_OK] with | Unix.Unix_error (Unix.ENOENT, _, _) -> begin try Unix.mkdir directory 0o750 with | Unix.Unix_error(error,_,_) -> failwith ( Printf.sprintf "Ocsidbm: can't create directory %s: %s" directory (Unix.error_message error) ) end | Unix.Unix_error(error,_,_) -> failwith ( Printf.sprintf "Ocsidbm: can't access directory %s: %s" directory (Unix.error_message error) ) let open_db name = let t = opendbm (directory^"/"^name^suffix) [Dbm_rdwr; Dbm_create] 0o640 in tableoftables := Tableoftables.add name t !tableoftables; t let open_db_if_exists name = try let t = opendbm (directory^"/"^name^suffix) [Dbm_rdwr] 0o640 in tableoftables := Tableoftables.add name t !tableoftables; t with | Unix.Unix_error (Unix.ENOENT, _, _) | Dbm.Dbm_error _ -> raise Not_found (* open all files and register them in the table of tables *) (* let _ = List.iter (fun a -> try ignore (open_db a) with ... -> errlog ("Error while openning database "^a)) (list_tables ()) si je remets a, a doit tre aprs la cration de la socket car si je n'arrive pas crer la socket, c'est peut-tre que les tables sont dj ouvertes *) let find_create_table name = try Tableoftables.find name !tableoftables with Not_found -> open_db name let find_dont_create_table name = try Tableoftables.find name !tableoftables with Not_found -> open_db_if_exists name let db_get store name = find (find_dont_create_table store) name let db_remove store name = try remove (find_dont_create_table store) name with | Not_found -> () | Dbm.Dbm_error "dbm_delete" -> () let db_replace store name value = replace (find_create_table store) name value let db_firstkey t = Dbm.firstkey (find_dont_create_table t) let db_nextkey t = Dbm.nextkey (find_dont_create_table t) let db_length t = let table = find_dont_create_table t in let rec aux f n = catch (fun () -> ignore (f table); Lwt_unix.yield () >>= (fun () -> aux Dbm.nextkey (n+1))) (function | Not_found -> return n | e -> fail e) in aux Dbm.firstkey 0 (* Because of Dbm implementation, the result may be less than the expected result in some case *) (*****************************************************************************) (* signals *) let close_all i _ = Unix.unlink (directory^"/"^socketname); Tableoftables.iter (fun k t -> Dbm.close t) !tableoftables; exit i let the_end i = exit i open Sys let sigs = [sigabrt;sigalrm;sigfpe;sighup;sigill;sigint; sigquit;sigsegv;sigterm;sigusr1;sigusr2; sigchld;sigttin;sigttou;sigvtalrm;sigprof] let _ = List.iter (fun s -> Sys.set_signal s (Signal_handle (close_all 0))) sigs let _ = Sys.set_signal Sys.sigpipe Sys.Signal_ignore let _ = Unix.setsid () (*****************************************************************************) (** Communication functions: *) let send outch v = Lwt_io.write_value outch v >>= fun () -> Lwt_io.flush outch let execute outch = let handle_errors f = try f () with e -> send outch (Error e) in function | Get (t, k) -> handle_errors (fun () -> try send outch (Value (db_get t k)) with | Not_found -> send outch Dbm_not_found) | Remove (t, k) -> handle_errors (fun () -> db_remove t k; send outch Ok) | Replace (t, k, v) -> handle_errors (fun () -> db_replace t k v; send outch Ok) | Replace_if_exists (t, k, v) -> handle_errors (fun () -> try ignore (db_get t k); db_replace t k v; send outch Ok with Not_found -> send outch Dbm_not_found) | Firstkey t -> handle_errors (fun () -> try send outch (Key (db_firstkey t)) with Not_found -> send outch End) | Nextkey t -> handle_errors (fun () -> try send outch (Key (db_nextkey t)) with Not_found -> send outch End) | Length t -> handle_errors (fun () -> catch (fun () -> db_length t >>= (fun i -> send outch (Value (Marshal.to_string i [])))) (function Not_found -> send outch Dbm_not_found | e -> send outch (Error e))) let nb_clients = ref 0 let rec listen_client inch outch = Lwt_io.read_value inch >>= fun v -> execute outch v >>= fun () -> listen_client inch outch let finish _ = nb_clients := !nb_clients - 1; if !nb_clients = 0 then close_all 0 (); return () let b = ref false let rec loop socket = Lwt_unix.accept socket >>= (fun (indescr, _) -> ignore ( b := true; nb_clients := !nb_clients + 1; let inch = Lwt_io.of_fd ~mode:Lwt_io.input indescr in let outch = Lwt_io.of_fd ~mode:Lwt_io.output indescr in catch (fun () -> listen_client inch outch >>= finish) finish); loop socket) let _ = Lwt_main.run (let socket = Lwt_unix.socket Unix.PF_UNIX Unix.SOCK_STREAM 0 in Lwt.catch (fun () -> Lwt_unix.bind socket (Unix.ADDR_UNIX (directory^"/"^socketname))) (fun exn -> errlog ("Please make sure that the directory "^directory^" exists, writable for ocsidbm, and no other ocsidbm process is running on the same directory. If not, remove the file "^(directory^"/"^socketname)); the_end 1) >>= fun () -> Lwt_unix.listen socket 20; (* Done in ocsipersist.ml let devnull = Unix.openfile "/dev/null" [Unix.O_WRONLY] 0 in Unix.dup2 devnull Unix.stdout; Unix.dup2 devnull Unix.stderr; Unix.close devnull; Unix.close Unix.stdin; *) ignore (Lwt_unix.sleep 4.1 >>= (fun () -> if not !b then close_all 0 (); return ())); (* If nothing happened during 5 seconds, I quit *) loop socket) (*****************************************************************************) (** Garbage collection of expired data *) (* Experimental exception Exn1 let dbm_fold f t beg = let rec aux nextkey beg = try let k = try nextkey t with Not_found -> raise Exn1 in let v = try Dbm.find k t with Not_found -> raise Exn1 in aux Dbm.nextkey (f k v beg) with Exn1 -> beg in aux Dbm.firstkey beg let _ = match sessiongcfrequency with None -> () (* No garbage collection *) | Some t -> let rec f () = Lwt_unix.sleep t >>= (fun () -> let now = Unix.time () in print_endline "GC of persistent data"; Tableoftables.fold (fun name t thr -> thr >>= (fun () -> dbm_fold (fun k v thr -> thr >>= (fun () -> (match fst (Marshal.from_string v 0) with | Some exp when exp < now -> try Dbm.remove t k with _ -> ()); Lwt_unix.yield () ) ) t (return ())) ) !tableoftables (return ()) ) >>= f in ignore (f ()) *) ocsigenserver-2.16.0/src/extensions/ocsipersist-dbm/ocsidbmtypes.mli000066400000000000000000000022531357715257700260100ustar00rootroot00000000000000(* Ocsigen * http://www.ocsigen.org * Copyright (C) 2007 Vincent Balat * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) exception Ocsidbm_error type query = | Get of string * string | Remove of string * string | Replace of string * string * string | Replace_if_exists of string * string * string | Nextkey of string | Firstkey of string | Length of string type answer = | Ok | Dbm_not_found | Value of string | End | Key of string | Error of exn ocsigenserver-2.16.0/src/extensions/ocsipersist-dbm/ocsipersist.ml000066400000000000000000000251651357715257700255100ustar00rootroot00000000000000(* Ocsigen * http://www.ocsigen.org * Module ocsipersist.ml * Copyright (C) 2007 Vincent Balat * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) (* FIX: the log file is never reopened *) (** Module Ocsipersist: persistent data *) open Ocsidbmtypes open Lwt let section = Lwt_log.Section.make "ocsigen:ocsipersist:dbm" (** Data are divided into stores. Create one store for your project, where you will save all your data. *) type store = string exception Ocsipersist_error let socketname = "socket" (*****************************************************************************) (** Internal functions: storage directory *) (** getting the directory from config file *) let rec parse_global_config (store, ocsidbm, delayloading as d) = function | [] -> d | Xml.Element ("delayloading", [("val", ("true" | "1"))], []) :: ll -> parse_global_config (store, ocsidbm, true) ll | Xml.Element ("store", [("dir", s)], []) :: ll -> if store = None then parse_global_config ((Some s), ocsidbm, delayloading) ll else Ocsigen_extensions.badconfig "Ocsipersist: Duplicate tag" | Xml.Element ("ocsidbm", [("name", s)], []) :: ll -> if ocsidbm = None then parse_global_config (store, (Some s), delayloading) ll else Ocsigen_extensions.badconfig "Ocsipersist: Duplicate tag" | (Xml.Element (s,_,_))::ll -> Ocsigen_extensions.badconfig "Bad tag %s" s | _ -> Ocsigen_extensions.badconfig "Unexpected content inside Ocsipersist config" let (directory, ocsidbm) = (ref ((Ocsigen_config.get_datadir ())^"/ocsipersist"), ref ((Ocsigen_config.get_extdir ())^"/ocsidbm"^Ocsigen_config.native_ext)) (*****************************************************************************) (** Communication with the DB server *) external sys_exit : int -> 'a = "caml_sys_exit" let rec try_connect sname = catch (fun () -> let socket = Lwt_unix.socket Unix.PF_UNIX Unix.SOCK_STREAM 0 in Lwt_unix.connect socket (Unix.ADDR_UNIX sname) >>= fun () -> return socket) (fun _ -> Lwt_log.ign_warning_f ~section "Launching a new Ocsidbm process: %s on directory %s." !ocsidbm !directory; let param = [|!ocsidbm; !directory|] in let child () = let log = Unix.openfile (Ocsigen_messages.error_log_path ()) [Unix.O_WRONLY; Unix.O_CREAT; Unix.O_APPEND] 0o640 in Unix.dup2 log Unix.stderr; Unix.close log; let devnull = Unix.openfile "/dev/null" [Unix.O_WRONLY] 0 in Unix.dup2 devnull Unix.stdout; Unix.close devnull; Unix.close Unix.stdin; Unix.execv !ocsidbm param in let pid = Lwt_unix.fork () in if pid = 0 then begin (* double fork *) if Lwt_unix.fork () = 0 then begin child () end else sys_exit 0; end else Lwt_unix.waitpid [] pid >>= (fun _ -> Lwt_unix.sleep 1.1 >>= (fun () -> let socket = Lwt_unix.socket Unix.PF_UNIX Unix.SOCK_STREAM 0 in Lwt_unix.connect socket (Unix.ADDR_UNIX sname) >>= fun () -> return socket))) let rec get_indescr i = (catch (fun () -> try_connect (!directory^"/"^socketname)) (fun e -> if i = 0 then begin Lwt_log.ign_error_f ~section "Cannot connect to Ocsidbm. Will continue \ without persistent session support. \ Error message is: %s .\ Have a look at the logs to see if there is an \ error message from the Ocsidbm process." (match e with | Unix.Unix_error (a,b,c) -> Printf.sprintf "%a in %s(%s)" (fun () -> Unix.error_message) a b c | _ -> Printexc.to_string e); fail e end else (Lwt_unix.sleep 2.1) >>= (fun () -> get_indescr (i-1)))) let inch = ref (Lwt.fail (Failure "Ocsipersist not initialised")) let outch = ref (Lwt.fail (Failure "Ocsipersist not initialised")) let init_fun config = let (store, ocsidbmconf, delay_loading) = parse_global_config (None, None, false) config in (match store with | None -> () | Some d -> directory := d); (match ocsidbmconf with | None -> () | Some d -> ocsidbm := d); (if delay_loading then Lwt_log.ign_warning ~section "Asynchronuous initialization (may fail later)" else Lwt_log.ign_warning ~section "Initializing ..."); let indescr = get_indescr 2 in if delay_loading then ( inch := Lwt.map (Lwt_io.of_fd ~mode:Lwt_io.input) indescr; outch := Lwt.map (Lwt_io.of_fd ~mode:Lwt_io.output) indescr; ) else ( let r = Lwt_main.run indescr in inch := Lwt.return (Lwt_io.of_fd ~mode:Lwt_io.input r); outch := Lwt.return (Lwt_io.of_fd ~mode:Lwt_io.output r); Lwt_log.ign_warning ~section "...Initialization complete"; ) let send = let previous = ref (return Ok) in fun v -> catch (fun () -> !previous) (fun _ -> return Ok) >>= (fun _ -> !inch >>= fun inch -> !outch >>= fun outch -> previous := (Lwt_io.write_value outch v >>= fun () -> Lwt_io.flush outch >>= fun () -> Lwt_io.read_value inch); !previous) let db_get (store, name) = send (Get (store, name)) >>= (function | Value v -> return v | Dbm_not_found -> fail Not_found | Error e -> fail e | _ -> fail Ocsipersist_error) let db_remove (store, name) = send (Remove (store, name)) >>= (function | Ok -> return () | Error e -> fail e | _ -> fail Ocsipersist_error) let db_replace (store, name) value = send (Replace (store, name, value)) >>= (function | Ok -> return () | Error e -> fail e | _ -> fail Ocsipersist_error) let db_replace_if_exists (store, name) value = send (Replace_if_exists (store, name, value)) >>= (function | Ok -> return () | Dbm_not_found -> fail Not_found | Error e -> fail e | _ -> fail Ocsipersist_error) let db_firstkey store = send (Firstkey store) >>= (function | Key k -> return (Some k) | Error e -> fail e | _ -> return None) let db_nextkey store = send (Nextkey store) >>= (function | Key k -> return (Some k) | Error e -> fail e | _ -> return None) let db_length store = send (Length store) >>= (function | Value v -> return (Marshal.from_string v 0) | Dbm_not_found -> return 0 | Error e -> fail e | _ -> fail Ocsipersist_error) (*****************************************************************************) (** Public functions: *) (** Type of persistent data *) type 'a t = store * string let open_store name = Lwt.return name let make_persistent_lazy_lwt ~store ~name ~default = let pvname = (store, name) in (catch (fun () -> db_get pvname >>= (fun _ -> return ())) (function | Not_found -> default () >>= fun def -> db_replace pvname (Marshal.to_string def []) | e -> fail e)) >>= (fun () -> return pvname) let make_persistent_lazy ~store ~name ~default = let default () = Lwt.wrap default in make_persistent_lazy_lwt ~store ~name ~default let make_persistent ~store ~name ~default = make_persistent_lazy ~store ~name ~default:(fun () -> default) let get (pvname : 'a t) : 'a = db_get pvname >>= (fun r -> return (Marshal.from_string r 0)) let set pvname v = let data = Marshal.to_string v [] in db_replace pvname data (** Type of persistent tables *) type 'value table = string let open_table name = Lwt.return name let table_name n = Lwt.return n let find table key = db_get (table, key) >>= (fun v -> return (Marshal.from_string v 0)) let add table key value = let data = Marshal.to_string value [] in db_replace (table, key) data let replace_if_exists table key value = let data = Marshal.to_string value [] in db_replace_if_exists (table, key) data let remove table key = db_remove (table, key) let iter_table f table = let rec aux nextkey = nextkey table >>= (function | None -> return () | Some k -> find table k >>= f k >>= (fun () -> aux db_nextkey)) in aux db_firstkey let iter_step = iter_table let fold_table f table beg = let rec aux nextkey beg = nextkey table >>= (function | None -> return beg | Some k -> find table k >>= fun r -> f k r beg >>= (fun res -> aux db_nextkey res)) in aux db_firstkey beg let fold_step = fold_table let iter_block a b = failwith "iter_block not implemented for DBM. Please use Ocsipersist with sqlite" (* iterator: with a separate connexion: exception Exn1 let iter_table f table = let first = Marshal.to_string (Firstkey table) [] in let firstl = String.length first in let next = Marshal.to_string (Nextkey table) [] in let nextl = String.length next in (Lwt_unix.socket Unix.PF_UNIX Unix.SOCK_STREAM 0 >>= (fun socket -> Lwt_unix.connect (Lwt_unix.Plain socket) (Unix.ADDR_UNIX (directory^"/"^socketname)) >>= (fun () -> return (Lwt_unix.Plain socket)) >>= (fun indescr -> let inch = Lwt_unix.in_channel_of_descr indescr in let nextkey next nextl = Lwt_unix.write indescr next 0 nextl >>= (fun l2 -> if l2 <> nextl then fail Ocsipersist_error else (Lwt_unix.input_line inch >>= fun answ -> return (Marshal.from_string answ 0))) in let rec aux n l = nextkey n l >>= (function | End -> return () | Key k -> find table k >>= f k | Error e -> fail e | _ -> fail Ocsipersist_error) >>= (fun () -> aux next nextl) in catch (fun () -> aux first firstl >>= (fun () -> Unix.close socket; return ())) (fun e -> Unix.close socket; fail e)))) *) let length table = db_length table (* Because of Dbm implementation, the result may be less than the expected result in some case (with a version of ocsipersist based on Dbm) *) let _ = Ocsigen_extensions.register_extension ~name:"ocsipersist" ~init_fun () ocsigenserver-2.16.0/src/extensions/ocsipersist-pgsql/000077500000000000000000000000001357715257700231625ustar00rootroot00000000000000ocsigenserver-2.16.0/src/extensions/ocsipersist-pgsql/.depend000066400000000000000000000002361357715257700244230ustar00rootroot00000000000000ocsipersist.cmo : ../../server/ocsigen_extensions.cmi ocsipersist.cmi ocsipersist.cmx : ../../server/ocsigen_extensions.cmx ocsipersist.cmi ocsipersist.cmi : ocsigenserver-2.16.0/src/extensions/ocsipersist-pgsql/Makefile000066400000000000000000000024361357715257700246270ustar00rootroot00000000000000include ../../../Makefile.config PACKAGE := xml-light pgocaml lwt lwt_log LIBS := -I ../../baselib -I ../../http -I ../../server \ ${addprefix -package ,${PACKAGE}} OCAMLC := $(OCAMLFIND) ocamlc${BYTEDBG} ${THREAD} OCAMLOPT := $(OCAMLFIND) ocamlopt ${OPTDBG} ${THREAD} OCAMLDOC := $(OCAMLFIND) ocamldoc OCAMLDEP := $(OCAMLFIND) ocamldep all: byte opt ### byte: ocsipersist-pgsql.cma opt:: ocsipersist-pgsql.cmxa ifeq "$(NATDYNLINK)" "YES" opt:: ocsipersist-pgsql.cmxs endif PREDEP := ocsipersist.mli ocsipersist-pgsql.cma: ocsipersist.cmo $(OCAMLC) -a -o $@ $^ cp ocsipersist.cmi .. cp $@ .. ocsipersist-pgsql.cmxa: ocsipersist.cmx $(OCAMLOPT) -a -o $@ $^ cp ocsipersist.cmi .. cp $@ ${patsubst %.cmxa,%.a,$@} .. ocsipersist-pgsql.cmxs: ocsipersist-pgsql.cmxa $(OCAMLOPT) -shared -linkall -o $@ $^ cp $@ .. ocsipersist.mli: ln -s -f ../ocsipersist.mli . ########## %.cmi: %.mli $(OCAMLC) ${LIBS} -c $< %.cmo: %.ml $(OCAMLC) ${LIBS} -c $< %.cmx: %.ml $(OCAMLOPT) ${LIBS} -c $< %.cmxs: %.cmxa $(OCAMLOPT) -shared -linkall -o $@ $< ## Clean up clean: -rm -f *.cm[ioax] *.cmxa *.cmxs *.o *.a *.annot -rm -f ${PREDEP} distclean: clean -rm -f *~ \#* .\#* -rm -f .depend ## Dependencies depend: ${PREDEP} $(OCAMLDEP) ${LIBS} *.mli *.ml > .depend FORCE: -include .depend ocsigenserver-2.16.0/src/extensions/ocsipersist-pgsql/ocsipersist.ml000066400000000000000000000233641357715257700260730ustar00rootroot00000000000000(** PostgreSQL (>= 9.5) backend for Ocsipersist. *) let section = Lwt_log.Section.make "ocsigen:ocsipersist:pgsql" module Lwt_thread = struct include Lwt let close_in = Lwt_io.close let really_input = Lwt_io.read_into_exactly let input_binary_int = Lwt_io.BE.read_int let input_char = Lwt_io.read_char let output_string = Lwt_io.write let output_binary_int = Lwt_io.BE.write_int let output_char = Lwt_io.write_char let flush = Lwt_io.flush let open_connection x = Lwt_io.open_connection x type out_channel = Lwt_io.output_channel type in_channel = Lwt_io.input_channel end module PGOCaml = PGOCaml_generic.Make(Lwt_thread) open Lwt open Printf exception Ocsipersist_error let host = ref None let port = ref None let user = ref None let password = ref None let database = ref "ocsipersist" let unix_domain_socket_dir = ref None let size_conn_pool = ref 16 let make_hashtbl () = Hashtbl.create 8 let connect () = PGOCaml.connect ?host:!host ?port:!port ?user:!user ?password:!password ?database:(Some !database) ?unix_domain_socket_dir:!unix_domain_socket_dir () >>= fun dbhandle -> PGOCaml.set_private_data dbhandle @@ make_hashtbl (); Lwt.return dbhandle let (>>) f g = f >>= fun _ -> g let dispose db = Lwt.catch (fun () -> PGOCaml.close db) (fun _ -> Lwt.return_unit) let conn_pool : (string, unit) Hashtbl.t PGOCaml.t Lwt_pool.t ref = (* This connection pool will be overwritten by init_fun! *) ref (Lwt_pool.create !size_conn_pool ~validate:PGOCaml.alive ~dispose connect) let use_pool f = Lwt_pool.use !conn_pool @@ fun db -> Lwt.catch (fun () -> f db) (function | PGOCaml.Error msg as e -> Lwt_log.ign_error_f ~section "postgresql protocol error: %s" msg; PGOCaml.close db >>= fun () -> Lwt.fail e | Lwt.Canceled as e -> Lwt_log.ign_error ~section "thread canceled"; PGOCaml.close db >>= fun () -> Lwt.fail e | e -> Lwt.fail e ) (* escapes characters that are not in the range of 0x20..0x7e; this is to meet PostgreSQL's format requirements for text fields while keeping the key column readable whenever possible. *) let escape_string s = let len = String.length s in let buf = Buffer.create (len * 2) in for i = 0 to len - 1 do let c = s.[i] in let cc = Char.code c in if cc < 0x20 || cc > 0x7e then Buffer.add_string buf (sprintf "\\%03o" cc) (* non-print -> \ooo *) else if c = '\\' then Buffer.add_string buf "\\\\" (* \ -> \\ *) else Buffer.add_char buf c done; Buffer.contents buf let unescape_string str = let is_first_oct_digit c = c >= '0' && c <= '3' and is_oct_digit c = c >= '0' && c <= '7' and oct_val c = Char.code c - 0x30 in let len = String.length str in let buf = Buffer.create len in let i = ref 0 in while !i < len do let c = str.[!i] in if c = '\\' then ( incr i; if !i < len && str.[!i] = '\\' then ( Buffer.add_char buf '\\'; incr i ) else if !i+2 < len && is_first_oct_digit str.[!i] && is_oct_digit str.[!i+1] && is_oct_digit str.[!i+2] then ( let byte = oct_val str.[!i] in incr i; let byte = (byte lsl 3) + oct_val str.[!i] in incr i; let byte = (byte lsl 3) + oct_val str.[!i] in incr i; Buffer.add_char buf (Char.chr byte) ) ) else ( incr i; Buffer.add_char buf c ) done; Buffer.contents buf type 'a parameter = Key of string | Value of 'a let pack = function | Key k -> escape_string k | Value v -> PGOCaml.string_of_bytea @@ Marshal.to_string v [] let unpack_key = unescape_string let unpack_value value = Marshal.from_string (PGOCaml.bytea_of_string value) 0 let key_value_of_row = function | [Some key; Some value] -> unpack_key key, unpack_value value | _ -> raise Ocsipersist_error (* get one value from the result of a query *) let one_value = function | [Some value]::xs -> unpack_value value | _ -> raise Not_found let prepare db query = let hashtbl = PGOCaml.private_data db in (* Get a unique name for this query using an MD5 digest. *) let name = Digest.to_hex (Digest.string query) in (* Have we prepared this statement already? If not, do so. *) let is_prepared = Hashtbl.mem hashtbl name in begin if is_prepared then Lwt.return () else begin PGOCaml.prepare db ~name ~query () >> Lwt.return @@ Hashtbl.add hashtbl name () end end >>= fun () -> Lwt.return name let exec db query params = prepare db query >>= fun name -> let params = List.map (fun x -> Some (pack x)) params in PGOCaml.execute db ~name ~params () let (@.) f g = fun x -> f (g x) (* function composition *) let create_table db table = let query = sprintf "CREATE TABLE IF NOT EXISTS %s \ (key TEXT, value BYTEA, PRIMARY KEY(key))" table in exec db query [] >> Lwt.return () type store = string type 'a t = { store : string; name : string; } let open_store store = use_pool @@ fun db -> create_table db store >> Lwt.return store let make_persistent_worker ~store ~name ~default db = let query = sprintf "INSERT INTO %s VALUES ( $1 , $2 ) ON CONFLICT ( key ) DO NOTHING" store in (* NOTE: incompatible with < 9.5 *) exec db query [Key name; Value default] >> Lwt.return {store; name} let make_persistent ~store ~name ~default = use_pool @@ fun db -> make_persistent_worker ~store ~name ~default db let make_persistent_lazy_lwt ~store ~name ~default = use_pool @@ fun db -> let query = sprintf "SELECT 1 FROM %s WHERE key = $1 " store in exec db query [Key name] >>= function | [] -> default () >>= fun default -> make_persistent_worker ~store ~name ~default db | _ -> Lwt.return {store = store; name = name} let make_persistent_lazy ~store ~name ~default = let default () = Lwt.wrap default in make_persistent_lazy_lwt ~store ~name ~default let get p = use_pool @@ fun db -> let query = sprintf "SELECT value FROM %s WHERE key = $1 " p.store in Lwt.map one_value (exec db query [Key p.name]) let set p v = use_pool @@ fun db -> let query = sprintf "UPDATE %s SET value = $2 WHERE key = $1 " p.store in exec db query [Key p.name; Value v] >> Lwt.return () type 'value table = string let table_name table = Lwt.return table let existing_tables = Hashtbl.create 16 let open_table table = if Hashtbl.mem existing_tables table then Lwt.return table else begin use_pool @@ fun db -> create_table db table >>= fun () -> Hashtbl.add existing_tables table (); Lwt.return table end let find table key = use_pool @@ fun db -> let query = sprintf "SELECT value FROM %s WHERE key = $1 " table in Lwt.map one_value (exec db query [Key key]) let add table key value = use_pool @@ fun db -> let query = sprintf "INSERT INTO %s VALUES ( $1 , $2 ) ON CONFLICT ( key ) DO UPDATE SET value = $2 " table (* NOTE: incompatible with < 9.5 *) in exec db query [Key key; Value value] >> Lwt.return () let replace_if_exists table key value = use_pool @@ fun db -> let query = sprintf "UPDATE %s SET value = $2 WHERE key = $1 RETURNING 0" table in exec db query [Key key; Value value] >>= function | [] -> raise Not_found | _ -> Lwt.return () let remove table key = use_pool @@ fun db -> let query = sprintf "DELETE FROM %s WHERE key = $1 " table in exec db query [Key key] >> Lwt.return () let length table = use_pool @@ fun db -> let query = sprintf "SELECT count(*) FROM %s " table in Lwt.map one_value (exec db query []) let rec list_last l = match l with | [x] -> x | _ :: r -> list_last r | [] -> assert false let rec iter_rec f table last = let (query, args) = match last with | None -> (sprintf "SELECT * FROM %s ORDER BY key LIMIT 1000" table, []) | Some last -> (sprintf "SELECT * FROM %s WHERE key > $1 ORDER BY key LIMIT 1000" table, [Key last]) in (use_pool @@ fun db -> exec db query args) >>= fun l -> Lwt_list.iter_s (fun row -> let key, value = key_value_of_row row in f key value) l >>= fun () -> if l = [] then Lwt.return_unit else let last, _ = key_value_of_row (list_last l) in iter_rec f table (Some last) let iter_step f table = iter_rec f table None let iter_table = iter_step let fold_step f table x = let res = ref x in let g key value = f key value !res >>= fun res' -> res := res'; Lwt.return () in iter_step g table >> Lwt.return !res let fold_table = fold_step let iter_block a b = failwith "Ocsipersist.iter_block: not implemented" let parse_global_config = function | [] -> () | [Xml.Element ("database", attrs, [])] -> let parse_attr = function | ("host", h) -> host := Some h | ("port", p) -> begin try port := Some (int_of_string p) with Failure _ -> raise @@ Ocsigen_extensions.Error_in_config_file "port is not an integer" end | ("user", u) -> user := Some u | ("password", pw) -> password := Some pw | ("database", db) -> database := db | ("unix_domain_socket_dir", udsd) -> unix_domain_socket_dir := Some udsd | ("size_conn_pool", scp) -> begin try size_conn_pool := int_of_string scp with Failure _ -> raise @@ Ocsigen_extensions.Error_in_config_file "size_conn_pool is not an integer" end | _ -> raise @@ Ocsigen_extensions.Error_in_config_file "Unexpected attribute for in Ocsipersist config" in ignore @@ List.map parse_attr attrs; () | _ -> raise @@ Ocsigen_extensions.Error_in_config_file "Unexpected content inside Ocsipersist config" let init_fun config = parse_global_config config; conn_pool := Lwt_pool.create !size_conn_pool ~validate:PGOCaml.alive connect let _ = Ocsigen_extensions.register_extension ~name:"ocsipersist" ~init_fun () ocsigenserver-2.16.0/src/extensions/ocsipersist-sqlite/000077500000000000000000000000001357715257700233355ustar00rootroot00000000000000ocsigenserver-2.16.0/src/extensions/ocsipersist-sqlite/.depend000066400000000000000000000004761357715257700246040ustar00rootroot00000000000000ocsipersist.cmo : ../../baselib/ocsigen_messages.cmi \ ../../server/ocsigen_extensions.cmi ../../baselib/ocsigen_config.cmi \ ocsipersist.cmi ocsipersist.cmx : ../../baselib/ocsigen_messages.cmx \ ../../server/ocsigen_extensions.cmx ../../baselib/ocsigen_config.cmx \ ocsipersist.cmi ocsipersist.cmi : ocsigenserver-2.16.0/src/extensions/ocsipersist-sqlite/Makefile000066400000000000000000000024451357715257700250020ustar00rootroot00000000000000include ../../../Makefile.config PACKAGE := lwt lwt_log xml-light sqlite3 LIBS := -I ../../baselib -I ../../http -I ../../server \ ${addprefix -package ,${PACKAGE}} OCAMLC := $(OCAMLFIND) ocamlc${BYTEDBG} ${THREAD} OCAMLOPT := $(OCAMLFIND) ocamlopt ${OPTDBG} ${THREAD} OCAMLDOC := $(OCAMLFIND) ocamldoc OCAMLDEP := $(OCAMLFIND) ocamldep all: byte opt ### byte: ocsipersist-sqlite.cma opt:: ocsipersist-sqlite.cmxa ifeq "$(NATDYNLINK)" "YES" opt:: ocsipersist-sqlite.cmxs endif PREDEP := ocsipersist.mli ocsipersist-sqlite.cma: ocsipersist.cmo $(OCAMLC) -a -o $@ $^ cp ocsipersist.cmi .. cp $@ .. ocsipersist-sqlite.cmxa: ocsipersist.cmx $(OCAMLOPT) -a -o $@ $^ cp ocsipersist.cmi .. cp $@ ${patsubst %.cmxa,%.a,$@} .. ocsipersist-sqlite.cmxs: ocsipersist-sqlite.cmxa $(OCAMLOPT) -shared -linkall -o $@ $^ cp $@ .. ocsipersist.mli: ln -s -f ../ocsipersist.mli . ########## %.cmi: %.mli $(OCAMLC) ${LIBS} -c $< %.cmo: %.ml $(OCAMLC) ${LIBS} -c $< %.cmx: %.ml $(OCAMLOPT) ${LIBS} -c $< %.cmxs: %.cmxa $(OCAMLOPT) -shared -linkall -o $@ $< ## Clean up clean: -rm -f *.cm[ioax] *.cmxa *.cmxs *.o *.a *.annot -rm -f ${PREDEP} distclean: clean -rm -f *~ \#* .\#* -rm -f .depend ## Dependencies depend: ${PREDEP} $(OCAMLDEP) ${LIBS} *.mli *.ml > .depend FORCE: -include .depend ocsigenserver-2.16.0/src/extensions/ocsipersist-sqlite/ocsipersist.ml000066400000000000000000000231201357715257700262340ustar00rootroot00000000000000(* Ocsigen * http://www.ocsigen.org * Module ocsipersist.ml * Copyright (C) 2007 Vincent Balat - Gabriel Kerneis * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) let section = Lwt_log.Section.make "ocsigen:ocsipersist:sqlite" (** Module Ocsipersist: persistent data *) open Lwt open Sqlite3 open Printf (** Data are divided into stores. Create one store for your project, where you will save all your data. *) type store = string exception Ocsipersist_error (*****************************************************************************) (** getting the directory from config file *) let rec parse_global_config = function | [] -> None | (Xml.Element ("database", [("file", s)], []))::[] -> Some s | _ -> raise (Ocsigen_extensions.Error_in_config_file ("Unexpected content inside Ocsipersist config")) (* This reference is overwritten when the init function (at the end of the file) is run, which occurs when the extension is loaded *) let db_file = ref ((Ocsigen_config.get_datadir ())^"/ocsidb") (*****************************************************************************) (** Useful functions on database *) let yield () = Thread.yield () let rec bind_safely stmt = function | [] -> stmt | (value, name)::q as l -> match Sqlite3.bind stmt (bind_parameter_index stmt name) value with | Rc.OK -> bind_safely stmt q | Rc.BUSY | Rc.LOCKED -> yield () ; bind_safely stmt l | rc -> ignore(finalize stmt) ; failwith (Rc.to_string rc) let close_safely db = if not (db_close db) then Lwt_log.ign_error ~section "Couldn't close database" let m = Mutex.create () let exec_safely f = let aux () = let db = Mutex.lock m ; try db_open !db_file with e -> Mutex.unlock m; raise e in (try let r = f db in close_safely db ; Mutex.unlock m ; r with e -> ( close_safely db ; Mutex.unlock m ; raise e)) in Lwt_preemptive.detach aux () (* Rfrence indispensable pour les codes de retours et leur signification : * http://sqlite.org/capi3ref.html * Langage compris par SQLite : http://www.sqlite.org/lang.html *) let db_create table = let sql = sprintf "CREATE TABLE IF NOT EXISTS %s (key TEXT, value BLOB, PRIMARY KEY(key) ON CONFLICT REPLACE)" table in let create db = let stmt = prepare db sql in let rec aux () = match step stmt with | Rc.DONE -> ignore(finalize stmt) | Rc.BUSY | Rc.LOCKED -> yield () ; aux () | rc -> ignore(finalize stmt) ; failwith (Rc.to_string rc) in aux () in exec_safely create >>= fun () -> return table let db_remove (table, key) = let sql = sprintf "DELETE FROM %s WHERE key = :key " table in let remove db = let stmt = bind_safely (prepare db sql) [Data.TEXT key,":key"] in let rec aux () = match step stmt with | Rc.DONE -> ignore(finalize stmt) | Rc.BUSY | Rc.LOCKED -> yield () ; aux () | rc -> ignore(finalize stmt) ; failwith (Rc.to_string rc) in aux () in exec_safely remove let (db_get, db_replace, db_replace_if_exists) = let get (table, key) db = let sqlget = sprintf "SELECT value FROM %s WHERE key = :key " table in let stmt = bind_safely (prepare db sqlget) [Data.TEXT key,":key"] in let rec aux () = match step stmt with | Rc.ROW -> let value = match column stmt 0 with | Data.BLOB s -> s | _ -> assert false in ignore (finalize stmt); value | Rc.DONE -> ignore(finalize stmt) ; raise Not_found | Rc.BUSY | Rc.LOCKED -> yield () ; aux () | rc -> ignore(finalize stmt) ; failwith (Rc.to_string rc) in aux () in let replace (table, key) value db = let sqlreplace = sprintf "INSERT INTO %s VALUES ( :key , :value )" table in let stmt = bind_safely (prepare db sqlreplace) [Data.TEXT key,":key"; Data.BLOB value, ":value"] in let rec aux () = match step stmt with | Rc.DONE -> ignore(finalize stmt) | Rc.BUSY | Rc.LOCKED -> yield () ; aux () | rc -> ignore(finalize stmt) ; failwith (Rc.to_string rc) in aux () in ((fun tablekey -> exec_safely (get tablekey)), (fun tablekey value -> exec_safely (replace tablekey value)), (fun tablekey value -> exec_safely (fun db -> ignore (get tablekey db); replace tablekey value db))) let db_iter_step table rowid = let sql = sprintf "SELECT key , value , ROWID FROM %s WHERE ROWID > :rowid" table in let iter db = let stmt = bind_safely (prepare db sql) [Data.INT rowid, ":rowid"] in let rec aux () = match step stmt with | Rc.ROW -> (match (column stmt 0,column stmt 1, column stmt 2) with | (Data.TEXT k, Data.BLOB v, Data.INT rowid) -> ignore(finalize stmt) ; Some (k, v, rowid) | _ -> assert false ) | Rc.DONE -> ignore(finalize stmt) ; None | Rc.BUSY | Rc.LOCKED -> yield () ; aux () | rc -> ignore(finalize stmt) ; failwith (Rc.to_string rc) in aux () in exec_safely iter let db_iter_block table f = let sql = sprintf "SELECT key , value FROM %s " table in let iter db = let stmt = prepare db sql in let rec aux () = match step stmt with | Rc.ROW -> (match (column stmt 0,column stmt 1) with | (Data.TEXT k, Data.BLOB v) -> f k (Marshal.from_string v 0); aux() | _ -> assert false ) | Rc.DONE -> ignore(finalize stmt) | Rc.BUSY | Rc.LOCKED -> yield () ; aux () | rc -> ignore(finalize stmt) ; failwith (Rc.to_string rc) in aux () in exec_safely iter let db_length table = let sql = sprintf "SELECT count(*) FROM %s " table in let length db = let stmt = prepare db sql in let rec aux () = match step stmt with | Rc.ROW -> let value = match column stmt 0 with | Data.INT s -> Int64.to_int s | _ -> assert false in ignore (finalize stmt); value | Rc.DONE -> ignore(finalize stmt) ; raise Not_found | Rc.BUSY | Rc.LOCKED -> yield () ; aux () | rc -> ignore(finalize stmt) ; failwith (Rc.to_string rc) in aux () in exec_safely length (*****************************************************************************) (** Public functions: *) (** Type of persistent data *) type 'a t = string * string let open_store name = let s = "store___"^name in db_create s let make_persistent_lazy_lwt ~store ~name ~default = let pvname = (store, name) in (catch (fun () -> db_get pvname >>= (fun _ -> return ())) (function | Not_found -> default () >>= fun def -> db_replace pvname (Marshal.to_string def []) | e -> fail e)) >>= (fun () -> return pvname) let make_persistent_lazy ~store ~name ~default = let default () = Lwt.wrap default in make_persistent_lazy_lwt ~store ~name ~default let make_persistent ~store ~name ~default = make_persistent_lazy ~store ~name ~default:(fun () -> default) let get (pvname : 'a t) : 'a = db_get pvname >>= (fun r -> return (Marshal.from_string r 0)) let set pvname v = let data = Marshal.to_string v [] in db_replace pvname data (** Type of persistent tables *) type 'value table = string (** name SHOULD NOT begin with "store___" *) let open_table name = db_create name let table_name table = Lwt.return table let find table key = db_get (table, key) >>= fun v -> return (Marshal.from_string v 0) let add table key value = let data = Marshal.to_string value [] in db_replace (table, key) data let replace_if_exists table key value = let data = Marshal.to_string value [] in db_replace_if_exists (table, key) data let remove table key = db_remove (table, key) let iter_step f table = let rec aux rowid = db_iter_step table rowid >>= (function | None -> return () | Some (k,v,rowid') -> f k (Marshal.from_string v 0) >>= (fun () -> aux rowid')) in aux Int64.zero let fold_step f table beg = let rec aux rowid beg = db_iter_step table rowid >>= (function | None -> return beg | Some (k, v, rowid') -> f k (Marshal.from_string v 0) beg >>= (fun res -> aux rowid' res)) in aux Int64.zero beg let iter_block f table = db_iter_block table f let iter_table = iter_step let fold_table = fold_step let length table = db_length table (* Registration of the extension *) let init config = db_file := Ocsigen_config.get_datadir () ^"/ocsidb"; (match parse_global_config config with | None -> () | Some d -> db_file := d ); (* We check that we can access the database *) try Lwt_main.run (exec_safely (fun _ -> ())) with e -> Ocsigen_messages.errlog (Printf.sprintf "Error opening database file '%s' when registering Ocsipersist. \ Check that the directory exists, and that Ocsigen has enough \ rights" !db_file); raise e let _ = Ocsigen_extensions.register_extension ~name:"ocsipersist" ~init_fun:init () ocsigenserver-2.16.0/src/extensions/ocsipersist.mli000066400000000000000000000120011357715257700225330ustar00rootroot00000000000000(* Ocsigen * http://www.ocsigen.org * Module ocsipersist.mli * Copyright (C) 2007 Vincent Balat - Gabriel Kerneis * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) (** Persistent data on hard disk. *) (** There are currently three implementations of this module, one using a DBM database, on the PostgreSQL database, and one using SQLITE. Link the one your want with your program. *) (*****************************************************************************) (** {2 Persistent references} *) (** When launching the program, if the value exists on hard disk, it is loaded, otherwise it is initialised to the default value *) (** Type of persistent data *) type 'a t (** Data are divided into stores. Create one store for your project, where you will save all your data. *) type store (** Open a store (and create it if it does not exist) *) val open_store : string -> store Lwt.t val make_persistent : store:store -> name:string -> default:'a -> 'a t Lwt.t (** [make_persistent store name default] find a persistent value named [name] in store [store] from database, or create it with the default value [default] if it does not exist. *) val make_persistent_lazy : store:store -> name:string -> default:(unit -> 'a) -> 'a t Lwt.t (** Same as make_persistent but the default value is evaluated only if needed *) val make_persistent_lazy_lwt : store:store -> name:string -> default:(unit -> 'a Lwt.t) -> 'a t Lwt.t (** Lwt version of make_persistent_lazy. *) val get : 'a t -> 'a Lwt.t (** [get pv] gives the value of [pv] *) val set : 'a t -> 'a -> unit Lwt.t (** [set pv value] sets a persistent value [pv] to [value] *) (*****************************************************************************) (** {2 Persistent tables} *) (** Type of persistent table *) type 'value table (** returns the name of the table *) val table_name : 'value table -> string Lwt.t (** Open a table (and create it if it does not exist) *) val open_table : string -> 'value table Lwt.t val find : 'value table -> string -> 'value Lwt.t (** [find table key] gives the value associated to [key]. Fails with [Not_found] if not found. *) val add : 'value table -> string -> 'value -> unit Lwt.t (** [add table key value] associates [value] to [key]. If the database already contains data associated with [key], that data is discarded and silently replaced by the new data. *) val replace_if_exists : 'value table -> string -> 'value -> unit Lwt.t (** [replace_if_exists table key value] associates [value] to [key] only if [key] is already bound. If the database does not contain any data associated with [key], fails with [Not_found]. *) val remove : 'value table -> string -> unit Lwt.t (** [remove table key] removes the entry in the table if it exists *) val length : 'value table -> int Lwt.t (** Size of a table. *) val iter_step : (string -> 'a -> unit Lwt.t) -> 'a table -> unit Lwt.t (** Important warning: this iterator may not iter on all data of the table if another thread is modifying it in the same time. Nonetheless, it should not miss more than a very few data from time to time, except if the table is very old (at least 9 223 372 036 854 775 807 insertions). *) val iter_table : (string -> 'a -> unit Lwt.t) -> 'a table -> unit Lwt.t (** Legacy interface for iter_step *) val fold_step : (string -> 'a -> 'b -> 'b Lwt.t) -> 'a table -> 'b -> 'b Lwt.t (** Important warning: this iterator may not iter on all data of the table if another thread is modifying it in the same time. Nonetheless, it should not miss more than a very few data from time to time, except if the table is very old (at least 9 223 372 036 854 775 807 insertions). *) val fold_table : (string -> 'a -> 'b -> 'b Lwt.t) -> 'a table -> 'b -> 'b Lwt.t (** Legacy interface for fold_step *) (**/**) val iter_block : (string -> 'a -> unit) -> 'a table -> unit Lwt.t (** MAJOR WARNING: Unlike iter_step, this iterator won't miss any entry and will run in one shot. It is therefore more efficient, BUT: it will lock the WHOLE database during its execution, thus preventing ANYBODY from accessing it (including the function f which is iterated). As a consequence : you MUST NOT use any function from ocsipersist in f, otherwise you would lock yourself and everybody else ! Be VERY cautious. *) ocsigenserver-2.16.0/src/extensions/outputfilter.ml000066400000000000000000000136141357715257700225740ustar00rootroot00000000000000(* Ocsigen * http://www.ocsigen.org * Module outputfilter.ml * Copyright (C) 2008 Vincent Balat * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) (*****************************************************************************) (*****************************************************************************) (* This module allows to rewrite the output sent by the server *) (*****************************************************************************) (*****************************************************************************) open Lwt open Ocsigen_extensions open Ocsigen_headers type outputfilter = | Rewrite_header of (Http_headers.name * Netstring_pcre.regexp * string) | Add_header of (Http_headers.name * string * bool option) let gen filter = function | Req_not_found (code,_) -> return (Ext_next code) | Req_found (ri, res) -> let new_headers = match filter with | Rewrite_header (header, regexp, dest) -> begin try let header_values = Http_headers.find_all header (Ocsigen_http_frame.Result.headers res) in let h = Http_headers.replace_opt header None (Ocsigen_http_frame.Result.headers res) in List.fold_left (fun h value -> Http_headers.add header (Netstring_pcre.global_replace regexp dest value) h ) h header_values with | Not_found -> Ocsigen_http_frame.Result.headers res end | Add_header (header, dest, replace) -> begin match replace with | None -> begin try ignore (Http_headers.find header (Ocsigen_http_frame.Result.headers res)); (Ocsigen_http_frame.Result.headers res) with | Not_found -> Http_headers.add header dest (Ocsigen_http_frame.Result.headers res) end | Some false -> Http_headers.add header dest (Ocsigen_http_frame.Result.headers res) | Some true -> Http_headers.replace header dest (Ocsigen_http_frame.Result.headers res) end in Lwt.return (Ocsigen_extensions.Ext_found (fun () -> Lwt.return (Ocsigen_http_frame.Result.update res ~headers:new_headers ()))) let gen_code code = function | Req_not_found (code,_) -> return (Ext_next code) | Req_found (ri, res) -> Lwt.return (Ocsigen_extensions.Ext_found (fun () -> Lwt.return (Ocsigen_http_frame.Result.update res ~code ()))) (*****************************************************************************) let parse_config config_elem = let header = ref None in let regexp = ref None in let dest = ref None in let replace = ref None in let code = ref None in Ocsigen_extensions.( Configuration.process_element ~in_tag:"host" ~other_elements:(fun t _ _ -> raise (Bad_config_tag_for_extension t)) ~elements:[ Configuration.element ~name:"outputfilter" ~attributes:[ Configuration.attribute ~name:"header" (fun s -> header := Some s); Configuration.attribute ~name:"regexp" (fun s -> regexp := Some (Netstring_pcre.regexp s)); Configuration.attribute ~name:"dest" (fun s -> dest := Some s); Configuration.attribute ~name:"replace" (fun s -> try replace := Some (bool_of_string s) with | Invalid_argument _ -> badconfig "Wrong value for attribute \ replace of : \ %s. It should be true or false" s ); ] (); Configuration.element ~name:"sethttpcode" ~attributes:[ Configuration.attribute ~name:"code" (fun s -> try code := Some (int_of_string s) with Failure _ -> badconfig "Invalid code attribute in " ); ] ()] config_elem ); match !code with | None -> begin match !header, !regexp, !dest, !replace with | (_, Some _, _, Some _) -> badconfig "Wrong attributes for : attributes regexp and \ replace can't be set simultaneously" | (Some h, Some r, Some d, None) -> gen (Rewrite_header (Http_headers.name h, r, d)) | (Some h, None, Some d, rep) -> gen (Add_header (Http_headers.name h, d, rep)) | _ -> badconfig "Wrong attributes for " end | Some code -> gen_code code (*****************************************************************************) (** Registration of the extension *) let () = register_extension ~name:"outputfilter" ~fun_site:(fun _ _ _ _ _ -> parse_config) ~user_fun_site:(fun _ _ _ _ _ _ -> parse_config) () ocsigenserver-2.16.0/src/extensions/redirectmod.ml000066400000000000000000000127601357715257700223300ustar00rootroot00000000000000(* Ocsigen * http://www.ocsigen.org * Module redirectmod.ml * Copyright (C) 2007 Vincent Balat * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) (*****************************************************************************) (*****************************************************************************) (* Ocsigen extension for defining page redirections *) (* in the configuration file *) (*****************************************************************************) (*****************************************************************************) (* To compile it: ocamlfind ocamlc -thread -package netstring-pcre,ocsigen -c extensiontemplate.ml Then load it dynamically from Ocsigen's config file: *) open Ocsigen_lib open Ocsigen_extensions let section = Lwt_log.Section.make "ocsigen:ext:redirectmod" (*****************************************************************************) (* The table of redirections for each virtual server *) type assockind = | Regexp of Netstring_pcre.regexp * string * yesnomaybe (* full url *) * bool (* temporary *) (*****************************************************************************) (** The function that will generate the pages from the request. *) let gen dir = function | Ocsigen_extensions.Req_found _ -> Lwt.return Ocsigen_extensions.Ext_do_nothing | Ocsigen_extensions.Req_not_found (err, ri) -> Lwt.catch (* Is it a redirection? *) (fun () -> Lwt_log.ign_info ~section "Is it a redirection?"; let Regexp (regexp, dest, full, temp) = dir in let redir = let fi full = Ocsigen_extensions.find_redirection regexp full dest (Ocsigen_request_info.ssl ri.request_info) (Ocsigen_request_info.host ri.request_info) (Ocsigen_request_info.server_port ri.request_info) (Ocsigen_request_info.get_params_string ri.request_info) (Ocsigen_request_info.sub_path_string ri.request_info) (Ocsigen_request_info.full_path_string ri.request_info) in match full with | Yes -> fi true | No -> fi false | Maybe -> try fi false with Ocsigen_extensions.Not_concerned -> fi true in Lwt_log.ign_info_f ~section "YES! %s redirection to: %s" (if temp then "Temporary " else "Permanent ") redir; let empty_result = Ocsigen_http_frame.Result.empty () in Lwt.return (Ext_found (fun () -> Lwt.return (Ocsigen_http_frame.Result.update empty_result ~location:(Some redir) ~code: (if temp then 302 else 301) ()))) ) (function | Ocsigen_extensions.Not_concerned -> Lwt.return (Ext_next err) | e -> Lwt.fail e) (*****************************************************************************) let parse_config config_elem = let pattern = ref None in let dest = ref "" in let mode = ref Yes in let temporary = ref false in Ocsigen_extensions.( Configuration.process_element ~in_tag:"host" ~other_elements:(fun t _ _ -> raise (Bad_config_tag_for_extension t)) ~elements:[ Configuration.element ~name:"redirect" ~attributes:[ Configuration.attribute ~name:"regexp" (fun s -> pattern := Some ("^" ^ s ^ "$"); mode := Maybe); Configuration.attribute ~name:"fullurl" (fun s -> pattern := Some ("^" ^ s ^ "$"); mode := Yes); Configuration.attribute ~name:"suburl" (fun s -> pattern := Some ("^" ^ s ^ "$"); mode := No); Configuration.attribute ~name:"dest" ~obligatory:true (fun s -> dest := s); Configuration.attribute ~name:"temporary" (function "temporary" -> temporary := true | _ -> ()); ] ()] config_elem ); match !pattern with | None -> badconfig "Missing attribute regexp for " | Some regexp -> gen (Regexp (Netstring_pcre.regexp regexp, !dest, !mode, !temporary)) (*****************************************************************************) (** Registration of the extension *) let () = register_extension ~name:"redirectmod" ~fun_site:(fun _ _ _ _ _ -> parse_config) ~user_fun_site:(fun _ _ _ _ _ _ -> parse_config) () ocsigenserver-2.16.0/src/extensions/revproxy.ml000066400000000000000000000260451357715257700217260ustar00rootroot00000000000000(* Ocsigen * http://www.ocsigen.org * Module revproxy.ml * Copyright (C) 2007 Vincent Balat * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) (** Reverse proxy for Ocsigen *) (* The reverse proxy is still experimental because it relies on the experimental Ocsigen_http_client module. TODO - Change the policy for trusted servers for pipelining? (see ocsigen_http_client.ml) - enhance pipelining - HTTP/1.0 - ... - Make possible to return for example (Ext_next 404) to allow other extensions to take the request? There is a problem if the body contains data (POST request) ... this data has been sent and is lost ... *) open Ocsigen_lib open Lwt open Ocsigen_extensions let section = Lwt_log.Section.make "ocsigen:ext:revproxy" exception Bad_answer_from_http_server (*****************************************************************************) (* The table of redirections for each virtual server *) type redir = { regexp: Netstring_pcre.regexp; full_url: yesnomaybe; dest: string; pipeline: bool; keephost: bool} (*****************************************************************************) (* Finding redirections *) (** The function that will generate the pages from the request. *) let gen dir = function | Ocsigen_extensions.Req_found _ -> Lwt.return Ocsigen_extensions.Ext_do_nothing | Ocsigen_extensions.Req_not_found (err, ri) -> catch (* Is it a redirection? *) (fun () -> Lwt_log.ign_info ~section "Is it a redirection?"; let dest = let ri = ri.request_info in let fi full = Ocsigen_extensions.find_redirection dir.regexp full dir.dest (Ocsigen_request_info.ssl ri) (Ocsigen_request_info.host ri) (Ocsigen_request_info.server_port ri) (Ocsigen_request_info.get_params_string ri) (Ocsigen_request_info.sub_path_string ri) (Ocsigen_request_info.full_path_string ri) in match dir.full_url with | Yes -> fi true | No -> fi false | Maybe -> try fi false with Ocsigen_extensions.Not_concerned -> fi true in let (https, host, port, uri) = try match Url.parse dest with | (Some https, Some host, port, uri, _, _, _) -> let port = match port with | None -> if https then 443 else 80 | Some p -> p in (https, host, port, uri) | _ -> raise (Ocsigen_extensions.Error_in_config_file ("Revproxy : error in destination URL "^dest)) (*VVV catch only Neturl exceptions! *) with e -> raise (Ocsigen_extensions.Error_in_config_file ("Revproxy : error in destination URL "^dest^" - "^ Printexc.to_string e)) in let uri = "/"^uri in Lwt_log.ign_info_f ~section "YES! Redirection to http%s://%s:%d%s" (if https then "s" else "") host port uri; Ip_address.get_inet_addr host >>= fun inet_addr -> (* It is now safe to start processing next request. We are sure that the request won't be taken in disorder. => We return. *) let host = match if dir.keephost then match Ocsigen_request_info.host ri.request_info with | Some h -> Some h | None -> None else None with | Some h -> h | None -> host in let do_request = let ri = ri.request_info in let address = Unix.string_of_inet_addr (fst (get_server_address ri)) in let forward = String.concat ", " ((Ocsigen_request_info.remote_ip ri) :: ((Ocsigen_request_info.forward_ip ri) @ [address])) in let proto = if Ocsigen_request_info.ssl ri then "https" else "http" in let headers = Http_headers.replace Http_headers.x_forwarded_proto proto (Http_headers.replace Http_headers.x_forwarded_for forward ((Ocsigen_request_info.http_frame ri) .Ocsigen_http_frame.frame_header .Ocsigen_http_frame.Http_header.headers)) in if dir.pipeline then Ocsigen_http_client.raw_request ~headers ~https ~port ~client:(Ocsigen_request_info.client ri) ~keep_alive:true ~content: (Ocsigen_request_info.http_frame ri) .Ocsigen_http_frame.frame_content ?content_length:(Ocsigen_request_info.content_length ri) ~http_method:(Ocsigen_request_info.meth ri) ~host ~inet_addr ~uri () else fun () -> Ocsigen_http_client.basic_raw_request ~headers ~https ~port ~content: (Ocsigen_request_info.http_frame ri) .Ocsigen_http_frame.frame_content ?content_length:(Ocsigen_request_info.content_length ri) ~http_method:(Ocsigen_request_info.meth ri) ~host ~inet_addr ~uri () in Lwt.return (Ext_found (fun () -> do_request () >>= fun http_frame -> let headers = http_frame .Ocsigen_http_frame.frame_header .Ocsigen_http_frame.Http_header.headers in let code = match http_frame .Ocsigen_http_frame.frame_header .Ocsigen_http_frame.Http_header.mode with | Ocsigen_http_frame.Http_header.Answer code -> code | _ -> raise Bad_answer_from_http_server in match http_frame.Ocsigen_http_frame.frame_content with | None -> let empty_result = Ocsigen_http_frame.Result.empty () in let length = Ocsigen_headers.get_content_length http_frame in Ocsigen_stream.add_finalizer (fst (Ocsigen_http_frame.Result.stream empty_result)) (fun outcome -> match outcome with `Failure -> http_frame.Ocsigen_http_frame.frame_abort () | `Success -> Lwt.return ()); Lwt.return (Ocsigen_http_frame.Result.update empty_result ~content_length:length ~headers ~code ()) | Some stream -> let default_result = Ocsigen_http_frame.Result.default () in let length = Ocsigen_headers.get_content_length http_frame in Ocsigen_stream.add_finalizer stream (fun outcome -> match outcome with `Failure -> http_frame.Ocsigen_http_frame.frame_abort () | `Success -> Lwt.return ()); Lwt.return (Ocsigen_http_frame.Result.update default_result ~content_length:length ~stream:(stream, None) ~headers ~code ()) ) ) ) (function | Not_concerned -> return (Ext_next err) | e -> fail e) (*****************************************************************************) let parse_config config_elem = let regexp = ref None in let full_url = ref Yes in let dest = ref None in let pipeline = ref true in let keephost = ref false in Ocsigen_extensions.( Configuration.process_element ~in_tag:"host" ~other_elements:(fun t _ _ -> raise (Bad_config_tag_for_extension t)) ~elements:[ Configuration.element ~name:"revproxy" ~attributes:[ Configuration.attribute ~name:"regexp" (fun s -> regexp := Some s; full_url := Yes); Configuration.attribute ~name:"fullurl" (fun s -> regexp := Some s; full_url := Yes); Configuration.attribute ~name:"suburl" (fun s -> regexp := Some s; full_url := No); Configuration.attribute ~name:"dest" (fun s -> dest := Some s); Configuration.attribute ~name:"keephost" (function "keephost" -> keephost := true | _ -> ()); Configuration.attribute ~name:"nopipeline" (function "nopipeline" -> pipeline := false | _ -> ()); ] ()] config_elem ); match !regexp, !full_url, !dest, !pipeline, !keephost with | (None, _, _, _, _) -> badconfig "Missing attribute 'regexp' for " | (_, _, None, _, _) -> badconfig "Missing attribute 'dest' for " | (Some regexp, full_url, Some dest, pipeline, keephost) -> gen { regexp = Netstring_pcre.regexp ("^" ^ regexp ^ "$"); full_url; dest; pipeline; keephost; } (*****************************************************************************) (** Registration of the extension *) let () = register_extension ~name:"revproxy" ~fun_site:(fun _ _ _ _ _ -> parse_config) ~user_fun_site:(fun _ _ _ _ _ _ -> parse_config) ~respect_pipeline:true (* We ask ocsigen to respect pipeline order when sending to extensions! *) () ocsigenserver-2.16.0/src/extensions/rewritemod.ml000066400000000000000000000142351357715257700222070ustar00rootroot00000000000000(* Ocsigen * http://www.ocsigen.org * Module rewritemod.ml * Copyright (C) 2008 Vincent Balat * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) (*****************************************************************************) (*****************************************************************************) (* Ocsigen extension for rewriteing URLs *) (* in the configuration file *) (*****************************************************************************) (*****************************************************************************) (* IMPORTANT WARNING It is really basic for now: - rewrites only subpaths (and do not change get parameters) - changes only ri_sub_path and ri_sub_path_tring not ri_full_path and ri_full_path_string and ri_url_string and ri_url This is probably NOT what we want ... *) (* To compile it: ocamlfind ocamlc -thread -package netstring-pcre,ocsigen -c extensiontemplate.ml Then load it dynamically from Ocsigen's config file: *) open Lwt open Ocsigen_extensions let section = Lwt_log.Section.make "ocsigen:ext:rewritemod" exception Not_concerned (*****************************************************************************) (* The table of rewrites for each virtual server *) type assockind = | Regexp of Netstring_pcre.regexp * string * bool (*****************************************************************************) (* Finding rewrites *) let find_rewrite (Regexp (regexp, dest, fullrewrite)) suburl = (match Netstring_pcre.string_match regexp suburl 0 with | None -> raise Not_concerned | Some _ -> (* Matching regexp found! *) Netstring_pcre.global_replace regexp dest suburl), fullrewrite (*****************************************************************************) (** The function that will generate the pages from the request. *) let gen regexp continue = function | Ocsigen_extensions.Req_found _ -> Lwt.return Ocsigen_extensions.Ext_do_nothing | Ocsigen_extensions.Req_not_found (err, ri) -> catch (* Is it a rewrite? *) (fun () -> Lwt_log.ign_info ~section "Is it a rewrite?"; let redir, fullrewrite = let ri = ri.request_info in find_rewrite regexp (match Ocsigen_request_info.get_params_string ri with | None -> Ocsigen_request_info.sub_path_string ri | Some g -> (Ocsigen_request_info.sub_path_string ri) ^ "?" ^ g) in Lwt_log.ign_info_f ~section "YES! rewrite to: %s" redir; if continue then return (Ext_continue_with ({ ri with request_info = Ocsigen_extensions.ri_of_url ~full_rewrite:fullrewrite redir ri.request_info }, Ocsigen_cookies.Cookies.empty, err) ) else return (Ext_retry_with ({ ri with request_info = Ocsigen_extensions.ri_of_url ~full_rewrite:fullrewrite redir ri.request_info }, Ocsigen_cookies.Cookies.empty) ) ) (function | Not_concerned -> return (Ext_next err) | e -> fail e) (*****************************************************************************) let parse_config element = let regexp = ref "" in let dest = ref None in let fullrewrite = ref false in let continue = ref false in Ocsigen_extensions.( Configuration.process_element ~in_tag:"host" ~other_elements:(fun t _ _ -> raise (Bad_config_tag_for_extension t)) ~elements:[Configuration.element ~name:"rewrite" ~attributes:[Configuration.attribute ~name:"regexp" ~obligatory:true (fun s -> regexp := s); Configuration.attribute ~name:"url" (fun s -> dest := Some s); Configuration.attribute ~name:"dest" (fun s -> dest := Some s); Configuration.attribute ~name:"fullrewrite" (fun s -> fullrewrite := (s = "fullrewrite" || s = "true")); Configuration.attribute ~name:"continue" (fun s -> continue := (s = "continue" || s = "true")); ] ()] element ); match !dest with | None -> raise (Error_in_config_file "url attribute expected for ") | Some dest -> gen (Regexp ((Netstring_pcre.regexp ("^"^ !regexp^"$")), dest, !fullrewrite)) !continue (*****************************************************************************) (** Registration of the extension *) let () = register_extension ~name:"rewritemod" ~fun_site:(fun _ _ _ _ _ -> parse_config) ~user_fun_site:(fun _ _ _ _ _ _ -> parse_config) () ocsigenserver-2.16.0/src/extensions/staticmod.ml000066400000000000000000000253451357715257700220210ustar00rootroot00000000000000(* Ocsigen * http://www.ocsigen.org * Module staticmod.ml * Copyright (C) 2005 Vincent Balat * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) (*****************************************************************************) (*****************************************************************************) (* Ocsigen module to load static pages *) (*****************************************************************************) (*****************************************************************************) open Lwt open Ocsigen_lib open Ocsigen_extensions let section = Lwt_log.Section.make "ocsigen:ext:staticmod" exception Not_concerned (*****************************************************************************) (* Structures describing the static pages a each virtual server *) (* A static site is either an entire directory served unconditionally, or a more elaborate redirection based on regexpes and http error codes. See the web documentation of staticmod for detail *) type static_site_kind = | Dir of string (* Serves an entire directory *) | Regexp of regexp_site and regexp_site = { source_regexp: Netstring_pcre.regexp; dest: Ocsigen_extensions.ud_string; http_status_filter: Netstring_pcre.regexp option; root_checks: Ocsigen_extensions.ud_string option; } (*****************************************************************************) (* Finding files *) (* Does the http status code returned for the page match the given filter ? *) let http_status_match status_filter status = match status_filter with | None -> true | Some r -> Netstring_pcre.string_match r (string_of_int status) 0 <> None (* Checks that the path specified in a userconf is correct. Currently, we check that the path does not contain ".." *) let correct_user_local_file = let regexp = Netstring_pcre.regexp "(/\\.\\./)|(/\\.\\.$)" in fun path -> try ignore(Netstring_pcre.search_forward regexp path 0); false with Not_found -> true (* Find the local file corresponding to [path] in the static site [dir], with [err] as the current http status (in case [dir] is a filter). Raises [Not_Concerned] if [dir] does not match, or returns - a boolean indicating that [dir] is an error handler - the local file If the parameter [usermode] is true, we check that the path is valid. *) let find_static_page ~request ~usermode ~dir ~err ~pathstring = let status_filter, filename, root = match dir with | Dir d -> (false, Filename.concat d pathstring, (match usermode with | None -> Some d | Some { localfiles_root = r } -> Some r )) | Regexp { source_regexp = source; dest = dest; http_status_filter = status_filter; root_checks = rc } when http_status_match status_filter err -> let status_filter = status_filter <> None and file = match Netstring_pcre.string_match source pathstring 0 with | None -> raise Not_concerned | Some _ -> Ocsigen_extensions.replace_user_dir source dest pathstring and root_checks = (match rc, usermode with | None, Some { localfiles_root = r } -> Some r | Some _, Some _ -> raise (Ocsigen_extensions.Error_in_user_config_file "Staticmod: cannot specify option 'root' in \ user configuration files") | None, None -> None | Some rc, None -> Some (Ocsigen_extensions.replace_user_dir source rc pathstring) ) in status_filter, file, root_checks | _ -> raise Not_concerned in if usermode = None || correct_user_local_file filename then (status_filter, Ocsigen_local_files.resolve ?no_check_for:root ~request ~filename ()) else raise (Ocsigen_extensions.Error_in_user_config_file "Staticmod: cannot use '..' in user paths") let gen ~usermode ?cache dir = function | Ocsigen_extensions.Req_found (_, r) -> Lwt.return (Ocsigen_extensions.Ext_do_nothing) | Ocsigen_extensions.Req_not_found (err, ri) -> catch (fun () -> Lwt_log.ign_info ~section "Is it a static file?"; let status_filter, page = find_static_page ~request:ri ~usermode ~dir ~err ~pathstring:(Url.string_of_url_path ~encode:false (Ocsigen_request_info.sub_path ri.request_info)) in Ocsigen_local_files.content ri page >>= fun answer -> let answer = if status_filter = false then answer else (* The page is an error handler, we propagate the original error code *) (Ocsigen_http_frame.Result.update answer ~code:err ()) in let (<~) h (n, v) = Http_headers.replace n v h in let answer = match cache with | None -> answer | Some 0 -> (Ocsigen_http_frame.Result.update answer ~headers: ((Ocsigen_http_frame.Result.headers answer) <~ (Http_headers.cache_control, "no-cache") <~ (Http_headers.expires, "0")) ()) | Some duration -> (Ocsigen_http_frame.Result.update answer ~headers: ((Ocsigen_http_frame.Result.headers answer) <~ (Http_headers.cache_control, "max-age="^ string_of_int duration) <~ (Http_headers.expires, Ocsigen_http_com.gmtdate (Unix.time () +. float_of_int duration))) ()) in Lwt.return (Ext_found (fun () -> Lwt.return answer)) ) (function | Ocsigen_local_files.Failed_403 -> return (Ext_next 403) (* XXX We should try to leave an information about this error for later *) | Ocsigen_local_files.NotReadableDirectory -> return (Ext_next err) | NoSuchUser | Not_concerned | Ocsigen_local_files.Failed_404 -> return (Ext_next err) | e -> fail e ) (*****************************************************************************) (** Parsing of config file *) (* In userconf modes, paths must be relative to the root of the userconf config *) let rewrite_local_path userconf path = match userconf with | None -> path | Some { Ocsigen_extensions.localfiles_root = root } -> root ^ "/" ^ path type options = { opt_dir: string option; opt_regexp: Netstring_pcre.regexp option; opt_code: Netstring_pcre.regexp option; opt_dest: Ocsigen_extensions.ud_string option; opt_root_checks: Ocsigen_extensions.ud_string option; opt_cache: int option; } let parse_config userconf _ : parse_config_aux = fun _ _ _ element -> let opt = ref { opt_dir = None; opt_regexp = None; opt_code = None; opt_dest = None; opt_root_checks = None; opt_cache = None; } in Ocsigen_extensions.( Configuration.process_element ~in_tag:"host" ~other_elements:(fun t _ _ -> raise (Bad_config_tag_for_extension t)) ~elements:[ Configuration.element ~name:"static" ~attributes:[ Configuration.attribute ~name:"dir" (fun s -> opt := { !opt with opt_dir = Some (rewrite_local_path userconf s) }); Configuration.attribute ~name:"regexp" (fun s -> let s = try Netstring_pcre.regexp ("^"^s^"$") with Pcre.Error (Pcre.BadPattern _) -> badconfig "Bad regexp \"%s\" in " s in opt := { !opt with opt_regexp = Some s}); Configuration.attribute ~name:"code" (fun s -> let c = try Netstring_pcre.regexp ("^" ^ s ^"$") with Pcre.Error (Pcre.BadPattern _) -> badconfig "Bad regexp \"%s\" in " s in opt := { !opt with opt_code = Some c }); Configuration.attribute ~name:"dest" (fun s -> let s = Some (parse_user_dir (rewrite_local_path userconf s)) in opt := { !opt with opt_dest = s }); Configuration.attribute ~name:"root" (fun s -> let s = Some (parse_user_dir s) in opt := { !opt with opt_root_checks = s }); Configuration.attribute ~name:"cache" (fun s -> let duration = match s with | "no" -> 0 | s -> try int_of_string s with Failure _ -> badconfig "Bad integer \"%s\" in " s in opt := { !opt with opt_cache = Some duration }); ] () ] element ); let kind = match !opt.opt_dir, !opt.opt_regexp, !opt.opt_code, !opt.opt_dest, !opt.opt_root_checks with | (None, None, None, _, _) -> badconfig "Missing attribute dir, regexp, or code for " | (Some d, None, None, None, None) -> Dir (Url.remove_end_slash d) | (None, Some r, code, Some t, rc) -> Regexp { source_regexp = r; dest = t; http_status_filter = code; root_checks = rc; } | (None, None, (Some _ as code), Some t, None) -> Regexp { dest = t; http_status_filter = code; root_checks = None; source_regexp = Netstring_pcre.regexp "^.*$" } | _ -> badconfig "Wrong attributes for " in gen ~usermode:userconf ?cache:!opt.opt_cache kind (*****************************************************************************) (** extension registration *) let () = register_extension ~name:"staticmod" ~fun_site:(fun _ -> parse_config None) ~user_fun_site:(fun path _ -> parse_config (Some path)) () ocsigenserver-2.16.0/src/extensions/userconf.ml000066400000000000000000000203741357715257700216530ustar00rootroot00000000000000(* Ocsigen * http://www.ocsigen.org * Module userconf.ml * Copyright (C) 2007 Vincent Balat * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) (*****************************************************************************) (*****************************************************************************) (* Ocsigen module to allow local (users) config files *) (*****************************************************************************) (*****************************************************************************) open Lwt open Ocsigen_lib open Ocsigen_extensions exception NoConfFile let section = Lwt_log.Section.make "ocsigen:ext:userconf" (*****************************************************************************) let err_500 = Ocsigen_extensions.Ext_stop_site (Ocsigen_cookies.Cookies.empty, 500) (* Catch invalid userconf files and report an error *) let handle_parsing_error req = function | Ocsigen_extensions.Error_in_config_file s -> Lwt_log.ign_error_f ~section "Syntax error in userconf configuration file for url %s: %s" (Ocsigen_request_info.url_string req.request_info) s; Lwt.return err_500 | Ocsigen_extensions.Error_in_user_config_file s -> Lwt_log.ign_error_f ~section "Unauthorized option in user configuration for url %s: %s" (Ocsigen_request_info.url_string req.request_info) s; Lwt.return err_500 | e -> Lwt.fail e (* Answer returned by userconf when the url matches *) let subresult new_req user_parse_site conf previous_err req req_state = Ext_sub_result (fun awake cookies_to_set rs -> (* XXX why is rs above never used ?? *) Lwt.catch (fun () -> user_parse_site conf awake cookies_to_set (Ocsigen_extensions.Req_not_found (previous_err, new_req)) >>= fun (answer, cookies) -> (* If the request is not satisfied by userconf, the changes in configuration (in request_config) are preserved for the remainder of the enclosing (in the Ext_continue and Ext_found_continue cases below) *) let rec aux ((answer, cts) as r) = match answer with | Ext_sub_result sr -> (* XXX Are these the good cookies ?? *) sr awake cookies_to_set req_state >>= aux | Ext_continue_with (newreq, cookies, err) -> Lwt.return ((Ext_continue_with ({req with request_config = newreq.request_config }, cookies, err)), cts) | Ext_found_continue_with r -> (* We keep config information outside userconf! *) Lwt.return (Ext_found_continue_with (fun () -> r () >>= fun (r, newreq) -> Lwt.return (r, { req with request_config = newreq.request_config }) ), cts) | _ -> Lwt.return r in aux (answer, cookies) ) (fun e -> handle_parsing_error req e >>= fun answer -> Lwt.return (answer, Ocsigen_cookies.Cookies.empty)) ) let conf_to_xml conf = try [Xml.parse_file conf] with | Sys_error _ -> raise NoConfFile | Xml.Error (s, loc) -> let begin_char, end_char = Xml.range loc and line = Xml.line loc in raise (Ocsigen_extensions.Error_in_config_file (Printf.sprintf "%s, line %d, characters %d-%d" (Xml.error_msg s) line begin_char end_char)) let gen hostpattern sitepath (regexp, conf, url, prefix, localpath) = function | Req_found _ -> (* We do not allow setting filters through userconf files right now *) Lwt.return Ext_do_nothing | Req_not_found (previous_err, req) as req_state-> let path = (Ocsigen_request_info.sub_path_string req.request_info) in match Netstring_pcre.string_match regexp path 0 with | None -> Lwt.return (Ext_next previous_err) | Some _ -> try Lwt_log.ign_info ~section "Using user configuration"; let conf0 = Ocsigen_extensions.replace_user_dir regexp conf path in let url = Netstring_pcre.global_replace regexp url path and prefix = Netstring_pcre.global_replace regexp prefix path and userconf_options = { Ocsigen_extensions.localfiles_root = Ocsigen_extensions.replace_user_dir regexp localpath path } and conf = conf_to_xml conf0 in let user_parse_host = Ocsigen_extensions.parse_user_site_item userconf_options hostpattern req.request_config in (* Inside userconf, we create a new virtual site starting after [prefix], and use a request modified accordingly*) let user_parse_site = Ocsigen_extensions.make_parse_config (sitepath@[prefix]) user_parse_host and path = Url.remove_slash_at_beginning (Url.remove_dotdot (Neturl.split_path url)) in let new_req = { req with request_info = (Ocsigen_request_info.update req.request_info ~sub_path:path ~sub_path_string:url ())} in Lwt.return (subresult new_req user_parse_site conf previous_err req req_state) with | Ocsigen_extensions.NoSuchUser | NoConfFile | Unix.Unix_error (Unix.EACCES,_,_) | Unix.Unix_error (Unix.ENOENT, _, _) -> Lwt.return (Ocsigen_extensions.Ext_next previous_err) | e -> handle_parsing_error req e (*****************************************************************************) (** Parsing of config file *) let parse_config hostpattern _ path _ _ config_elem = let regexp = ref None in let conf = ref None in let url = ref None in let prefix = ref None in let localpath = ref None in Ocsigen_extensions.( Configuration.process_element ~in_tag:"host" ~other_elements:(fun t _ _ -> raise (Bad_config_tag_for_extension t)) ~elements:[ Configuration.element ~name:"userconf" ~attributes:[ Configuration.attribute ~name:"regexp" ~obligatory:true (fun s -> let s = Netstring_pcre.regexp ("^" ^ s ^ "$") in regexp := Some s); Configuration.attribute ~name:"conf" ~obligatory:true (fun s -> let s = Ocsigen_extensions.parse_user_dir s in conf := Some s); Configuration.attribute ~name:"url" ~obligatory:true (fun s -> url := Some s); Configuration.attribute ~name:"prefix" ~obligatory:true (fun s -> prefix := Some s); Configuration.attribute ~name:"localpath" ~obligatory:true (fun s -> let s = Ocsigen_extensions.parse_user_dir s in localpath := Some s) ] ()] config_elem ); let info = match !regexp, !conf, !url, !prefix, !localpath with | (Some r, Some t, Some u, Some p, Some p') -> (r, t, u, p, p') | _ -> badconfig "Missing attributes for " in gen hostpattern path info (*****************************************************************************) (** extension registration *) let () = register_extension ~name:"userconf" ~fun_site:parse_config () ocsigenserver-2.16.0/src/files/000077500000000000000000000000001357715257700163725ustar00rootroot00000000000000ocsigenserver-2.16.0/src/files/META.in000066400000000000000000000143011357715257700174470ustar00rootroot00000000000000description = "Ocsigen server library" version = "dev" requires = "%%NAME%%.commandline,%%NAME%%.polytables,%%NAME%%.http,%%NAME%%.baselib" archive(byte) = "ocsigenserver.cma" archive(native) = "ocsigenserver.cmxa" package "polytables" ( exists_if = "polytables.cmo,polytables.cmx" version = "[distributed with Ocsigen server]" description = "Polymorphic tables" archive(byte) = "polytables.cmo" archive(native) = "polytables.cmx" ) package "commandline" ( description = "Read the commandline during server initialization" version = "[distributed with Ocsigen server]" archive(byte) = "parsecommandline.cma" archive(native) = "parsecommandline.cmxa" archive(byte,nocommandline) = "donotparsecommandline.cma" archive(native,nocommandline) = "donotparsecommandline.cmxa" ) package "baselib" ( requires = "%%DEPS%%" version = "[distributed with Ocsigen server]" description = "Base library for Ocsigen server" archive(byte) = "baselib.cma" archive(native) = "baselib.cmxa" package "base" ( description = "Just extensions of the stdlib (also for usage on the client side)" version = "[distributed with Ocsigen server]" requires = "%%BASEDEPS%%" archive(byte) = "ocsigen_lib_base.cmo" archive(native) = "ocsigen_lib_base.cmx" ) ) package "http" ( requires = "%%NAME%%.baselib,lwt_ssl,tyxml" version = "[distributed with Ocsigen server]" description = "HTTP library for Ocsigen server" archive(byte) = "http.cma" archive(native) = "http.cmxa" ) package "cookies" ( version = "[distributed with Ocsigen server]" archive(byte) = "ocsigen_cookies.cmo" ) package "ext" ( directory = "extensions" version = "[distributed with Ocsigen server]" description = "Basic extensions for Ocsigen server" package "redirectmod" ( exists_if = "redirectmod.cmo,redirectmod.cmx" requires = "ocsigenserver" version = "[distributed with Ocsigen server]" description = "HTTP redirections" archive(byte) = "redirectmod.cmo" archive(native) = "redirectmod.cmx" ) package "outputfilter" ( exists_if = "outputfilter.cmo,outputfilter.cmx" requires = "ocsigenserver" version = "[distributed with Ocsigen server]" description = "Changing HTTP answers before sending" archive(byte) = "outputfilter.cmo" archive(native) = "outputfilter.cmx" ) package "userconf" ( exists_if = "userconf.cmo,userconf.cmx" requires = "ocsigenserver" version = "[distributed with Ocsigen server]" description = "Allowing users to have their own configuration files" archive(byte) = "userconf.cmo" archive(native) = "userconf.cmx" ) package "staticmod" ( exists_if = "staticmod.cmo,staticmod.cmx" requires = "ocsigenserver" version = "[distributed with Ocsigen server]" description = "Serving static files" archive(byte) = "staticmod.cmo" archive(native) = "staticmod.cmx" ) package "revproxy" ( exists_if = "revproxy.cmo,revproxy.cmx" version = "[distributed with Ocsigen server]" description = "Reverse proxy" archive(byte) = "revproxy.cmo" archive(native) = "revproxy.cmx" ) package "accesscontrol" ( exists_if = "accesscontrol.cmo,accesscontrol.cmx" requires = "ocsigenserver,ipaddr" version = "[distributed with Ocsigen server]" description = "Filtering HTTP requests" archive(byte) = "accesscontrol.cmo" archive(native) = "accesscontrol.cmx" ) package "cors" ( exists_if = "cors.cmo,cors.cmx" requires = "ocsigenserver" version = "[distributed with Ocsigen server]" description = "Handling cross-origin requests" archive(byte) = "cors.cmo" archive(native) = "cors.cmx" ) package "extendconfiguration" ( exists_if = "extendconfiguration.cmo,extendconfiguration.cmx" requires = "ocsigenserver" version = "[distributed with Ocsigen server]" description = "Updating server options" archive(byte) = "extendconfiguration.cmo" archive(native) = "extendconfiguration.cmx" ) package "authbasic" ( exists_if = "authbasic.cmo,authbasic.cmx" requires = "ocsigenserver" version = "[distributed with Ocsigen server]" description = "Basic HTTP Authentication" archive(byte) = "authbasic.cmo" archive(native) = "authbasic.cmx" ) package "cgimod" ( exists_if = "cgimod.cmo,cgimod.cmx" version = "[distributed with Ocsigen server]" description = "CGI support" archive(byte) = "cgimod.cmo" archive(native) = "cgimod.cmx" ) package "ocsipersist-sqlite" ( exists_if = "ocsipersist-sqlite.cma,ocsipersist-sqlite.cmxa" requires = "sqlite3" version = "[distributed with Ocsigen server]" description = "Persistent data storage with SQLite3" archive(byte) = "ocsipersist-sqlite.cma" archive(native) = "ocsipersist-sqlite.cmxa" ) package "ocsipersist-pgsql" ( exists_if = "ocsipersist-pgsql.cma,ocsipersist-pgsql.cmxa" requires = "pgocaml" version = "[distributed with Ocsigen server]" description = "Persistent data storage with PostgreSQL" archive(byte) = "ocsipersist-pgsql.cma" archive(native) = "ocsipersist-pgsql.cmxa" ) package "ocsipersist-dbm" ( exists_if = "ocsipersist-dbm.cma,ocsipersist-dbm.cmxa" version = "[distributed with Ocsigen server]" description = "Persistent data storage with DBM" archive(byte) = "ocsipersist-dbm.cma" archive(native) = "ocsipersist-dbm.cmxa" ) package "deflatemod" ( exists_if = "deflatemod.cmo,deflatemod.cmx" requires = "ocsigenserver,%%CAMLZIPNAME%%" version = "[distributed with Ocsigen server]" description = "Compressing HTTP reply bodies" archive(byte) = "deflatemod.cmo" archive(native) = "deflatemod.cmx" ) package "rewritemod" ( exists_if = "rewritemod.cmo,rewritemod.cmx" requires = "ocsigenserver" version = "[distributed with Ocsigen server]" description = "Rewriting URLs" archive(byte) = "rewritemod.cmo" archive(native) = "rewritemod.cmx" ) package "comet" ( exists_if = "ocsigen_comet.cmo,ocsigen_comet.cmx" requires = "ocsigenserver,lwt_react" version = "[distributed with Ocsigen server]" description = "Comet server-to-client communication" archive(byte) = "ocsigen_comet.cmo" archive(native) = "ocsigen_comet.cmx" ) ) ocsigenserver-2.16.0/src/files/Makefile.sample000066400000000000000000000016651357715257700213220ustar00rootroot00000000000000# Write here all the findlib packages you need, for example: PACKAGES= lwt.unix,ocsigen # Write here all your .ml files, in dependency order (default: all) FILES=$(wildcard *.ml) CAMLC = ocamlfind ocamlc -g $(LIB) CAMLOPT = ocamlfind ocamlopt $(LIB) CAMLDOC = ocamlfind ocamldoc $(LIB) CAMLDEP = ocamlfind ocamldep #OCSIGENREP = `ocamlfind query ocsigen` #OCSIGENREP = ../ocsigen/lib LIB = -package $(PACKAGES) # If you use the syntax extension: # PP = -pp "camlp4o $(OCSIGENREP)/xhtmlsyntax.cma" # otherwise: PP = OBJS = $(FILES:.ml=.cmo) CMA = site.cma all: $(CMA) $(CMA): $(OBJS) $(CAMLC) -a -o $(CMA) $(OBJS) .SUFFIXES: .SUFFIXES: .ml .mli .cmo .cmi .cmx .PHONY: doc .ml.cmo: $(CAMLC) $(PP) -c $< .mli.cmi: $(CAMLC) -c $< .ml.cmx: $(CAMLOPT) $(PP) -c $< doc: # $(CAMLDOC) -d doc -html foo.mli clean: -rm -f *.cm[ioxa] *~ $(NAME) depend: $(CAMLDEP) $(PP) $(LIB) $(FILES:.ml=.mli) $(FILES) > .depend FORCE: include .depend ocsigenserver-2.16.0/src/files/logrotate.in000066400000000000000000000005231357715257700207220ustar00rootroot00000000000000LOGDIR/*.log { weekly missingok rotate 52 compress delaycompress notifempty create 640 USER GROUP sharedscripts postrotate if fuser -s _COMMANDPIPE_; then echo reopen_logs > _COMMANDPIPE_ fi endscript } ocsigenserver-2.16.0/src/files/mime.types000066400000000000000000000354741357715257700204240ustar00rootroot00000000000000# This is a comment. I love comments. # This file controls what Internet media types are sent to the client for # given file extension(s). Sending the correct media type to the client # is important so they know how to handle the content of the file. # Extra types can either be added here or by using an AddType directive # in your config files. For more information about Internet media types, # please read RFC 2045, 2046, 2047, 2048, and 2077. The Internet media type # registry is at . # MIME type Extensions application/activemessage application/andrew-inset ez application/applefile application/atom+xml atom application/atomicmail application/batch-smtp application/beep+xml application/cals-1840 application/cnrp+xml application/commonground application/cpl+xml application/cybercash application/dca-rft application/dec-dx application/dvcs application/edi-consent application/edifact application/edi-x12 application/eshop application/font-tdpfr application/http application/hyperstudio application/iges application/index application/index.cmd application/index.obj application/index.response application/index.vnd application/iotp application/ipp application/isup application/mac-binhex40 hqx application/mac-compactpro cpt application/macwriteii application/marc application/mathematica application/mathml+xml mathml application/msword doc application/news-message-id application/news-transmission application/ocsp-request application/ocsp-response application/octet-stream bin dms lha lzh exe class so dll dmg application/oda oda application/ogg ogg application/parityfec application/pdf pdf application/pgp-encrypted application/pgp-keys application/pgp-signature application/pkcs10 application/pkcs7-mime application/pkcs7-signature application/pkix-cert application/pkix-crl application/pkixcmp application/postscript ai eps ps application/prs.alvestrand.titrax-sheet application/prs.cww application/prs.nprend application/prs.plucker application/qsig application/rdf+xml rdf application/reginfo+xml application/remote-printing application/riscos application/rtf application/sdp application/set-payment application/set-payment-initiation application/set-registration application/set-registration-initiation application/sgml application/sgml-open-catalog application/sieve application/slate application/smil smi smil application/srgs gram application/srgs+xml grxml application/timestamp-query application/timestamp-reply application/tve-trigger application/vemmi application/vnd.3gpp.pic-bw-large application/vnd.3gpp.pic-bw-small application/vnd.3gpp.pic-bw-var application/vnd.3gpp.sms application/vnd.3m.post-it-notes application/vnd.accpac.simply.aso application/vnd.accpac.simply.imp application/vnd.acucobol application/vnd.acucorp application/vnd.adobe.xfdf application/vnd.aether.imp application/vnd.amiga.ami application/vnd.anser-web-certificate-issue-initiation application/vnd.anser-web-funds-transfer-initiation application/vnd.audiograph application/vnd.blueice.multipass application/vnd.bmi application/vnd.bw-fontobject eot application/vnd.businessobjects application/vnd.canon-cpdl application/vnd.canon-lips application/vnd.cinderella application/vnd.claymore application/vnd.commerce-battelle application/vnd.commonspace application/vnd.contact.cmsg application/vnd.cosmocaller application/vnd.criticaltools.wbs+xml application/vnd.ctc-posml application/vnd.cups-postscript application/vnd.cups-raster application/vnd.cups-raw application/vnd.curl application/vnd.cybank application/vnd.data-vision.rdz application/vnd.dna application/vnd.dpgraph application/vnd.dreamfactory application/vnd.dxr application/vnd.ecdis-update application/vnd.ecowin.chart application/vnd.ecowin.filerequest application/vnd.ecowin.fileupdate application/vnd.ecowin.series application/vnd.ecowin.seriesrequest application/vnd.ecowin.seriesupdate application/vnd.enliven application/vnd.epson.esf application/vnd.epson.msf application/vnd.epson.quickanime application/vnd.epson.salt application/vnd.epson.ssf application/vnd.ericsson.quickcall application/vnd.eudora.data application/vnd.fdf application/vnd.ffsns application/vnd.fints application/vnd.flographit application/vnd.framemaker application/vnd.fsc.weblaunch application/vnd.fujitsu.oasys application/vnd.fujitsu.oasys2 application/vnd.fujitsu.oasys3 application/vnd.fujitsu.oasysgp application/vnd.fujitsu.oasysprs application/vnd.fujixerox.ddd application/vnd.fujixerox.docuworks application/vnd.fujixerox.docuworks.binder application/vnd.fut-misnet application/vnd.grafeq application/vnd.groove-account application/vnd.groove-help application/vnd.groove-identity-message application/vnd.groove-injector application/vnd.groove-tool-message application/vnd.groove-tool-template application/vnd.groove-vcard application/vnd.hbci application/vnd.hhe.lesson-player application/vnd.hp-hpgl application/vnd.hp-hpid application/vnd.hp-hps application/vnd.hp-pcl application/vnd.hp-pclxl application/vnd.httphone application/vnd.hzn-3d-crossword application/vnd.ibm.afplinedata application/vnd.ibm.electronic-media application/vnd.ibm.minipay application/vnd.ibm.modcap application/vnd.ibm.rights-management application/vnd.ibm.secure-container application/vnd.informix-visionary application/vnd.intercon.formnet application/vnd.intertrust.digibox application/vnd.intertrust.nncp application/vnd.intu.qbo application/vnd.intu.qfx application/vnd.irepository.package+xml application/vnd.is-xpr application/vnd.japannet-directory-service application/vnd.japannet-jpnstore-wakeup application/vnd.japannet-payment-wakeup application/vnd.japannet-registration application/vnd.japannet-registration-wakeup application/vnd.japannet-setstore-wakeup application/vnd.japannet-verification application/vnd.japannet-verification-wakeup application/vnd.jisp application/vnd.kde.karbon application/vnd.kde.kchart application/vnd.kde.kformula application/vnd.kde.kivio application/vnd.kde.kontour application/vnd.kde.kpresenter application/vnd.kde.kspread application/vnd.kde.kword application/vnd.kenameaapp application/vnd.koan application/vnd.liberty-request+xml application/vnd.llamagraphics.life-balance.desktop application/vnd.llamagraphics.life-balance.exchange+xml application/vnd.lotus-1-2-3 application/vnd.lotus-approach application/vnd.lotus-freelance application/vnd.lotus-notes application/vnd.lotus-organizer application/vnd.lotus-screencam application/vnd.lotus-wordpro application/vnd.mcd application/vnd.mediastation.cdkey application/vnd.meridian-slingshot application/vnd.micrografx.flo application/vnd.micrografx.igx application/vnd.mif mif application/vnd.minisoft-hp3000-save application/vnd.mitsubishi.misty-guard.trustweb application/vnd.mobius.daf application/vnd.mobius.dis application/vnd.mobius.mbk application/vnd.mobius.mqy application/vnd.mobius.msl application/vnd.mobius.plc application/vnd.mobius.txf application/vnd.mophun.application application/vnd.mophun.certificate application/vnd.motorola.flexsuite application/vnd.motorola.flexsuite.adsi application/vnd.motorola.flexsuite.fis application/vnd.motorola.flexsuite.gotap application/vnd.motorola.flexsuite.kmr application/vnd.motorola.flexsuite.ttc application/vnd.motorola.flexsuite.wem application/vnd.mozilla.xul+xml xul application/vnd.ms-artgalry application/vnd.ms-asf application/vnd.ms-excel xls application/vnd.ms-fontobject eot application/vnd.ms-lrm application/vnd.ms-powerpoint ppt application/vnd.ms-project application/vnd.ms-tnef application/vnd.ms-works application/vnd.ms-wpl application/vnd.mseq application/vnd.msign application/vnd.music-niff application/vnd.musician application/vnd.netfpx application/vnd.noblenet-directory application/vnd.noblenet-sealer application/vnd.noblenet-web application/vnd.novadigm.edm application/vnd.novadigm.edx application/vnd.novadigm.ext application/vnd.obn application/vnd.osa.netdeploy application/vnd.palm application/vnd.pg.format application/vnd.pg.osasli application/vnd.powerbuilder6 application/vnd.powerbuilder6-s application/vnd.powerbuilder7 application/vnd.powerbuilder7-s application/vnd.powerbuilder75 application/vnd.powerbuilder75-s application/vnd.previewsystems.box application/vnd.publishare-delta-tree application/vnd.pvi.ptid1 application/vnd.pwg-multiplexed application/vnd.pwg-xhtml-print+xml application/vnd.quark.quarkxpress application/vnd.rapid application/vnd.s3sms application/vnd.sealed.net application/vnd.seemail application/vnd.shana.informed.formdata application/vnd.shana.informed.formtemplate application/vnd.shana.informed.interchange application/vnd.shana.informed.package application/vnd.smaf application/vnd.sss-cod application/vnd.sss-dtf application/vnd.sss-ntf application/vnd.street-stream application/vnd.svd application/vnd.swiftview-ics application/vnd.triscape.mxs application/vnd.trueapp application/vnd.truedoc application/vnd.ufdl application/vnd.uplanet.alert application/vnd.uplanet.alert-wbxml application/vnd.uplanet.bearer-choice application/vnd.uplanet.bearer-choice-wbxml application/vnd.uplanet.cacheop application/vnd.uplanet.cacheop-wbxml application/vnd.uplanet.channel application/vnd.uplanet.channel-wbxml application/vnd.uplanet.list application/vnd.uplanet.list-wbxml application/vnd.uplanet.listcmd application/vnd.uplanet.listcmd-wbxml application/vnd.uplanet.signal application/vnd.vcx application/vnd.vectorworks application/vnd.vidsoft.vidconference application/vnd.visio application/vnd.visionary application/vnd.vividence.scriptfile application/vnd.vsf application/vnd.wap.sic application/vnd.wap.slc application/vnd.wap.wbxml wbxml application/vnd.wap.wmlc wmlc application/vnd.wap.wmlscriptc wmlsc application/vnd.webturbo application/vnd.wrq-hp3000-labelled application/vnd.wt.stf application/vnd.wv.csp+wbxml application/vnd.xara application/vnd.xfdl application/vnd.yamaha.hv-dic application/vnd.yamaha.hv-script application/vnd.yamaha.hv-voice application/vnd.yellowriver-custom-menu application/voicexml+xml vxml application/watcherinfo+xml application/whoispp-query application/whoispp-response application/wita application/wordperfect5.1 application/x-bcpio bcpio application/x-cdlink vcd application/x-chess-pgn pgn application/x-compress application/x-cpio cpio application/x-csh csh application/x-director dcr dir dxr application/x-dvi dvi application/x-font-ttf ttf application/x-font-woff woff application/x-futuresplash spl application/x-gtar gtar application/x-gzip application/x-hdf hdf application/x-javascript js application/x-koan skp skd skt skm application/x-latex latex application/x-netcdf nc cdf application/x-sh sh application/x-shar shar application/x-shockwave-flash swf application/x-stuffit sit application/x-sv4cpio sv4cpio application/x-sv4crc sv4crc application/x-tar tar application/x-tcl tcl application/x-tex tex application/x-texinfo texinfo texi application/x-troff t tr roff application/x-troff-man man application/x-troff-me me application/x-troff-ms ms application/x-ustar ustar application/x-wais-source src application/x400-bp application/xhtml+xml xhtml xht application/xslt+xml xslt application/xml xml xsl application/xml-dtd dtd application/xml-external-parsed-entity application/zip zip audio/32kadpcm audio/amr audio/amr-wb audio/basic au snd audio/cn audio/dat12 audio/dsr-es201108 audio/dvi4 audio/evrc audio/evrc0 audio/g722 audio/g.722.1 audio/g723 audio/g726-16 audio/g726-24 audio/g726-32 audio/g726-40 audio/g728 audio/g729 audio/g729D audio/g729E audio/gsm audio/gsm-efr audio/l8 audio/l16 audio/l20 audio/l24 audio/lpc audio/midi mid midi kar audio/mpa audio/mpa-robust audio/mp4a-latm audio/mpeg mpga mp2 mp3 audio/parityfec audio/pcma audio/pcmu audio/prs.sid audio/qcelp audio/red audio/smv audio/smv0 audio/telephone-event audio/tone audio/vdvi audio/vnd.3gpp.iufp audio/vnd.cisco.nse audio/vnd.cns.anp1 audio/vnd.cns.inf1 audio/vnd.digital-winds audio/vnd.everad.plj audio/vnd.lucent.voice audio/vnd.nortel.vbk audio/vnd.nuera.ecelp4800 audio/vnd.nuera.ecelp7470 audio/vnd.nuera.ecelp9600 audio/vnd.octel.sbc audio/vnd.qcelp audio/vnd.rhetorex.32kadpcm audio/vnd.vmx.cvsd audio/x-aiff aif aiff aifc audio/x-alaw-basic audio/x-mpegurl m3u audio/x-pn-realaudio ram ra audio/x-pn-realaudio-plugin application/vnd.rn-realmedia rm audio/x-wav wav chemical/x-pdb pdb chemical/x-xyz xyz image/bmp bmp image/cgm cgm image/g3fax image/gif gif image/ief ief image/jpeg jpeg jpg jpe image/naplps image/png png image/prs.btif image/prs.pti image/svg+xml svg image/t38 image/tiff tiff tif image/tiff-fx image/vnd.cns.inf2 image/vnd.djvu djvu djv image/vnd.dwg image/vnd.dxf image/vnd.fastbidsheet image/vnd.fpx image/vnd.fst image/vnd.fujixerox.edmics-mmr image/vnd.fujixerox.edmics-rlc image/vnd.globalgraphics.pgb image/vnd.mix image/vnd.ms-modi image/vnd.net-fpx image/vnd.svf image/vnd.wap.wbmp wbmp image/vnd.xiff image/x-cmu-raster ras image/x-icon ico image/x-portable-anymap pnm image/x-portable-bitmap pbm image/x-portable-graymap pgm image/x-portable-pixmap ppm image/x-rgb rgb image/x-xbitmap xbm image/x-xpixmap xpm image/x-xwindowdump xwd message/delivery-status message/disposition-notification message/external-body message/http message/news message/partial message/rfc822 message/s-http message/sip message/sipfrag model/iges igs iges model/mesh msh mesh silo model/vnd.dwf model/vnd.flatland.3dml model/vnd.gdl model/vnd.gs-gdl model/vnd.gtw model/vnd.mts model/vnd.parasolid.transmit.binary model/vnd.parasolid.transmit.text model/vnd.vtu model/vrml wrl vrml multipart/alternative multipart/appledouble multipart/byteranges multipart/digest multipart/encrypted multipart/form-data multipart/header-set multipart/mixed multipart/parallel multipart/related multipart/report multipart/signed multipart/voice-message text/calendar ics ifb text/css css text/directory text/enriched text/html html htm text/parityfec text/plain asc txt text/prs.lines.tag text/rfc822-headers text/richtext rtx text/rtf rtf text/sgml sgml sgm text/t140 text/tab-separated-values tsv text/uri-list text/vnd.abc text/vnd.curl text/vnd.dmclientscript text/vnd.fly text/vnd.fmi.flexstor text/vnd.in3d.3dml text/vnd.in3d.spot text/vnd.iptc.nitf text/vnd.iptc.newsml text/vnd.latex-z text/vnd.motorola.reflex text/vnd.ms-mediapackage text/vnd.net2phone.commcenter.command text/vnd.sun.j2me.app-descriptor text/vnd.wap.si text/vnd.wap.sl text/vnd.wap.wml wml text/vnd.wap.wmlscript wmls text/x-setext etx text/xml text/xml-external-parsed-entity video/bmpeg video/bt656 video/celb video/dv video/h261 video/h263 video/h263-1998 video/h263-2000 video/jpeg video/mp1s video/mp2p video/mp2t video/mp4v-es video/mpv video/mpeg mpeg mpg mpe video/nv video/parityfec video/pointer video/quicktime qt mov video/smpte292m video/vnd.fvt video/vnd.motorola.video video/vnd.motorola.videop video/vnd.mpegurl mxu m4u video/vnd.nokia.interleaved-multimedia video/vnd.objectvideo video/vnd.vivo video/x-msvideo avi video/x-sgi-movie movie x-conference/x-cooltalk ice ocsigenserver-2.16.0/src/files/ocsigenserver.1000066400000000000000000000026771357715257700213460ustar00rootroot00000000000000.\" Hey, EMACS: -*- nroff -*- .TH OCSIGEN 1 2006-09-14 .SH NAME ocsigen \- web programming framework in OCaml .SH SYNOPSIS .B ocsigen .RI [ options ] .SH DESCRIPTION .B ocsigen is a programming framework providing a new way to create dynamic web sites. Its goal is to offer an alternative to Apache/PHP, based on cutting-edge technologies coming from research in programming languages. With .BR ocsigen , you program in a concise and modular way, with a strong type system which helps you to produce valid xhtml. The server handles sessions, URLs, and page parameters automatically. .SH OPTIONS .TP .BR \-c ,\ \-\-config Alternate configuration file. .TP .BR \-d ,\ \-\-daemon Daemon mode (detach the process). This is the default when there are more than 1 process. .TP .BR \-help ,\ \-\-help Show summary of options. .TP .BR \-p ,\ \-\-pidfile Specify a file where to write the PIDs of the servers. .TP .BR \-s ,\ \-\-silent Silent mode (error messages go in errors.log only). .TP .BR \-v ,\ \-\-verbose Verbose mode (notice). .TP .B \-vv ,\ \-\-veryverbose Very verbose mode (info). .TP .B \-vvv ,\ \-\-debug Extremely verbose mode (debug). .TP .B \-\-version Show version of program. .SH SEE ALSO .BR ocamlc (1). .SH AUTHOR ocsigen was written by Vincent Balat . .PP This manual page was written by Samuel Mimram , for the Debian project (but may be used by others). ocsigenserver-2.16.0/src/files/ocsigenserver.conf.in000066400000000000000000000035751357715257700225360ustar00rootroot00000000000000 80 _LOGDIR_ _DATADIR_ _OCSIGENUSER_ _OCSIGENGROUP_ utf-8 --> ocsigenserver-2.16.0/src/http/000077500000000000000000000000001357715257700162475ustar00rootroot00000000000000ocsigenserver-2.16.0/src/http/.depend000066400000000000000000000063521357715257700175150ustar00rootroot00000000000000framepp.cmo : ocsigen_http_frame.cmi http_headers.cmi framepp.cmi framepp.cmx : ocsigen_http_frame.cmx http_headers.cmx framepp.cmi framepp.cmi : ocsigen_http_frame.cmi http_headers.cmo : http_headers.cmi http_headers.cmx : http_headers.cmi http_headers.cmi : http_lexer.cmo : ocsigen_http_frame.cmi http_headers.cmi http_lexer.cmx : ocsigen_http_frame.cmx http_headers.cmx multipart.cmo : ../baselib/ocsigen_stream.cmi ../baselib/ocsigen_lib.cmi \ multipart.cmi multipart.cmx : ../baselib/ocsigen_stream.cmx ../baselib/ocsigen_lib.cmx \ multipart.cmi multipart.cmi : ../baselib/ocsigen_stream.cmi ocsigen_charset_mime.cmo : ../baselib/ocsigen_lib.cmi \ ../baselib/ocsigen_config.cmi ocsigen_charset_mime.cmi ocsigen_charset_mime.cmx : ../baselib/ocsigen_lib.cmx \ ../baselib/ocsigen_config.cmx ocsigen_charset_mime.cmi ocsigen_charset_mime.cmi : ocsigen_cookies.cmo : ocsigen_cookies.cmi ocsigen_cookies.cmx : ocsigen_cookies.cmi ocsigen_cookies.cmi : ../baselib/ocsigen_lib.cmi ocsigen_headers.cmo : ocsigen_senders.cmi ../baselib/ocsigen_lib.cmi \ ocsigen_http_frame.cmi ocsigen_cookies.cmi http_headers.cmi \ ocsigen_headers.cmi ocsigen_headers.cmx : ocsigen_senders.cmx ../baselib/ocsigen_lib.cmx \ ocsigen_http_frame.cmx ocsigen_cookies.cmx http_headers.cmx \ ocsigen_headers.cmi ocsigen_headers.cmi : ../baselib/ocsigen_lib.cmi ocsigen_http_frame.cmi \ ocsigen_cookies.cmi ocsigen_http_com.cmo : ../baselib/ocsigen_stream.cmi \ ../baselib/ocsigen_lib.cmi ocsigen_http_frame.cmi ocsigen_cookies.cmi \ ../baselib/ocsigen_config.cmi http_lexer.cmo http_headers.cmi framepp.cmi \ ocsigen_http_com.cmi ocsigen_http_com.cmx : ../baselib/ocsigen_stream.cmx \ ../baselib/ocsigen_lib.cmx ocsigen_http_frame.cmx ocsigen_cookies.cmx \ ../baselib/ocsigen_config.cmx http_lexer.cmx http_headers.cmx framepp.cmx \ ocsigen_http_com.cmi ocsigen_http_com.cmi : ../baselib/ocsigen_stream.cmi ocsigen_http_frame.cmi \ http_headers.cmi ocsigen_http_frame.cmo : ../baselib/ocsigen_stream.cmi \ ../baselib/ocsigen_lib.cmi ocsigen_cookies.cmi http_headers.cmi \ ocsigen_http_frame.cmi ocsigen_http_frame.cmx : ../baselib/ocsigen_stream.cmx \ ../baselib/ocsigen_lib.cmx ocsigen_cookies.cmx http_headers.cmx \ ocsigen_http_frame.cmi ocsigen_http_frame.cmi : ../baselib/ocsigen_stream.cmi \ ../baselib/ocsigen_lib.cmi ocsigen_cookies.cmi http_headers.cmi ocsigen_senders.cmo : ../baselib/ocsigen_stream.cmi \ ../baselib/ocsigen_lib.cmi ocsigen_http_frame.cmi ocsigen_http_com.cmi \ ocsigen_cookies.cmi ../baselib/ocsigen_config.cmi \ ocsigen_charset_mime.cmi http_headers.cmi ocsigen_senders.cmi ocsigen_senders.cmx : ../baselib/ocsigen_stream.cmx \ ../baselib/ocsigen_lib.cmx ocsigen_http_frame.cmx ocsigen_http_com.cmx \ ocsigen_cookies.cmx ../baselib/ocsigen_config.cmx \ ocsigen_charset_mime.cmx http_headers.cmx ocsigen_senders.cmi ocsigen_senders.cmi : ../baselib/ocsigen_stream.cmi ocsigen_http_frame.cmi \ ocsigen_http_com.cmi ocsigen_cookies.cmi ocsigen_charset_mime.cmi \ http_headers.cmi test_parser.cmo : ocsigen_http_frame.cmi http_lexer.cmo test_parser.cmx : ocsigen_http_frame.cmx http_lexer.cmx test_pp.cmo : ocsigen_http_frame.cmi framepp.cmi test_pp.cmx : ocsigen_http_frame.cmx framepp.cmx ocsigenserver-2.16.0/src/http/LICENSE-multipart000066400000000000000000000020271357715257700212740ustar00rootroot00000000000000The librarie "netlwtstream.ml", comes from ocamlnet, and is distributed under the terms of the following license. ====================================================================== Copyright (c) 2001 Patrick Doane and Gerd Stolpmann This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution.ocsigenserver-2.16.0/src/http/Makefile000066400000000000000000000025041357715257700177100ustar00rootroot00000000000000include ../../Makefile.config PACKAGE := \ bytes \ netstring \ netstring-pcre \ lwt_ssl \ lwt_log \ tyxml LIBS := -I ../baselib ${addprefix -package ,${PACKAGE}} OCAMLC := $(OCAMLFIND) ocamlc ${BYTEDBG} ${THREAD} OCAMLOPT := $(OCAMLFIND) ocamlopt ${OPTDBG} ${THREAD} OCAMLDOC := $(OCAMLFIND) ocamldoc OCAMLDEP := $(OCAMLFIND) ocamldep all: byte opt ### Common files ### FILES := multipart.ml \ http_headers.ml \ ocsigen_cookies.ml \ ocsigen_http_frame.ml \ ocsigen_headers.ml \ http_lexer.ml \ framepp.ml \ ocsigen_http_com.ml \ ocsigen_charset_mime.ml \ ocsigen_senders.ml \ PREDEP := http_lexer.ml \ byte: http.cma opt: http.cmxa http.cma: $(FILES:.ml=.cmo) ${OCAMLC} -a -o $@ $^ http.cmxa: $(FILES:.ml=.cmx) ${OCAMLOPT} -a -o $@ $^ ########## %.ml: %.mll $(OCAMLLEX) $< %.cmi: %.mli $(OCAMLC) ${LIBS} -c $< %.cmo: %.ml $(OCAMLC) ${LIBS} -c $< %.cmx: %.ml $(OCAMLOPT) ${LIBS} -c $< %.cmxs: %.cmxa $(OCAMLOPT) -shared -linkall -o $@ $< ## Clean up clean: -rm -f *.cm* *.o *.a *.annot -rm -f ${PREDEP} distclean: clean -rm -f *~ \#* .\#* ## Dependencies depend: ${PREDEP} $(OCAMLDEP) ${LIBS} *.mli *.ml > .depend type: $(FILES:.ml=.gmli) %.gmli: %.ml $(OCAMLC) ${LIBS} -i $< > $@ FORCE: -include .depend ocsigenserver-2.16.0/src/http/framepp.ml000066400000000000000000000077421357715257700202450ustar00rootroot00000000000000(* Ocsigen * framepp.ml Copyright (C) 2005 Denis Berthod * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) (** pretty printer for http frames*) open Ocsigen_http_frame module H = Http_header (** converts the method into a string*) let string_of_method = function | H.GET -> "GET" | H.POST -> "POST" | H.HEAD -> "HEAD" | H.PUT -> "PUT" | H.DELETE -> "DELETE" | H.TRACE -> "TRACE" | H.OPTIONS -> "OPTIONS" | H.CONNECT -> "CONNECT" | H.LINK -> "LINK" | H.UNLINK -> "UNLINK" | H.PATCH -> "PATCH" (** converts a string to a method *) let method_of_string = function | "GET" -> H.GET | "POST" -> H.POST | "HEAD" -> H.HEAD | "PUT" -> H.PUT | "DELETE" -> H.DELETE | "TRACE" -> H.TRACE | "OPTIONS" -> H.OPTIONS | "CONNECT" -> H.CONNECT | "LINK" -> H.LINK | "UNLINK" -> H.UNLINK | "PATCH" -> H.PATCH | _ -> failwith "method_of_string" (** converts the protocol into a string *) let string_of_proto = function | H.HTTP10 -> "HTTP/1.0" | H.HTTP11 -> "HTTP/1.1" (** converts a string to a protocol *) let proto_of_string = function | "HTTP/1.0" -> H.HTTP10 | "HTTP/1.1" -> H.HTTP11 | _ -> failwith "proto_of_string" (** Write the first line of an HTTP frame to a string buffer *) let fst_line buf header = match header.H.mode with | H.Nofirstline -> () | H.Answer code -> Printf.bprintf buf "%s %i %s\r\n" (string_of_proto header.H.proto) code (Http_error.expl_of_code code) | H.Query (meth, url) -> Printf.bprintf buf "%s %s %s\r\n" (string_of_method meth) url (string_of_proto header.H.proto) (** Prints the content of a header. To prevent http header injection, we insert spaces (' ') after CRLF, in case the user has not done this himself. Also, if we find single CR or LF, we replace them by spaces . (This is correct according to the RFC, as the headers content should not contain single CR or LF anyway) *) let print_header_content buf content = let s = String.length content in let rec aux prev i = if i = s then (if prev < s then Buffer.add_substring buf content prev (s-prev)) else let add_prev () = Buffer.add_substring buf content prev (i-prev) in match content.[i] with | '\n' | '\r' as c -> let i' = i+1 in let escape_c () = add_prev (); Buffer.add_char buf c; Buffer.add_char buf ' '; aux i' i' in if i' < s then (match content.[i'] with | '\n' | '\r' as c' when c <> c' -> add_prev (); Buffer.add_char buf c; Buffer.add_char buf c'; Buffer.add_char buf ' '; aux (i+2) (i+2) | _ -> escape_c () ) else escape_c () | _ -> aux prev (i+1) in aux 0 0 (* Debug *) let test s = let b = Buffer.create 0 in print_header_content b s; Buffer.contents b (** Write the header lines to a string buffer *) let headers buf header = Http_headers.iter (fun name value -> Printf.bprintf buf "%s: %a\r\n" (Http_headers.name_to_string name) print_header_content value) header.H.headers (** Convert a HTTP header into a string *) let string_of_header hds = let buf = Buffer.create 200 in fst_line buf hds; headers buf hds; Printf.bprintf buf "\r\n%!"; Buffer.contents buf ocsigenserver-2.16.0/src/http/framepp.mli000066400000000000000000000023411357715257700204040ustar00rootroot00000000000000(* Ocsigen * framepp.mli Copyright (C) 2005 Denis Berthod * Laboratoire PPS - CNRS Université Paris Diderot * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) val string_of_header : Ocsigen_http_frame.Http_header.http_header -> string val string_of_method : Ocsigen_http_frame.Http_header.http_method -> string val method_of_string : string -> Ocsigen_http_frame.Http_header.http_method val string_of_proto : Ocsigen_http_frame.Http_header.proto -> string val proto_of_string : string -> Ocsigen_http_frame.Http_header.proto ocsigenserver-2.16.0/src/http/http_headers.ml000066400000000000000000000101301357715257700212460ustar00rootroot00000000000000(* Ocsigen * http://www.ocsigen.org * Module http_headers.mli * Copyright (C) 2007 Jérôme Vouillon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) type name = string * string let name s = (s, String.lowercase s) let name_to_string = fst let accept = name "Accept" let accept_charset = name "Accept-Charset" let accept_encoding = name "Accept-Encoding" let accept_language = name "Accept-Language" let accept_ranges = name "Accept-Ranges" let authorization = name "Authorization" let cache_control = name "Cache-Control" let connection = name "Connection" let content_disposition = name "Content-Disposition" let content_encoding = name "Content-Encoding" let content_range = name "Content-Range" let content_length = name "Content-Length" let content_type = name "Content-Type" let cookie = name "Cookie" let date = name "Date" let etag = name "ETag" let expect = name "Expect" let expires = name "Expires" let host = name "Host" let if_match = name "If-Match" let if_modified_since = name "If-Modified-Since" let if_none_match = name "If-None-Match" let if_unmodified_since = name "If-Unmodified-Since" let if_range = name "If-Range" let last_modified = name "Last-Modified" let location = name "Location" let pragma = name "Pragma" let server = name "Server" let set_cookie = name "Set-Cookie" let status = name "Status" let transfer_encoding = name "Transfer-Encoding" let user_agent = name "User-Agent" let referer = name "Referer" let range = name "Range" let x_forwarded_for = name "X-Forwarded-For" let x_forwarded_proto = name "X-Forwarded-Proto" (* CORS headers *) let origin = name "Origin" let access_control_request_method = name "Access-Control-Request-Method" let access_control_request_headers = name "Access-Control-Request-Headers" let access_control_allow_origin = name "Access-Control-Allow-Origin" let access_control_allow_credentials = name "Access-Control-Allow-Credentials" let access_control_expose_headers = name "Access-Control-Expose-Headers" let access_control_max_age = name "Access-Control-Max-Age" let access_control_allow_methods = name "Access-Control-Allow-Methods" let access_control_allow_headers = name "Access-Control-Allow-Headers" module NameHtbl = Hashtbl.Make (struct type t = name let equal (_, n : _ * string) (_, n') = n = n' let hash (_,n) = Hashtbl.hash n end) (****) module Map = Map.Make (struct type t = name let compare (_,n) (_,n') = compare n n' end) type t = string list Map.t let empty = Map.empty let find_all n h = List.rev (Map.find n h) (*XXX We currently return the last header. Should we fail if there is more than one? *) let find n h = match Map.find n h with v :: _ -> v | _ -> assert false let replace n v h = Map.add n [v] h let replace_opt n v h = match v with None -> Map.remove n h | Some v -> replace n v h let add n v h = let vl = try find_all n h with Not_found -> [] in Map.add n (v :: vl) h let iter f h = Map.iter (fun n vl -> match vl with [v] -> f n v | _ -> List.iter (fun v -> f n v) (List.rev vl)) h let fold f h acc = Map.fold (fun n vl acc -> f n (List.rev vl) acc) h acc let with_defaults h h' = Map.fold Map.add h h' (****) let (<<) h (n, v) = replace n v h let dyn_headers = empty << (cache_control, "no-cache") << (expires, "0") type accept = ( (string option * string option) * float option * (string * string) list ) list ocsigenserver-2.16.0/src/http/http_headers.mli000066400000000000000000000064071357715257700214330ustar00rootroot00000000000000(* Ocsigen * http://www.ocsigen.org * Module http_headers.mli * Copyright (C) 2007 Jérôme Vouillon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) (*XXX Can have multiple headers with the same name...*) type name val name : string -> name val name_to_string : name -> string module NameHtbl : Hashtbl.S with type key = name (****) val accept : name val accept_charset : name val accept_encoding : name val accept_language : name val accept_ranges : name val authorization : name val cache_control : name val connection : name val content_disposition : name val content_encoding : name val content_length : name val content_type : name val content_range : name val cookie : name val date : name val etag : name val expect: name val expires : name val host : name val if_match : name val if_modified_since : name val if_none_match : name val if_unmodified_since : name val if_range : name val last_modified : name val location : name val pragma : name val server : name val set_cookie : name val status : name val transfer_encoding : name val user_agent : name val referer : name val range : name val x_forwarded_for : name val x_forwarded_proto : name val origin : name val access_control_request_method : name val access_control_request_headers : name val access_control_allow_origin : name val access_control_allow_credentials : name val access_control_expose_headers : name val access_control_max_age : name val access_control_allow_methods : name val access_control_allow_headers : name (****) type t val empty : t (** returns an empty set of HTTP headers *) val add : name -> string -> t -> t (** [add name s h] adds the header [name: s] to [h]. *) val replace : name -> string -> t -> t (** replace a header by another one. If it does not exist, adds it. *) val replace_opt : name -> string option -> t -> t (** replace or remove a header. *) val find : name -> t -> string (** find one of the values bound to [name] in the HTTP header [t]. Raise [Not_found] if it is not bound. *) val find_all : name -> t -> string list (** find all the values bound to [name] in the HTTP header [t]. Raise [Not_found] if it is not bound. *) val iter : (name -> string -> unit) -> t -> unit val fold : (name -> string list -> 'a -> 'a) -> t -> 'a -> 'a val with_defaults : t -> t -> t (** [with_defaults h1 h2] adds headers from [h1] to [h2]. If some headers were present, the are replaced by those from [h1]. *) val dyn_headers : t (** Headers for dynamic pages (non cacheable) *) type accept = ( (string option * string option) * float option * (string * string) list ) list ocsigenserver-2.16.0/src/http/http_lexer.mll000066400000000000000000000126501357715257700211370ustar00rootroot00000000000000{ (* Ocsigen * http://www.ocsigen.org * http_lexer.mll Copyright (C) 2005 Denis Berthod * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) open Ocsigen_http_frame open Http_header let meth_of_string = function | "GET" -> GET | "POST" -> POST | "HEAD" -> HEAD | "PUT" -> PUT | "DELETE" -> DELETE | "TRACE" -> TRACE | "OPTIONS" -> OPTIONS | "CONNECT" -> CONNECT | "LINK" -> LINK | "UNLINK" -> UNLINK | "PATCH" -> PATCH | s -> raise (Http_error.Http_exception (405, Some ("unknown method "^s), None)) let proto_of_string s = match String.uppercase s with | "HTTP/1.1" -> HTTP11 | "HTTP/1.0" -> HTTP10 | s -> raise (Http_error.Http_exception (505, None, None)) let add_header (n, v) h = let field = String.concat " " (List.rev v) in { h with headers = Http_headers.add n field h.headers } let handle_eof lexbuf = raise (Http_error.Http_exception (400, Some "unexpected end of file", None)) let handle_other lexbuf = raise (Http_error.Http_exception (400, Some ("unexpected character " ^ Lexing.lexeme lexbuf), None)) } (* RFC 2616, sect. 2.2 *) let char = ['\000'-'\127'] let ctl = ['\000'-'\031' '\127'] let lowalpha = ['a'-'z'] let upalpha = ['A'-'Z'] let alpha = upalpha | lowalpha let digit = ['0'-'9'] let alpha = lowalpha | upalpha let blank = [' ' '\t'] let crlf = "\r"? "\n" let separators = ['(' ')' '<' '>' '@' ',' ';' ':' '\\' '\"' '/' '[' ']' '?' '=' '{' '}' ' ' '\t'] let lws = crlf? [' ' '\t'] + let method_ = alpha* (* it is more general than what RFC request *) let request_URI = (_ #ctl #[' '] )* let http_version = ['h' 'H'] ['t' 'T'] ['t' 'T'] ['p' 'P'] "/" digit+ "." digit+ let token = ((char #separators) #ctl)+ (* token = 1* *) let field_name = token let field_content = (_ #ctl #[' '] )* (* *) let status_code = digit digit digit let reason_phrase = (_ #ctl)* let sp = blank+ rule header = parse | (method_ as meth) sp (request_URI as uri) sp (http_version as version) crlf {nofirstline { proto = proto_of_string(version); mode = Query ( meth_of_string(meth), uri ); headers = Http_headers.empty } lexbuf } | (http_version as version) sp ( status_code as status_code ) sp reason_phrase crlf {nofirstline { proto = proto_of_string(version); mode = Answer ( int_of_string status_code ); headers = Http_headers.empty } lexbuf } | eof { handle_eof lexbuf } | _ { handle_other lexbuf } and nofirstline h = parse | crlf { h } | (field_name as field_name) ":" { line field_name [] h lexbuf } | eof { handle_eof lexbuf } | _ { handle_other lexbuf } and line name content h = parse | crlf crlf { add_header (Http_headers.name name,content) h } | crlf (field_name as field_name) ":" { line field_name [] (add_header (Http_headers.name name,content) h) lexbuf } | lws { line name content h lexbuf } | blank { line name content h lexbuf } | ( field_content as c ) { line name (c::content) h lexbuf } | eof { handle_eof lexbuf } | _ { handle_other lexbuf } ocsigenserver-2.16.0/src/http/multipart.ml000066400000000000000000000210411357715257700206200ustar00rootroot00000000000000(* This code is inspired by mimestring.ml from OcamlNet *) (* Copyright Gerd Stolpmann, Patrick Doane *) (* Modified for Ocsigen/Lwt by Nataliya Guts and Vincent Balat *) (*VVV Check whether we should support int64 for large files? *) open Ocsigen_lib module S = Netstring_pcre open Lwt open Ocsigen_stream exception Multipart_error of string let cr_or_lf_re = S.regexp "[\013\n]";; let header_stripped_re = S.regexp "([^ \t\r\n:]+):[ \t]*((.*[^ \t\r\n])?([ \t\r]*\n[ \t](.*[^ \t\r\n])?)*)[ \t\r]*\n";; let header_unstripped_re = S.regexp "([^ \t\r\n:]+):([ \t]*.*\n([ \t].*\n)*)";; (* This much simpler expression returns the name and the unstripped * value. *) let empty_line_re = S.regexp "\013?\n";; let end_of_header_re = S.regexp "\n\013?\n";; let scan_header ?(downcase=true) ?(unfold=true) ?(strip=false) parstr ~start_pos:i0 ~end_pos:i1 = let header_re = if unfold || strip then header_stripped_re else header_unstripped_re in let rec parse_header i l = match S.string_match header_re parstr i with Some r -> let i' = S.match_end r in if i' > i1 then raise (Multipart_error "Mimestring.scan_header"); let name = if downcase then String.lowercase(S.matched_group r 1 parstr) else S.matched_group r 1 parstr in let value_with_crlf = S.matched_group r 2 parstr in let value = if unfold then S.global_replace cr_or_lf_re "" value_with_crlf else value_with_crlf in parse_header i' ( (name,value) :: l) | None -> (* The header must end with an empty line *) begin match S.string_match empty_line_re parstr i with Some r' -> List.rev l, S.match_end r' | None -> raise (Multipart_error "Mimestring.scan_header") end in parse_header i0 [] ;; let read_header ?downcase ?unfold ?strip s = let rec find_end_of_header s = catch (fun () -> let b = Ocsigen_stream.current_buffer s in (* Maybe the header is empty. In this case, there is an empty line * right at the beginning *) match S.string_match empty_line_re b 0 with Some r -> return (s, (S.match_end r)) | None -> (* Search the empty line: *) return (s, (S.match_end (snd (S.search_forward end_of_header_re b 0)))) ) (function | Not_found -> Ocsigen_stream.enlarge_stream s >>= (function Finished _ -> fail Stream_too_small | Cont (stri, _) as s -> find_end_of_header s) | e -> fail e) in find_end_of_header s >>= (fun (s, end_pos) -> let b = Ocsigen_stream.current_buffer s in let header, _ = scan_header ?downcase ?unfold ?strip b ~start_pos:0 ~end_pos in Ocsigen_stream.skip s (Int64.of_int end_pos) >>= (fun s -> return (s, header))) ;; let lf_re = S.regexp "[\n]";; let read_multipart_body decode_part boundary s = let rec search_window s re start = try return (s, snd (S.search_forward re (Ocsigen_stream.current_buffer s) start)) with Not_found -> Ocsigen_stream.enlarge_stream s >>= (function | Finished _ -> fail Stream_too_small | Cont (stri, _) as s -> search_window s re start) in let search_end_of_line s k = (* Search LF beginning at position k *) catch (fun () -> (search_window s lf_re k) >>= (fun (s, x) -> return (s, (S.match_end x)))) (function | Not_found -> fail (Multipart_error "read_multipart_body: MIME boundary without line end") | e -> fail e) in let search_first_boundary s = (* Search boundary per regexp; return the position of the character * immediately following the boundary (on the same line), or * raise Not_found. *) let re = S.regexp ("\n--" ^ S.quote boundary) in (search_window s re 0) >>= (fun (s, x) -> return (s, (S.match_end x))) in let check_beginning_is_boundary s = let del = "--" ^ boundary in let ldel = String.length del in Ocsigen_stream.stream_want s (ldel + 2) >>= (function | Finished _ as str2 -> return (str2, false, false) | Cont (ss, f) as str2 -> let long = String.length ss in let isdelim = (long >= ldel) && (String.sub ss 0 ldel = del) in let islast = isdelim && (String.sub ss ldel 2 = "--") in return (str2, isdelim, islast)) in let rec parse_parts s uses_crlf = (* PRE: [s] is at the beginning of the next part. * [uses_crlf] must be true if CRLF is used as EOL sequence, and false * if only LF is used as EOL sequence. *) let delimiter = (if uses_crlf then "\r" else "" ) ^ "\n--" ^ boundary in Ocsigen_stream.substream delimiter s >>= fun a -> decode_part a >>= fun (y, s) -> (* Now the position of [s] is at the beginning of the delimiter. * Check if there is a "--" after the delimiter (==> last part) *) let l_delimiter = String.length delimiter in Ocsigen_stream.next s >>= fun s -> Ocsigen_stream.stream_want s (l_delimiter+2) >>= fun s -> let last_part = match s with | Finished _ -> false | Cont (ss, f) -> let long = String.length ss in (long >= (l_delimiter+2)) && (ss.[l_delimiter] = '-') && (ss.[l_delimiter+1] = '-') in if last_part then return [ y ] else begin search_end_of_line s 2 >>= fun (s, k) -> (* [k]: Beginning of next part *) Ocsigen_stream.skip s (Int64.of_int k) >>= fun s -> parse_parts s uses_crlf >>= fun l -> return (y :: l) end in (* Check whether s directly begins with a boundary: *) check_beginning_is_boundary s >>= fun (s, b, islast) -> if islast then return [] else if b then begin (* Move to the beginning of the next line: *) search_end_of_line s 0 >>= (fun (s, k_eol) -> let uses_crlf = (Ocsigen_stream.current_buffer s).[k_eol-2] = '\r' in Ocsigen_stream.skip s (Int64.of_int k_eol) >>= fun s -> (* Begin with first part: *) parse_parts s uses_crlf) end else begin (* Search the first boundary: *) catch (fun () -> search_first_boundary s >>= fun (s, k_eob) -> (* or Not_found *) (* Printf.printf "k_eob=%d\n" k_eob; *) (* Move to the beginning of the next line: *) search_end_of_line s k_eob >>= fun (s, k_eol) -> let uses_crlf = (Ocsigen_stream.current_buffer s).[k_eol-2] = '\r' in (* Printf.printf "k_eol=%d\n" k_eol; *) Ocsigen_stream.skip s (Int64.of_int k_eol) >>= fun s -> (* Begin with first part: *) parse_parts s uses_crlf) (function | Not_found -> (* No boundary at all: The body is empty. *) return [] | e -> fail e) end ;; let empty_stream = Ocsigen_stream.get (Ocsigen_stream.make (fun () -> Ocsigen_stream.empty None)) let scan_multipart_body_from_stream s ~boundary ~create ~add ~stop ~maxsize= let decode_part stream = read_header stream >>= (fun (s, header) -> let p = create header in let rec while_stream size = function | Finished None -> return (size, empty_stream) | Finished (Some ss) -> return (size, ss) | Cont (stri, f) -> let long = String.length stri in let size2 = Int64.add size (Int64.of_int long) in if (match maxsize with None -> false | Some m -> (Int64.compare size2 m) > 0) then fail Ocsigen_Request_too_long else if stri = "" then Ocsigen_stream.next f >>= while_stream size else ((* catch (fun () -> add p stri) (fun e -> f () >>= Ocsigen_stream.consume >>= (fun () -> fail e)) *) add p stri >>= fun () -> Ocsigen_stream.next f >>= while_stream size2) in catch (fun () -> while_stream Int64.zero s >>= (fun (size, s) -> stop size p >>= fun r -> return (r, s))) (function error -> stop Int64.zero p >>= fun _ -> fail error)) in catch (fun () -> (* read the multipart body: *) Ocsigen_stream.next s >>= fun s -> read_multipart_body decode_part boundary s >>= (fun _ -> return ())) (function | Stream_too_small -> fail Ocsigen_Bad_Request | e -> fail e) ;; ocsigenserver-2.16.0/src/http/multipart.mli000066400000000000000000000003711357715257700207740ustar00rootroot00000000000000 val scan_multipart_body_from_stream: string Ocsigen_stream.stream -> boundary:string -> create:((string * string) list -> 'a) -> add:('a -> string -> unit Lwt.t) -> stop:(int64 -> 'a -> 'b Lwt.t) -> maxsize:Int64.t option -> unit Lwt.t ocsigenserver-2.16.0/src/http/ocsigen_charset_mime.ml000066400000000000000000000115061357715257700227530ustar00rootroot00000000000000(* Ocsigen * http://www.ocsigen.org * ocsigen_charset_mime.ml Copyright (C) 2008 * Boris Yakobowski * Laboratoire PPS - CNRS Université Paris Diderot * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) open Ocsigen_lib module MapString = Map.Make(String) type extension = string type filename = string type file = string let section = Lwt_log.Section.make "ocsigen:mimetype" type 'a assoc_item = | Extension of extension * 'a | File of filename * 'a | Regexp of Netstring_pcre.regexp * 'a | Map of 'a MapString.t type 'a assoc = { assoc_list: 'a assoc_item list; assoc_default: 'a } let find_in_assoc file assoc = let filename = Filename.basename file in let ext = try String.lowercase (Filename.extension_no_directory file) with Not_found -> "" in let rec aux = function | [] -> assoc.assoc_default | Extension (ext', v) :: q -> if ext = ext' then v else aux q | File (filename', v) :: q -> if filename = filename' then v else aux q | Regexp (reg, v) :: q -> if Netstring_pcre.string_match reg file 0 <> None then v else aux q | Map m :: q -> try MapString.find ext m with Not_found -> aux q in aux assoc.assoc_list let default assoc = assoc.assoc_default let set_default assoc default = { assoc with assoc_default = default } let update_ext assoc (ext : extension) v = { assoc with assoc_list = Extension (String.lowercase ext, v) :: assoc.assoc_list} let update_file assoc (file : filename) v = { assoc with assoc_list = File (file, v) :: assoc.assoc_list} let update_regexp assoc r v = { assoc with assoc_list = Regexp (r, v) :: assoc.assoc_list} let empty default () = { assoc_list = []; assoc_default = default } (* Handling of charset and mime ; specific values and declarations *) type charset = string type mime_type = string type charset_assoc = charset assoc type mime_assoc = mime_type assoc let no_charset : charset = "" let default_mime_type : mime_type = "application/octet-stream" let empty_charset_assoc ?(default=no_charset) = empty default let empty_mime_assoc ?(default=default_mime_type) = empty default (* Generic functions *) let default_charset = default let default_mime = default let update_charset_ext = update_ext let update_mime_ext = update_ext let update_charset_file = update_file let update_mime_file = update_file let update_charset_regexp = update_regexp let update_mime_regexp = update_regexp let set_default_mime = set_default let set_default_charset = set_default let find_charset = find_in_assoc let find_mime = find_in_assoc (* Specific handling of content-type *) let parse_mime_types ~filename : mime_type assoc = let rec read_and_split mimemap in_ch = try let line = input_line in_ch in let line_upto = try let upto = String.index line '#' in String.sub line 0 upto with Not_found -> line in let strlist = Netstring_pcre.split (Netstring_pcre.regexp "\\s+") line_upto in match strlist with | [] | [_] -> (* No extension on this line *) read_and_split mimemap in_ch | mime :: extensions -> let mimemap = List.fold_left (fun mimemap ext -> MapString.add ext mime mimemap) mimemap extensions in read_and_split mimemap in_ch with End_of_file -> mimemap in { assoc_list = [ Map(try let in_ch = open_in filename in let map = (try read_and_split MapString.empty in_ch with e -> close_in in_ch; raise e) in close_in in_ch; map with exn -> Lwt_log.ign_error ~section ~exn "unable to read the mime.types file"; MapString.empty )]; assoc_default = default_mime_type; } let default_mime_assoc () = let parsed = ref None in match !parsed with | None -> let file = Ocsigen_config.get_mimefile () in Lwt_log.ign_info_f ~section "Loading mime types in '%s'" file; let map = parse_mime_types file in parsed := Some map; map | Some map -> map ocsigenserver-2.16.0/src/http/ocsigen_charset_mime.mli000066400000000000000000000056471357715257700231350ustar00rootroot00000000000000(* Ocsigen * http://www.ocsigen.org * ocsigen_charset_mime.mli Copyright (C) 2008 * Boris Yakobowski * Laboratoire PPS - CNRS Université Paris Diderot * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) type extension = string type file = string type filename = string (** Charset *) (** By convention, "no specified charset" is represented by the empty string *) type charset = string val no_charset: charset (** Association between extensions and charset, with a default value. *) type charset_assoc (** All files are mapped to [no_charset] *) val empty_charset_assoc : ?default:charset -> unit -> charset_assoc val find_charset : string -> charset_assoc -> charset (** Functions related to the default charset in the association *) val default_charset : charset_assoc -> charset val set_default_charset : charset_assoc -> charset -> charset_assoc (** Updates the mapping between extensions from a file to its charset. The update can be specified using the extension of the file, the name of the file, or the entire file (with its path) *) val update_charset_ext : charset_assoc -> extension -> charset -> charset_assoc val update_charset_file : charset_assoc -> filename -> charset -> charset_assoc val update_charset_regexp : charset_assoc -> Netstring_pcre.regexp -> charset -> charset_assoc (** MIME types; the default value is ["application/octet-stream"] *) type mime_type = string val default_mime_type : mime_type (** association between extensions and mime types, with default value *) type mime_assoc (** Default values, obtained by reading the file specified by [Ocsigen_config.get_mimefile] *) val default_mime_assoc : unit -> mime_assoc (** Parsing of a file containing mime associations, such as /etc/mime-types *) val parse_mime_types : filename:string -> mime_assoc (* The other functions are as for charsets *) val find_mime : file -> mime_assoc -> string val default_mime : mime_assoc -> mime_type val set_default_mime : mime_assoc -> mime_type -> mime_assoc val update_mime_ext : mime_assoc -> extension -> mime_type -> mime_assoc val update_mime_file : mime_assoc -> filename -> mime_type -> mime_assoc val update_mime_regexp : mime_assoc -> Netstring_pcre.regexp -> mime_type -> mime_assoc ocsigenserver-2.16.0/src/http/ocsigen_cookies.ml000066400000000000000000000041301357715257700217420ustar00rootroot00000000000000(* Ocsigen * Copyright (C) 2010 Vincent Balat * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) module CookiesTable = Map.Make(String) module Cookies = Map.Make(struct type t = string list let compare = compare end) type cookie = | OSet of float option * string * bool | OUnset type cookieset = cookie CookiesTable.t Cookies.t let empty_cookieset = Cookies.empty let add_cookie path n v t = let ct = try Cookies.find path t with Not_found -> CookiesTable.empty in (* We replace the old value if it exists *) Cookies.add path (CookiesTable.add n v ct) t let remove_cookie path n t = try let ct = Cookies.find path t in let newct = CookiesTable.remove n ct in if CookiesTable.is_empty newct then Cookies.remove path t else (* We replace the old value *) Cookies.add path newct t with Not_found -> t (* [add_cookies newcookies oldcookies] adds the cookies from [newcookies] to [oldcookies]. If cookies are already bound in oldcookies, the previous binding disappear. *) let add_cookies newcookies oldcookies = Cookies.fold (fun path ct t -> CookiesTable.fold (fun n v beg -> match v with | OSet (expo, v, secure) -> add_cookie path n (OSet (expo, v, secure)) beg | OUnset -> add_cookie path n OUnset beg ) ct t ) newcookies oldcookies ocsigenserver-2.16.0/src/http/ocsigen_cookies.mli000066400000000000000000000045121357715257700221170ustar00rootroot00000000000000(* Ocsigen * Copyright (C) 2010 Vincent Balat * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) open Ocsigen_lib module CookiesTable : Map.S with type key = string (** This table is to store cookie values for each path. The key has type Url.path option: it is for the path (default: root of the site), *) module Cookies : Map.S with type key = Url.path (** Type used for cookies to set. The float option is the timestamp for the expiration date. The string is the value. If the bool is true and the protocol is https, the cookie will be secure (will ask the browser to send it only through secure connections). *) type cookie = | OSet of float option * string * bool | OUnset type cookieset = cookie CookiesTable.t Cookies.t val empty_cookieset : 'a CookiesTable.t Cookies.t (** [add_cookie path c v cookie_table] adds the cookie [c] to the table [cookie_table]. If the cookie is already bound, the previous binding disappear. *) val add_cookie : Url.path -> string -> 'a -> 'a CookiesTable.t Cookies.t -> 'a CookiesTable.t Cookies.t (** [remove_cookie c cookie_table] removes the cookie [c] from the table [cookie_table]. Warning: it is not equivalent to [add_cookie ... OUnset ...]). *) val remove_cookie : Url.path -> string -> 'a CookiesTable.t Cookies.t -> 'a CookiesTable.t Cookies.t (** [add_cookies newcookies oldcookies] adds the cookies from [newcookies] to [oldcookies]. If cookies are already bound in oldcookies, the previous binding disappear. *) val add_cookies : cookie CookiesTable.t Cookies.t -> cookie CookiesTable.t Cookies.t -> cookie CookiesTable.t Cookies.t ocsigenserver-2.16.0/src/http/ocsigen_headers.ml000066400000000000000000000240261357715257700217270ustar00rootroot00000000000000(* Ocsigen * ocsigen_headers.ml Copyright (C) 2005 Vincent Balat * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) (* TODO: rewrite header parsing! *) (** This module is for getting information from HTTP header. *) (** It uses the lowel level module Ocsigen_http_frame.Http_header. *) (** It is very basic and must be completed for exhaustiveness. *) (* Operation on strings are hand-written ... *) (* Include in a better cooperative parser for header or use regexp?. *) open Ocsigen_http_frame open Ocsigen_senders open Ocsigen_lib open Ocsigen_cookies let find name frame = Http_headers.find (Http_headers.name name) frame.frame_header.Http_header.headers let find_all name frame = Http_headers.find_all (Http_headers.name name) frame.frame_header.Http_header.headers (* XXX Get rid of all "try ... with _ -> ..." *) let list_flat_map f l = List.flatten (List.map f l) (* splits a quoted string, for ex "azert", " sdfmlskdf", "dfdsfs" *) (* We are too kind ... We accept even if the separator is not ok :-( ? *) let rec quoted_split char (* char is not used in that version *) s = let longueur = String.length s in let rec aux deb = let rec nextquote s i = if i>=longueur then failwith "" else if s.[i] = '"' then i else if s.[i] = '\\' then nextquote s (i+2) else nextquote s (i+1) in try let first = (nextquote s deb) + 1 in let afterlast = nextquote s first in let value = String.sub s first (afterlast - first) in value:: (if (afterlast + 1) < longueur then aux (afterlast + 1) else []) with Failure _ | Invalid_argument _ -> [] in aux 0 let parse_quality parse_name s = try let a,b = String.sep ';' s in let q,qv = String.sep '=' b in if q="q" then ((parse_name a), Some (float_of_string qv)) else failwith "Parse error" with _ -> ((parse_name s), None) let parse_star a = if a = "*" then None else Some a let parse_mime_type a = let b,c = String.sep '/' a in ((parse_star b), (parse_star c)) let parse_extensions parse_name s = try let a,b = String.sep ';' s in ((parse_name a), List.map (String.sep '=') (String.split ';' b)) with _ -> ((parse_name s), []) let parse_list_with_quality parse_name s = let splitted = list_flat_map (String.split ',') s in List.map (parse_quality parse_name) splitted let parse_list_with_extensions parse_name s = let splitted = list_flat_map (String.split ',') s in List.map (parse_extensions parse_name) splitted (*****************************************************************************) let rec parse_cookies s = let splitted = String.split ';' s in try List.fold_left (fun beg a -> try let (n, v) = String.sep '=' a in CookiesTable.add n v beg with Not_found -> beg) CookiesTable.empty splitted with _ -> CookiesTable.empty (*VVV Actually the real syntax of cookies is more complex! *) (* http://www.w3.org/Protocols/rfc2109/rfc2109 Mozilla spec + RFC2109 http://ws.bokeland.com/blog/376/1043/2006/10/27/76832 *) let get_keepalive http_header = Http_header.get_proto http_header = Ocsigen_http_frame.Http_header.HTTP11 && try String.lowercase (Http_header.get_headers_value http_header Http_headers.connection) <> "close" with Not_found -> true (* 06/02/2008 If HTTP/1.0, we do not keep alive, even if the client asks so. It would be possible, but only if the content-length is known. Chunked encoding is not possible with HTTP/1.0. As we cannot know if the output will be chunked or not, we decided that we won't keep the connection open at all for HTTP/1.0. Another solution would be to keep it open if the client asks so, and answer connection:close (and close) if we don't know the size of the document. In that case, all requests that have been pipelined would be processed by the server, but not sent back to the client. Which one is the best? It really depends on the client. If the client waits the answer before doing the following request, it would be ok to keep the connection opened, otherwise it is better not. (+ pb with non-idempotent requests, that should not be pipelined) *) (* RFC 2616, sect. 14.23 *) (* XXX Not so simple: the host name may contain a colon! (RFC 3986) *) let get_host_from_host_header = let host_re = Netstring_pcre.regexp "^(\\[[0-9A-Fa-f:.]+\\]|[^:]+)(:([0-9]+))?$" in fun http_frame -> try let hostport = Http_header.get_headers_value http_frame.Ocsigen_http_frame.frame_header Http_headers.host in match Netstring_pcre.string_match host_re hostport 0 with | Some m -> (Some (Netstring_pcre.matched_group m 1 hostport), try Some (int_of_string (Netstring_pcre.matched_group m 3 hostport)) with Not_found -> None | Failure _ -> raise Ocsigen_Bad_Request) | None -> raise Ocsigen_Bad_Request with Not_found -> (None, None) let get_user_agent http_frame = try (Http_header.get_headers_value http_frame.Ocsigen_http_frame.frame_header Http_headers.user_agent) with Not_found -> "" let get_cookie_string http_frame = try Some (Http_header.get_headers_value http_frame.Ocsigen_http_frame.frame_header Http_headers.cookie) with Not_found -> None let get_expect http_frame = try String.split ',' ( Http_header.get_headers_value http_frame.Ocsigen_http_frame.frame_header Http_headers.expect ) with Not_found -> [] let get_if_modified_since http_frame = try Some (Netdate.parse_epoch (Http_header.get_headers_value http_frame.Ocsigen_http_frame.frame_header Http_headers.if_modified_since)) with _ -> None let get_if_unmodified_since http_frame = try Some (Netdate.parse_epoch (Http_header.get_headers_value http_frame.Ocsigen_http_frame.frame_header Http_headers.if_unmodified_since)) with _ -> None let get_if_none_match http_frame = try Some (list_flat_map (quoted_split ',') (Http_header.get_headers_values http_frame.Ocsigen_http_frame.frame_header Http_headers.if_none_match)) with _ -> None let get_if_match http_frame = try Some (list_flat_map (quoted_split ',') (Http_header.get_headers_values http_frame.Ocsigen_http_frame.frame_header Http_headers.if_match)) with _ -> None let get_content_type http_frame = try Some (Http_header.get_headers_value http_frame.Ocsigen_http_frame.frame_header Http_headers.content_type) with Not_found -> None let parse_content_type = function | None -> None | Some s -> match String.split ';' s with | [] -> None | a::l -> try let typ, subtype = String.sep '/' a in let params = try List.map (String.sep '=') l with Not_found -> [] in (*VVV If syntax error, we return no parameter at all *) Some ((typ, subtype), params) (*VVV If syntax error in type, we return None *) with Not_found -> None let get_content_length http_frame = try Some (Int64.of_string (Http_header.get_headers_value http_frame.Ocsigen_http_frame.frame_header Http_headers.content_length)) with Not_found | Failure _ | Invalid_argument _ -> None let get_referer http_frame = try Some (Http_header.get_headers_value http_frame.Ocsigen_http_frame.frame_header Http_headers.referer) with _ -> None let get_origin http_frame = try Some (Http_header.get_headers_value http_frame.Ocsigen_http_frame.frame_header Http_headers.origin) with _ -> None let get_access_control_request_method http_frame = try Some (Http_header.get_headers_value http_frame.Ocsigen_http_frame.frame_header Http_headers.access_control_request_method) with _ -> None let get_access_control_request_headers http_frame = try let s = (Http_header.get_headers_value http_frame.Ocsigen_http_frame.frame_header Http_headers.access_control_request_headers) in Some (String.split ',' s) with _ -> None let get_referrer = get_referer let get_accept http_frame = try let l = parse_list_with_extensions parse_mime_type (Http_header.get_headers_values http_frame.Ocsigen_http_frame.frame_header Http_headers.accept) in let change_quality (a, l) = try let q,ll = List.assoc_remove "q" l in (a, Some (float_of_string q), ll) with _ -> (a, None, l) in List.map change_quality l with _ -> [] let get_accept_charset http_frame = try parse_list_with_quality parse_star (Http_header.get_headers_values http_frame.Ocsigen_http_frame.frame_header Http_headers.accept_charset) with _ -> [] let get_accept_encoding http_frame = try parse_list_with_quality parse_star (Http_header.get_headers_values http_frame.Ocsigen_http_frame.frame_header Http_headers.accept_encoding) with _ -> [] let get_accept_language http_frame = try parse_list_with_quality id (Http_header.get_headers_values http_frame.Ocsigen_http_frame.frame_header Http_headers.accept_language) with _ -> [] ocsigenserver-2.16.0/src/http/ocsigen_headers.mli000066400000000000000000000057461357715257700221100ustar00rootroot00000000000000(* Ocsigen * ocsigen_headers.mli Copyright (C) 2005 Vincent Balat * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) (** Getting information from HTTP header. *) (** This module uses the lowel level module Ocsigen_http_frame.Http_header. It is very basic and must be completed for exhaustiveness. *) open Ocsigen_lib open Ocsigen_cookies val find : string -> Ocsigen_http_frame.t -> string (** find one of the values bound to [name] in the HTTP headers of the frame. Raise [Not_found] if it is not bound. *) val find_all : string -> Ocsigen_http_frame.t -> string list (** find all the values bound to [name] in the HTTP headers of the frame. Raise [Not_found] if it is not bound.*) val get_keepalive : Ocsigen_http_frame.Http_header.http_header -> bool val parse_cookies : string -> string CookiesTable.t val parse_mime_type : string -> string option * string option val get_host_from_host_header : Ocsigen_http_frame.t -> string option * int option val get_user_agent : Ocsigen_http_frame.t -> string val get_cookie_string : Ocsigen_http_frame.t -> string option val get_expect : Ocsigen_http_frame.t -> string list val get_if_modified_since : Ocsigen_http_frame.t -> float option val get_if_unmodified_since : Ocsigen_http_frame.t -> float option val get_if_none_match : Ocsigen_http_frame.t -> string list option val get_if_match : Ocsigen_http_frame.t -> string list option val get_content_type : Ocsigen_http_frame.t -> string option val parse_content_type : string option -> ((string * string) * (string * string) list) option val get_content_length : Ocsigen_http_frame.t -> int64 option val get_referer : Ocsigen_http_frame.t -> string option val get_referrer : Ocsigen_http_frame.t -> string option val get_origin : Ocsigen_http_frame.t -> string option val get_access_control_request_method : Ocsigen_http_frame.t -> string option val get_access_control_request_headers : Ocsigen_http_frame.t -> string list option val get_accept : Ocsigen_http_frame.t -> ((string option * string option) * float option * (string * string) list) list val get_accept_charset : Ocsigen_http_frame.t -> (string option * float option) list val get_accept_encoding : Ocsigen_http_frame.t -> (string option * float option) list val get_accept_language : Ocsigen_http_frame.t -> (string * float option) list ocsigenserver-2.16.0/src/http/ocsigen_http_com.ml000066400000000000000000001016211357715257700221260ustar00rootroot00000000000000(* Ocsigen * http://www.ocsigen.org * ocsigen_http_com.ml Copyright (C) 2005 * Denis Berthod, Vincent Balat, Jérôme Vouillon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) (* TODO - server.ml - shorter timeouts for keep-alive - check the code against the HTTP spec - defunctorize sender code - rewrite HTTP parser - check HTTP version (at the moment, we always respond with HTTP/1.1!!!) - for possibly large reads/writes on the network (streaming of more than a few kilobytes), we should use Lwt_unix.wait_read and Lwt_unix.wait_write: this gives some time for other requests to be handled, and the read/write is resumed only when necessary PERSISTENT CONNECTIONS ====================== http://www.tools.ietf.org/html/draft-ietf-http-connection-00 *) open Ocsigen_lib open Ocsigen_http_frame open Ocsigen_cookies let section = Lwt_log.Section.make "ocsigen:http:com" (** this module provide a mechanism to communicate with some http frames *) let (>>=) = Lwt.(>>=) (****) (** Internal exceptions *) exception Buffer_full (** Exported exceptions *) exception Connection_closed exception Lost_connection of exn exception Timeout exception Keepalive_timeout exception Aborted (*XXX Provide the max size? *) let request_too_large max = Ocsigen_http_frame.Http_error.Http_exception (413, Some "request contents too large", None) let convert_io_error e = match e with Unix.Unix_error(Unix.ECONNRESET,_,_) | Ssl.Read_error (Ssl.Error_syscall | Ssl.Error_ssl) | End_of_file | Ssl.Write_error (Ssl.Error_zero_return | Ssl.Error_syscall | Ssl.Error_ssl) | Unix.Unix_error (Unix.EPIPE, _, _) -> Lost_connection e | _ -> e let catch_io_errors f = Lwt.catch f (fun e -> Lwt.fail (convert_io_error e)) (****) type mode = Answer | Query | Nofirstline type waiter = { w_wait : unit Lwt.t; w_waker: unit Lwt.u option; mutable w_did_wait : bool } let create_waiter block = if block then let (t, u) = Lwt.wait () in { w_wait = t; w_waker = Some u; w_did_wait = false } else { w_wait = Lwt.return (); w_waker = None; w_did_wait = false } (** Communication buffer to receive messages. *) type connection = { id : int; fd : Lwt_ssl.socket; chan : Lwt_io.output_channel; timeout : Lwt_timeout.t; r_mode : mode; closed : unit Lwt.t * unit Lwt.u; mutable buf : bytes; mutable read_pos : int; mutable write_pos : int; mutable read_mutex : Lwt_mutex.t; mutable extension_mutex : Lwt_mutex.t; (* to keep requests in right order *) mutable senders : waiter; mutable sender_count : int } let connection_id x = x.id let connection_fd x = x.fd let new_id = let c = ref 0 in fun () -> incr c; !c let create_receiver timeout mode fd = let buffer_size = Ocsigen_config.get_netbuffersize () in let timeout = Lwt_timeout.create timeout (fun () -> Lwt_ssl.abort fd Timeout) in { id = new_id (); fd = fd; chan = Lwt_io.make ~mode:Lwt_io.output ~buffer:(Lwt_bytes.create buffer_size) (fun buf pos len -> Lwt_timeout.start timeout; Lwt.try_bind (fun () -> Lwt_ssl.write_bytes fd buf pos len) (fun l -> Lwt_timeout.stop timeout; Lwt.return l) (fun e -> Lwt_timeout.stop timeout; Lwt.fail (convert_io_error e))); timeout = timeout; r_mode = mode; buf=Bytes.create buffer_size; read_pos = 0; write_pos = 0; closed = Lwt.wait (); read_mutex = Lwt_mutex.create (); extension_mutex = Lwt_mutex.create (); senders = create_waiter false; sender_count = 0 } (*XXX Do we really need to export this function? *) let lock_receiver receiver = Lwt_mutex.lock receiver.read_mutex let unlock_receiver receiver = Lwt_mutex.unlock receiver.read_mutex let abort conn = Lwt.wakeup (snd conn.closed) (); Lwt_ssl.abort conn.fd Aborted let closed conn = fst conn.closed let wakeup_next_request conn = Lwt_mutex.unlock conn.extension_mutex let block_next_request conn = Lwt_mutex.lock conn.extension_mutex (****) (** the number of byte in the buffer*) let buf_used buffer = buffer.write_pos - buffer.read_pos let buf_size buffer = Bytes.length buffer.buf let buf_get_string buffer len = let pos = buffer.read_pos in assert (pos + len <= buffer.write_pos); buffer.read_pos <- buffer.read_pos + len; Bytes.sub_string buffer.buf pos len (** Receive some more data. *) let receive receiver = let used = buf_used receiver in let free = buf_size receiver - used in if free = 0 then Lwt.fail Buffer_full else begin if receiver.read_pos > 0 then begin Bytes.blit receiver.buf receiver.read_pos receiver.buf 0 used; receiver.write_pos <- used; receiver.read_pos <- 0 end; if receiver.sender_count = 0 then Lwt_timeout.start receiver.timeout; Lwt_ssl.read receiver.fd receiver.buf receiver.write_pos free >>= fun len -> Lwt_timeout.stop receiver.timeout; receiver.write_pos <- used + len; if len = 0 then Lwt.fail End_of_file else begin Lwt.return () end end (** Receive data until at least [len] chars are available *) let rec fill receiver len = if buf_used receiver >= len then Lwt.return () else begin receive receiver >>= fun () -> fill receiver len end (****) type size = Exact of int64 | Bounded of int64 option let rec extract_aux receiver pos bound cont = let avail = buf_used receiver in if avail = 0 then Lwt.try_bind (fun () -> receive receiver) (fun () -> extract_aux receiver pos bound cont) (fun e -> match e, bound with End_of_file, Bounded _ -> Ocsigen_stream.empty None | _ -> Lwt.fail (convert_io_error e)) else let pos' = Int64.add pos (Int64.of_int avail) in match bound with Exact l when pos' >= l -> let len = Int64.to_int (Int64.sub l pos) in let s = buf_get_string receiver len in Ocsigen_stream.cont s cont | Bounded (Some l) when pos' > l -> Lwt.fail (request_too_large l) | _ -> let s = buf_get_string receiver avail in Ocsigen_stream.cont s (fun () -> extract_aux receiver pos' bound cont) (** Stream from the receiver channel. *) let extract receiver bound = Ocsigen_stream.make (fun () -> extract_aux receiver 0L bound (fun () -> Lwt_mutex.unlock receiver.read_mutex; Ocsigen_stream.empty None)) type pat_res = Found of int | Retry of int (** Wait for a given pattern to be received *) let rec wait_pattern find_pattern receiver cur_pos = let read_pos = receiver.read_pos in let avail = receiver.write_pos - (cur_pos + read_pos) in match find_pattern receiver.buf (cur_pos + read_pos) avail with Found end_pos -> Lwt.return (end_pos - read_pos) | Retry retry_pos -> let pos = max 0 (retry_pos - read_pos) in receive receiver >>= fun () -> wait_pattern find_pattern receiver pos (** Find the first sequence crlfcrlf or lflf in the buffer *) let rec find_header buf pos rem = if rem < 2 then Retry (pos - 3) else if Bytes.get buf (pos + 1) <> '\n' then find_header buf (pos + 1) (rem - 1) else if Bytes.get buf pos = '\n' then Found (pos + 2) else if rem >= 4 && Bytes.get buf pos = '\r' && Bytes.get buf (pos + 2) = '\r' && Bytes.get buf (pos + 3) = '\n' then Found (pos + 4) else find_header buf (pos + 1) (rem - 1) (** Wait until a full header is received. Returns the length of the header *) let wait_http_header receiver = Lwt.catch (fun () -> wait_pattern find_header receiver 0) (fun e -> Lwt.fail (match e with Buffer_full -> Ocsigen_http_frame.Http_error.Http_exception (413, Some "header too long", None) | End_of_file when buf_used receiver = 0 -> Connection_closed | Timeout when buf_used receiver = 0 && receiver.sender_count = 0 -> Keepalive_timeout | _ -> convert_io_error e)) (** Find an end of line crlf or lf in the buffer *) let rec find_line buf pos rem = if rem < 1 then Retry pos else if Bytes.get buf pos = '\n' then Found (pos + 1) else find_line buf (pos + 1) (rem - 1) (** Wait until a full line is received. Returns the length of the line *) let wait_line receiver = wait_pattern find_line receiver 0 (** extract chunked data in destructive way from the buffer. The optional [?finish] parameter is an action that will be executed when the stream is finished. *) let extract_chunked receiver = let ec_fail e = let e = if e = Buffer_full then Ocsigen_http_frame.Http_error.Http_exception (400, Some "bad chunked data", None) else convert_io_error e in Lwt.fail e in let extract_crlf receiver = Lwt.catch (fun () -> fill receiver 2 >>= fun () -> let pos = receiver.read_pos in if Bytes.get receiver.buf pos = '\r' && Bytes.get receiver.buf (pos + 1) = '\n' then begin receiver.read_pos <- pos + 2; Lwt.return () end else Lwt.fail (Ocsigen_http_frame.Http_error.Http_exception (400, Some "bad chunked data", None))) ec_fail in let rec aux () = Lwt.try_bind (fun () -> wait_line receiver) (fun len -> let chunksize = buf_get_string receiver len in (*XXX Should check that we really have chunked data *) let chunksize = Scanf.sscanf chunksize "%x" (fun x -> x) in if chunksize = 0 then begin extract_crlf receiver >>= fun () -> Lwt_mutex.unlock receiver.read_mutex; Ocsigen_stream.empty None end else extract_aux receiver 0L (Exact (Int64.of_int chunksize)) (fun () -> extract_crlf receiver >>= fun () -> aux ())) ec_fail in Ocsigen_stream.make aux (* RFC2616, sect 4.3 *) let code_without_message_body code = (code >= 100 && code < 200) || code = 204 || code = 304 let parse_http_header mode s = (*XXX Should check that the message corresponds to the mode *) let lexbuf = Lexing.from_string s in try Lwt.return (if mode = Nofirstline then Http_lexer.nofirstline { Ocsigen_http_frame.Http_header.mode = Ocsigen_http_frame.Http_header.Nofirstline; proto = Ocsigen_http_frame.Http_header.HTTP11; headers = Http_headers.empty } lexbuf else Http_lexer.header lexbuf) with Parsing.Parse_error -> Lwt.fail (Ocsigen_http_frame.Http_error.Http_exception (400, Some "parse error", None)) let get_maxsize = function | Nofirstline | Answer -> None (* Ocsigen_config.get_maxanswerbodysize () Do we need a limit? If yes, add an exception Ocsigen_Answer_too_long. (like Ocsigen_Request_too_long) *) | Query -> Ocsigen_config.get_maxrequestbodysize () let return_with_no_body receiver = Lwt.return None (** get an http frame *) let get_http_frame ?(head = false) receiver = Lwt_mutex.lock receiver.read_mutex >>= fun () -> wait_http_header receiver >>= fun len -> let string_header = buf_get_string receiver len in parse_http_header receiver.r_mode string_header >>= fun header -> (* RFC2616, sect 4.4 1. Any response message which "MUST NOT" include a message-body (such as the 1xx, 204, and 304 responses and any response to a HEAD request) is always terminated by the first empty line after the header fields, regardless of the entity-header fields present in the message. *) begin match header.Ocsigen_http_frame.Http_header.mode with | Ocsigen_http_frame.Http_header.Answer code when code_without_message_body code -> return_with_no_body receiver | _ -> if head then begin return_with_no_body receiver end else begin (* RFC 2. If a Transfer-Encoding header field (section 14.41) is present and has any value other than "identity", then the transfer-length is defined by use of the "chunked" transfer-coding (section 3.6), unless the message is terminated by closing the connection. *) let chunked = try Ocsigen_http_frame.Http_header.get_headers_value header Http_headers.transfer_encoding <> "identity" with Not_found -> false in if chunked then Lwt.return (Some (extract_chunked receiver)) else begin (* RFC 3. If a Content-Length header field (section 14.13) is present, its decimal value in OCTETs represents both the entity-length and the transfer-length. The Content-Length header field MUST NOT be sent if these two lengths are different (i.e., if a Transfer-Encoding header field is present). If a message is received with both a Transfer-Encoding header field and a Content-Length header field, the latter MUST be ignored. *) let content_length = try (*XXX Check for overflow/malformed field... *) Some (Int64.of_string (Ocsigen_http_frame.Http_header.get_headers_value header Http_headers.content_length)) with Not_found -> None in match content_length with | Some cl -> if cl < 0L then (*XXX Malformed field!!!*) Lwt.fail (Ocsigen_http_frame.Http_error.Http_exception (400, Some "ill-formed content-length header", None)) else if cl = 0L then return_with_no_body receiver else let max = get_maxsize receiver.r_mode in begin match max with Some m when cl > m -> Lwt.fail (request_too_large m) | _ -> Lwt.return (Some (extract receiver (Exact cl))) end | None -> (* RFC 4. If the message uses the media type "multipart/byteranges", and the transfer-length is not otherwise specified, then this self- delimiting media type defines the transfer-length. This media type MUST NOT be used unless the sender knows that the recipient can parse it; the presence in a request of a Range header with multiple byte- range specifiers from a 1.1 client implies that the client can parse multipart/byteranges responses. NOT IMPLEMENTED 5. By the server closing the connection. (Closing the connection cannot be used to indicate the end of a request body, since that would leave no possibility for the server to send back a response.) *) match header.Ocsigen_http_frame.Http_header.mode with Ocsigen_http_frame.Http_header.Query (_, s) -> return_with_no_body receiver | _ -> let st = extract receiver (Bounded (get_maxsize receiver.r_mode)) in Lwt.return (Some st) end end end >>= fun b -> let la = (match b with | None -> Lwt_mutex.unlock receiver.read_mutex; None | Some s -> Ocsigen_stream.add_finalizer s (fun _ -> Ocsigen_stream.consume s); Some s ) in Lwt.return {Ocsigen_http_frame.frame_header = header; frame_content = la; frame_abort = (fun () -> Lwt_ssl.close receiver.fd)} (*VVV close or shutdown? *) (****) type slot = { sl_waiter : waiter; sl_chan : Lwt_io.output_channel; sl_ssl : bool (* for secure cookies only *)} let create_slot conn = { sl_waiter = conn.senders; sl_chan = conn.chan; sl_ssl = Lwt_ssl.is_ssl conn.fd} (****) let start_processing conn f = let slot = create_slot conn in let next_waiter = create_waiter true in conn.senders <- next_waiter; conn.sender_count <- conn.sender_count + 1; Lwt_timeout.stop conn.timeout; ignore (* We can ignore the thread as all the exceptions are caught *) (Lwt.try_bind (fun () -> Lwt.finalize (fun () -> (* If we want to serialize query processing, we can call [wait_previous_senders slot] here. But then, we should also flush the channel sooner. *) f slot >>= (fun () -> (*XXX Check that we waited: slot.sl_did_wait = true *) (*XXX It would be clearer to put this code at the end of the sender function, but we don't have access to [next_slot] there *) if not next_waiter.w_did_wait then Lwt_io.flush conn.chan else Lwt.return ())) (fun () -> conn.sender_count <- conn.sender_count - 1; if conn.sender_count = 0 then Lwt_timeout.start conn.timeout; Lwt.return ())) (fun () -> (match next_waiter.w_waker with | None -> () | Some wk -> Lwt.wakeup wk ()) ; Lwt.return () ) (fun e -> (match next_waiter.w_waker with | None -> () | Some wk -> Lwt.wakeup_exn wk e) ; Lwt.return () )) let wait_previous_senders slot = slot.sl_waiter.w_did_wait <- true; slot.sl_waiter.w_wait let wait_all_senders conn = Lwt.finalize (fun () -> Lwt.catch (*XXX Do we need a flush here? Are we properly flushing in case of an error? *) (fun () -> conn.senders.w_wait >>= fun () -> Lwt_io.flush conn.chan) (fun e -> match e with Aborted -> Lwt.return () | _ -> Lwt.fail e)) (fun () -> Lwt_timeout.stop conn.timeout; Lwt.return ()) let (<<) h (n, v) = Http_headers.replace n v h let (< h (* None means: do not change the value *) | Some v -> Http_headers.replace n v h let gmtdate d = let x = Netdate.mk_mail_date ~zone:0 d in try let x = Bytes.of_string x in (*XXX !!!*) let ind_plus = Bytes.index x '+' in Bytes.set x ind_plus 'G'; Bytes.set x (ind_plus + 1) 'M'; Bytes.set x (ind_plus + 2) 'T'; Bytes.sub_string x 0 (ind_plus + 3) with Invalid_argument _ | Not_found -> Lwt_log.ign_debug ~section "no +"; x type sender_type = { (** protocol to be used : HTTP/1.0 HTTP/1.1 *) mutable s_proto: Ocsigen_http_frame.Http_header.proto; (** the options to send with each frame, for example : server name , ... *) mutable s_headers: Http_headers.t } (** create a new sender *) let create_sender ?server_name ?(headers=Http_headers.empty) ?(proto=Ocsigen_http_frame.Http_header.HTTP11) () = let headers = Http_headers.replace Http_headers.accept_ranges "none" headers in let headers = Http_headers.replace_opt Http_headers.server server_name headers in { s_headers = headers; s_proto = proto } let default_sender = create_sender ~server_name:Ocsigen_config.server_name () (* Old version (* XXX Maybe we should merge small strings *) (* XXX We should probably make sure that any exception raised by the stream is properly caught *) let rec write_stream_chunked out_ch stream = Ocsigen_stream.next stream >>= fun e -> match e with Ocsigen_stream.Finished _ -> Lwt_io.write out_ch "0\r\n\r\n" | Ocsigen_stream.Cont (s, next) -> let l = String.length s in begin if l = 0 then (* It is incorrect to send an empty chunk *) Lwt.return () else begin Lwt_io.write out_ch (Format.sprintf "%x\r\n" l) >>= fun () -> Lwt_io.write out_ch s >>= fun () -> Lwt_io.write out_ch "\r\n" end end >>= fun () -> write_stream_chunked out_ch next *) (* XXX We should probably make sure that any exception raised by the stream is properly caught *) (* 20071128 Current xhtml pretty printer is making lots of very small strings. We bufferise them before creating a thunk. Benchmarks cannot prove that it is better, but at least the network stream is readable ... It is then buffered again by Lwt_io. Is there a way to have only one buffer? *) let write_stream_chunked out_ch stream = let buf_size = 4096 in let size_for_not_buffering = 900 in let buffer = Bytes.create buf_size in let rec aux stream len = Ocsigen_stream.next stream >>= fun e -> match e with | Ocsigen_stream.Finished _ -> (if len > 0 then begin (* It is incorrect to send an empty chunk *) Lwt_io.write out_ch (Format.sprintf "%x\r\n" len) >>= fun () -> Lwt_io.write_from_exactly out_ch buffer 0 len >>= fun () -> Lwt_io.write out_ch "\r\n" end else Lwt.return ()) >>= fun () -> Lwt_io.write out_ch "0\r\n\r\n" | Ocsigen_stream.Cont (s, next) -> let l = String.length s in if l = 0 then aux next len else if l >= size_for_not_buffering then begin (if len > 0 then begin Lwt_io.write out_ch (Format.sprintf "%x\r\n" len) >>= fun () -> Lwt_io.write_from_exactly out_ch buffer 0 len >>= fun () -> Lwt_io.write out_ch "\r\n" end else Lwt.return ()) >>= fun () -> Lwt_io.write out_ch (Format.sprintf "%x\r\n" l) >>= fun () -> Lwt_io.write_from_string_exactly out_ch s 0 l >>= fun () -> Lwt_io.write out_ch "\r\n" >>= fun () -> aux next 0 end else (* Will not work if l is very large: *) let available = buf_size - len in if l > available then begin Lwt_io.write out_ch (Format.sprintf "%x\r\n" buf_size) >>= fun () -> Lwt_io.write_from_exactly out_ch buffer 0 len >>= fun () -> Lwt_io.write_from_string_exactly out_ch s 0 available >>= fun () -> Lwt_io.write out_ch "\r\n" >>= fun () -> let newlen = l - available in Bytes.blit_string s available buffer 0 newlen; aux next newlen end else begin Bytes.blit_string s 0 buffer len l; aux next (len + l) end in aux stream 0 let rec write_stream_raw out_ch stream = Ocsigen_stream.next stream >>= fun e -> match e with | Ocsigen_stream.Finished _ -> Lwt.return () | Ocsigen_stream.Cont (s, next) -> Lwt_io.write out_ch s >>= fun () -> write_stream_raw out_ch next (*XXX We should check the length of the stream: - do not send more than expected - abort the connection before the right length is emitted so that the client can know something wrong happened *) let write_stream ?(chunked=false) out_ch stream = let stream = Ocsigen_stream.get stream in if chunked then write_stream_chunked out_ch stream else write_stream_raw out_ch stream module H = Ocsigen_http_frame.Http_header let set_result_observer, observe_result = let observer = ref (fun _ _ -> Lwt.return ()) in ((fun f -> let o = !observer in observer := (fun a b -> o a b >>= fun () -> f a b)), (fun a b -> !observer a b)) let send_100_continue slot = wait_previous_senders slot >>= fun () -> let out_ch = slot.sl_chan in let hh = Framepp.string_of_header { H.mode = H.Answer 100; proto = H.HTTP11; headers = Http_headers.empty } in Lwt_log.ign_info ~section "writing 100-continue"; Lwt_log.ign_info ~section hh; Lwt_io.write out_ch hh (** Sends the HTTP frame. * The headers are merged with those of the sender, the priority * being given to the newly defined header in case of conflict. * code is the code of the http answer * keep_alive is a boolean value that set the field Connection *) let send ?reopen slot ~clientproto ?mode ?proto ?keep_alive (* now (06/02/2008) used only to request keep-alive while used as client *) ~head (* send only the header *) ~sender res = let send_aux ~mode hds = Lwt.catch (fun () -> (* [slot] is here for pipelining: we must wait before sending the page, because the previous one may not be sent. *) wait_previous_senders slot >>= fun () -> let out_ch = slot.sl_chan in let empty_content = match mode with | H.Nofirstline -> false | H.Answer code -> code_without_message_body code | H.Query _ -> false in let chunked = (Result.content_length res) = None && clientproto <> Ocsigen_http_frame.Http_header.HTTP10 && not empty_content && not head in (* if HTTP/1.0 we do not use chunked encoding even if the client tells that it supports it, because it may be an HTTP/1.0 proxy that transmits the header by mistake. In that case, we close the connection after the answer. *) let with_default v default = match v with None -> default | Some v -> v in (*XXX Make sure that there is no way to put wrong headers *) (*VVV and that all required headers are here ... *) let hds = Http_headers.with_defaults hds sender.s_headers in let hds = Http_headers.replace_opt Http_headers.transfer_encoding (if chunked then Some "chunked" else None) hds in let hds = Http_headers.replace_opt Http_headers.content_length (match Result.content_length res with | None -> None | Some l -> Some (Int64.to_string l)) hds in (* 06/02/2008 We decided that we won't include a connection header at all for answers. If HTTP/1.0, we always close (which is the default). If HTTP/1.1, we always keep alive, but if the client asked to close. The default is to keep alive. But the client will close. *) let hds = match keep_alive with | None -> hds | Some ka -> hds < Lwt.catch (fun () -> let hh = Framepp.string_of_header hd in Lwt_log.ign_info_f ~section "writing header\n%s" hh; observe_result hd hh >>= fun () -> Lwt_io.write out_ch hh >>= fun () -> (if reopen <> None then (* If we want to give a possibility to reopen if it fails, we must detect the failure before beginning to read the stream *) Lwt_io.flush out_ch else Lwt.return ()) ) (fun e -> (* *** If we are doing a request, we may want to retry once (in the case when we reuse an old connection) *** *) match reopen with | None -> Lwt.fail e | Some reopen -> match convert_io_error e with | Keepalive_timeout | Timeout | Connection_closed | Unix.Unix_error (Unix.EBADF,_ ,_) | Lost_connection _ -> reopen () >>= fun () -> Lwt.fail e | _ -> Lwt_log.ign_warning ~section ~exn:e "reopening after exception (Is that right?) Please report this error."; ignore (reopen ()); Lwt.fail e ) ) >>= fun () -> (if empty_content || head then begin Lwt.return () end else begin Lwt_log.ign_info ~section "writing body"; write_stream ~chunked out_ch (fst (Result.stream res)) end) >>= fun () -> Lwt_io.flush out_ch (* Vincent: I add this otherwise HEAD answers are not flushed by the reverse proxy *) >>= fun () -> Ocsigen_stream.finalize (fst (Result.stream res)) `Success ) (fun e -> Ocsigen_stream.finalize (fst (Result.stream res)) `Failure >>= fun () -> Lwt.fail e ) in (*XXX Maybe we can compute this only at most once a second*) (* Add options specific to the page. *) let date = gmtdate (Unix.time ()) in let headers = (Result.headers res) < None (* We do not put last modified for dynamically generated pages, otherwise it is not possible to cache them. Without Last-Modified, ETag is taken into account by proxies/browsers *) | Some l -> Some (gmtdate l)) in let mode = match mode with | None -> Http_header.Answer (Result.code res) | Some m -> m in let headers = match mode with | H.Query _ -> headers (* We do not put date in headers for queries. cf bug #134 in trac "the header "Date" is sometime used to compute the signature of the REST request, and it's value may change between the time you compute the signature and the time the request is actually performed." RFC 2616 says: "Clients SHOULD only send a Date header field in messages that include an entity-body, as in the case of the PUT and POST requests, and even then it is optional. A client without a clock MUST NOT send a Date header field in a request." *) (*VVV What about Nofirstline? *) | _ -> headers << (Http_headers.date, date) in let mkcook path exp name c secure = Format.sprintf "%s=%s%s%s" name c (*VVV encode = true? *) ("; path=/" ^ Url.string_of_url_path ~encode:true path) (if secure && slot.sl_ssl then "; secure" else "")^ (match exp with | Some s -> "; expires=" ^ Netdate.format "%a, %d-%b-%Y %H:%M:%S GMT" (Netdate.create s) | None -> "") in let mkcookl path t hds = CookiesTable.fold (fun name c h -> let exp, v, secure = match c with | Ocsigen_cookies.OUnset -> (Some 0., "", false) | Ocsigen_cookies.OSet (t, v, secure) -> (t, v, secure) in Http_headers.add Http_headers.set_cookie (mkcook path exp name v secure) h) t hds in let headers = Cookies.fold mkcookl (Result.cookies res) headers < None | Some l -> Some (Format.sprintf "\"%s\"" l)) (*XXX Is it the right place to perform quoting?*) < None | Some s -> if String.length s >= 4 then match String.sub s 0 4, Result.charset res with | "text", Some "" -> Some s | "text", Some c -> Some (Format.sprintf "%s; charset=%s" s c) | _ -> match String.sub s (String.length s - 4) 4, Result.charset res with | ("+xml"|"/xml"), Some "" -> Some s | ("+xml"|"/xml"), Some c -> Some (Format.sprintf "%s; charset=%s" s c) | _ -> Result.content_type res else (Result.content_type res) ) in send_aux ~mode headers ocsigenserver-2.16.0/src/http/ocsigen_http_com.mli000066400000000000000000000067571357715257700223150ustar00rootroot00000000000000(* Ocsigen * http://www.ocsigen.org * ocsigen_http_com.ml Copyright (C) 2005 * Denis Berthod, Vincent Balat, Jérôme Vouillon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) (** Sending and receiving HTTP frames *) (* Who can raise the exceptions: R: receiver r: receiver stream S: sender *) (** The other side has cleanly closed the connection after a message *) exception Connection_closed (* R *) (** The connection has been unexpectedly broken *) exception Lost_connection of exn (* RrS *) (** No activity on the other side *) exception Timeout (* RrS *) exception Keepalive_timeout (* R *) (** Connection killed *) exception Aborted (* RrS *) type mode = Answer | Query | Nofirstline type connection val create_receiver : int -> mode -> Lwt_ssl.socket -> connection val lock_receiver : connection -> unit Lwt.t val unlock_receiver : connection -> unit val wakeup_next_request : connection -> unit val block_next_request : connection -> unit Lwt.t val get_http_frame : ?head:bool -> connection -> Ocsigen_http_frame.t Lwt.t val connection_id : connection -> int val connection_fd : connection -> Lwt_ssl.socket (** [closed conn] is a thread waking up when the connection is closed *) val closed : connection -> unit Lwt.t (****) type slot val start_processing : connection -> (slot -> unit Lwt.t) -> unit val wait_all_senders : connection -> unit Lwt.t (****) (** This function may return any I/O error from the channel, or a interrupted stream exception. *) val write_stream : ?chunked:bool -> Lwt_io.output_channel -> string Ocsigen_stream.t -> unit Lwt.t (****) type sender_type val create_sender : ?server_name:string -> ?headers:Http_headers.t -> ?proto:Ocsigen_http_frame.Http_header.proto -> unit -> sender_type (** Sender with only the server name, and HTTP/1.1 *) val default_sender : sender_type (** send an HTTP/1.1 100 Continue message *) val send_100_continue : slot -> unit Lwt.t (** send an HTTP message. [send] may also fail with [Interrupted_stream] exception if the input stream is interrupted. *) val send : ?reopen:(unit -> unit Lwt.t) -> slot -> clientproto:Ocsigen_http_frame.Http_header.proto -> ?mode:Ocsigen_http_frame.Http_header.http_mode -> ?proto:Ocsigen_http_frame.Http_header.proto -> ?keep_alive:bool -> head:bool -> sender:sender_type -> Ocsigen_http_frame.result -> unit Lwt.t val abort : connection -> unit (** Use this function to make an action just before sending the result (for example observe the headers that will be sent). The parameter is a function taking the set of headers twice, first as [Ocsigen_http_frame.Http_headers.http_header], second as a [string]. *) val set_result_observer : (Ocsigen_http_frame.Http_header.http_header -> string -> unit Lwt.t) -> unit (**/**) val gmtdate: float -> string ocsigenserver-2.16.0/src/http/ocsigen_http_frame.ml000066400000000000000000000242371357715257700224510ustar00rootroot00000000000000(* Ocsigen * http://www.ocsigen.org * ocsigen_http_frame.ml Copyright (C) 2005 * Denis Berthod, Vincent Balat, Jérôme Vouillon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) open Ocsigen_lib (** this set of modules describes the http protocol and the operation on this protocol*) (** this signature provides a template to describe the content of a http frame *) open Ocsigen_stream open Ocsigen_cookies let section = Lwt_log.Section.make "ocsigen:http:frame" type etag = string (* The following cookie function is not in Ocsigen_cookies because ri is not used client side *) (* [compute_new_ri_cookies now path ri_cookies cookies_to_set] adds the cookies from [cookies_to_set] to [ri_cookies], as if the cookies had been send to the browser and the browser was doing a new request to the url [path]. Only the cookies that match [path] (current path) are added. *) let compute_new_ri_cookies now ripath ricookies cookies_set_by_page = let prefix path p = Url.is_prefix_skip_end_slash (Url.remove_slash_at_beginning path) (Url.remove_slash_at_beginning p) in Cookies.fold (fun path ct t -> if prefix path ripath then String.Table.fold (fun n v beg -> match v with | OSet (Some ti, v, _) when ti>now -> String.Table.add n v t | OSet (None, v, _) -> String.Table.add n v t | OSet (_, _, _) | OUnset -> String.Table.remove n t ) ct t else t ) cookies_set_by_page ricookies module Result = struct (** The type of answers to send *) type result = {cookies: cookieset; (** cookies to set (with optional path) *) lastmodified: float option; (** Default: [None] *) etag: string option; code: int; (** HTTP code, if not 200 *) stream: string Ocsigen_stream.t * (string Ocsigen_stream.t -> int64 -> string Ocsigen_stream.step Lwt.t) option ; (** Default: empty stream. The second field is (optionally) the function used to skip a part of the stream, if you do not you want to use a basic reading of the stream. For example, for static files, you can optimize it by using a [seek] function. *) (* It is not a new field of the record to remember to change it if we change the stream. *) content_length: int64 option; (** [None] means Transfer-encoding: chunked *) content_type: string option; headers: Http_headers.t; (** The headers you want to add *) charset: string option; (** Default: None *) location: string option; (** Default: None *) } let cookies { cookies; _ } = cookies let lastmodified { lastmodified; _ } = lastmodified let etag { etag; _ } = etag let code { code; _ } = code let stream { stream; _ } = stream let content_length { content_length; _ } = content_length let content_type { content_type; _ } = content_type let headers { headers; _ } = headers let charset { charset; _ } = charset let location { location; _ } = location (** Default [result] to use as a base for constructing others. *) let default () = { cookies = Cookies.empty; lastmodified = None; (* No date => proxies use etag *) etag = None; code = 200; stream = (Ocsigen_stream.make (fun () -> Ocsigen_stream.empty None), None); content_length = Some 0L; content_type = None; headers= Http_headers.empty; charset= None; location= None; } let update result ?(cookies=result.cookies) ?(lastmodified=result.lastmodified) ?(etag=result.etag) ?(code=result.code) ?(stream=result.stream) ?(content_length=result.content_length) ?(content_type=result.content_type) ?(headers=result.headers) ?(charset=result.charset) ?(location=result.location) () = { cookies; lastmodified; etag; code; stream; content_length; content_type; headers; charset; location; } (** [result] for an empty page. *) let empty () = { cookies = Cookies.empty; lastmodified = None; etag = None; code = 204; (* No content *) stream = (Ocsigen_stream.make (fun () -> Ocsigen_stream.empty None), None); content_length = Some 0L; content_type = None; headers= Http_headers.empty; charset= None; location= None; } end include Result module type HTTP_CONTENT = sig (** abstract type of the content *) type t type options (** convert a content into a thread returning the default [result] for this content *) val result_of_content : ?options:options -> t -> Result.result Lwt.t (** compute etag for content *) val get_etag : ?options:options -> t -> etag option end (** this module describes the type of an http header *) module Http_header = struct (** type of the http_method *) type http_method = | GET | POST | HEAD | PUT | DELETE | TRACE | OPTIONS | CONNECT | LINK | UNLINK | PATCH (** type of ocsigen_http_frame mode. The int is the HTTP answer code *) type http_mode = | Query of (http_method * string) | Answer of int | Nofirstline type proto = HTTP10 | HTTP11 (** type of the http headers *) type http_header = { (** the mode of the header : Query or Answer *) mode:http_mode; (** protocol used for the Query or the Answer *) proto: proto; (** list of the headers options *) headers: Http_headers.t; } (* (** gets the url raise Not_found if Answer *) let get_url header = match header.mode with | Query (_, s) -> s | _ -> raise Not_found *) (** gets the firstline of the header *) let get_firstline header = header.mode (** gets the headers *) let get_headers header = header.headers (** gets the value of a given header's option *) let get_headers_value header key = Http_headers.find key header.headers (** gets all the values of a given header's option *) let get_headers_values header key = Http_headers.find_all key header.headers (** gets the value of the protocol used *) let get_proto header = header.proto (* (** gets the value of the http method used *) let get_method header = match header.mode with | Query (meth, _) -> meth | _ -> raise Not_found *) (** adds an header option in the header option list*) let add_headers header key value = { header with headers = Http_headers.add key value header.headers } end module Http_error = struct (** Exception raised on an http error. It is possible to pass the code of the error, some comment, and some headers. *) exception Http_exception of int * string option * Http_headers.t option (* this function provides the translation mecanisme between a code and * its explanation *) let expl_of_code = function | 100 -> "Continue" | 101 -> "Switching Protocol" | 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 -> "Moved Temporarily" | 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 URL Too Long" | 415 -> "Unsupported Media type" | 416 -> "Request Range Not Satisfiable" | 417 -> "Expectation Failed" | 500 -> "Internal Server Error" | 501 -> "Not Implemented" | 502 -> "Bad Gateway" | 503 -> "Service Unavailable" | 504 -> "Gateway Time-out" | 505 -> "Version Not Supported" | _ -> "Unknown Error" (*!!!*) let display_http_exception e = match e with | Http_exception (n, Some s, Some _) -> Lwt_log.ign_info_f ~section "%s: %s (with headers)" (expl_of_code n) s | Http_exception (n, Some s, None) -> Lwt_log.ign_info_f ~section "%s: %s" (expl_of_code n) s | Http_exception (n, None, _) -> Lwt_log.ign_info ~section (expl_of_code n) | _ -> raise e let string_of_http_exception e = match e with | Http_exception (n, Some s, Some _) -> Format.sprintf "Error %d, %s: %s (with headers)" n (expl_of_code n) s | Http_exception (n, Some s, None) -> Format.sprintf "Error %d, %s: %s" n (expl_of_code n) s | Http_exception (n, None, _) -> Format.sprintf "Error %d, %s" n (expl_of_code n) | _ -> raise e end (** HTTP messages *) type t = { frame_header : Http_header.http_header; frame_content : string Ocsigen_stream.t option; frame_abort : unit -> unit Lwt.t (*VVV abort looks like a hack. It has been added for the reverse proxy, to enable closing the connection if the request is cancelled ... *) } ocsigenserver-2.16.0/src/http/ocsigen_http_frame.mli000066400000000000000000000114501357715257700226130ustar00rootroot00000000000000(* Ocsigen * http://www.ocsigen.org * ocsigen_http_frame.ml Copyright (C) 2005 * Denis Berthod, Vincent Balat, Jérôme Vouillon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) open Ocsigen_lib open Ocsigen_cookies type etag = string (** [compute_new_ri_cookies now path ri_cookies cookies_to_set] adds the cookies from [cookies_to_set] to [ri_cookies], as if the cookies had been send to the browser and the browser was doing a new request to the url [path]. Only the cookies that match [path] (current path) are added. *) val compute_new_ri_cookies : float -> string list -> string String.Table.t -> cookie String.Table.t Cookies.t -> string String.Table.t module Result : sig (** The type of answers to send *) type result (** accessor for cookies of result *) val cookies : result -> Ocsigen_cookies.cookieset (** accessor for Last-Modified value of header of result *) val lastmodified : result -> float option (** accessor for ETag value of header of result *) val etag : result -> string option (** accessor for response code of result *) val code : result -> int (** accessor for content of result *) val stream : result -> string Ocsigen_stream.t * (string Ocsigen_stream.t -> int64 -> string Ocsigen_stream.step Lwt.t) option (** accessor for Content-Length value of header of result *) val content_length : result -> int64 option (** accessor for Content-Type value of header of result *) val content_type : result -> string option (** accessor for HTTP header of result *) val headers : result -> Http_headers.t (** accessor for charset of result *) val charset : result -> string option (** accessor for location of result *) val location : result -> string option (** Default [result] to use as a base for constructing others. *) val default : unit -> result (** Update [result] before sending. If argument is unspecified, this function use old value of result. *) val update : result -> ?cookies:Ocsigen_cookies.cookieset -> ?lastmodified:float option -> ?etag:string option -> ?code:int -> ?stream:string Ocsigen_stream.t * (string Ocsigen_stream.t -> int64 -> string Ocsigen_stream.step Lwt.t) option -> ?content_length:int64 option -> ?content_type:string option -> ?headers:Http_headers.t -> ?charset:string option -> ?location:string option -> unit -> result (** [result] for an empty page. *) val empty : unit -> result end include (module type of Result with type result = Result.result) module type HTTP_CONTENT = sig type t type options val result_of_content : ?options:options -> t -> Result.result Lwt.t val get_etag : ?options:options -> t -> etag option end module Http_header : sig type http_method = GET | POST | HEAD | PUT | DELETE | TRACE | OPTIONS | CONNECT | LINK | UNLINK | PATCH type http_mode = Query of (http_method * string) | Answer of int | Nofirstline type proto = HTTP10 | HTTP11 type http_header = { mode : http_mode; proto : proto; headers : Http_headers.t; } val get_firstline : http_header -> http_mode val get_headers : http_header -> Http_headers.t val get_headers_value : http_header -> Http_headers.name -> string val get_headers_values : http_header -> Http_headers.name -> string list val get_proto : http_header -> proto val add_headers : http_header -> Http_headers.name -> string -> http_header end module Http_error : sig exception Http_exception of int * string option * Http_headers.t option val expl_of_code : int -> string val display_http_exception : exn -> unit val string_of_http_exception : exn -> string end (** The type of HTTP frames. The content may be void (no body) or a stream. While sending, a stream will be sent with chunked encoding if no content-length is supplied. abort is the function to be called if you want to cancel the stream reading (closes the connection). *) type t = { frame_header : Http_header.http_header; frame_content : string Ocsigen_stream.t option; frame_abort : unit -> unit Lwt.t } ocsigenserver-2.16.0/src/http/ocsigen_senders.ml000066400000000000000000000427751357715257700217720ustar00rootroot00000000000000(* Ocsigen * http://www.ocsigen.org * sender_helpers.ml Copyright (C) 2005 Denis Berthod * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) (** This module provides predefined "senders" for usual types of pages to be sent by the server: xhtml, files, ... *) open Ocsigen_lib open Ocsigen_http_frame open Ocsigen_http_com open Lwt open Ocsigen_stream let section = Lwt_log.Section.make "ocsigen:http:sender" (*****************************************************************************) (** this module instantiate the HTTP_CONTENT signature for an Html content*) module Make_XML_Content(Xml : Xml_sigs.Iterable) (Typed_xml : Xml_sigs.Typed_xml with module Xml := Xml) = struct module Htmlprinter = Xml_print.Make_typed_fmt(Xml)(Typed_xml) type t = Typed_xml.doc type options = Http_headers.accept Lazy.t let get_etag_aux x = None let get_etag ?options c = None let choose_content_type accepted alt default = match accepted, alt with | None, _ | _, [] -> default | Some accepted, alt -> try List.find (fun content_type -> List.exists (function | ((Some a, Some b),_,_) -> a^"/"^b = content_type | _ -> false) (Lazy.force accepted)) (default :: alt) with Not_found -> default let result_of_content ?options c = let content_type = choose_content_type options Typed_xml.Info.alternative_content_types Typed_xml.Info.content_type in let encode x = fst (Xml_print.Utf8.normalize_html x) in let x = Ocsigen_stream.of_string (Format.asprintf "%a" (Htmlprinter.pp ~encode ~advert ()) c) in let default_result = Result.default () in Lwt.return (Result.update default_result ~content_length:None ~content_type:(Some content_type) ~etag:(get_etag c) ~charset:(Some "utf-8") ~headers:Http_headers.dyn_headers ~stream:(x, None) ()) end module Html_content = Make_XML_Content(Tyxml.Xml)(Tyxml.Html) (*****************************************************************************) module Text_content = struct type t = string (* content *) * string (* content-type *) type options = unit let get_etag ?options (x, _) = None (* Some (Digest.to_hex (Digest.string x)) *) (* We do not add etags here because the content is probably generated and there are problemes with etags and POST requests: when doing POST request with etags, the semantinc is not to do side effects on the server when the etag match and respond with code 412 ( precondition failed ). Since the etag is calculated from the content of the answer, we cannot enforce this semantic correctly, so it is better not to send etags with POST requests. Here, we cannot know whether the request was in POST or GET, so the easiest way to fix that is to never send etags *) let result_of_content ?(options = ()) ((c, ct) as content) = let md5 = get_etag content in let default_result = Result.default () in Lwt.return (Result.update default_result ~content_length:(Some (Int64.of_int (String.length c))) ~etag:md5 ~content_type:(Some ct) ~headers:Http_headers.dyn_headers ~stream: (Ocsigen_stream.make (fun () -> Ocsigen_stream.cont c (fun () -> Ocsigen_stream.empty None)), None) ()) end (*****************************************************************************) module Stream_content = (* Used to send data from a stream *) struct type t = string Ocsigen_stream.t type options = unit let get_etag ?options c = None let result_of_content ?(options = ()) c = let default_result = Result.default () in Lwt.return (Result.update default_result ~content_length:None ~headers:Http_headers.dyn_headers ~stream:(c, None) ()) end (*****************************************************************************) module Streamlist_content = (* Used to send data from streams *) struct type t = (unit -> string Ocsigen_stream.t Lwt.t) list * string (* content-type *) type options = unit let get_etag ?options c = None let result_of_content ?(options = ()) (c, ct) = let finalizer = ref (fun _ -> Lwt.return ()) in let finalize status = let f = !finalizer in finalizer := (fun _ -> Lwt.return ()); f status in let rec next stream l = Lwt.try_bind (fun () -> Ocsigen_stream.next stream) (fun s -> match s with Ocsigen_stream.Finished None -> finalize `Success >>= fun () -> next_stream l | Ocsigen_stream.Finished (Some stream) -> next stream l | Ocsigen_stream.Cont (v, stream) -> Ocsigen_stream.cont v (fun () -> next stream l)) (function Interrupted e | e -> (*XXX string_of_exn should know how to print "Interrupted _" exceptions*) exnhandler e l) and next_stream l = match l with [] -> Ocsigen_stream.empty None | f :: l -> Lwt.try_bind f (fun stream -> finalizer := (fun status -> Ocsigen_stream.finalize stream status); next (Ocsigen_stream.get stream) l) (fun e -> exnhandler e l) and exnhandler e l = Lwt_log.ign_warning ~section ~exn:e "Error while reading stream list"; finalize `Failure >>= fun () -> next_stream l in let default_result = Result.default () in Lwt.return (Result.update default_result ~content_length:None ~etag:(get_etag c) ~stream: (Ocsigen_stream.make ~finalize (fun _ -> next_stream c), None) ~headers:(Http_headers.dyn_headers) ~content_type:(Some ct) ()) end (*****************************************************************************) module Empty_content = struct type t = unit type options = unit let get_etag ?options c = None let result_of_content ?(options = ()) c = Lwt.return (Result.empty ()) end (*****************************************************************************) (* Files *) (** this module instantiate the HTTP_CONTENT signature for files *) module File_content = struct type t = string (* nom du fichier *) * Ocsigen_charset_mime.charset_assoc * Ocsigen_charset_mime.mime_assoc type options = unit let read_file ?buffer_size fd = let buffer_size = match buffer_size with | None -> Ocsigen_config.get_filebuffersize () | Some s -> s in Lwt_log.ign_info ~section "start reading file (file opened)"; let buf = Bytes.create buffer_size in let rec read_aux () = Lwt_unix.read fd buf 0 buffer_size >>= fun read -> if read = 0 then Ocsigen_stream.empty None else begin if read = buffer_size then Ocsigen_stream.cont (Bytes.to_string buf) read_aux (* copying :( *) else Ocsigen_stream.cont (Bytes.sub_string buf 0 read) read_aux end in read_aux let get_etag_aux st = Some (Printf.sprintf "%Lx-%x-%f" st.Unix.LargeFile.st_size st.Unix.LargeFile.st_ino st.Unix.LargeFile.st_mtime) let get_etag ?options (f, _, _) = let st = Unix.LargeFile.stat f in get_etag_aux st let skip fd stream k = try ignore (Unix.LargeFile.lseek (Lwt_unix.unix_file_descr fd) k Unix.SEEK_CUR); Ocsigen_stream.next (Ocsigen_stream.get stream) with e -> Lwt.fail e let result_of_content ?options (c, charset_assoc, mime_assoc) = (* open the file *) try let fdu = Unix.openfile c [Unix.O_RDONLY;Unix.O_NONBLOCK] 0o666 in let fd = Lwt_unix.of_unix_file_descr fdu in try let st = Unix.LargeFile.fstat fdu in let etag = get_etag_aux st in let buffer_size = if st.Unix.LargeFile.st_size <= Int64.of_int (Ocsigen_config.get_filebuffersize ()) then Some (Int64.to_int st.Unix.LargeFile.st_size) else None in let stream = read_file ?buffer_size fd in let default_result = Result.default () in Lwt.return (Result.update default_result ~content_length:(Some st.Unix.LargeFile.st_size) ~content_type: (Some (Ocsigen_charset_mime.find_mime c mime_assoc)) ~charset: (Some (Ocsigen_charset_mime.find_charset c charset_assoc)) ~lastmodified:(Some st.Unix.LargeFile.st_mtime) ~etag:etag ~stream: (Ocsigen_stream.make ~finalize: (fun _ -> Lwt_log.ign_info ~section "closing file"; Lwt_unix.close fd) stream, Some (skip fd)) ()) with e -> Lwt_unix.close fd >>= fun () -> raise e with e -> Lwt_log.ign_info ~section ~exn:e "Exc"; fail e end (*****************************************************************************) (* directory listing - by Gabriel Kerneis *) (** this module instantiate the HTTP_CONTENT signature for directories *) module Directory_content = struct type t = string (* dir name *) * string list (* corresponding URL path *) type options = unit let get_etag_aux st = Some (Printf.sprintf "%Lx-%x-%f" st.Unix.LargeFile.st_size st.Unix.LargeFile.st_ino st.Unix.LargeFile.st_mtime) let get_etag ?options (f, _) = let st = Unix.LargeFile.stat f in get_etag_aux st let date fl = let t = Unix.gmtime fl in Printf.sprintf "%02d-%02d-%04d %02d:%02d:%02d" t.Unix.tm_mday (t.Unix.tm_mon + 1) (1900 + t.Unix.tm_year) t.Unix.tm_hour t.Unix.tm_min t.Unix.tm_sec let image_found fich = if fich="README" || fich="README.Debian" then "/ocsigenstuff/readme.png" else let reg=Netstring_pcre.regexp "([^//.]*)(.*)" in match Netstring_pcre.global_replace reg "$2" fich with | ".jpeg" | ".jpg" | ".gif" | ".tif" | ".png" -> "/ocsigenstuff/image.png" | ".ps" -> "/ocsigenstuff/postscript.png" | ".pdf" -> "/ocsigenstuff/pdf.png" | ".html" | ".htm" | ".php" -> "/ocsigenstuff/html.png" | ".mp3" | ".wma" -> "/ocsigenstuff/sound.png" | ".c" -> "/ocsigenstuff/source_c.png" | ".java" -> "/ocsigenstuff/source_java.png" | ".pl" -> "/ocsigenstuff/source_pl.png" | ".py" -> "/ocsigenstuff/source_py.png" | ".iso" | ".mds" | ".mdf" | ".cue" | ".nrg" | ".cdd" -> "/ocsigenstuff/cdimage.png" | ".deb" -> "/ocsigenstuff/deb.png" | ".dvi" -> "/ocsigenstuff/dvi.png" | ".rpm" -> "/ocsigenstuff/rpm.png" | ".tar" | ".rar" -> "/ocsigenstuff/tar.png" | ".gz" | ".tar.gz" | ".tgz" | ".zip" | ".jar" -> "/ocsigenstuff/tgz.png" | ".tex" -> "/ocsigenstuff/tex.png" | ".avi" | ".mov" -> "/ocsigenstuff/video.png" | ".txt" -> "/ocsigenstuff/txt.png" | _ -> "/ocsigenstuff/unknown.png" (* An html row for a file in the directory listing *) let file_row name icon stat = Printf.sprintf " \"\" %s %Ld %s " icon (Netencoding.Url.encode ~plus:false name) name stat.Unix.LargeFile.st_size (date stat.Unix.LargeFile.st_mtime) let directory filename = let dir = Unix.opendir filename in let rec aux d = try let f = Unix.readdir dir in try let stat = Unix.LargeFile.stat (filename^f) in if stat.Unix.LargeFile.st_kind = Unix.S_DIR && f <> "." && f <> ".." then (`Dir, f, file_row f "/ocsigenstuff/folder_open.png" stat) :: aux d else if stat.Unix.LargeFile.st_kind = Unix.S_REG && f.[(String.length f) - 1] <> '~' then (`Reg, f, file_row f (image_found f) stat) :: aux d else aux d with _ (* Unix.stat can fail for a lot of reasons *) -> aux d with End_of_file -> Unix.closedir d;[] in let trie li = List.sort (fun (a1, b1, _) (a2, b2, _) -> match a1, a2 with | `Dir, `Dir -> if b1 0 | _, `Dir -> 1 | _, _-> if b1 "" | (_, _, i)::l -> i^(aux2 l) in aux2 (trie (aux dir)) let result_of_content ?(options = ()) (filename, path) = let stat = Unix.LargeFile.stat filename in let rec back = function | [] | [""] -> assert false | [_] | [_ ; ""] -> [] | i::j -> i :: (back j) in let parent = if path = [] || path = [""] then None else Some ("/"^Url.string_of_url_path ~encode:true (back path)) in let before = let st = Url.string_of_url_path ~encode:false path in "\n\ \n\ \n\ Listing Directory: "^st^"\n\n\

    "^st^"

    \n\ \n\ \ \n" and back = match parent with | None -> "" | Some parent -> "\n\ \n\ \n\ \n\ \n\ \n" and after= "
    NameSizeLast modified
    \"\"Parent Directory"^(Int64.to_string stat.Unix.LargeFile.st_size)^""^(date stat.Unix.LargeFile.st_mtime)^"
    \

    Ocsigen Webserver

    \ " in let c = before^back^(directory filename)^after in let etag = get_etag_aux stat in Text_content.result_of_content (c, "text/html") >>= fun r -> Lwt.return (Result.update r ~lastmodified:(Some stat.Unix.LargeFile.st_mtime) ~etag:etag ~charset:(Some "utf-8") ()) end (*****************************************************************************) module Error_content = (** sends an error page that fit the error number *) struct type t = int option * exn option * Ocsigen_cookies.cookieset type options = unit let get_etag ?options c = None let error_page s msg c = Tyxml.Html.html (Tyxml.Html.head (Tyxml.Html.title (Tyxml.Html.pcdata s)) []) (Tyxml.Html.body (Tyxml.Html.h1 [Tyxml.Html.pcdata msg]:: Tyxml.Html.p [Tyxml.Html.pcdata s]:: c) ) let result_of_content ?(options = ()) (code, exn, cookies_to_set) = let code = match code with | None -> 500 | Some c -> c in let (error_code, error_msg, headers) = match exn with | Some (Http_error.Http_exception (errcode, msgs, h) as e) -> let msg = Http_error.string_of_http_exception e in let headers = match h with | Some h -> h | None -> Http_headers.dyn_headers in (errcode, msg, headers) | _ -> let error_mes = Http_error.expl_of_code code in (code, error_mes, Http_headers.empty) in let headers = (* puts dynamic headers *) let (<<) h (n, v) = Http_headers.replace n v h in headers << (Http_headers.cache_control, "no-cache") << (Http_headers.expires, "0") in let str_code = string_of_int error_code in let err_page = match exn with | Some exn when Ocsigen_config.get_debugmode () -> error_page ("Error "^str_code) error_msg [Tyxml.Html.p [Tyxml.Html.pcdata (Printexc.to_string exn); Tyxml.Html.br (); Tyxml.Html.em [Tyxml.Html.pcdata "(Ocsigen running in debug mode)"] ]] | _ -> error_page ("Error "^str_code) error_msg [] in Html_content.result_of_content err_page >>= fun r -> Lwt.return (Result.update r ~cookies:cookies_to_set ~code:error_code ~charset:(Some "utf-8") ~headers:headers ()) end let send_error ?code ?exn slot ~clientproto ?mode ?proto ?(cookies = Ocsigen_cookies.Cookies.empty) ~head ~sender () = Error_content.result_of_content (code, exn, cookies) >>= fun r -> send slot ~clientproto ?mode ?proto ~head ~sender r ocsigenserver-2.16.0/src/http/ocsigen_senders.mli000066400000000000000000000047651357715257700221400ustar00rootroot00000000000000(* Ocsigen * http://www.ocsigen.org * sender_helpers.ml Copyright (C) 2005 Denis Berthod * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) (** Functions to create results for various kinds of documents *) module File_content : Ocsigen_http_frame.HTTP_CONTENT with type t = string * Ocsigen_charset_mime.charset_assoc * Ocsigen_charset_mime.mime_assoc module Html_content : Ocsigen_http_frame.HTTP_CONTENT with type t = Tyxml.Html.doc module Make_XML_Content(Xml : Xml_sigs.Iterable) (Typed_xml : Xml_sigs.Typed_xml with module Xml := Xml) : Ocsigen_http_frame.HTTP_CONTENT with type t = Typed_xml.doc and type options = Http_headers.accept Lazy.t (** content * content-type *) module Text_content : Ocsigen_http_frame.HTTP_CONTENT with type t = string * string module Stream_content : Ocsigen_http_frame.HTTP_CONTENT with type t = string Ocsigen_stream.t (** streams and content-type *) module Streamlist_content : Ocsigen_http_frame.HTTP_CONTENT with type t = (unit -> string Ocsigen_stream.t Lwt.t) list * string module Empty_content : Ocsigen_http_frame.HTTP_CONTENT with type t = unit (** directory name and corresponding URL path *) module Directory_content : Ocsigen_http_frame.HTTP_CONTENT with type t = string * string list (** error code and/or exception *) module Error_content : Ocsigen_http_frame.HTTP_CONTENT with type t = int option * exn option * Ocsigen_cookies.cookieset (** Sending an error page *) val send_error : ?code:int -> ?exn:exn -> Ocsigen_http_com.slot -> clientproto:Ocsigen_http_frame.Http_header.proto -> ?mode:Ocsigen_http_frame.Http_header.http_mode -> ?proto:Ocsigen_http_frame.Http_header.proto -> ?cookies:Ocsigen_cookies.cookieset -> head:bool -> sender:Ocsigen_http_com.sender_type -> unit -> unit Lwt.t ocsigenserver-2.16.0/src/http/test_parser.ml000066400000000000000000000023111357715257700211310ustar00rootroot00000000000000(* Ocsigen * test_parser.ml Copyright (C) 2005 Denis Berthod * Laboratoire PPS - CNRS Université Paris Diderot * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) let parse_file f = let input = open_in f in let lexbuf = Lexing.from_channel input in try Http_parser.header Http_lexer.token lexbuf with Parsing.Parse_error -> failwith ("erreur vers "^ (Lexing.lexeme lexbuf)) |e -> Ocsigen_http_frame.Http_error.display_http_exception e;failwith "erreur" let _ = parse_file Sys.argv.(1) ocsigenserver-2.16.0/src/http/test_pp.ml000066400000000000000000000010051357715257700202530ustar00rootroot00000000000000open Ocsigen_http_frame open Framepp module H = Http_header module C = struct type t = string let string_of_content c = c let content_of_string s = s end module Http = FHttp_frame (C) module PP = Fframepp(C) let hd1= { H.mode = H.Query; H.meth = Some H.GET; H.url = Some "pop"; H.code = None; H.proto = "HTML/1.0"; headers = [] } let frame = {Http.header = hd1; Http.content = Some (C.content_of_string "Bonjour")} let _ = print_endline (PP.string_of_http_frame frame) ocsigenserver-2.16.0/src/server/000077500000000000000000000000001357715257700165765ustar00rootroot00000000000000ocsigenserver-2.16.0/src/server/.depend000066400000000000000000000134161357715257700200430ustar00rootroot00000000000000ocsigen_command.cmo : ../baselib/ocsigen_messages.cmi ocsigen_command.cmi ocsigen_command.cmx : ../baselib/ocsigen_messages.cmx ocsigen_command.cmi ocsigen_command.cmi : ocsigen_common_server.cmi : ocsigen_request_info.cmi \ ../http/ocsigen_http_frame.cmi ../http/ocsigen_cookies.cmi ocsigen_extensions.cmo : ocsigen_request_info.cmi \ ../baselib/ocsigen_loader.cmi ../baselib/ocsigen_lib.cmi \ ../http/ocsigen_http_frame.cmi ../http/ocsigen_http_com.cmi \ ../http/ocsigen_cookies.cmi ../baselib/ocsigen_config.cmi \ ocsigen_command.cmi ../http/ocsigen_charset_mime.cmi \ ocsigen_extensions.cmi ocsigen_extensions.cmx : ocsigen_request_info.cmx \ ../baselib/ocsigen_loader.cmx ../baselib/ocsigen_lib.cmx \ ../http/ocsigen_http_frame.cmx ../http/ocsigen_http_com.cmx \ ../http/ocsigen_cookies.cmx ../baselib/ocsigen_config.cmx \ ocsigen_command.cmx ../http/ocsigen_charset_mime.cmx \ ocsigen_extensions.cmi ocsigen_extensions.cmi : ocsigen_request_info.cmi ../baselib/ocsigen_lib.cmi \ ../http/ocsigen_http_frame.cmi ../http/ocsigen_http_com.cmi \ ../http/ocsigen_cookies.cmi ocsigen_command.cmi \ ../http/ocsigen_charset_mime.cmi ocsigen_http_client.cmo : ../baselib/ocsigen_stream.cmi \ ../http/ocsigen_senders.cmi ../baselib/ocsigen_lib.cmi \ ../http/ocsigen_http_frame.cmi ../http/ocsigen_http_com.cmi \ ../http/ocsigen_headers.cmi ocsigen_extensions.cmi \ ../baselib/ocsigen_config.cmi ../http/http_headers.cmi \ ocsigen_http_client.cmi ocsigen_http_client.cmx : ../baselib/ocsigen_stream.cmx \ ../http/ocsigen_senders.cmx ../baselib/ocsigen_lib.cmx \ ../http/ocsigen_http_frame.cmx ../http/ocsigen_http_com.cmx \ ../http/ocsigen_headers.cmx ocsigen_extensions.cmx \ ../baselib/ocsigen_config.cmx ../http/http_headers.cmx \ ocsigen_http_client.cmi ocsigen_http_client.cmi : ../baselib/ocsigen_stream.cmi \ ../http/ocsigen_http_frame.cmi ocsigen_extensions.cmi \ ../http/http_headers.cmi ocsigen_local_files.cmo : ../http/ocsigen_senders.cmi \ ocsigen_request_info.cmi ocsigen_extensions.cmi \ ../baselib/ocsigen_config.cmi ocsigen_local_files.cmi ocsigen_local_files.cmx : ../http/ocsigen_senders.cmx \ ocsigen_request_info.cmx ocsigen_extensions.cmx \ ../baselib/ocsigen_config.cmx ocsigen_local_files.cmi ocsigen_local_files.cmi : ../http/ocsigen_http_frame.cmi \ ocsigen_extensions.cmi ocsigen_parseconfig.cmo : ocsigen_socket.cmi ../baselib/ocsigen_loader.cmi \ ../baselib/ocsigen_lib.cmi ocsigen_extensions.cmi \ ../baselib/ocsigen_config.cmi ../http/ocsigen_charset_mime.cmi \ ocsigen_parseconfig.cmi ocsigen_parseconfig.cmx : ocsigen_socket.cmx ../baselib/ocsigen_loader.cmx \ ../baselib/ocsigen_lib.cmx ocsigen_extensions.cmx \ ../baselib/ocsigen_config.cmx ../http/ocsigen_charset_mime.cmx \ ocsigen_parseconfig.cmi ocsigen_parseconfig.cmi : ocsigen_socket.cmi ocsigen_extensions.cmi ocsigen_range.cmo : ../baselib/ocsigen_stream.cmi ocsigen_request_info.cmi \ ../baselib/ocsigen_lib.cmi ../http/ocsigen_http_frame.cmi \ ocsigen_extensions.cmi ../baselib/ocsigen_config.cmi \ ../http/http_headers.cmi ocsigen_range.cmi ocsigen_range.cmx : ../baselib/ocsigen_stream.cmx ocsigen_request_info.cmx \ ../baselib/ocsigen_lib.cmx ../http/ocsigen_http_frame.cmx \ ocsigen_extensions.cmx ../baselib/ocsigen_config.cmx \ ../http/http_headers.cmx ocsigen_range.cmi ocsigen_range.cmi : ocsigen_request_info.cmi ../http/ocsigen_http_frame.cmi ocsigen_request_info.cmo : ../baselib/polytables.cmi \ ../baselib/ocsigen_lib.cmi ../http/ocsigen_http_frame.cmi \ ../http/ocsigen_http_com.cmi ../http/ocsigen_cookies.cmi \ ocsigen_request_info.cmi ocsigen_request_info.cmx : ../baselib/polytables.cmx \ ../baselib/ocsigen_lib.cmx ../http/ocsigen_http_frame.cmx \ ../http/ocsigen_http_com.cmx ../http/ocsigen_cookies.cmx \ ocsigen_request_info.cmi ocsigen_request_info.cmi : ../baselib/polytables.cmi \ ../baselib/ocsigen_lib.cmi ../http/ocsigen_http_frame.cmi \ ../http/ocsigen_http_com.cmi ../http/ocsigen_cookies.cmi \ ../http/http_headers.cmi ocsigen_server.cmo : ../baselib/polytables.cmi ../baselib/ocsigen_stream.cmi \ ocsigen_socket.cmi ../http/ocsigen_senders.cmi ocsigen_request_info.cmi \ ocsigen_range.cmi ocsigen_parseconfig.cmi ../baselib/ocsigen_messages.cmi \ ../baselib/ocsigen_loader.cmi ../baselib/ocsigen_lib.cmi \ ../http/ocsigen_http_frame.cmi ../http/ocsigen_http_com.cmi \ ocsigen_http_client.cmi ../http/ocsigen_headers.cmi \ ocsigen_extensions.cmi ../http/ocsigen_cookies.cmi \ ../baselib/ocsigen_config.cmi ../baselib/ocsigen_commandline.cmo \ ocsigen_command.cmi ../baselib/ocsigen_cache.cmi ../http/multipart.cmi \ ../http/http_headers.cmi ../baselib/dynlink_wrapper.cmo \ ocsigen_server.cmi ocsigen_server.cmx : ../baselib/polytables.cmx ../baselib/ocsigen_stream.cmx \ ocsigen_socket.cmx ../http/ocsigen_senders.cmx ocsigen_request_info.cmx \ ocsigen_range.cmx ocsigen_parseconfig.cmx ../baselib/ocsigen_messages.cmx \ ../baselib/ocsigen_loader.cmx ../baselib/ocsigen_lib.cmx \ ../http/ocsigen_http_frame.cmx ../http/ocsigen_http_com.cmx \ ocsigen_http_client.cmx ../http/ocsigen_headers.cmx \ ocsigen_extensions.cmx ../http/ocsigen_cookies.cmx \ ../baselib/ocsigen_config.cmx ../baselib/ocsigen_commandline.cmx \ ocsigen_command.cmx ../baselib/ocsigen_cache.cmx ../http/multipart.cmx \ ../http/http_headers.cmx ../baselib/dynlink_wrapper.cmx \ ocsigen_server.cmi ocsigen_server.cmi : ocsigen_socket.cmo : ../baselib/ocsigen_lib_base.cmi \ ../baselib/ocsigen_lib.cmi ocsigen_socket.cmi ocsigen_socket.cmx : ../baselib/ocsigen_lib_base.cmx \ ../baselib/ocsigen_lib.cmx ocsigen_socket.cmi ocsigen_socket.cmi : server_main.cmo : ocsigen_server.cmi server_main.cmx : ocsigen_server.cmx ocsigenserver-2.16.0/src/server/Makefile000066400000000000000000000043271357715257700202440ustar00rootroot00000000000000include ../../Makefile.config all: byte opt PACKAGE := ${SERVER_PACKAGE} ## See ../../Makefile.options LIBS := -I ../baselib -I ../http ${addprefix -package ,${PACKAGE}} -I . OCAMLC := $(OCAMLFIND) ocamlc ${BYTEDBG} ${THREAD} OCAMLOPT := $(OCAMLFIND) ocamlopt ${OPTDBG} ${THREAD} OCAMLDOC := $(OCAMLFIND) ocamldoc OCAMLDEP := $(OCAMLFIND) ocamldep all: byte opt ### Common files ### FILES := ocsigen_socket.ml \ ocsigen_request_info.ml \ ocsigen_command.ml \ ocsigen_range.ml \ ocsigen_extensions.ml \ ocsigen_parseconfig.ml \ ocsigen_http_client.ml \ ocsigen_local_files.ml \ ocsigen_server.ml \ byte:: ${PROJECTNAME}.cma opt:: ${PROJECTNAME}.cmxa ${PROJECTNAME}.cma: $(FILES:.ml=.cmo) ${OCAMLC} -a -o $@ $^ ${PROJECTNAME}.cmxa: $(FILES:.ml=.cmx) ${OCAMLOPT} -a -o $@ $^ ### Server ### byte:: ${PROJECTNAME} opt:: ${PROJECTNAME}.opt ifdef DONOTPARSECOMMANDLINE PARSECOMMANDLINE := ../baselib/donotparsecommandline.cma else PARSECOMMANDLINE := ../baselib/parsecommandline.cma endif SERVERLIBS := ${PARSECOMMANDLINE} \ ../baselib/baselib.cma \ ../baselib/polytables.cma \ ../http/http.cma \ ${PROJECTNAME}.cma \ SERVEROBJS := server_main.cmo ${PROJECTNAME}: ${SERVERLIBS} ${SERVEROBJS} ${OCAMLC} -o $@ -linkpkg -linkall ${THREAD} ${LIBS} $^ ${PROJECTNAME}.opt: ${SERVERLIBS:.cma=.cmxa} ${SERVEROBJS:.cmo=.cmx} ${OCAMLOPT} -o $@ -linkpkg -linkall ${THREAD} ${LIBS} $^ ### Toplevel #### top: servertop OCAMLPATH=${SRC}/src/files/:${OCAMLPATH} ${RLWRAP} ./servertop servertop: ${SERVERLIBS} OCAMLPATH=${SRC}/src/files/:${OCAMLPATH} ${OCAMLFIND} ocamlmktop \ -o $@ -linkall -linkpkg ${THREAD} ${LIBS} $^ ########## %.cmi: %.mli $(OCAMLC) ${LIBS} -c $< %.cmo: %.ml $(OCAMLC) ${LIBS} -c $< %.cmx: %.ml $(OCAMLOPT) ${LIBS} -c $< %.cmxs: %.cmxa $(OCAMLOPT) -shared -linkall -o $@ $< %.o: %.c $(OCAMLC) -c $< ## Clean up clean: -rm -f *.cm* *.o *.a *.annot -rm -f ${PREDEP} -rm -f ${PROJECTNAME} ${PROJECTNAME}.opt -rm -f servertop distclean: clean -rm -f *~ \#* .\#* ## Dependencies depend: ${PREDEP} $(OCAMLDEP) ${LIBS} *.mli *.ml > .depend FORCE: -include .depend ocsigenserver-2.16.0/src/server/ocsigen_command.ml000066400000000000000000000031741357715257700222620ustar00rootroot00000000000000(* Ocsigen * http://www.ocsigen.org * Module ocsigen_extensions.ml * Copyright (C) 2015 Vincent Balat * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) let section = Lwt_log.Section.make "ocsigen:command" exception Unknown_command let register_command_function, get_command_function = let command_function = ref (fun ?prefix _ _ -> Lwt.fail Unknown_command) in ((fun ?prefix f -> let prefix' = prefix in let old_command_function = !command_function in command_function := (fun ?prefix s c -> Lwt.catch (fun () -> old_command_function ?prefix s c) (function | Unknown_command -> if prefix = prefix' then f s c else Lwt.fail Unknown_command | e -> Lwt.fail e))), (fun () -> !command_function)) let () = register_command_function ~prefix:"logs" (Ocsigen_messages.command_f Unknown_command) ocsigenserver-2.16.0/src/server/ocsigen_command.mli000066400000000000000000000032161357715257700224300ustar00rootroot00000000000000(* Ocsigen * http://www.ocsigen.org * Module ocsigen_extensions.ml * Copyright (C) 2015 Vincent Balat * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) (** Extending server commands *) exception Unknown_command (** Use a prefix for all your commands when you want to create extension-specific commands. For example if the prefix is "myextension" and the commande "blah", the actual command to be written by the user is "myextension:blah". Give as parameter the function that will parse the command and do an action. Its first parameter is the full command as a string. The second one is the command without prefix, split by word. It must raise [ocsigen_extensions.Unknown_command] if it does not recognize the command. *) val register_command_function : ?prefix:string -> (string -> string list -> unit Lwt.t) -> unit (**/**) val get_command_function : unit -> (?prefix:string -> string -> string list -> unit Lwt.t) ocsigenserver-2.16.0/src/server/ocsigen_common_server.mli000066400000000000000000000017561357715257700236770ustar00rootroot00000000000000module type S = sig (** compute a redirection if path links to a directory *) exception Ocsigen_Is_a_directory of (Ocsigen_request_info.request_info -> Neturl.url) exception Ocsigen_unsupported_media exception Ocsigen_http_error of (Ocsigen_cookies.cookieset * int) module Connection : sig exception Lost_connection of exn exception Aborted exception Timeout exception Keepalive_timeout exception Connection_closed end (** accessor to get number of client (used by eliom monitoring) *) val number_of_client : unit -> int (** alias of [number_of_client] *) val get_number_of_connected : unit -> int (** shutdown main loop of server *) val shutdown_server : float option -> unit (** initialize a main loop of http server *) val service : ?ssl:string * string * (bool -> string) option -> address:string -> port:int -> connector:(Ocsigen_request_info.request_info -> unit -> Ocsigen_http_frame.result Lwt.t) -> unit -> unit Lwt.t end ocsigenserver-2.16.0/src/server/ocsigen_extensions.ml000066400000000000000000001142041357715257700230400ustar00rootroot00000000000000(* Ocsigen * http://www.ocsigen.org * Module ocsigen_extensions.ml * Copyright (C) 2005 2007 Vincent Balat * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) (*****************************************************************************) (*****************************************************************************) (*****************************************************************************) (*****************************************************************************) (** Writing extensions for Ocsigen *) (* TODO - awake must be called after each Ext_found or Ext_found_continue_with or Ext_found_stop sent by an extension. It is perhaps called too often. *) let section = Lwt_log.Section.make "ocsigen:ext" open Lwt open Ocsigen_lib open Ocsigen_cookies include Ocsigen_request_info include Ocsigen_command module Ocsigen_request_info = Ocsigen_request_info exception Ocsigen_http_error of (Ocsigen_cookies.cookieset * int) exception Ocsigen_Looping_request (** Xml tag not recognized by an extension (usually not a real error) *) exception Bad_config_tag_for_extension of string (** Error in a tag inside the main ocsigen.conf file *) exception Error_in_config_file of string (** Option incorrect in a userconf file *) exception Error_in_user_config_file of string let badconfig fmt = Printf.ksprintf (fun s -> raise (Error_in_config_file s)) fmt (*****************************************************************************) (* virtual hosts: *) type virtual_hosts = (string * Netstring_pcre.regexp * int option) list (* We cannot use generic comparison, as regexpes are abstract values that cannot be compared or hashed. However the string essentially contains the value that is compiled into a regexp, and we compare this instead *) let hash_virtual_hosts (l : virtual_hosts) = Hashtbl.hash (List.map (fun (s, _, p) -> (s, p)) l) let rec equal_virtual_hosts (l1 : virtual_hosts) (l2 : virtual_hosts) = match l1, l2 with | [], [] -> true | [], _::_ | _::_, [] -> false | (s1, _, p1) :: q1, (s2, _, p2) :: q2 -> s1 = s2 && p1 = p2 && equal_virtual_hosts q1 q2 (*****************************************************************************) type client = Ocsigen_http_com.connection let client_id = Ocsigen_http_com.connection_id let client_connection x = x let client_of_connection x = x (*****************************************************************************) (* Server configuration, for local files that must not be sent *) type do_not_serve = { do_not_serve_regexps: string list; do_not_serve_files: string list; do_not_serve_extensions: string list; } (* BY TODO : Use unbalanced trees instead *) let join_do_not_serve d1 d2 = { do_not_serve_regexps = d1.do_not_serve_regexps @ d2.do_not_serve_regexps; do_not_serve_files = d1.do_not_serve_files @ d2.do_not_serve_files; do_not_serve_extensions = d1.do_not_serve_extensions @ d2.do_not_serve_extensions; } let hash_consed_do_not_serve = Hashtbl.create 17 exception IncorrectRegexpes of do_not_serve let do_not_serve_to_regexp d = try Hashtbl.find hash_consed_do_not_serve d with Not_found -> let wrap l = if l = [] then None else Some l and bind f = function None -> None | Some v -> Some (f v) in let files, extensions, regexps = wrap d.do_not_serve_files, wrap d.do_not_serve_extensions, wrap d.do_not_serve_regexps in let paren_quote l = String.concat "|" (List.map (fun s -> Printf.sprintf "(%s)" (Netstring_pcre.quote s)) l) and paren l = String.concat "|" (List.map (fun s -> Printf.sprintf "(%s)" s) l) in let files = bind paren_quote files and extensions = bind paren_quote extensions and regexps = bind paren regexps in let files = bind (Printf.sprintf ".*/(%s)") files and extensions = bind (Printf.sprintf ".*\\.(%s)") extensions in let l = List.fold_left (fun r -> function None -> r | Some v -> v :: r) [] [files;extensions;regexps] in let regexp = if l = [] then (* This regexp should not never match *) "$^" else Printf.sprintf "^(%s)$" (paren l) in (try Lwt_log.ign_info_f ~section "Compiling exclusion regexp %s" regexp; let r = Netstring_pcre.regexp regexp in Hashtbl.add hash_consed_do_not_serve d r; r with _ -> raise (IncorrectRegexpes d) ) (*****************************************************************************) (* Main server configuration *) type config_info = { default_hostname: string; default_httpport: int; default_httpsport: int; default_protocol_is_https: bool; mime_assoc: Ocsigen_charset_mime.mime_assoc; charset_assoc : Ocsigen_charset_mime.charset_assoc; (** Default name to use as index file when a directory is requested. Use [None] if no index should be tried. The various indexes are tried in the given order. If no index is specified, or the index does not exists, the content of the directory might be listed, according to [list_directry_content] *) default_directory_index : string list; (** Should the list of files in a directory be displayed if there is no index in this directory ? *) list_directory_content : bool; (** Should symlinks be followed when accessign a local file? *) follow_symlinks: follow_symlink; do_not_serve_404: do_not_serve; do_not_serve_403: do_not_serve; uploaddir: string option; maxuploadfilesize: int64 option; } and follow_symlink = | DoNotFollowSymlinks (** Never follow a symlink *) | FollowSymlinksIfOwnerMatch (** Follow a symlink if the symlink and its target have the same owner *) | AlwaysFollowSymlinks (** Always follow symlinks *) (* Requests *) type request = { request_info: request_info; request_config: config_info; } exception Ocsigen_Is_a_directory of (Ocsigen_request_info.request_info -> Neturl.url) type answer = | Ext_do_nothing (** I don't want to do anything *) | Ext_found of (unit -> Ocsigen_http_frame.result Lwt.t) (** "OK stop! I will take the page. You can start the following request of the same pipelined connection. Here is the function to generate the page". The extension must return Ext_found as soon as possible when it is sure it is safe to start next request. Usually immediately. But in some case, for example proxies, you don't want the request of one connection to be handled in different order. (for example revproxy.ml starts its requests to another server before returning Ext_found, to ensure that all requests are done in same order). *) | Ext_found_stop of (unit -> Ocsigen_http_frame.result Lwt.t) (** Found but do not try next extensions *) | Ext_next of int (** Page not found. Try next extension. The integer is the HTTP error code. It is usually 404, but may be for ex 403 (forbidden) if you want another extension to try after a 403. Same as Ext_continue_with but does not change the request. *) | Ext_stop_site of (Ocsigen_cookies.cookieset * int) (** Error. Do not try next extension, but try next site. The integer is the HTTP error code, usually 403. *) | Ext_stop_host of (Ocsigen_cookies.cookieset * int) (** Error. Do not try next extension, do not try next site, but try next host. The integer is the HTTP error code, usually 403. *) | Ext_stop_all of (Ocsigen_cookies.cookieset * int) (** Error. Do not try next extension, do not try next site, do not try next host. The integer is the HTTP error code, usually 403. *) | Ext_continue_with of (request * Ocsigen_cookies.cookieset * int) (** Used to modify the request before giving it to next extension. The extension returns the request (possibly modified) and a set of cookies if it wants to set or cookies ({!Ocsigen_cookies.Cookies.empty} for no cookies). You must add these cookies yourself in request if you want them to be seen by subsequent extensions, for example using {!Ocsigen_http_frame.compute_new_ri_cookies}. The integer is usually equal to the error code received from preceding extension (but you may want to modify it). *) | Ext_retry_with of request * Ocsigen_cookies.cookieset (** Used to retry all the extensions with a new request. The extension returns the request (possibly modified) and a set of cookies if it wants to set or cookies ({!Ocsigen_cookies.Cookies.empty} for no cookies). You must add these cookies yourself in request if you want them to be seen by subsequent extensions, for example using {!Ocsigen_http_frame.compute_new_ri_cookies}. *) | Ext_sub_result of extension2 (** Used if your extension want to define option that may contain other options from other extensions. In that case, while parsing the configuration file, call the parsing function (of type [parse_fun]), that will return something of type [extension2]. *) | Ext_found_continue_with of (unit -> (Ocsigen_http_frame.result * request) Lwt.t) (** Same as [Ext_found] but may modify the request. *) | Ext_found_continue_with' of (Ocsigen_http_frame.result * request) (** Same as [Ext_found_continue_with] but does not allow to delay the computation of the page. You should probably not use it, but for output filters. *) and request_state = | Req_not_found of (int * request) | Req_found of (request * Ocsigen_http_frame.result) and extension2 = (unit -> unit) -> Ocsigen_cookies.cookieset -> request_state -> (answer * Ocsigen_cookies.cookieset) Lwt.t type extension = request_state -> answer Lwt.t type parse_fun = Xml.xml list -> extension2 type parse_host = Parse_host of (Url.path -> parse_host -> parse_fun -> Xml.xml -> extension) let (hosts : (virtual_hosts * config_info * extension2) list ref) = ref [] let set_hosts v = hosts := v let get_hosts () = !hosts (* Default hostname is either the Host header or the hostname set in the configuration file. *) let get_hostname req = if Ocsigen_config.get_usedefaulthostname () then req.request_config.default_hostname else match Ocsigen_request_info.host req.request_info with | None -> req.request_config.default_hostname | Some host -> host (* Default port is either - the port the server is listening at - or the port in the Host header - or the default port set in the configuration file. *) let get_port req = if Ocsigen_config.get_usedefaulthostname () then (if Ocsigen_request_info.ssl req.request_info then req.request_config.default_httpsport else req.request_config.default_httpport) else match Ocsigen_request_info.port_from_host_field req.request_info with | Some p -> p | None -> match Ocsigen_request_info.host req.request_info with | Some _ -> if Ocsigen_request_info.ssl req.request_info then 443 else 80 | None -> Ocsigen_request_info.server_port req.request_info let http_url_syntax = Hashtbl.find Neturl.common_url_syntax "http" let new_url_of_directory_request request ri = Lwt_log.ign_info ~section "Sending 301 Moved permanently"; let port = get_port request in let ssl = Ocsigen_request_info.ssl ri in let new_url = Neturl.make_url ~scheme:(if ssl then "https" else "http") ~host:(get_hostname request) ?port:(if (port = 80 && not ssl) || (ssl && port = 443) then None else Some port) ~path:(""::(Url.add_end_slash_if_missing (Ocsigen_request_info.full_path ri))) ?query:(Ocsigen_request_info.get_params_string ri) http_url_syntax in new_url (*****************************************************************************) (* To give parameters to extensions: *) let dynlinkconfig = ref ([] : Xml.xml list) let set_config s = dynlinkconfig := s let get_config () = !dynlinkconfig (*****************************************************************************) let site_match request (site_path : string list) url = (* We are sure that there is no / at the end or beginning of site_path *) (* and no / at the beginning of url *) (* and no // or ../ inside both of them *) (* We return the subpath without / at beginning *) let rec aux site_path url = match site_path, url with | [], [] -> raise (Ocsigen_Is_a_directory (new_url_of_directory_request request)) | [], p -> Some p | a::l, aa::ll when a = aa -> aux l ll | _ -> None in match site_path, url with | [], [] -> Some [] | _ -> aux site_path url let add_to_res_cookies res cookies_to_set = if cookies_to_set = Ocsigen_cookies.Cookies.empty then res else (Ocsigen_http_frame.Result.update res ~cookies: (Ocsigen_cookies.add_cookies (Ocsigen_http_frame.Result.cookies res) cookies_to_set) ()) let make_ext awake cookies_to_set req_state (genfun : extension) (genfun2 : extension2) = genfun req_state >>= fun res -> let rec aux cookies_to_set = function | Ext_do_nothing -> genfun2 awake cookies_to_set req_state | Ext_found r -> awake (); r () >>= fun r' -> let ri = match req_state with | Req_found (ri, _) -> ri | Req_not_found (_, ri) -> ri in genfun2 id (* already awoken *) Ocsigen_cookies.Cookies.empty (Req_found (ri, add_to_res_cookies r' cookies_to_set)) | Ext_found_continue_with r -> awake (); r () >>= fun (r', req) -> genfun2 id (* already awoken *) Ocsigen_cookies.Cookies.empty (Req_found (req, add_to_res_cookies r' cookies_to_set)) | Ext_found_continue_with' (r', req) -> awake (); genfun2 id (* already awoken *) Ocsigen_cookies.Cookies.empty (Req_found (req, add_to_res_cookies r' cookies_to_set)) | Ext_next e -> let ri = match req_state with | Req_found (ri, _) -> ri | Req_not_found (_, ri) -> ri in genfun2 awake cookies_to_set (Req_not_found (e, ri)) | Ext_continue_with (ri, cook, e) -> genfun2 awake (Ocsigen_cookies.add_cookies cook cookies_to_set) (Req_not_found (e, ri)) | Ext_found_stop _ | Ext_stop_site _ | Ext_stop_host _ | Ext_stop_all _ | Ext_retry_with _ as res -> Lwt.return (res, cookies_to_set) | Ext_sub_result sr -> sr awake cookies_to_set req_state >>= fun (res, cookies_to_set) -> aux cookies_to_set res in aux cookies_to_set res (*****************************************************************************) let fun_beg = ref (fun () -> ()) let fun_end = ref (fun () -> ()) let fun_exn = ref (fun exn -> (raise exn : string)) let rec default_parse_config (host : virtual_hosts) config_info prevpath (Parse_host parse_host) (parse_fun : parse_fun) = function | Xml.Element ("site", atts, l) -> let rec parse_site_attrs (enc,dir) = function | [] -> (match dir with | None -> raise (Ocsigen_config.Config_file_error ("Missing dir attribute in ")) | Some s -> (enc, s)) | ("path", s)::suite | ("dir", s)::suite -> (match dir with | None -> parse_site_attrs (enc, Some s) suite | _ -> raise (Ocsigen_config.Config_file_error ("Duplicate attribute dir in "))) | ("charset", s)::suite -> (match enc with | None -> parse_site_attrs ((Some s), dir) suite | _ -> raise (Ocsigen_config.Config_file_error ("Duplicate attribute charset in "))) | (s, _)::_ -> raise (Ocsigen_config.Config_file_error ("Wrong attribute for : "^s)) in let charset, dir = parse_site_attrs (None, None) atts in let path = prevpath@ Url.remove_slash_at_end (Url.remove_slash_at_beginning (Url.remove_dotdot (Neturl.split_path dir))) in let parse_config = make_parse_config path parse_host l in let ext awake cookies_to_set = function | Req_found (ri, res) -> Lwt.return (Ext_found_continue_with' (res, ri), cookies_to_set) | Req_not_found (e, oldri) -> let oldri = match charset with | None -> oldri | Some charset -> { oldri with request_config = { oldri.request_config with charset_assoc = Ocsigen_charset_mime.set_default_charset oldri.request_config.charset_assoc charset } } in match site_match oldri path (Ocsigen_request_info.full_path oldri.request_info) with | None -> Lwt_log.ign_info_f ~section "site \"%a\" does not match url \"%a\"." (fun () path -> Url.string_of_url_path ~encode:true path) path (fun () oldri -> Url.string_of_url_path ~encode:true (Ocsigen_request_info.full_path oldri.request_info)) oldri; Lwt.return (Ext_next e, cookies_to_set) | Some sub_path -> Lwt_log.ign_info_f ~section "site found: url \"%a\" matches \"%a\"." (fun () oldri -> Url.string_of_url_path ~encode:true (Ocsigen_request_info.full_path oldri.request_info)) oldri (fun () path -> Url.string_of_url_path ~encode:true path) path; let ri = {oldri with request_info = (Ocsigen_request_info.update oldri.request_info ~sub_path:sub_path ~sub_path_string: (Url.string_of_url_path ~encode:true sub_path) ()) } in parse_config awake cookies_to_set (Req_not_found (e, ri)) >>= function (* After a site, we turn back to old ri *) | (Ext_stop_site (cs, err), cookies_to_set) | (Ext_continue_with (_, cs, err), cookies_to_set) -> Lwt.return (Ext_continue_with (oldri, cs, err), cookies_to_set) | (Ext_found_continue_with r, cookies_to_set) -> awake (); r () >>= fun (r', req) -> Lwt.return (Ext_found_continue_with' (r', oldri), cookies_to_set) | (Ext_found_continue_with' (r, req), cookies_to_set) -> Lwt.return (Ext_found_continue_with' (r, oldri), cookies_to_set) | (Ext_do_nothing, cookies_to_set) -> Lwt.return (Ext_continue_with (oldri, Ocsigen_cookies.Cookies.empty, e), cookies_to_set) | r -> Lwt.return r in (function | Req_found (ri, r) -> Lwt.return (Ext_found_continue_with' (r, ri)) | Req_not_found (err, ri) -> Lwt.return (Ext_sub_result ext)) | Xml.Element (tag,_,_) -> raise (Bad_config_tag_for_extension tag) | _ -> raise (Ocsigen_config.Config_file_error ("Unexpected content inside ")) and make_parse_config path parse_host l : extension2 = let f = parse_host path (Parse_host parse_host) in (* creates all site data, if any *) let rec parse_config : _ -> extension2 = function | [] -> (fun (awake : unit -> unit) cookies_to_set -> function | Req_found (ri, res) -> Lwt.return (Ext_found_continue_with' (res, ri), cookies_to_set) | Req_not_found (e, ri) -> Lwt.return (Ext_continue_with (ri, Ocsigen_cookies.Cookies.empty, e), cookies_to_set)) (* was Lwt.return (Ext_next e, cookies_to_set)) but to use make_parse_site with userconf, we need to know current ri after parsing the sub-configuration. *) | xmltag::ll -> try let genfun = f parse_config xmltag in let genfun2 = parse_config ll in fun awake cookies_to_set req_state -> make_ext awake cookies_to_set req_state genfun genfun2 with | Bad_config_tag_for_extension t -> (* This case happens only if no extension has recognized the tag at all *) badconfig "Unexpected tag <%s> inside " t (Url.string_of_url_path ~encode:true path) | Error_in_config_file _ as e -> raise e | e -> badconfig "Error while parsing configuration file: %s" (try !fun_exn e with e -> Printexc.to_string e) in !fun_beg (); let r = try parse_config l with e -> !fun_end (); raise e (*VVV May be we should avoid calling fun_end after parinf user config files (with extension userconf) ... See eliommod.ml *) in !fun_end (); r (*****************************************************************************) type userconf_info = { localfiles_root : string; } type parse_config = virtual_hosts -> config_info -> parse_config_aux and parse_config_user = userconf_info -> parse_config and parse_config_aux = Url.path -> parse_host -> (parse_fun -> Xml.xml -> extension ) let user_extension_void_fun_site : parse_config_user = fun _ _ _ _ _ _ -> function | Xml.Element (t, _, _) -> raise (Bad_config_tag_for_extension t) | _ -> raise (Error_in_config_file "Unexpected data in config file") let extension_void_fun_site : parse_config = fun _ _ _ _ _ -> function | Xml.Element (t, _, _) -> raise (Bad_config_tag_for_extension t) | _ -> raise (Error_in_config_file "Unexpected data in config file") let register_extension, parse_config_item, parse_user_site_item, get_beg_init, get_end_init, get_init_exn_handler = let ref_fun_site = ref default_parse_config in let ref_user_fun_site = ref (fun (_ : userconf_info) -> default_parse_config) in ((* ********* register_extension ********* *) (fun ?fun_site ?user_fun_site ?begin_init ?end_init ?(exn_handler=raise) ?(respect_pipeline=false) () -> if respect_pipeline then Ocsigen_config.set_respect_pipeline (); (match fun_site with | None -> () | Some fun_site -> let old_fun_site = !ref_fun_site in ref_fun_site := (fun host conf_info -> let oldf = old_fun_site host conf_info in let newf = fun_site host conf_info in fun path parse_host -> let oldf = oldf path parse_host in let newf = newf path parse_host in fun parse_config config_tag -> try oldf parse_config config_tag with | Bad_config_tag_for_extension c -> newf parse_config config_tag )); (match user_fun_site with | None -> () | Some user_fun_site -> let old_fun_site = !ref_user_fun_site in ref_user_fun_site := (fun path host conf_info -> let oldf = old_fun_site path host conf_info in let newf = user_fun_site path host conf_info in fun path parse_host -> let oldf = oldf path parse_host in let newf = newf path parse_host in fun parse_config config_tag -> try oldf parse_config config_tag with | Bad_config_tag_for_extension c -> newf parse_config config_tag )); (match begin_init with | Some begin_init -> fun_beg := comp begin_init !fun_beg | None -> ()); (match end_init with | Some end_init -> fun_end := comp end_init !fun_end; | None -> ()); let curexnfun = !fun_exn in fun_exn := fun e -> try curexnfun e with e -> exn_handler e), (* ********* parse_config_item ********* *) (fun host conf -> !ref_fun_site host conf), (* ********* parse_user_site_item ********* *) (fun host conf -> !ref_user_fun_site host conf), (* ********* get_beg_init ********* *) (fun () -> !fun_beg), (* ********* get_end_init ********* *) (fun () -> !fun_end), (* ********* get_init_exn_handler ********* *) (fun () -> !fun_exn) ) let default_parse_extension ext_name = function | [] -> () | _ -> raise (Error_in_config_file (Printf.sprintf "Unexpected content found in configuration of extension %s: %s does not accept options" ext_name ext_name)) let register_extension ~name ?fun_site ?user_fun_site ?begin_init ?end_init ?init_fun ?exn_handler ?respect_pipeline () = Ocsigen_loader.set_module_init_function name (fun () -> (match init_fun with | None -> default_parse_extension name (get_config ()) | Some f -> f (get_config ())); register_extension ?fun_site ?user_fun_site ?begin_init ?end_init ?exn_handler ?respect_pipeline ()) module Configuration = struct type attribute' = { attribute_obligatory : bool; attribute_value_func : string -> unit } type attribute = string * attribute' type element' = { obligatory : bool; init : unit -> unit; elements : element list; attributes : attribute list; pcdata : (string -> unit) option; other_elements : (string -> (string * string) list -> Xml.xml list -> unit) option; other_attributes : (string -> string -> unit) option; } and element = string * element' let element ~name ?(obligatory=false) ?(init=ignore) ?(elements=[]) ?(attributes=[]) ?pcdata ?other_elements ?other_attributes () : element = name, { obligatory; init; elements; attributes; pcdata; other_elements; other_attributes } let attribute ~name ?(obligatory=false) f : attribute = name, { attribute_obligatory = obligatory; attribute_value_func = f } let ignore_blank_pcdata ~in_tag = fun str -> String.iter (fun c -> if not (List.mem c [' '; '\n'; '\r'; '\t']) then raise (Error_in_user_config_file ("Non-blank PCDATA in tag "^in_tag))) str let refuse_pcdata ~in_tag = fun _ -> raise (Error_in_user_config_file ("No PCDATA allowed in tag "^in_tag)) let check_attribute_occurrence ~in_tag ?other_elements attributes = function | name, { attribute_obligatory = true } -> (try ignore (List.assoc name attributes) with Not_found -> raise (Error_in_user_config_file ("Obligatory attribute "^name^" not in tag "^in_tag))) | _ -> () let check_element_occurrence ~in_tag elements = function | name, { obligatory = true } -> let corresponding_element = function | Xml.Element (name', _, _) -> name = name' | _ -> false in if not (List.exists corresponding_element elements) then raise (Error_in_user_config_file ("Obligatory element "^name^" not in tag "^in_tag)) | _ -> () let process_attribute = fun ~in_tag ~attributes:spec_attributes ?other_attributes:spec_other_attributes (attribute, value) -> try (List.assoc attribute spec_attributes).attribute_value_func value with Not_found -> match spec_other_attributes with | Some spec_other_attributes -> spec_other_attributes attribute value | None -> raise (Error_in_user_config_file ("Unexpected attribute "^attribute^" in tag "^in_tag)) let rec process_element ~in_tag ~elements:spec_elements ?pcdata:spec_pcdata ?other_elements:spec_other_elements = function | Xml.PCData str -> let spec_pcdata = Option.get (fun () -> ignore_blank_pcdata ~in_tag) spec_pcdata in spec_pcdata str | Xml.Element (name, attributes, elements) -> try let spec = List.assoc name spec_elements in List.iter (check_attribute_occurrence ~in_tag:name attributes) spec.attributes; List.iter (check_element_occurrence ~in_tag:name elements) spec.elements; spec.init (); List.iter (process_attribute ~in_tag:name ~attributes:spec.attributes ?other_attributes:spec.other_attributes) attributes; List.iter (process_element ~in_tag:name ~elements:spec.elements ?pcdata:spec.pcdata ?other_elements:spec.other_elements) elements with Not_found -> match spec_other_elements with | Some spec_other_elements -> spec_other_elements name attributes elements | None -> raise (Error_in_config_file ("Unknown tag "^name^" in tag "^in_tag)) let process_elements ~in_tag ~elements:spec_elements ?pcdata ?other_elements ?(init=ignore) elements = List.iter (check_element_occurrence ~in_tag elements) spec_elements; init (); List.iter (process_element ~in_tag ~elements:spec_elements ?pcdata ?other_elements) elements end (*****************************************************************************) let start_initialisation, during_initialisation, end_initialisation, get_numberofreloads = let init = ref true in let nb = ref (-1) in ((fun () -> init := true; nb := !nb+1; ), (fun () -> !init), (fun () -> init := false; ), (fun () -> !nb)) (********) let host_match ~(virtual_hosts : virtual_hosts) ~host ~port = let port_match = function | None -> true | Some p -> p = port in match host with | None -> List.exists (fun (_, _, p) -> port_match p) virtual_hosts (*VVV Warning! For HTTP/1.0, when host is absent, we take the first one, even if it doesn't match! *) | Some host -> let host_match regexp = (Netstring_pcre.string_match regexp host 0 <> None) in let rec aux = function | [] -> false | (_, a, p)::l -> (port_match p && host_match a) || aux l in aux virtual_hosts (* Currently used only for error messages. *) let string_of_host (h : virtual_hosts) = let aux1 (host, _, port) = match port with | None -> host | Some p -> host ^ ":" ^ string_of_int p in List.fold_left (fun d arg -> d ^ aux1 arg ^" ") "" h let compute_result ?(previous_cookies = Ocsigen_cookies.Cookies.empty) ?(awake_next_request = false) ri = let host = Ocsigen_request_info.host ri in let port = Ocsigen_request_info.server_port ri in let conn = client_connection (Ocsigen_request_info.client ri) in let awake = if awake_next_request then (let tobeawoken = ref true in (* must be awoken once and only once *) fun () -> if !tobeawoken then begin tobeawoken := false; Ocsigen_http_com.wakeup_next_request conn end) else id in let rec do2 sites cookies_to_set ri = Ocsigen_request_info.update_nb_tries ri (Ocsigen_request_info.nb_tries ri + 1); if (Ocsigen_request_info.nb_tries ri) > Ocsigen_config.get_maxretries () then fail Ocsigen_Looping_request else let string_of_host_option = function | None -> ":"^(string_of_int port) | Some h -> h^":"^(string_of_int port) in let rec aux_host ri prev_err cookies_to_set = function | [] -> fail (Ocsigen_http_error (cookies_to_set, prev_err)) | (h, conf_info, host_function)::l when host_match ~virtual_hosts:h ~host ~port -> Lwt_log.ign_info_f ~section "host found! %a matches %a" (fun () -> string_of_host_option) host (fun () -> string_of_host) h; host_function awake cookies_to_set (Req_not_found (prev_err, { request_info = ri; request_config = conf_info })) >>= fun (res_ext, cookies_to_set) -> (match res_ext with | Ext_found r | Ext_found_stop r -> awake (); r () >>= fun r' -> Lwt.return (add_to_res_cookies r' cookies_to_set) | Ext_do_nothing -> aux_host ri prev_err cookies_to_set l | Ext_found_continue_with r -> awake (); r () >>= fun (r', _) -> return (add_to_res_cookies r' cookies_to_set) | Ext_found_continue_with' (r, _) -> awake (); return (add_to_res_cookies r cookies_to_set) | Ext_next e -> aux_host ri e cookies_to_set l (* try next site *) | Ext_stop_host (cook, e) | Ext_stop_site (cook, e) -> aux_host ri e (Ocsigen_cookies.add_cookies cook cookies_to_set) l (* try next site *) | Ext_stop_all (cook, e) -> fail (Ocsigen_http_error (cookies_to_set, e)) | Ext_continue_with (_, cook, e) -> aux_host ri e (Ocsigen_cookies.add_cookies cook cookies_to_set) l | Ext_retry_with (request2, cook) -> do2 (get_hosts ()) (Ocsigen_cookies.add_cookies cook cookies_to_set) request2.request_info (* retry all *) | Ext_sub_result sr -> assert false ) | (h, _, _)::l -> Lwt_log.ign_info_f ~section "host = %a does not match %a" (fun () -> string_of_host_option) host (fun () -> string_of_host) h; aux_host ri prev_err cookies_to_set l in aux_host ri 404 cookies_to_set sites in Lwt.finalize (fun () -> do2 (get_hosts ()) previous_cookies ri ) (fun () -> awake (); Lwt.return () ) (*****************************************************************************) (* This is used by server.ml. I put that here because I need it to be accessible for profiling. *) let sockets = ref [] let sslsockets = ref [] let get_number_of_connected, incr_connected, decr_connected, wait_fewer_connected = let connected = ref 0 in let maxr = ref (-1000) in let mvar = Lwt_mvar.create_empty () in ((fun () -> !connected), (fun n -> connected := !connected + n), (fun () -> let c = !connected in connected := c - 1; if !connected <= 0 && !sockets = [] && !sslsockets = [] then exit 0; if c = !maxr then begin Lwt_log.ign_warning ~section "Number of connections now ok"; maxr := -1000; Lwt_mvar.put mvar () end else Lwt.return () ), (fun max -> maxr := max; Lwt_mvar.take mvar) ) let get_server_address ri = let socket = Ocsigen_http_com.connection_fd (client_connection (Ocsigen_request_info.client ri)) in match Lwt_ssl.getsockname socket with | Unix.ADDR_UNIX _ -> failwith "unix domain socket have no ip" | Unix.ADDR_INET (addr,port) -> addr,port (* user directories *) exception NoSuchUser type ud_string = Nodir of string | Withdir of string * string * string let user_dir_regexp = Netstring_pcre.regexp "(.*)\\$u\\(([^\\)]*)\\)(.*)" let parse_user_dir s = match Netstring_pcre.full_split user_dir_regexp s with | [ Netstring_pcre.Delim _; Netstring_pcre.Group (1, s1); Netstring_pcre.Group (2, u); Netstring_pcre.Group (3, s2)] -> Withdir (s1, u, s2) | _ -> Nodir s let replace_user_dir regexp dest pathstring = match dest with | Nodir dest -> Netstring_pcre.global_replace regexp dest pathstring | Withdir (s1, u, s2) -> try let s1 = Netstring_pcre.global_replace regexp s1 pathstring in let u = Netstring_pcre.global_replace regexp u pathstring in let s2 = Netstring_pcre.global_replace regexp s2 pathstring in let userdir = (Unix.getpwnam u).Unix.pw_dir in Lwt_log.ign_info_f ~section "User %s" u; s1^userdir^s2 with Not_found -> Lwt_log.ign_info_f ~section "No such user %s" u; raise NoSuchUser (*****************************************************************************) (* Finding redirections *) exception Not_concerned let find_redirection regexp full_url dest https host port get_params_string sub_path_string full_path_string = if full_url then match host with | None -> raise Not_concerned | Some host -> let path = match get_params_string with | None -> full_path_string | Some g -> full_path_string ^ "?" ^ g in let path = Url.make_absolute_url https host port ("/"^path) in (match Netstring_pcre.string_match regexp path 0 with | None -> raise Not_concerned | Some _ -> (* Matching regexp found! *) Netstring_pcre.global_replace regexp dest path ) else let path = match get_params_string with | None -> sub_path_string | Some g -> sub_path_string ^ "?" ^ g in match Netstring_pcre.string_match regexp path 0 with | None -> raise Not_concerned | Some _ -> (* Matching regexp found! *) Netstring_pcre.global_replace regexp dest path ocsigenserver-2.16.0/src/server/ocsigen_extensions.mli000066400000000000000000000477551357715257700232310ustar00rootroot00000000000000(* Ocsigen * http://www.ocsigen.org * Module pagesearch.mli * Copyright (C) 2005 Vincent Balat * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) (*****************************************************************************) (*****************************************************************************) (* Tables of services (global and session tables) *) (* Store and load dynamic pages *) (*****************************************************************************) (*****************************************************************************) (** Writing extensions for Ocsigen *) open Lwt open Ocsigen_lib open Ocsigen_cookies include (module type of Ocsigen_command) module Ocsigen_request_info : (module type of Ocsigen_request_info with type request_info = Ocsigen_request_info.request_info and type file_info = Ocsigen_request_info.file_info and type ifrange = Ocsigen_request_info.ifrange) exception Ocsigen_http_error of (Ocsigen_cookies.cookieset * int) (** Xml tag not recognized by an extension (usually not a real error) *) exception Bad_config_tag_for_extension of string (** Error in a tag inside the main ocsigen.conf file *) exception Error_in_config_file of string (** Option incorrect in a userconf file *) exception Error_in_user_config_file of string val badconfig : ('a, unit, string, 'b) format4 -> 'a (** Convenient function for raising Error_in_config_file exceptions with a sprintf-formatted argument. *) (*****************************************************************************) (** Type of the result of parsing the field [hostfiler] in the configuration file. Inside the list, the first argument is the host itself (which is a glob-like pattern that can contains [*]), a regexp parsing this pattern, and optionally a port. *) type virtual_hosts = (string * Netstring_pcre.regexp * int option) list val hash_virtual_hosts : virtual_hosts -> int val equal_virtual_hosts : virtual_hosts -> virtual_hosts -> bool val host_match: virtual_hosts:virtual_hosts -> host:string option -> port:int -> bool (*****************************************************************************) (** Configuration to hide/forbid local files *) type do_not_serve = { do_not_serve_regexps: string list; do_not_serve_files: string list; do_not_serve_extensions: string list; } exception IncorrectRegexpes of do_not_serve (** Compile a do_not_serve structure into a regexp. Raises [IncorrectRegexpes] if the compilation fails. The result is memoized for subsequent calls with the same argument *) val do_not_serve_to_regexp: do_not_serve -> Netstring_pcre.regexp val join_do_not_serve : do_not_serve -> do_not_serve -> do_not_serve (** Configuration options, passed to (and modified by) extensions *) type config_info = { default_hostname: string; default_httpport: int; default_httpsport: int; default_protocol_is_https: bool; mime_assoc: Ocsigen_charset_mime.mime_assoc; charset_assoc : Ocsigen_charset_mime.charset_assoc; (** Default name to use as index file when a directory is requested. Use [None] if no index should be tried. The various indexes are tried in the given order. If no index is specified, or the index does not exists, the content of the directory might be listed, according to [list_directory_content] *) default_directory_index : string list; (** Should the list of files in a directory be displayed if there is no index in this directory ? *) list_directory_content : bool; (** Should symlinks be followed when accessign a local file? *) follow_symlinks: follow_symlink; do_not_serve_404: do_not_serve; do_not_serve_403: do_not_serve; uploaddir: string option; maxuploadfilesize: int64 option; } and follow_symlink = | DoNotFollowSymlinks (** Never follow a symlink *) | FollowSymlinksIfOwnerMatch (** Follow a symlink if the symlink and its target have the same owner *) | AlwaysFollowSymlinks (** Always follow symlinks *) (*****************************************************) type client = Ocsigen_http_com.connection (** A value of this type represents the client who did the request. *) val client_id : client -> int (** Returns the id number of the connection *) val client_connection : client -> Ocsigen_http_com.connection (** Returns the connection *) type ifrange = Ocsigen_request_info.ifrange = | IR_No | IR_Ifunmodsince of float | IR_ifmatch of string type file_info = Ocsigen_request_info.file_info = { tmp_filename: string; filesize: int64; raw_original_filename: string; original_basename: string ; file_content_type: ((string * string) * (string * string) list) option; } type request_info = Ocsigen_request_info.request_info and request = { request_info: request_info; request_config: config_info; } exception Ocsigen_Is_a_directory of (Ocsigen_request_info.request_info -> Neturl.url) type answer = | Ext_do_nothing (** I don't want to do anything *) | Ext_found of (unit -> Ocsigen_http_frame.result Lwt.t) (** "OK stop! I will take the page. You can start the following request of the same pipelined connection. Here is the function to generate the page". The extension must return Ext_found as soon as possible when it is sure it is safe to start next request. Usually as soon as you know that the result will be Ext_found. But in some case, for example proxies, you don't want the request of one connection to be handled in different order. In that case, wait to be sure that the new request will not overtake this one. *) | Ext_found_stop of (unit -> Ocsigen_http_frame.result Lwt.t) (** Found but do not try next extensions *) | Ext_next of int (** Page not found. Try next extension. The integer is the HTTP error code. It is usually 404, but may be for ex 403 (forbidden) if you want another extension to try after a 403. Same as Ext_continue_with but does not change the request. *) | Ext_stop_site of (Ocsigen_cookies.cookieset * int) (** Error. Do not try next extension, but try next site. The integer is the HTTP error code, usually 403. *) | Ext_stop_host of (Ocsigen_cookies.cookieset * int) (** Error. Do not try next extension, do not try next site, but try next host. The integer is the HTTP error code, usually 403. *) | Ext_stop_all of (Ocsigen_cookies.cookieset * int) (** Error. Do not try next extension (even filters), do not try next site, do not try next host, do not . The integer is the HTTP error code, usually 403. *) | Ext_continue_with of (request * Ocsigen_cookies.cookieset * int) (** Used to modify the request before giving it to next extension. The extension returns the request_info (possibly modified) and a set of cookies if it wants to set or cookies ([!Ocsigen_cookies.Cookies.empty] for no cookies). You must add these cookies yourself in request_info if you want them to be seen by subsequent extensions, for example using {!Ocsigen_http_frame.compute_new_ri_cookies}. The integer is usually equal to the error code received from preceding extension (but you may want to modify it). *) | Ext_retry_with of request * Ocsigen_cookies.cookieset (** Used to retry all the extensions with a new request_info. The extension returns the request_info (possibly modified) and a set of cookies if it wants to set or cookies ([!Ocsigen_cookies.Cookies.empty] for no cookies). You must add these cookies yourself in request_info if you want them to be seen by subsequent extensions, for example using {!Ocsigen_http_frame.compute_new_ri_cookies}. *) | Ext_sub_result of extension2 (** Used if your extension want to define option that may contain other options from other extensions. In that case, while parsing the configuration file, call the parsing function (of type [parse_fun]), that will return something of type [extension2]. *) | Ext_found_continue_with of (unit -> (Ocsigen_http_frame.result * request) Lwt.t) (** Same as [Ext_found] but may modify the request. *) | Ext_found_continue_with' of (Ocsigen_http_frame.result * request) (** Same as [Ext_found_continue_with] but does not allow to delay the computation of the page. You should probably not use it, but for output filters. *) and request_state = | Req_not_found of (int * request) | Req_found of (request * Ocsigen_http_frame.result) and extension2 = (unit -> unit) -> Ocsigen_cookies.cookieset -> request_state -> (answer * Ocsigen_cookies.cookieset) Lwt.t type extension = request_state -> answer Lwt.t (** For each tag in the configuration file, you can set the extensions you want. Each extension is implemented as a function, taking the charset found in configuration file, the current state of the request, and returning an answer. If no page has been generated so far ([Req_not_found]), it receive the error code given by the previous extension (default 404), and the request information. If a page has been generated by previous extensions (case [Req_found]), the extension may want to modify the result (filters). *) type parse_fun = Xml.xml list -> extension2 (** Type of the functions parsing the content of a tag *) type parse_host (** Information received by extensions accepting userconf files. The parameter [localfiles_root] is an absolute path to the directory that the user is allowed to serve. This is used by staticmod, to disallow the user from allowing access to outside of this directory *) type userconf_info = { localfiles_root : string; } (** [parse_config] is the type of the functions parsing a tag (and returning an extension). Those are functions taking {ul {- the name of the virtual }} that will be called for each , and that will generate a function taking: {ul {- the path attribute of a tag}} that will be called for each , and that will generate a function taking: {ul {- an item of the config file}} that will be called on each tag inside and: {ul {- raise [Bad_config_tag_for_extension] if it does not recognize that tag} {- return something of type [extension] (filter or page generator)}} [parse_config_user] is the type of functions parsing a site tag inside an userconf file. They take one more parameter, of type userconf_info *) type parse_config = virtual_hosts -> config_info -> parse_config_aux and parse_config_user = userconf_info -> parse_config and parse_config_aux = Url.path -> parse_host -> (parse_fun -> Xml.xml -> extension ) (** For each extension generating pages, we register its name and six functions: - a function [fun_site] of type [parse_config]. This function will be responsible for handling the options of the configuration files that are recognized by the extension, and potentially generating a page. - a function [user_fun_site] of type [parse_user_config] which has the same role as [fun_site], but inside userconf files. Specify nothing if your extension is disallowed in userconf files. Otherwise, compared to [fun_site], you can selectively disallow some options, as [user_fun_site] must define only safe options (for example it is not safe to allow such options to load a cmo specified by a user, or to execute a program, as this program will be executed by ocsigen's user). Note that [user_fun_site] will be called for every request, whereas the [fun_site] is called only when starting or reloading the server. - a function [begin_init] that will be called at the beginning of the initialisation phase of each site, and each time the config file is reloaded. - a function [end_init] that will be called at the end of the initialisation phase of each site - a function [init_fun] that will be called just before registering the extension, taking as parameter the configuration options between [] and []. This allows to give configuration options to extensions. If no function is supplied, the extension is supposed to accept no option (and loading will fail if an option is supplied) See <> for the easy construction of such a function. - a function [exn_handler] that will create an error message from the exceptions that may be raised during the initialisation phase, and raise again all other exceptions Moreover, if the optional parameter [?respect_pipeline] is [true], the extension will ask the server to respect the order of the pipeline. That means that it will wait to be sure that the previous request from the same connection has been taken by an extension before giving a request to an extension. Use this to write proxies extensions, when you want to be able to pipeline the requests you to another server. It is false by default. *) val register_extension : name:string -> ?fun_site:parse_config -> ?user_fun_site:parse_config_user -> ?begin_init:(unit -> unit) -> ?end_init:(unit -> unit) -> ?init_fun:(Xml.xml list -> unit) -> ?exn_handler:(exn -> string) -> ?respect_pipeline:bool -> unit -> unit (** This modules contains types and constructor for the description of XML configurations and the accordingly parsing. *) module Configuration : sig (** Specification of a XML element. *) type element (** Specification of a XML attribute. *) type attribute (** Create the specification of a XML element. @param name Name of the XML tag @param init A function to be executed before processing the child elements and attributes @param obligatory Whether the element is obligatory ([false] by default) @param elements Specifications of the child elements @param attribute Specifications of the attributes @param pcdata Function to be applied on the pcdata ([ignore_blank_pcdata] by default) @param other_elements Optional function to be applied on the content of unspecified tags @param other_attributes Optional function to be applied on the unspecfied attributes *) val element : name:string -> ?obligatory:bool -> ?init:(unit -> unit) -> ?elements:element list -> ?attributes:attribute list -> ?pcdata:(string -> unit) -> ?other_elements:(string -> (string * string) list -> Xml.xml list -> unit) -> ?other_attributes:(string -> string -> unit) -> unit -> element (** [attribute ~name f] create a specification of a XML attribute. @param name The name of the XML attribute @param obligatory Whether the attribute is obligatory ([false] by default) @param f Function to be applied on the value string of the attribute *) val attribute : name:string -> ?obligatory:bool -> (string -> unit) -> attribute (** Process an XML element by the specifications. @param in_tag Name of the enclosing XML tag (just for generating error messages) @param elements Specifications of the child elements @param pcdata Function to be applied on the PCDATA ([ignore_blank_pcdata] by default) @param other_elements Optional function to be applied on unexpected child elements @raise Error_in_config_file If an element (resp. attribute) occurs which is not specified by the [element] (resp. [attribute]) parameter and no function [other_elements] (resp. other_attributes) is provided *) val process_element : in_tag:string -> elements:element list -> ?pcdata:(string -> unit) -> ?other_elements:(string -> (string * string) list -> Xml.xml list -> unit) -> Xml.xml -> unit (** Application of [process_element] on a list of XML elements. *) val process_elements : in_tag:string -> elements:element list -> ?pcdata:(string -> unit) -> ?other_elements:(string -> (string * string) list -> Xml.xml list -> unit) -> ?init:(unit -> unit) -> Xml.xml list -> unit (** The specification for ignoring blank PCDATA ('\n', '\r', ' ', '\t') and failing otherwise (a reasonable default). *) val ignore_blank_pcdata : in_tag:string -> string -> unit end (** Returns the hostname to be used for absolute links or redirections. It is either the Host header or the hostname set in the configuration file. *) val get_hostname : request -> string (** Returns the port to be used for absolute links or redirections. It is either: - the port the server is listening at - or the port in the Host header - or the default port set in the configuration file. *) val get_port : request -> int (** new_url_of_directory_request create a redirection and generating a new url for the client (depending on the server configuration and request) @param request configuration of the server @param ri request *) val new_url_of_directory_request : request -> request_info -> Neturl.url (** Parsing URLs. This allows to modify the URL in the request_info. (to be used for example with Ext_retry_with or Ext_continue_with) *) val ri_of_url : ?full_rewrite:bool -> string -> request_info -> request_info (** {3 User directories} *) (** Exception raised when an non-existing user is found *) exception NoSuchUser (** The type for string that may contain a $u(...) *) type ud_string val parse_user_dir : string -> ud_string val replace_user_dir : Netstring_pcre.regexp -> ud_string -> string -> string (** raises [Not_found] is the directory does not exist *) (** {3 Regular expressions for redirections} *) exception Not_concerned val find_redirection : Netstring_pcre.regexp -> bool -> string -> bool -> string option -> int -> string option -> string -> string -> string (**/**) (**/**) val make_parse_config : Url.path -> parse_config_aux -> parse_fun val parse_config_item : parse_config val parse_user_site_item : parse_config_user val set_hosts : (virtual_hosts * config_info * extension2) list -> unit val get_hosts : unit -> (virtual_hosts * config_info * extension2) list (** Compute the result to be sent to the client, by trying all extensions according the configuration file. *) val compute_result : ?previous_cookies:Ocsigen_cookies.cookieset -> ?awake_next_request:bool -> request_info -> Ocsigen_http_frame.result Lwt.t (** Profiling *) val get_number_of_connected : unit -> int val get_number_of_connected : unit -> int (** Server internal functions: *) val incr_connected : int -> unit val decr_connected : unit -> unit Lwt.t val wait_fewer_connected : int -> unit Lwt.t val during_initialisation : unit -> bool val start_initialisation : unit -> unit val end_initialisation : unit -> unit val get_numberofreloads : unit -> int val get_init_exn_handler : unit -> exn -> string val set_config : Xml.xml list -> unit val client_of_connection : Ocsigen_http_com.connection -> client val get_server_address : request_info -> Unix.inet_addr * int val sockets : Lwt_unix.file_descr list ref val sslsockets : Lwt_unix.file_descr list ref ocsigenserver-2.16.0/src/server/ocsigen_http_client.ml000066400000000000000000000712601357715257700231620ustar00rootroot00000000000000(* Ocsigen * http://www.ocsigen.org * ocsigen_http_client.ml Copyright (C) 2005 Vincent Balat * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) (* It is a first version. Many improvements are possible. TODO - get using pipeline (~client parameter) - better heuristic for trusting server keepalive? I think it could be less strict, as we redo requests? It should probably be different for a proxy or a reverse proxy ... - keep the name of the server in pipeline table? - Avoid the keepalive table to become too big - Add a parameter to disable reuse of free connections? - Allow to set parameters in config file (probing_time, etc) - Find a way to pipeline POST requests? at least PUT? - Does it work well if the server is using HTTP/1.0? (probably not because of chunks) - limit the number of concurrent requests to avoid too many opened file descriptors Notes: - Pipeline: If the server decided to close the pipeline when some request has already been sent, we redo the request. But ... it has never been tested!!!!! See exception Pipeline_failed. - Pipeline: we pipeline requests without body if possible, and we resend on a new connection if the pipeline failed. - We do not pipeline requests with non-empty body!!!!!!!!!!!!!! and requests using CONNECT method. CONNECT and POST because they are not idempotent (see RFC). Requests with body because we do not keep the content so we can not resend them ... - It is actually doing very few pipelining when used with Firefox or Konqueror ... The previous request is always received before sending the next one. Why? - Is it ok to reuse the same connection for several clients? May be restrict to several connections from the same client? What if the client is a proxy? - What to do with headers like user-agent, server, etc? For now we keep user-agent but send Ocsigen as server! - If I'm right, we are not supposed to change the User-Agent (end-to-end) but is there a hop-to-hop equivalent? What about other headers? Note that for now, we pipeline only if incoming requests were pipelined. But we can reuse a free connection. If the server says Connection:close once, we do not trust it any more for an amount of time ... Note (2011/02/09): I don't remember why there is a "head" field in the key of connection_table and free_connection_table ... *) open Ocsigen_lib let section = Lwt_log.Section.make "ocsigen:http:client" (* constants. Should be configurable *) let max_free_open_connections = 10 exception Connection_timed_out exception Connection_refused exception Pipeline_failed let (>>=) = Lwt.(>>=) (* let _ = Ssl_threads.init () (* Does not work for now (deadlock) -- bug in ocamlssl *) *) let _ = Ssl.init () let sslcontext = ref (Ssl.create_context Ssl.SSLv23 Ssl.Both_context) let request_sender = Ocsigen_http_com.create_sender ~proto:Ocsigen_http_frame.Http_header.HTTP11 () (*****************************************************************************) module T = Hashtbl.Make( struct type t = int * (Unix.inet_addr * int * bool) (* client ID, (IP, port, doing HEAD request) *) let equal = (=) let hash = Hashtbl.hash end) let connection_table = T.create 100 (* (comment added 2001/02/09) The connection table associates to each incoming connection (called "client") the thread and information about the output requests, in order to try to pipeline them on the same output connection. If the client parameter is not present, we do the requests independently. We try to find a free connection to the right server or we create one if there is none. In that case, the distant server may have the request in wrong order. If there is a body in the request we want to do, we do not try to pipeline, even if it comes from the same client. We use a free (or new) connection. *) module FT = struct module T = Hashtbl.Make( struct type t = Unix.inet_addr * int * bool (* IP, port, doing HEAD request *) let equal = (=) let hash = Hashtbl.hash end) let free_connection_table = T.create 100 (* contains unused opened output connections *) let add k v = let add_last v = let rec aux v = function | [] -> [v], 2 | a::l -> let l', size = aux v l in a::l', (size + 1) in function | [] -> v, [], 1 | a::l -> let l', size = aux v l in a, l', size in try let l = T.find free_connection_table k in let first, new_l, size = add_last v l in let new_l = if size > max_free_open_connections then begin Lwt_log.ign_info ~section "Too much free connections. Removing the oldest one."; ignore (!(fst first) >>= fun conn -> Lwt_ssl.shutdown (Ocsigen_http_com.connection_fd conn) Unix.SHUTDOWN_ALL; Lwt.return ()); new_l end else first::new_l in T.replace free_connection_table k new_l with Not_found -> T.replace free_connection_table k [v] let find_remove k = match T.find free_connection_table k with | [] -> T.remove free_connection_table k; raise Not_found | [a] -> T.remove free_connection_table k; a | a::l -> T.replace free_connection_table k l; a let remove k (conn, gf) = let rec aux = function | [] -> false, [] | ((conn2, _) as a)::l -> if conn2 == conn then true, l else let (b, ll) = aux l in b, a::ll in try match T.find free_connection_table k with | [] -> T.remove free_connection_table k; | [(conn2, _)] -> if conn == conn2 then T.remove free_connection_table k; | l -> let b, ll = aux l in if b then T.replace free_connection_table k ll; with Not_found -> () | exn -> Lwt_log.ign_info ~exn ~section "exception while removing from connection table" end let remove_on_error_from_free_conn key ((_, gf) as v) = ignore (Lwt.catch (fun () -> gf >>= fun _ -> Lwt.return ()) (fun _ -> FT.remove key v; Lwt.return () ) ) (*****************************************************************************) module KT = Hashtbl.Make( struct type t = Unix.inet_addr * int let equal = (=) let hash = Hashtbl.hash end) type k = Probing of int | Yes | No of float (* last failure date *) let pipelining_table = KT.create 100 let probing_time = 1000 (* number of requests for probing *) let purgatory_time = 10000 (* number of requests for probing after purgatory *) let purgatory_delay = 86400. (* 1 day *) let appreciate_server_pipeline inet_addr port = let key = (inet_addr, port) in match try match KT.find pipelining_table key with | Yes -> None | No t when Unix.time () -. t < purgatory_delay -> None | No t -> Lwt_log.ign_notice_f ~section "Give to server %a:%d a new probing period for pipelining." (fun () -> Unix.string_of_inet_addr) inet_addr port; Some purgatory_time (* second chance *) | Probing n -> Some (n-1) with Not_found -> Lwt_log.ign_notice_f ~section "Give to server %a:%d a first probing period for pipelining." (fun () -> Unix.string_of_inet_addr) inet_addr port; Some probing_time with | None -> () | Some n -> if n < 0 then begin Lwt_log.ign_warning_f ~section "Trusts server %a:%d for pipelining. He passed the probing period." (fun () -> Unix.string_of_inet_addr) inet_addr port; KT.replace pipelining_table key Yes end else KT.replace pipelining_table key (Probing n) let boycott_server_pipeline server_do_keepalive inet_addr port = if server_do_keepalive then Lwt_log.ign_warning_f ~section "Do not trust server %a:%d any more for pipelining. He just closed the connection!" (fun () -> Unix.string_of_inet_addr) inet_addr port; KT.replace pipelining_table (inet_addr, port) (No (Unix.time ())) let keep_alive_server inet_addr port = try match KT.find pipelining_table (inet_addr, port) with | Yes -> true | No _ -> Lwt_log.ign_info_f ~section "Do not trust server %a:%d for for pipelining." (fun () -> Unix.string_of_inet_addr) inet_addr port; false | Probing _ -> Lwt_log.ign_info_f ~section "Currently probing server %a:%d for pipelining. No pipeline for now." (fun () -> Unix.string_of_inet_addr) inet_addr port; false with Not_found -> false (*****************************************************************************) let handle_connection_error fd exn = match exn with | Unix.Unix_error (Unix.ECONNREFUSED, _, _) -> Lwt_unix.close fd >>= fun () -> Lwt.fail (Ocsigen_http_frame.Http_error.Http_exception (502, Some "Connection refused by distant server", None)) | Unix.Unix_error (Unix.ECONNRESET, _, _) -> (* Caused by shutting down the file descriptor after a timeout *) Lwt_unix.close fd >>= fun () -> Lwt.fail (Ocsigen_http_frame.Http_error.Http_exception (504, Some "Distant server closed connection", None)) | e -> Lwt.catch (fun () -> Lwt_unix.close fd) (fun _ -> Lwt.return ()) >>= fun () -> Lwt.fail e let raw_request ?client ?(keep_alive = true) ?headers ?(https=false) ?port ~content ?content_length ~http_method ~host ~inet_addr ~uri () = let uri = if uri = "" then "/" else uri in let head = http_method = Ocsigen_http_frame.Http_header.HEAD in let port = match port with | None -> if https then 443 else 80 | Some p -> p in Lwt_log.ign_info_f ~section "New request to host %s:%d for %s" host port uri; let keep_alive_asked = keep_alive in let server_do_keepalive = keep_alive_server inet_addr port in let do_keep_alive = keep_alive_asked && server_do_keepalive in let client = if not do_keep_alive then None else client in (* let do_pipeline = not client = None in *) if do_keep_alive then Lwt_log.ign_info ~section "Doing keep_alive" else Lwt_log.ign_info ~section "NOT doing keep_alive"; if client = None then Lwt_log.ign_info ~section "NOT pipelining" else Lwt_log.ign_info ~section "Will do pipelining if needed"; let close_on_error thr_conn gf = (* No need for lingering close, if I am not wrong *) ignore (thr_conn >>= fun conn -> (Lwt.catch (fun () -> gf >>= fun _ -> Lwt.return ()) (function | Ocsigen_http_com.Connection_closed -> Lwt_log.ign_info ~section "Connection closed by server (closing)"; Lwt_ssl.close (Ocsigen_http_com.connection_fd conn) | Ocsigen_http_com.Keepalive_timeout -> Lwt_log.ign_info ~section "Connection closed by keepalive timeout"; Lwt_ssl.close (Ocsigen_http_com.connection_fd conn) | exn -> Lwt_log.ign_warning ~section ~exn "Exception caught while receiving frame - closing connection to the server."; Lwt_ssl.close (Ocsigen_http_com.connection_fd conn) ))) in let new_conn () = let sockaddr = Unix.ADDR_INET (inet_addr, port) in let fd = Lwt_unix.socket (Unix.domain_of_sockaddr sockaddr) Unix.SOCK_STREAM 0 in Lwt_unix.set_close_on_exec fd; let thr_conn = let timeout = Lwt_timeout.create (Ocsigen_config.get_server_timeout ()) (fun () -> try Lwt_unix.shutdown fd Unix.SHUTDOWN_RECEIVE with _ -> ()); in Lwt.catch (fun () -> Lwt_timeout.start timeout; Lwt_unix.connect fd sockaddr >>= fun () -> (if https then let s = Lwt_ssl.embed_uninitialized_socket fd !sslcontext in Ssl.set_client_SNI_hostname (Lwt_ssl.ssl_socket_of_uninitialized_socket s) host; Lwt_ssl.ssl_perform_handshake s else Lwt.return (Lwt_ssl.plain fd)) >>= fun socket -> Lwt_timeout.stop timeout; Lwt.return (Ocsigen_http_com.create_receiver (Ocsigen_config.get_server_timeout ()) Ocsigen_http_com.Answer socket)) (Lwt_timeout.stop timeout; handle_connection_error fd) in let gf = thr_conn >>= fun conn -> Ocsigen_http_com.get_http_frame ~head conn in close_on_error thr_conn gf; (thr_conn, gf) in let find_conn () = (* If there is already a free connection for the same server, we reuse it *) try let c = FT.find_remove (inet_addr, port, head) in Lwt_log.ign_info ~section "Free connection found"; c with Not_found -> Lwt_log.ign_info ~section "Free connection not found - creating new one"; let (c, g) = new_conn () in (ref c, g) in let (key_new_waiter, ref_thr_conn, get_frame) = match client with | Some client when (content = None && (* Do not pipeline requests with content, as we cannot resend them for now if the pipeline failed. Do not pipeline CONNECT and POST. *) http_method <> Ocsigen_http_frame.Http_header.CONNECT) -> (* Trying to pipeline *) Lwt_log.ign_info_f ~section "Trying to find an opened connection for same client - connection number %a" (fun () x -> string_of_int (Ocsigen_extensions.client_id x)) client; let new_waiter, new_waiter_awakener = Lwt.wait () in let key = (Ocsigen_extensions.client_id client, (inet_addr, port, head)) in (* Is there already a connection for the same client? *) let (ref_thr_conn, get_frame, nb_users) = try let r = T.find connection_table key in Lwt_log.ign_info ~section "Connection FOUND for this client! PIPELINING!"; r with Not_found -> Lwt_log.ign_info ~section "Connection not found for this client's connection"; let (ref_thr_conn, gf) = find_conn () in (ref_thr_conn, gf, 0) in let new_get_frame = new_waiter >>= fun () -> !ref_thr_conn >>= fun conn -> let gf = Ocsigen_http_com.get_http_frame ~head conn in close_on_error !ref_thr_conn gf; gf in Lwt_log.ign_info ~section "Putting connection in connection_table"; T.replace connection_table key (ref_thr_conn, new_get_frame, nb_users + 1); (* remove_on_error key get_frame; *) (Some (key, new_waiter_awakener), ref_thr_conn, get_frame) | _ -> (* No pipeline *) let (ref_thr_conn, gf) = find_conn () in (None, ref_thr_conn, gf) in (* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ *) (* Now the request is pipelined. It is safe to return the function *) fun () -> let get_frame_ref = ref get_frame in let request_sent, request_sent_awakener = Lwt.wait () in (* awoken when request sent *) let query = Ocsigen_http_frame.Http_header.Query (http_method, uri) in let headers = Http_headers.replace (Http_headers.name "host") host (match headers with | None -> Http_headers.empty | Some h -> h) in let f ?reopen slot = Lwt_log.ign_info ~section "Will send request when slot opened"; Lwt.catch (fun () -> (match content with | None -> let empty_result = Ocsigen_http_frame.Result.empty () in Ocsigen_http_com.send ?reopen slot ~head:false (* We want to send the full request *) ~mode:query ~clientproto:Ocsigen_http_frame.Http_header.HTTP11 ~keep_alive:keep_alive_asked (* we request keep alive even if we do not pipeline if we don't trust the server *) ~sender:request_sender (Ocsigen_http_frame.Result.update empty_result ~headers ()) | Some stream -> Ocsigen_senders.Stream_content.result_of_content stream >>= fun r -> Ocsigen_http_com.send ?reopen slot ~mode:query ~head:false (* We want to send the full request *) ~clientproto:Ocsigen_http_frame.Http_header.HTTP11 ~keep_alive:keep_alive_asked ~sender:request_sender (Ocsigen_http_frame.Result.update r ~content_length ~headers ()) ) >>= fun () -> Lwt_log.ign_info ~section "request sent"; Lwt.wakeup request_sent_awakener (); Lwt.return ()) (fun e -> Lwt.wakeup_exn request_sent_awakener e; Lwt.fail e) in let reopen () = Lwt_log.ign_info ~section "Server not responding. Trying to open a new connection"; let (thr_conn, gf) = new_conn () in ref_thr_conn := thr_conn; get_frame_ref := gf; Lwt_log.ign_info ~section "Retrying to do the request"; thr_conn >>= fun conn -> Ocsigen_http_com.start_processing conn (f ?reopen:None); (* starting the request *) Lwt.return () in Lwt_log.ign_info ~section "Doing the request"; let thr_conn = !ref_thr_conn in thr_conn >>= fun conn -> Ocsigen_http_com.start_processing conn (f ~reopen); (* starting the request *) let finalize do_keep_alive = let put_in_free_conn ?gf () = Lwt_log.ign_info ~section "Putting in free connections"; let gf = match gf with | None -> let gf = !ref_thr_conn >>= fun conn -> Ocsigen_http_com.get_http_frame ~head conn in close_on_error !ref_thr_conn gf; gf | Some gf -> gf in try ignore (Lwt.poll gf); FT.add (inet_addr, port, head) (ref_thr_conn, gf); Lwt_log.ign_info ~section "Added in free connections"; remove_on_error_from_free_conn (inet_addr, port, head) (ref_thr_conn, gf) ; Lwt.return () with exn -> Lwt_log.ign_info ~section ~exn "exception while trying to keep free connection"; !ref_thr_conn >>= fun conn -> (* We can arrive here when there the server has closed the connection. In this case, we have already closed the connection as well, and we should ignore the error when attempting to close it again below. *) Lwt.catch (fun () -> Lwt_ssl.close (Ocsigen_http_com.connection_fd conn)) (fun _ -> Lwt.return ()) in if do_keep_alive then begin match key_new_waiter with | None -> (* no pipeline *) put_in_free_conn () | Some (key, _) -> (try let (ref_thr_conn, gf, nb_users) = T.find connection_table key in if nb_users = 1 then begin Lwt_log.ign_info ~section "The connection is not used any more by the client"; T.remove connection_table key; put_in_free_conn ~gf () end else begin T.replace connection_table key (ref_thr_conn, gf, nb_users - 1); Lwt.return () end with Not_found -> Lwt_log.ign_warning ~section "Strange: connection disappeared from connection_table"; Lwt.return ()) end else begin !ref_thr_conn >>= fun conn -> Lwt_ssl.close (Ocsigen_http_com.connection_fd conn) end in Lwt.catch (fun () -> Lwt.catch (fun () -> (* We wait for the request to be sent, because get_frame_ref may change *) request_sent >>= fun () -> (* getting and sending back the result: *) !get_frame_ref) (function | Pipeline_failed -> (* Previous request closed the pipeline but the request has been sent. We redo it. *) Lwt_log.ign_warning ~section "Previous request closed the pipeline. Redoing the request on a new connection."; reopen () >>= fun () -> !get_frame_ref | e -> Lwt.fail e)) (fun e -> (* We advice subsequent get_frame that the pipeline failed: *) (match key_new_waiter with | None -> () | Some (_, new_waiter_awakener) -> Lwt.wakeup_exn new_waiter_awakener Pipeline_failed); finalize false >>= fun () -> Lwt.fail e) >>= fun http_frame -> let server_keepalive = Ocsigen_headers.get_keepalive http_frame.Ocsigen_http_frame.frame_header in if keep_alive_asked && not server_keepalive then (* The server does not want to do keep-alive *) boycott_server_pipeline server_do_keepalive inet_addr port else if keep_alive_asked then appreciate_server_pipeline inet_addr port; let do_keep_alive = keep_alive_asked && server_keepalive in (* We keep alive even if we do not trust the server for pipelining *) (* It is now time for starting subsequent get_frame: *) (match key_new_waiter with | None -> () | Some (_, new_waiter) -> if server_keepalive then Lwt.wakeup new_waiter () else Lwt.wakeup_exn new_waiter Pipeline_failed); Lwt_log.ign_info ~section "frame received"; (match http_frame.Ocsigen_http_frame.frame_content with | None -> finalize do_keep_alive | Some c -> Ocsigen_stream.add_finalizer c (fun _ -> finalize do_keep_alive); Lwt.return () ) >>= fun () -> let headers = Http_headers.replace_opt Http_headers.connection None http_frame.Ocsigen_http_frame.frame_header.Ocsigen_http_frame.Http_header.headers in let headers = try let connection_value = Ocsigen_http_frame.Http_header.get_headers_value http_frame.Ocsigen_http_frame.frame_header Http_headers.connection in Http_headers.replace_opt (Http_headers.name connection_value) None headers with Not_found -> headers in Lwt.return {Ocsigen_http_frame.frame_header= {Ocsigen_http_frame.Http_header.mode = http_frame.Ocsigen_http_frame.frame_header.Ocsigen_http_frame.Http_header.mode; Ocsigen_http_frame.Http_header.proto = http_frame.Ocsigen_http_frame.frame_header.Ocsigen_http_frame.Http_header.proto; Ocsigen_http_frame.Http_header.headers = headers}; frame_content = http_frame.Ocsigen_http_frame.frame_content; frame_abort = http_frame.Ocsigen_http_frame.frame_abort; } (*****************************************************************************) let get ?v6 ?https ?port ?headers ~host ~uri () = Ip_address.get_inet_addr ?v6 host >>= fun inet_addr -> raw_request ?https ?port ?headers ~http_method:Ocsigen_http_frame.Http_header.GET ~content:None ~host:(match port with None -> host | Some p -> host^":"^string_of_int p) ~inet_addr ~uri () () let get_url ?v6 ?headers url = let (https, host, port, uri, _, _, _) = Url.parse url in let host = match host with None -> "localhost" | Some h -> h in let uri = "/"^uri in get ?v6 ?https ?port ?headers ~host ~uri () (*****************************************************************************) let post_string ?v6 ?https ?port ?(headers = Http_headers.empty) ~host ~uri ~content ~content_type () = Ip_address.get_inet_addr ?v6 host >>= fun inet_addr -> let content_type = String.concat "/" [fst content_type; snd content_type] in raw_request ?https ?port ~http_method:Ocsigen_http_frame.Http_header.POST ~content:(Some (Ocsigen_stream.of_string content)) ~content_length:(Int64.of_int (String.length content)) ~headers:(Http_headers.add Http_headers.content_type content_type headers) ~host:(match port with None -> host | Some p -> host^":"^string_of_int p) ~inet_addr ~uri () () let post_string_url ?v6 ?headers ~content ~content_type url = let (https, host, port, uri, _, _, _) = Url.parse url in let host = match host with None -> "localhost" | Some h -> h in let uri = "/"^uri in post_string ?v6 ?https ?port ?headers ~host ~uri ~content ~content_type () (*****************************************************************************) let post_urlencoded ?v6 ?https ?port ?headers ~host ~uri ~content () = post_string ?v6 ?https ?port ?headers ~host ~uri ~content:(Netencoding.Url.mk_url_encoded_parameters content) ~content_type:("application","x-www-form-urlencoded") () let post_urlencoded_url ?v6 ?headers ~content url = let (https, host, port, uri, _, _, _) = Url.parse url in let host = match host with None -> "localhost" | Some h -> h in let uri = "/"^uri in post_urlencoded ?v6 ?https ?port ?headers ~host ~uri ~content () (*****************************************************************************) let basic_raw_request ?headers ?(https=false) ?port ~content ?content_length ~http_method ~host ~inet_addr ~uri () = let port = match port with | None -> if https then 443 else 80 | Some p -> p in let sockaddr = Unix.ADDR_INET (inet_addr, port) in let fd = Lwt_unix.socket (Unix.domain_of_sockaddr sockaddr) Unix.SOCK_STREAM 0 in Lwt_unix.set_close_on_exec fd; Lwt.catch (fun () -> Lwt_unix.connect fd sockaddr >>= fun () -> (if https then let s = Lwt_ssl.embed_uninitialized_socket fd !sslcontext in Ssl.set_client_SNI_hostname (Lwt_ssl.ssl_socket_of_uninitialized_socket s) host; Lwt_ssl.ssl_perform_handshake s else Lwt.return (Lwt_ssl.plain fd))) (handle_connection_error fd) >>= fun socket -> let query = Ocsigen_http_frame.Http_header.Query (http_method, uri) in let conn = Ocsigen_http_com.create_receiver (Ocsigen_config.get_server_timeout ()) Ocsigen_http_com.Answer socket in let headers = Http_headers.replace (Http_headers.name "host") host (match headers with | None -> Http_headers.empty | Some h -> h) in let f slot = match content with | None -> let empty_result = Ocsigen_http_frame.Result.empty () in Ocsigen_http_com.send slot ~mode:query ~clientproto:Ocsigen_http_frame.Http_header.HTTP11 ~head:false ~keep_alive:false ~sender:request_sender (Ocsigen_http_frame.Result.update empty_result ~headers ()) | Some stream -> Ocsigen_senders.Stream_content.result_of_content stream >>= fun r -> Ocsigen_http_com.send slot ~mode:query ~clientproto:Ocsigen_http_frame.Http_header.HTTP11 ~head:false ~keep_alive:false ~sender:request_sender (Ocsigen_http_frame.Result.update r ~content_length ~headers ()) in Ocsigen_http_com.start_processing conn f; (* starting the request *) (* Ocsigen_http_com.wait_all_senders conn >>= fun () -> (* not needed *) *) Lwt.catch (fun () -> Ocsigen_http_com.get_http_frame ~head:(http_method = Ocsigen_http_frame.Http_header.HEAD) conn >>= fun http_frame -> (match http_frame.Ocsigen_http_frame.frame_content with | None -> Lwt_ssl.close socket | Some c -> Ocsigen_stream.add_finalizer c (fun _ -> Lwt_ssl.close socket); Lwt.return ()) >>= fun () -> Lwt.return http_frame) (fun e -> Lwt_ssl.close socket >>= fun () -> Lwt.fail e) ocsigenserver-2.16.0/src/server/ocsigen_http_client.mli000066400000000000000000000135411357715257700233310ustar00rootroot00000000000000(* Ocsigen * http://www.ocsigen.org * ocsigen_http_client.ml Copyright (C) 2005 Vincent Balat * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) (** Using Ocsigen as a HTTP client *) val get : ?v6:bool -> ?https: bool -> ?port:int -> ?headers: Http_headers.t -> host:string -> uri:string -> unit -> Ocsigen_http_frame.t Lwt.t (** Do a GET HTTP request. The default port is 80 for HTTP, 443 for HTTPS. The default protocol is http ([https=false]). Warning: the stream must be finalized manually after reading, using {!Ocsigen_stream.finalize}, otherwise you will have fd leaks. *) val get_url : ?v6:bool -> ?headers: Http_headers.t -> string -> Ocsigen_http_frame.t Lwt.t (** Do a GET HTTP request. The string must be a full URL. Warning: the stream must be finalized manually after reading, using {!Ocsigen_stream.finalize}, otherwise you will have fd leaks. *) val post_string : ?v6:bool -> ?https: bool -> ?port:int -> ?headers: Http_headers.t -> host:string -> uri:string -> content:string -> content_type:(string * string) -> unit -> Ocsigen_http_frame.t Lwt.t (** Do a POST HTTP request. The default port is 80 for HTTP, 443 for HTTPS. The default protocol is http ([https=false]). Warning: the stream must be finalized manually after reading, using {!Ocsigen_stream.finalize}, otherwise you will have fd leaks. *) val post_string_url : ?v6:bool -> ?headers: Http_headers.t -> content:string -> content_type:(string * string) -> string -> Ocsigen_http_frame.t Lwt.t (** Do a GET HTTP request. The string must be a full URL. Warning: the stream must be finalized manually after reading, using {!Ocsigen_stream.finalize}, otherwise you will have fd leaks. *) val post_urlencoded : ?v6:bool -> ?https: bool -> ?port:int -> ?headers: Http_headers.t -> host:string -> uri:string -> content:(string * string) list -> unit -> Ocsigen_http_frame.t Lwt.t (** Do a POST HTTP request with URL encoded parameters as content. The default port is 80 for HTTP, 443 for HTTPS. The default protocol is http ([https=false]). Warning: the stream must be finalized manually after reading, using {!Ocsigen_stream.finalize}, otherwise you will have fd leaks. *) val post_urlencoded_url : ?v6:bool -> ?headers: Http_headers.t -> content:(string * string) list -> string -> Ocsigen_http_frame.t Lwt.t (** Do a GET HTTP request with URL encoded parameters as content. The string must be a full URL. Warning: the stream must be finalized manually after reading, using {!Ocsigen_stream.finalize}, otherwise you will have fd leaks. *) val raw_request : ?client: Ocsigen_extensions.client -> ?keep_alive: bool -> ?headers: Http_headers.t -> ?https: bool -> ?port:int -> content: string Ocsigen_stream.t option -> ?content_length: int64 -> http_method: Ocsigen_http_frame.Http_header.http_method -> host:string -> inet_addr:Unix.inet_addr -> uri:string -> unit -> unit -> Ocsigen_http_frame.t Lwt.t (** Do an HTTP request (low level). If the optional argument [headers] is present, no headers will be added by Ocsigen, but those in this argument and host, and [connection: close] or [connection: keep-alive]. Be careful to respect HTTP/1.1 in this case! ([host] is the full Host HTTP field to send). The default port is 80 for HTTP, 443 for HTTPS. The default protocol is http ([https=false]). The optional parameter [~keep_alive] asks to keep the connection opened after the request for a short amount of time to allow other requests to the same server to use the same connection. It is true by default. If there is one opened free connection, we will use it instead of opening a new one. If you do this request to serve it later to a client or to generate a page for a client, add the optional parameter [~client]. Thus, the request you do will be pipelined with other requests coming from the same connection. A request will never be pipelined after a request from another client connection. Pipelining will be used only for requests to server we know supporting it (according to previous requests). It is recommended to specify this optional parameter for all requests (with the value found in field [ri_client] of type {!Ocsigen_extensions.request_info}). The optional parameter [?head] asks to do a [HEAD] HTTP request. It is [false] by default. When called without the last parameter, the function will pipeline the request (if needed), then return the function to get the page. This allows to keep pipeline order when writing an extension. *) (*VVV Dangerous!! *) val basic_raw_request : ?headers: Http_headers.t -> ?https: bool -> ?port:int -> content: string Ocsigen_stream.t option -> ?content_length: int64 -> http_method: Ocsigen_http_frame.Http_header.http_method -> host:string -> inet_addr:Unix.inet_addr -> uri:string -> unit -> Ocsigen_http_frame.t Lwt.t (** Same as {!Ocsigen_http_client.raw_request}, but does not try to reuse connections. Opens a new connections for each request. Far less efficient. *) (**/**) val sslcontext : Ssl.context ref ocsigenserver-2.16.0/src/server/ocsigen_local_files.ml000066400000000000000000000220631357715257700231160ustar00rootroot00000000000000(* Ocsigen * http://www.ocsigen.org * Copyright (C) 2009 Boris Yakobowski * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) open Ocsigen_extensions (* Displaying of a local file or directory. Currently used in staticmod and eliom_predefmod*) let section = Lwt_log.Section.make "ocsigen:local-file" exception Failed_403 exception Failed_404 exception NotReadableDirectory (* Policies for following symlinks *) type symlink_policy = stat:Unix.LargeFile.stats -> lstat:Unix.LargeFile.stats -> bool let never_follow_symlinks : symlink_policy = fun ~stat ~lstat -> false let follow_symlinks_if_owner_match : symlink_policy = fun ~stat ~lstat -> stat.Unix.LargeFile.st_uid = lstat.Unix.LargeFile.st_uid (* checks that [filename] can be followed depending on the predicate [policy] which must receives as argument both the results of calling [stat] and [lstat] on filenam. If supplied, [stat] must be the result of calling [Unix.stat] on [filename] *) let check_symlinks_aux filename ?(stat=Unix.LargeFile.stat filename) (policy : symlink_policy) = let lstat = Unix.LargeFile.lstat filename in if lstat.Unix.LargeFile.st_kind = Unix.S_LNK then policy ~stat ~lstat else true (* Check that there are no invalid symlinks in the directories leading to [filename]. Paths upwards [no_check_for] are not checked. *) let rec check_symlinks_parent_directories ~filename ~no_check_for (policy : symlink_policy) = if filename = "/" || filename = "." || Some filename = no_check_for then true else let dirname = Filename.dirname filename in check_symlinks_aux dirname policy && check_symlinks_parent_directories ~filename:dirname ~no_check_for policy (* Check that [filename] can be reached according to the given symlink policy *) let check_symlinks ~no_check_for ~filename policy = let aux policy = if filename = "/" then (* The root cannot be a symlink, and this avoids some degenerate cases later on *) true else let filename = (* [filename] should start by at least a slash, as [Filename.is_relative filename] should be false. Hence the length should be at least 1 *) (* We remove an eventual trailing slash, in order to avoid a needless recursion in check_symlinks_parent_directories, and so that Unix.lstat returns the correct result (Unix.lstat "foo/" and Unix.lstat "foo" return two different results...) *) let len = String.length filename - 1 in if filename.[len] = '/' then String.sub filename 0 len else filename in check_symlinks_aux filename policy && check_symlinks_parent_directories filename no_check_for policy in match policy with | AlwaysFollowSymlinks -> true | DoNotFollowSymlinks -> aux never_follow_symlinks | FollowSymlinksIfOwnerMatch -> aux follow_symlinks_if_owner_match let check_dotdot = let regexp = Netstring_pcre.regexp "(/\\.\\./)|(/\\.\\.$)" in fun ~filename -> (* We always reject .. in filenames. In URLs, .. have already been removed by the server, but the filename may come from somewhere else than URLs ... *) try ignore (Netstring_pcre.search_forward regexp filename 0); false with Not_found -> true let can_send filename request = let filename = Neturl.join_path (Neturl.norm_path (Neturl.split_path filename)) in Lwt_log.ign_info_f ~section "checking if file %s can be sent" filename; let matches arg = Netstring_pcre.string_match (Ocsigen_extensions.do_not_serve_to_regexp arg) filename 0 <> None in if matches request.do_not_serve_403 then ( Lwt_log.ign_info ~section "this file is forbidden"; raise Failed_403) else if matches request.do_not_serve_404 then ( Lwt_log.ign_info ~section "this file must be hidden"; raise Failed_404) (* Return type of a request for a local file. The string argument represents the real file/directory to serve, eg. foo/index.html instead of foo *) type resolved = | RFile of string | RDir of string (* given [filename], we search for it in the local filesystem and - we return ["filename/index.html"] if [filename] corresponds to a directory, ["filename/index.html"] is valid, and ["index.html"] is one possible index (trying all possible indexes in order) - we raise [Failed_404] if [filename] corresponds to a directory, no index exists and [list_dir_content] is false. Warning: this behaviour is not the same as Apache's but it corresponds to a missing service in Eliom (answers 404). This also allows to have an Eliom service after a "forbidden" directory - we raise [Failed_403] if [filename] is a symlink that must not be followed - raises [Failed_404] if [filename] does not exist, or is a special file - otherwise returns [filename] *) (* See also module Files in eliom.ml *) let resolve ?no_check_for ~request ~filename () = (* We only accept absolute filenames in daemon mode, as we do not really know what is the current directory *) let filename = if Filename.is_relative filename && Ocsigen_config.get_daemon () then "/"^filename else filename in try Lwt_log.ign_info_f ~section "Testing \"%s\"." filename; let stat = Unix.LargeFile.stat filename in let (filename, stat) = if stat.Unix.LargeFile.st_kind = Unix.S_DIR then if filename.[String.length filename - 1] <> '/' then begin (* In this case, [filename] is a directory but this is not visible in its name as there is no final slash. We signal this fact to Ocsigen, which will then issue a 301 redirection to "filename/" *) Lwt_log.ign_info_f ~section "LocalFiles: %s is a directory" filename; raise (Ocsigen_extensions.Ocsigen_Is_a_directory (Ocsigen_extensions.new_url_of_directory_request request)) end else let rec find_index = function | [] -> (* No suitable index, we try to list the directory *) if request.request_config.list_directory_content then ( Lwt_log.ign_info ~section "Displaying directory content"; (filename, stat)) else ( (* No suitable index *) Lwt_log.ign_info ~section "No index and no listing"; raise NotReadableDirectory) | e :: q -> let index = filename ^ e in Lwt_log.ign_info_f ~section "Testing \"%s\" as possible index." index; try (index, Unix.LargeFile.stat index) with | Unix.Unix_error (Unix.ENOENT, _, _) -> find_index q in find_index request.request_config.default_directory_index else (filename, stat) in if not (check_dotdot ~filename) then (Lwt_log.ign_info_f ~section "Filenames cannot contain .. as in \"%s\"." filename; raise Failed_403) else if check_symlinks ~filename ~no_check_for request.request_config.follow_symlinks then ( can_send filename request.request_config; (* If the previous function did not fail, we are authorized to send this file *) Lwt_log.ign_info_f ~section "Returning \"%s\"." filename; if stat.Unix.LargeFile.st_kind = Unix.S_REG then RFile filename else if stat.Unix.LargeFile.st_kind = Unix.S_DIR then RDir filename else raise Failed_404 ) else ( (* [filename] is accessed through as symlink which we should not follow according to the current policy *) Lwt_log.ign_info_f ~section "Failed symlink check for \"%s\"." filename; raise Failed_403) with (* We can get an EACCESS here, if are missing some rights on a directory *) | Unix.Unix_error (Unix.EACCES,_,_) -> raise Failed_403 | Unix.Unix_error (Unix.ENOENT,_,_) -> raise Failed_404 (* Given a local file or directory, we retrieve its content *) let content ~request ~file = try match file with | RDir dirname -> Ocsigen_senders.Directory_content.result_of_content (dirname, Ocsigen_request_info.full_path request.request_info) | RFile filename -> Ocsigen_senders.File_content.result_of_content (filename, request.request_config.charset_assoc, request.request_config.mime_assoc ) with | Unix.Unix_error (Unix.EACCES,_,_) -> raise Failed_403 ocsigenserver-2.16.0/src/server/ocsigen_local_files.mli000066400000000000000000000057061357715257700232740ustar00rootroot00000000000000(* Ocsigen * http://www.ocsigen.org * Copyright (C) 2009 Boris Yakobowski * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) (** The requested file does not exists *) exception Failed_404 (** The requested file cannot be served: does not exists, not enough permissions ... *) exception Failed_403 (** The file is a directory which we should not display *) exception NotReadableDirectory (* (** Default options: - never follow symlinks - use "index.html" as default index - do not list the content of directories *) val default_options : options *) (** Local file corresponding to a request. The string argument represents the real file or directory to serve, eg. foo/index.html instead of foo *) type resolved = | RFile of string | RDir of string (** Finds [filename] in the filesystem, with a possible redirection if it is a directory. Takes into account the fact that [filename] does not exists, is a symlink or is a directory, and raises Failed_404 or Failed_403 accordingly. - we return ["filename/index.html"] if [filename] corresponds to a directory, ["filename/index.html"] is valid, and ["index.html"] is one possible index (trying all possible indexes in order) - we raise [Failed_404] if [filename] corresponds to a directory, no index exists and [list_dir_content] is false. Warning: this behaviour is not the same as Apache's but it corresponds to a missing service in Eliom (answers 404). This also allows to have an Eliom service after a "forbidden" directory - we raise [Failed_403] if [filename] is a symlink that must not be followed - raises [Failed_404] if [filename] does not exist, or is a special file - otherwise returns [filename] [no_check_for] is supposed to be a prefix of [filename] ; directories above [no_check_for] are not checked for symlinks *) val resolve : ?no_check_for:string -> request:Ocsigen_extensions.request -> filename:string -> unit -> resolved (** Given the local file [file], with a request originating at url [url], returns a viewable content of [file]. Currently, the [url] parameter is used only if [url] is a directory *) val content: request:Ocsigen_extensions.request -> file:resolved -> Ocsigen_http_frame.result Lwt.t ocsigenserver-2.16.0/src/server/ocsigen_parseconfig.ml000066400000000000000000000737411357715257700231530ustar00rootroot00000000000000(* Ocsigen * http://www.ocsigen.org * Module ocsigen_parseconfig.ml * Copyright (C) 2005-2008 Vincent Balat, Nataliya Guts, Stéphane Glondu * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) (******************************************************************) (** Config file parsing *) open Ocsigen_lib open Ocsigen_socket open Xml open Ocsigen_config let section = Lwt_log.Section.make "ocsigen:config" let blah_of_string f tag s = try f (String.remove_spaces s 0 ((String.length s) -1)) with Failure _ -> raise (Ocsigen_config.Config_file_error ("While parsing <"^tag^"> - "^s^ " is not a valid value.")) let int_of_string = blah_of_string int_of_string let float_of_string = blah_of_string float_of_string type ssl_info = { ssl_certificate : string option; ssl_privatekey : string option; ssl_ciphers : string option; ssl_dhfile : string option; ssl_curve : string option } (*****************************************************************************) let default_default_hostname = let hostname = Unix.gethostname () in try (*VVV Is it ok? Is it reliable? *) (List.hd (Unix.getaddrinfo hostname "www" [Unix.AI_CANONNAME; Unix.AI_SOCKTYPE Unix.SOCK_STREAM])).Unix.ai_canonname with Failure _ -> Lwt_log.ign_warning_f ~section "Cannot determine default host name. Will use \"%s\" \ to create absolute links or redirections dynamically \ if you do not set \ in config file." hostname; (*VVV Is it the right behaviour? *) hostname (*****************************************************************************) let parse_size = let kilo = Int64.of_int 1000 in let mega = Int64.of_int 1000000 in let giga = Int64.mul kilo mega in let tera = Int64.mul mega mega in let kibi = Int64.of_int 1024 in let mebi = Int64.of_int 1048576 in let gibi = Int64.mul kibi mebi in let tebi = Int64.mul mebi mebi in fun s -> let l = String.length s in let s = String.remove_spaces s 0 (l-1) in let v l = try Int64.of_string (String.sub s 0 l) with Failure _ -> failwith "Ocsigen_parseconfig.parse_size" in let o l = let l1 = l-1 in if l1>0 then let c1 = s.[l1] in if (c1 = 'o') || (c1 = 'B') then v l1 else v l else v l in if (s = "") || (s = "infinity") then None else Some (let l = String.length s in let l1 = l-1 in if l1>0 then let c1 = String.sub s l1 1 in if (c1 = "T") then Int64.mul tebi (v l1) else if (c1 = "G") then Int64.mul gibi (v l1) else if (c1 = "M") then Int64.mul mebi (v l1) else if (c1 = "k") then Int64.mul kibi (v l1) else let l2 = l-2 in if l2>0 then let c2 = String.sub s l2 2 in if (c2 = "To") || (c2 = "TB") then Int64.mul tera (v l2) else if (c2 = "Go") || (c2 = "GB") then Int64.mul giga (v l2) else if (c2 = "Mo") || (c2 = "MB") then Int64.mul mega (v l2) else if (c2 = "ko") || (c2 = "kB") then Int64.mul kilo (v l2) else let l3 = l-3 in if l3>0 then let c3 = String.sub s l3 3 in if (c3 = "Tio") || (c3 = "TiB") then Int64.mul tebi (v l3) else if (c3 = "Gio") || (c3 = "GiB") then Int64.mul gibi (v l3) else if (c3 = "Mio") || (c3 = "MiB") then Int64.mul mebi (v l3) else if (c3 = "kio") || (c3 = "kiB") then Int64.mul kibi (v l3) else o l else o l else o l else o l) let parse_size_tag tag s = try parse_size s with Failure _ -> raise (Ocsigen_config.Config_file_error ("While parsing <"^tag^"> - "^s^" is not a valid size.")) (* My xml parser is not really adapted to this. It is the parser for the syntax extension. But it works. *) let rec parse_string = function | [] -> "" | (PCData s)::l -> s^(parse_string l) | _ -> failwith "ocsigen_parseconfig.parse_string" let parse_string_tag tag s = try parse_string s with Failure _ -> raise (Ocsigen_config.Config_file_error ("While parsing <"^tag^"> - String expected.")) let rec parser_config = let rec parse_servers n = function | [] -> (match n with | [] -> raise (Config_file_error (" tag expected")) | _ -> n) | (Element ("server", [], nouveau))::ll -> (match ll with | [] -> () | _ -> Lwt_log.ign_warning ~section "At most one tag possible in config file. \ Ignoring trailing data."); parse_servers (n@[nouveau]) [] (* ll *) (* Multiple server not supported any more *) (* nouveau at the end *) | _ -> raise (Config_file_error ("syntax error inside ")) in function | (Element ("ocsigen", [], l)) -> parse_servers [] l | _ -> raise (Config_file_error " tag expected") let parse_ext file = parser_config (Xml.parse_file file) let preloadfile config () = Ocsigen_extensions.set_config config let postloadfile () = Ocsigen_extensions.set_config [] (* Checking hostnames. We make only make looze efforts. See RFC 921 and 952 for further details *) let correct_hostname = let regexp = Netstring_pcre.regexp "^[a-zA-Z0-9]+((\\.|-)[a-zA-Z0-9]+)*$" in fun h -> Netstring_pcre.string_match regexp h 0 <> None (* Splits the [host] field, first according to spaces (which encode disjunction), and then according to wildcards '*' ; we then transform the hosts-with-regexp into a regepx that matches a potential host. The whole result is cached because user config files (for userconf) are read at each request. *) let parse_host_field = let h = Hashtbl.create 17 in (fun (hostfilter : string option) -> try Hashtbl.find h hostfilter with Not_found -> let r = match hostfilter with | None -> ["*", Netstring_pcre.regexp ".*$", None] (* default = "*:*" *) | Some s -> let parse_one_host ss = let host, port = try let dppos = String.index ss ':' and len = String.length ss in let host = String.sub ss 0 dppos and port = match String.sub ss (dppos+1) ((len - dppos) - 1) with | "*" -> None | p -> Some (int_of_string "host" p) in host, port with | Not_found -> ss, None | Failure _ -> raise (Config_file_error "bad port number") in let split_host = function | Netstring_str.Delim _ -> ".*" | Netstring_str.Text t -> Netstring_pcre.quote t in (host, Netstring_pcre.regexp (String.concat "" ((List.map split_host (Netstring_str.full_split (Netstring_str.regexp "[*]+") host))@["$"])), port) in List.map parse_one_host (Netstring_str.split (Netstring_str.regexp "[ \t]+") s) in Hashtbl.add h hostfilter r; (r : Ocsigen_extensions.virtual_hosts) ) (* Extract a default hostname from the "host" field if no default is provided *) let get_defaulthostname ~defaulthostname ~defaulthttpport ~host = match defaulthostname with | Some d -> d | None -> (* We look for a hostname without wildcard (second case) *) (* Something more clever could be envisioned *) let rec aux = function | [] -> default_default_hostname | (t, _, (Some 80 | None)) :: _ when String.contains t '*' = false -> t | _ :: q -> aux q in let host = aux host in Lwt_log.ign_warning_f ~section "While parsing config file, tag : No defaulthostname, \ assuming it is \"%s\"" host; if correct_hostname host then host else raise (Ocsigen_config.Config_file_error ("Incorrect hostname " ^ host)) (* Config file is parsed twice. This is the second parsing (site loading) *) let parse_server isreloading c = let rec parse_server_aux = function | [] -> [] | (Element ("port", atts, p))::ll -> parse_server_aux ll | (Element ("charset" as st, atts, p))::ll -> set_default_charset (Some (parse_string_tag st p)); parse_server_aux ll | (Element ("logdir", [], p))::ll -> parse_server_aux ll | (Element ("syslog", [], p))::ll -> parse_server_aux ll | (Element ("ssl", [], p))::ll -> parse_server_aux ll | (Element ("user", [], p))::ll -> parse_server_aux ll | (Element ("group", [], p))::ll -> parse_server_aux ll | (Element ("uploaddir" as st, [], p))::ll -> set_uploaddir (Some (parse_string_tag st p)); parse_server_aux ll | (Element ("datadir" as st, [], p))::ll -> set_datadir (parse_string_tag st p); parse_server_aux ll | (Element ("minthreads" as st, [], p))::ll -> set_minthreads (int_of_string st (parse_string_tag st p)); parse_server_aux ll | (Element ("maxthreads" as st, [], p))::ll -> set_maxthreads (int_of_string st (parse_string_tag st p)); parse_server_aux ll | (Element ("maxdetachedcomputationsqueued" as st, [], p))::ll -> set_max_number_of_threads_queued (int_of_string st (parse_string_tag st p)); parse_server_aux ll | (Element ("maxconnected" as st, [], p))::ll -> set_max_number_of_connections (int_of_string st (parse_string_tag st p)); parse_server_aux ll | (Element ("mimefile" as st, [], p))::ll -> Ocsigen_config.set_mimefile (parse_string_tag st p); parse_server_aux ll | (Element ("maxretries" as st, [], p))::ll -> set_maxretries (int_of_string st (parse_string_tag st p)); parse_server_aux ll | (Element ("timeout" as st, [], p))::ll (*VVV timeout: backward compatibility with <= 0.99.4 *) | (Element ("clienttimeout" as st, [], p))::ll -> set_client_timeout (int_of_string st (parse_string_tag st p)); parse_server_aux ll | (Element ("servertimeout" as st, [], p))::ll -> set_server_timeout (int_of_string st (parse_string_tag st p)); parse_server_aux ll (*VVV For now we use silentservertimeout and silentclienttimeout also for keep alive :-( | (Element ("keepalivetimeout" as st, [], p))::ll -> set_keepalive_timeout (int_of_string st (parse_string_tag st p)); parse_server_aux ll | (Element ("keepopentimeout" as st, [], p))::ll -> set_keepopen_timeout (int_of_string st (parse_string_tag st p)); parse_server_aux ll *) | (Element ("netbuffersize" as st, [], p))::ll -> set_netbuffersize (int_of_string st (parse_string_tag st p)); parse_server_aux ll | (Element ("filebuffersize" as st, [], p))::ll -> set_filebuffersize (int_of_string st (parse_string_tag st p)); parse_server_aux ll | (Element ("maxrequestbodysize" as st, [], p))::ll -> set_maxrequestbodysize (parse_size_tag st (parse_string_tag st p)); parse_server_aux ll | (Element ("maxuploadfilesize" as st, [], p))::ll -> set_maxuploadfilesize (parse_size_tag st (parse_string_tag st p)); parse_server_aux ll | (Element ("commandpipe" as st, [], p))::ll -> set_command_pipe (parse_string_tag st p); parse_server_aux ll | (Element ("shutdowntimeout" as st, [], p))::ll -> let p = parse_string_tag st p in let t = if p = "notimeout" then None else Some (float_of_string st p) in set_shutdown_timeout t; parse_server_aux ll | (Element ("debugmode", [], []))::ll -> set_debugmode true; parse_server_aux ll | (Element ("usedefaulthostname", [], []))::ll -> set_usedefaulthostname true; parse_server_aux ll | (Element ("disablepartialrequests", [], []))::ll -> set_disablepartialrequests true; parse_server_aux ll | (Element ("respectpipeline", [], []))::ll -> set_respect_pipeline (); parse_server_aux ll | (Element ("findlib", ["path",p], []))::ll -> Ocsigen_loader.add_ocamlpath p; parse_server_aux ll | (Element ("require", atts, l))::ll | (Element ("extension", atts, l))::ll -> (* We do not reload extensions *) let modules = match atts with | [] -> raise (Config_file_error "missing module, name or findlib-package attribute in ") | [("name", s)] -> `Name s | [("module", s)] -> `Files [s] | [("findlib-package", s)] -> `Files (Ocsigen_loader.findfiles s) | _ -> raise (Config_file_error "Wrong attribute for ") in begin match modules with | `Files modules -> Ocsigen_loader.loadfiles (preloadfile l) postloadfile false modules; | `Name name -> Ocsigen_loader.init_module (preloadfile l) postloadfile false name end; parse_server_aux ll | (Element ("library", atts, l))::ll -> let modules = match atts with | [] -> raise (Config_file_error "missing module or findlib-package attribute in ") | [("name", s)] -> `Name s | [("module", s)] -> `Files [s] | [("findlib-package", s)] -> `Files (Ocsigen_loader.findfiles s) | _ -> raise (Config_file_error "Wrong attribute for ") in begin match modules with | `Files modules -> Ocsigen_loader.loadfiles (preloadfile l) postloadfile true modules; | `Name name -> Ocsigen_loader.init_module (preloadfile l) postloadfile true name end; parse_server_aux ll | (Element ("host", atts, l))::ll -> let rec parse_attrs ((name, charset, defaulthostname, defaulthttpport, defaulthttpsport, ishttps) as r) = function | [] -> r | ("hostfilter", s)::suite -> (match name with | None -> parse_attrs ((Some s), charset, defaulthostname, defaulthttpport, defaulthttpsport, ishttps) suite | _ -> raise (Ocsigen_config.Config_file_error ("Duplicate attribute name in "))) | ("charset", s)::suite -> (match charset with | None -> parse_attrs (name, Some s, defaulthostname, defaulthttpport, defaulthttpsport, ishttps) suite | _ -> raise (Ocsigen_config.Config_file_error ("Duplicate attribute charset in "))) | ("defaulthostname", s)::suite -> (match defaulthostname with | None -> if correct_hostname s then parse_attrs (name, charset, (Some s), defaulthttpport, defaulthttpsport, ishttps) suite else raise (Ocsigen_config.Config_file_error ("Incorrect hostname " ^ s)) | _ -> raise (Ocsigen_config.Config_file_error ("Duplicate attribute defaulthostname in "))) | ("defaulthttpport", s)::suite -> (match defaulthttpport with | None -> parse_attrs (name, charset, defaulthostname, (Some s), defaulthttpsport, ishttps) suite | _ -> raise (Ocsigen_config.Config_file_error ("Duplicate attribute defaulthttpport in "))) | ("defaulthttpsport", s)::suite -> (match defaulthttpsport with | None -> parse_attrs (name, charset, defaulthostname, defaulthttpport, Some s, ishttps) suite | _ -> raise (Ocsigen_config.Config_file_error ("Duplicate attribute defaulthttpsport in "))) | ("defaultprotocol", s)::suite -> (match ishttps with | None -> parse_attrs (name, charset, defaulthostname, defaulthttpport, defaulthttpsport, Some s) suite | _ -> raise (Ocsigen_config.Config_file_error ("Duplicate attribute defaultprotocol in "))) | (s, _)::_ -> raise (Ocsigen_config.Config_file_error ("Wrong attribute for : "^s)) in let host, charset, defaulthostname, defaulthttpport, defaulthttpsport, defaultprotocol = parse_attrs (None, None, None, None, None, None) atts in let host = parse_host_field host in let charset = match charset, Ocsigen_config.get_default_charset () with | Some charset, _ | None, Some charset -> charset | None, None -> "utf-8" in let defaulthttpport = match defaulthttpport with | None -> Ocsigen_config.get_default_port () | Some p -> int_of_string "host" p in let defaulthostname = get_defaulthostname ~defaulthostname ~defaulthttpport ~host in let defaulthttpsport = match defaulthttpsport with | None -> Ocsigen_config.get_default_sslport () | Some p -> int_of_string "host" p in let serve_everything = { Ocsigen_extensions.do_not_serve_regexps = []; do_not_serve_files = []; do_not_serve_extensions = []; } in let conf = { Ocsigen_extensions.default_hostname = defaulthostname; default_httpport = defaulthttpport; default_httpsport = defaulthttpsport; default_protocol_is_https = defaultprotocol = Some "https"; mime_assoc = Ocsigen_charset_mime.default_mime_assoc (); charset_assoc = Ocsigen_charset_mime.empty_charset_assoc ~default:charset (); default_directory_index = ["index.html"]; list_directory_content = false; follow_symlinks = Ocsigen_extensions.FollowSymlinksIfOwnerMatch; do_not_serve_404 = serve_everything; do_not_serve_403 = serve_everything; uploaddir = Ocsigen_config.get_uploaddir (); maxuploadfilesize = Ocsigen_config.get_maxuploadfilesize (); } in let parse_host = Ocsigen_extensions.parse_config_item host conf in let parse_config = Ocsigen_extensions.make_parse_config [] parse_host in (* default site for host *) (host, conf, parse_config l)::(parse_server_aux ll) | (Element ("extconf", [("dir", dir)], []))::ll -> let one = try let files = Sys.readdir dir in Array.sort compare files; Array.fold_left (fun l s -> if Filename.check_suffix s "conf" then let filename = dir^"/"^s in let filecont = try Lwt_log.ign_info_f ~section "Parsing configuration file %s" filename; parse_ext filename with e -> Lwt_log.ign_error_f ~section ~exn:e "Error while loading configuration file %s (ignored)" filename; [] in (match filecont with | [] -> l | s::_ -> l@(parse_server_aux s) ) else l ) [] files with | Sys_error _ as e -> Lwt_log.ign_error ~section ~exn:e "Error while loading configuration file (ignored)"; [] in one@(parse_server_aux ll) | (Element (tag, _, _))::_ -> raise (Config_file_error ("tag <"^tag^"> unexpected inside ")) | _ -> raise (Config_file_error "Syntax error") in Ocsigen_extensions.set_hosts (parse_server_aux c) (* Parsing tags *) let parse_port = let all_ipv6 = Netstring_pcre.regexp "^\\[::\\]:([0-9]+)$" in let all_ipv4 = Netstring_pcre.regexp "^\\*:([0-9]+)$" in let single_ipv6 = Netstring_pcre.regexp "^\\[([0-9A-Fa-f.:]+)\\]:([0-9]+)$" in let single_ipv4 = Netstring_pcre.regexp "^([0-9.]+):([0-9]+)$" in fun s -> let do_match r = Netstring_pcre.string_match r s 0 in let get x i = Netstring_pcre.matched_group x i s in match do_match all_ipv6 with | Some r -> IPv6 (Unix.inet6_addr_any), int_of_string "port" (get r 1) | None -> match do_match all_ipv4 with | Some r -> IPv4 (Unix.inet_addr_any), int_of_string "port" (get r 1) | None -> match do_match single_ipv6 with | Some r -> IPv6 (Unix.inet_addr_of_string (get r 1)), int_of_string "port" (get r 2) | None -> match do_match single_ipv4 with | Some r -> IPv4 (Unix.inet_addr_of_string (get r 1)), int_of_string "port" (get r 2) | None -> All, int_of_string "port" s let parse_facility = function | "auth" -> `Auth | "authpriv" -> `Authpriv | "console" -> `Console | "cron" -> `Cron | "daemon" -> `Daemon | "ftp" -> `FTP | "kernel" -> `Kernel | "lpr" -> `LPR | "local0" -> `Local0 | "local1" -> `Local1 | "local2" -> `Local2 | "local3" -> `Local3 | "local4" -> `Local4 | "local5" -> `Local5 | "local6" -> `Local6 | "local7" -> `Local7 | "mail" -> `Mail | "ntp" -> `NTP | "news" -> `News | "security" -> `Security | "syslog" -> `Syslog | "uucp" -> `UUCP | "user" -> `User | t -> raise (Config_file_error ("Unknown " ^ t ^ " facility in ")) (* First parsing of config file *) let config_error_for_some s = function | None -> () | _ -> raise (Config_file_error s) let make_ssl_info ~certificate ~privatekey ~ciphers ~dhfile ~curve = { ssl_certificate = certificate; ssl_privatekey = privatekey; ssl_ciphers = ciphers; ssl_dhfile = dhfile; ssl_curve = curve } let rec parse_ssl l ~certificate ~privatekey ~ciphers ~dhfile ~curve = match l with | [] -> Some (make_ssl_info ~certificate ~privatekey ~ciphers ~dhfile ~curve) | Element ("certificate" as st, [], p) :: l -> config_error_for_some "Two certificates inside " certificate; let certificate = Some (parse_string_tag st p) in parse_ssl ~certificate ~privatekey ~ciphers ~dhfile ~curve l | Element ("privatekey" as st, [], p) ::l -> config_error_for_some "Two private keys inside " privatekey; let privatekey = Some (parse_string_tag st p) in parse_ssl ~certificate ~privatekey ~ciphers ~dhfile ~curve l | Element ("ciphers" as st, [], p) :: l -> config_error_for_some "Two cipher strings inside " ciphers; let ciphers = Some (parse_string_tag st p) in parse_ssl ~certificate ~privatekey ~ciphers ~dhfile ~curve l | Element ("dhfile" as st, [], p) :: l -> config_error_for_some "Two DH files inside " dhfile; let dhfile = Some (parse_string_tag st p) in parse_ssl ~certificate ~privatekey ~ciphers ~dhfile ~curve l | Element ("curve" as st, [], p) :: l -> config_error_for_some "Two (EC) curves inside " curve; let curve = Some (parse_string_tag st p) in parse_ssl ~certificate ~privatekey ~ciphers ~dhfile ~curve l | Element (tag, _, _) :: l -> raise (Config_file_error ("<"^tag^"> tag unexpected inside ")) | _ -> raise (Config_file_error ("Unexpected content inside ")) let extract_info c = let rec aux user group ssl ports sslports minthreads maxthreads = function [] -> ((user, group), (ssl, ports,sslports), (minthreads, maxthreads)) | (Element ("logdir" as st, [], p))::ll -> set_logdir (parse_string_tag st p); aux user group ssl ports sslports minthreads maxthreads ll | (Element ("syslog" as st, [], p))::ll -> let str = String.lowercase (parse_string_tag st p) in set_syslog_facility (Some (parse_facility str)); aux user group ssl ports sslports minthreads maxthreads ll | (Element ("port" as st, atts, p))::ll -> (match atts with [] | [("protocol", "HTTP")] -> let po = try parse_port (parse_string_tag st p) with Failure _ -> raise (Config_file_error "Wrong value for tag") in aux user group ssl (po::ports) sslports minthreads maxthreads ll | [("protocol", "HTTPS")] -> let po = try parse_port (parse_string_tag st p) with Failure _ -> raise (Config_file_error "Wrong value for tag") in aux user group ssl ports (po::sslports) minthreads maxthreads ll | _ -> raise (Config_file_error "Wrong attribute for ")) | (Element ("minthreads" as st, [], p))::ll -> aux user group ssl ports sslports (Some (int_of_string st (parse_string_tag st p))) maxthreads ll | (Element ("maxthreads" as st, [], p))::ll -> aux user group ssl ports sslports minthreads (Some (int_of_string st (parse_string_tag st p))) ll | (Element ("ssl", [], p))::ll -> (match ssl with None -> let ssl = let certificate = None and privatekey = None and ciphers = None and dhfile = None and curve = None in parse_ssl ~certificate ~privatekey ~ciphers ~dhfile ~curve p in aux user group ssl ports sslports minthreads maxthreads ll | _ -> raise (Config_file_error "Only one ssl certificate for each server supported for now")) | (Element ("user" as st, [], p))::ll -> (match user with None -> aux (Some (parse_string_tag st p)) group ssl ports sslports minthreads maxthreads ll | _ -> raise (Config_file_error "Only one tag for each server allowed")) | (Element ("group" as st, [], p))::ll -> (match group with None -> aux user (Some (parse_string_tag st p)) ssl ports sslports minthreads maxthreads ll | _ -> raise (Config_file_error "Only one tag for each server allowed")) | (Element ("commandpipe" as st, [], p))::ll -> set_command_pipe (parse_string_tag st p); aux user group ssl ports sslports minthreads maxthreads ll | (Element (tag, _, _))::ll -> aux user group ssl ports sslports minthreads maxthreads ll | _ -> raise (Config_file_error "Syntax error") in let (user, group), si, (mint, maxt) = aux None None None [] [] None None c in let user = match user with None -> None (* Some (get_default_user ()) *) | Some s -> if s = "" then None else Some s in let group = match group with None -> None (* Some (get_default_group ()) *) | Some s -> if s = "" then None else Some s in let mint = match mint with | Some t -> t | None -> get_minthreads () in let maxt = match maxt with | Some t -> t | None -> get_maxthreads () in ((user, group), si, (mint, maxt)) let parse_config ?file () = let file = match file with | None -> Ocsigen_config.get_config_file () | Some f -> f in parser_config (Xml.parse_file file) (******************************************************************) ocsigenserver-2.16.0/src/server/ocsigen_parseconfig.mli000066400000000000000000000061171357715257700233150ustar00rootroot00000000000000(* Ocsigen * http://www.ocsigen.org * Module ocsigen_parseconfig.ml * Copyright (C) 2005 Vincent Balat, Nataliya Guts * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) (** Config file parsing. See also module {! Ocsigen_extensions.​Configuration } *) (** Parse a size ("infinity" or using SI or binary units, e.g. 10 10B 10o 10ko 10kB 10kiB 10MiB 10TB ...). Raises [Failure "Ocsigen_parseconfig.parse_size"] in case of error. *) val parse_size : string -> int64 option (** [parse_size_tag tag s] parses a size (same syntax as [parse_size]). In case of error, raises [Ocsigen_config.Config_file_error m] where [m] is an error message explaining that a size was expected in tag []. *) val parse_size_tag : string -> string -> int64 option (** Parse a string (PCDATA) as XML content. Raises [Failure "Ocsigen_parseconfig.parse_string"] in case of error. *) val parse_string : Xml.xml list -> string (** [parse_string_tag tag s] parses a string (same syntax as [parse_string]). In case of error, raises [Ocsigen_config.Config_file_error m] where [m] is an error message explaining that a string was expected in tag []. *) val parse_string_tag : string -> Xml.xml list -> string (** Parses the [hostfilter] field of the configuration file, which is a disjunction of possible hostnames (that can themselves contain wildcards) *) val parse_host_field: string option -> Ocsigen_extensions.virtual_hosts (**/**) val parser_config : Xml.xml -> Xml.xml list list val parse_server : bool -> Xml.xml list -> unit type ssl_info = { ssl_certificate : string option; ssl_privatekey : string option; ssl_ciphers : string option; ssl_dhfile : string option; ssl_curve : string option } (** First pass of parse XML file. Extracts this information: {ul {- user to execute OcsigenServer (ex: www-data) } {- group to execute OcsigenServer (ex: www-data) } {- SSL key, SSL certificate, SSL ciphers list, SSL DH file, SSL EC curve } {- list of HTTP port to listen (ex: 80) } {- list of HTTPS port to listen (ex: 443) } {- minimum and maximum of threads } } *) val extract_info : Xml.xml list -> (string option * string option) * (ssl_info option * (Ocsigen_socket.socket_type * int) list * (Ocsigen_socket.socket_type * int) list) * (int * int) val parse_config : ?file:string -> unit -> Xml.xml list list ocsigenserver-2.16.0/src/server/ocsigen_range.ml000066400000000000000000000202621357715257700217350ustar00rootroot00000000000000(* Ocsigen * http://www.ocsigen.org * ocsigen_range.ml Copyright (C) 2008 * Vincent Balat * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) (* - We send the range only if we know the content length (the header of partial answers must contain the length) - We compute range after content-encoding (deflation) - We do not support multipart ranges. We send only an interval. - The following works with any stream. For files, it should be optimized with seek!!!!! *) open Ocsigen_lib exception Range_416 (* We do not support multipart ranges. We send only an interval. The following function checks if we support the range requested. *) let rec change_range = function | Some ([], Some b, ifmatch) -> Some (b, None, ifmatch) | Some ([ (b, e) ], None, ifmatch) -> Some (b, Some e, ifmatch) | _ -> None let select_range length beg endopt skipfun stream = let rec aux step num () = if num = 0L then Ocsigen_stream.empty None else (match step with | Ocsigen_stream.Finished _ -> Lwt.fail Ocsigen_stream.Stream_too_small | Ocsigen_stream.Cont (c, f) -> Lwt.return (c, f)) >>= fun (buf, nextstream) -> let buflen = String.length buf in let buflen64 = Int64.of_int buflen in if (Int64.compare buflen64 num) <= 0 then Ocsigen_stream.cont buf (fun () -> Ocsigen_stream.next nextstream >>= fun next -> aux next (Int64.sub num buflen64) ()) else Ocsigen_stream.cont (String.sub buf 0 (Int64.to_int num)) (fun () -> Ocsigen_stream.empty None) in Lwt.catch (fun () -> skipfun stream beg >>= fun new_s -> Lwt.return (match endopt with | None -> Ocsigen_stream.make ~finalize: (fun status -> Ocsigen_stream.finalize stream status) (fun () -> Lwt.return new_s) | Some endc -> Ocsigen_stream.make ~finalize: (fun status -> Ocsigen_stream.finalize stream status) (aux new_s length)) ) (function | Ocsigen_stream.Stream_too_small -> Lwt.fail Range_416 (* RFC 2616 A server SHOULD return a response with this status code if a request included a Range request-header field, and none of the range-specifier values in this field overlap the current extent of the selected resource, and the request did not include an If-Range request-header field. (For byte-ranges, this means that the first- byte-pos of all of the byte-range-spec values were greater than the current length of the selected resource.) *) | e -> Lwt.fail e) let compute_range ri res = match Ocsigen_http_frame.Result.content_length res with (* We support Ranges only if we know the content length, because Content-Range always contains the length ... *) | None -> Lwt.return res | Some cl -> (* Send range only if the code is 200!! *) if (Ocsigen_http_frame.Result.code res <> 200) || (Ocsigen_config.get_disablepartialrequests ()) then Lwt.return res else begin let res = Ocsigen_http_frame.Result.update res ~headers: (Http_headers.replace Http_headers.accept_ranges "bytes" (Ocsigen_http_frame.headers res)) () in match change_range (Lazy.force (Ocsigen_request_info.range ri)) with | None -> Lwt.return res | Some (_, _, Ocsigen_extensions.IR_ifmatch etag) when (match Ocsigen_http_frame.Result.etag res with | None -> true | Some resetag -> String.compare etag resetag <> 0) -> Lwt.return res | Some (_, _, Ocsigen_extensions.IR_Ifunmodsince date) when (match Ocsigen_http_frame.Result.lastmodified res with | None -> true | Some l -> l > date) -> Lwt.return res | Some (beg, endopt, _) -> Lwt.catch (fun () -> (if Int64.compare cl beg <= 0 then Lwt.fail Range_416 else Lwt.return ()) >>= fun () -> let endc, length = match endopt with | None -> (Int64.sub cl 1L, Int64.sub cl beg) | Some e -> (e, Int64.add (Int64.sub e beg) 1L) in let resstream, skipfun = (Ocsigen_http_frame.Result.stream res) in (* stream transform *) let skipfun = match skipfun with | None -> (fun stream beg -> (Ocsigen_stream.next (Ocsigen_stream.get stream) >>= fun s -> Ocsigen_stream.skip s beg)) | Some f -> f in select_range length beg endopt skipfun resstream >>= fun new_s -> Lwt.return (Ocsigen_http_frame.Result.update res ~stream:(new_s, None) ~code:206 ~headers: (Http_headers.replace Http_headers.content_range ("bytes "^Int64.to_string beg^"-"^ Int64.to_string endc^"/"^ Int64.to_string cl) (Ocsigen_http_frame.Result.headers res)) ~content_length:(Some length) ()) ) (function | Range_416 -> (* RFC 2616 When this status code is returned for a byte-range request, the response SHOULD include a Content-Range entity-header field specifying the current length of the selected resource *) let dr = Ocsigen_http_frame.Result.default () in Lwt.return (Ocsigen_http_frame.Result.update dr ~code:416 ~headers: (Http_headers.replace Http_headers.content_range ("bytes */"^Int64.to_string cl) (Ocsigen_http_frame.Result.headers dr)) ()) | e -> Lwt.fail e) end let get_range http_frame = try let rangeheader = Ocsigen_http_frame.Http_header.get_headers_value http_frame.Ocsigen_http_frame.frame_header Http_headers.range in let decode_int index d e = let a = Int64.of_string d in let b = Int64.of_string e in assert (Int64.compare index a < 0); assert (Int64.compare a b <= 0); (a, b) in let interval, from = let a,b = String.sep '=' rangeheader in if String.compare a "bytes" <> 0 then raise Not_found else let l = String.split ',' b in let rec f index = function | [] -> [], None | [a] -> let d, e = String.sep '-' a in if e = "" then [], Some (Int64.of_string d) else [decode_int index d e], None | a::l -> let d, e = String.sep '-' a in let a, b = decode_int index d e in let ll, fr = f b l in (* not tail rec *) (a, b)::ll, fr in f (-1L) l in let ifrange = try let ifrangeheader = Ocsigen_http_frame.Http_header.get_headers_value http_frame.Ocsigen_http_frame.frame_header Http_headers.if_range in try Ocsigen_extensions.IR_Ifunmodsince (Netdate.parse_epoch ifrangeheader) with _ -> Ocsigen_extensions.IR_ifmatch ifrangeheader with Not_found -> Ocsigen_extensions.IR_No in Some (interval, from, ifrange) with _ -> None ocsigenserver-2.16.0/src/server/ocsigen_range.mli000066400000000000000000000022151357715257700221040ustar00rootroot00000000000000(* Ocsigen * http://www.ocsigen.org * ocsigen_range.ml Copyright (C) 2008 * Vincent Balat * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) (** [compute_range] add in header of result a media-range *) val compute_range : Ocsigen_request_info.request_info -> Ocsigen_http_frame.result -> Ocsigen_http_frame.result Lwt.t val get_range : Ocsigen_http_frame.t -> ((int64 * int64) list * int64 option * Ocsigen_request_info.ifrange) option ocsigenserver-2.16.0/src/server/ocsigen_request_info.ml000066400000000000000000000327731357715257700233560ustar00rootroot00000000000000open Ocsigen_cookies open Ocsigen_lib type ifrange = IR_No | IR_Ifunmodsince of float | IR_ifmatch of string type file_info = { tmp_filename: string; filesize: int64; raw_original_filename: string; original_basename: string ; file_content_type: ((string * string) * (string * string) list) option; } (** The request *) type request_info = {url_string: string; (** full URL *) meth: Ocsigen_http_frame.Http_header.http_method; (** GET, POST, HEAD... *) protocol: Ocsigen_http_frame.Http_header.proto; (** HTTP protocol used by client *) ssl: bool; (** true if HTTPS, false if HTTP *) full_path_string: string; (** full path of the URL *) full_path: string list; (** full path of the URL *) original_full_path_string: string; (** full path of the URL, as first sent by the client. Should not be changed by extensions, even rewritemod. It is used to create relative links. *) original_full_path: string list; (** full path of the URL, as first sent by the client. See below. *) sub_path: string list; (** path of the URL (only part concerning the site) *) sub_path_string: string; (** path of the URL (only part concerning the site) *) get_params_string: string option; (** string containing GET parameters *) host: string option; (** Host field of the request (if any), without port *) port_from_host_field: int option; (** Port in the host field of the request (if any) *) get_params: (string * string) list Lazy.t; (** Association list of get parameters *) initial_get_params: (string * string) list Lazy.t; (** Association list of get parameters, as sent by the browser (must not be modified by extensions) *) post_params: ((string option * Int64.t option) -> (string * string) list Lwt.t) option; (** Association list of post parameters, if urlencoded form parameters or multipart data. None if other content type or no content. *) files: ((string option * Int64.t option) -> (string * file_info) list Lwt.t) option; (** Files sent in the request (multipart data). None if other content type or no content. *) remote_inet_addr: Unix.inet_addr; (** IP of the client *) remote_ip: string; (** IP of the client *) remote_ip_parsed: Ipaddr.t Lazy.t; (** IP of the client, parsed *) remote_port: int; (** Port used by the client *) forward_ip: string list; (** IPs of gateways the request went through *) server_port: int; (** Port of the request (server) *) user_agent: string; (** User_agent of the browser *) cookies_string: string option Lazy.t; (** Cookies sent by the browser *) cookies: string CookiesTable.t Lazy.t; (** Cookies sent by the browser *) ifmodifiedsince: float option; (** if-modified-since field *) ifunmodifiedsince: float option; (** if-unmodified-since field *) ifnonematch: string list option; (** if-none-match field ( * and weak entity tags not implemented) *) ifmatch: string list option; (** if-match field ( * not implemented) *) content_type: ((string * string) * (string * string) list) option; (** Content-Type HTTP header *) content_type_string: string option; (** Content-Type HTTP header *) content_length: int64 option; (** Content-Length HTTP header *) referer: string option Lazy.t; (** Referer HTTP header *) origin: string option Lazy.t; (** Where the cross-origin request or preflight request originates from. http://www.w3.org/TR/cors/#origin-request-header *) access_control_request_method : string option Lazy.t; (** which method will be used in the actual request as part of the preflight request. http://www.w3.org/TR/cors/#access-control-request-method-request-he*) access_control_request_headers : string list option Lazy.t; (** Which headers will be used in the actual request as part of the preflight request. http://www.w3.org/TR/cors/#access-control-request-headers-request-h *) accept: ((string option * string option) * float option * (string * string) list) list Lazy.t; (** Accept HTTP header. For example [(Some "text", None)] means ["text/*"]. The float is the "quality" value, if any. The last association list is for other extensions. *) accept_charset: (string option * float option) list Lazy.t; (** Accept-Charset HTTP header. [None] for the first value means "*". The float is the "quality" value, if any. *) accept_encoding: (string option * float option) list Lazy.t; (** Accept-Encoding HTTP header. [None] for the first value means "*". The float is the "quality" value, if any. *) accept_language: (string * float option) list Lazy.t; (** Accept-Language HTTP header. The float is the "quality" value, if any. *) http_frame: Ocsigen_http_frame.t; (** The full http_frame *) mutable request_cache: Polytables.t; (** Use this to put anything you want, for example, information for subsequent extensions *) client: Ocsigen_http_com.connection; (** The request connection *) range: ((int64 * int64) list * int64 option * ifrange) option Lazy.t; (** Range HTTP header. [None] means all the document. List of intervals + possibly from an index to the end of the document. *) timeofday: float; (** An Unix timestamp computed at the beginning of the request *) mutable nb_tries: int; (** For internal use: used to prevent loops of requests *) connection_closed: unit Lwt.t; (** a thread waking up when the connection is closed *) } (** If you force [ri_files] or [ri_post_params], the request is fully read, so it is not possible any more to read it from [ri_http_frame] (and vice versa). *) (* used to modify the url in ri (for example for retrying after rewrite) *) let ri_of_url ?(full_rewrite = false) url ri = let (_, host, _, url, path, params, get_params) = Url.parse url in let host = match host with | Some h -> host | None -> ri.host in let path_string = Url.string_of_url_path ~encode:true path in let original_full_path, original_full_path_string = if full_rewrite then (path, path_string) else (ri.original_full_path, ri.original_full_path_string) in {ri with url_string = url; host ; full_path_string = path_string; full_path = path; original_full_path_string ; original_full_path ; sub_path = path; sub_path_string = path_string; get_params_string = params; get_params ; } let make ~url_string ~meth ~protocol ?(ssl=false) ~full_path_string ~full_path ?(original_full_path_string=full_path_string) ?(original_full_path=full_path) ?(sub_path=full_path) ?(sub_path_string=Url.string_of_url_path ~encode:true full_path) ~get_params_string ~host ~port_from_host_field ~get_params ?(initial_get_params=get_params) ~post_params ~files ~remote_inet_addr ~remote_ip ?(remote_ip_parsed=lazy (Ipaddr.of_string_exn remote_ip)) ~remote_port ?(forward_ip=[]) ~server_port ~user_agent ~cookies_string ~cookies ~ifmodifiedsince ~ifunmodifiedsince ~ifnonematch ~ifmatch ~content_type ~content_type_string ~content_length ~referer ~origin ~access_control_request_method ~access_control_request_headers ~accept ~accept_charset ~accept_encoding ~accept_language ~http_frame ?(request_cache=Polytables.create ()) ~client ~range (* XXX: We should have this line but it would produce a circular dependency * between the two modules * * ?(range=lazy (Ocsigen_range.get_range http_frame)) *) ?(timeofday=Unix.gettimeofday ()) ?(nb_tries=0) ?(connection_closed=Ocsigen_http_com.closed client) () = { url_string; meth; protocol; ssl; full_path_string; full_path; original_full_path_string; original_full_path; sub_path; sub_path_string; get_params_string; host; port_from_host_field; get_params; initial_get_params; post_params; files; remote_inet_addr; remote_ip; remote_ip_parsed; remote_port; forward_ip; server_port; user_agent; cookies_string; cookies; ifmodifiedsince; ifunmodifiedsince; ifnonematch; ifmatch; content_type; content_type_string; content_length; referer; origin; access_control_request_method; access_control_request_headers; accept; accept_charset; accept_encoding; accept_language; http_frame; request_cache; client; range; timeofday; nb_tries; connection_closed; } let update ri ?(url_string=ri.url_string) ?(meth=ri.meth) ?(protocol=ri.protocol) ?(ssl=ri.ssl) ?(full_path_string=ri.full_path_string) ?(full_path=ri.full_path) ?(original_full_path_string=ri.original_full_path_string) ?(original_full_path=ri.original_full_path) ?(sub_path=ri.sub_path) ?(sub_path_string=ri.sub_path_string) ?(get_params_string=ri.get_params_string) ?(host=ri.host) ?(port_from_host_field=ri.port_from_host_field) ?(get_params=ri.get_params) ?(initial_get_params=ri.initial_get_params) ?(post_params=ri.post_params) ?(files=ri.files) ?(remote_inet_addr=ri.remote_inet_addr) ?(remote_ip=ri.remote_ip) ?(remote_ip_parsed=ri.remote_ip_parsed) ?(remote_port=ri.remote_port) ?(forward_ip=ri.forward_ip) ?(server_port=ri.server_port) ?(user_agent=ri.user_agent) ?(cookies_string=ri.cookies_string) ?(cookies=ri.cookies) ?(ifmodifiedsince=ri.ifmodifiedsince) ?(ifunmodifiedsince=ri.ifunmodifiedsince) ?(ifnonematch=ri.ifnonematch) ?(ifmatch=ri.ifmatch) ?(content_type=ri.content_type) ?(content_type_string=ri.content_type_string) ?(content_length=ri.content_length) ?(referer=ri.referer) ?(origin=ri.origin) ?(access_control_request_method=ri.access_control_request_method) ?(access_control_request_headers=ri.access_control_request_headers) ?(accept=ri.accept) ?(accept_charset=ri.accept_charset) ?(accept_encoding=ri.accept_encoding) ?(accept_language=ri.accept_language) ?(http_frame=ri.http_frame) ?(request_cache=ri.request_cache) ?(client=ri.client) ?(range=ri.range) ?(timeofday=ri.timeofday) ?(nb_tries=ri.nb_tries) ?(connection_closed=ri.connection_closed) () = { url_string; meth; protocol; ssl; full_path_string; full_path; original_full_path_string; original_full_path; sub_path; sub_path_string; get_params_string; host; port_from_host_field; get_params; initial_get_params; post_params; files; remote_inet_addr; remote_ip; remote_ip_parsed; remote_port; forward_ip; server_port; user_agent; cookies_string; cookies; ifmodifiedsince; ifunmodifiedsince; ifnonematch; ifmatch; content_type; content_type_string; content_length; referer; origin; access_control_request_method; access_control_request_headers; accept; accept_charset; accept_encoding; accept_language; http_frame; request_cache; client; range; timeofday; nb_tries; connection_closed; } let update_nb_tries ri value = ri.nb_tries <- value let update_request_cache ri value = ri.request_cache <- value let range { range; _ } = range let url_string { url_string; _ } = url_string let protocol { protocol; _ } = protocol let http_frame { http_frame; _ } = http_frame let meth { meth; _ } = meth let ifmatch { ifmatch; _ } = ifmatch let ifunmodifiedsince { ifunmodifiedsince; _ } = ifunmodifiedsince let ifnonematch { ifnonematch; _ } = ifnonematch let ifmodifiedsince { ifmodifiedsince; _ } = ifmodifiedsince let remote_ip { remote_ip; _ } = remote_ip let user_agent { user_agent; } = user_agent let host { host; _ } = host let ssl { ssl; _ } = ssl let port_from_host_field { port_from_host_field; _ } = port_from_host_field let server_port { server_port; _ } = server_port let full_path { full_path; _ } = full_path let get_params_string { get_params_string; _ } = get_params_string let client { client; _ } = client let nb_tries { nb_tries; _ } = nb_tries let sub_path { sub_path; _ } = sub_path let content_length { content_length; _ } = content_length let content_type_string { content_type_string; _ } = content_type_string let remote_port { remote_port; _ } = remote_port let sub_path_string { sub_path_string; _ } = sub_path_string let full_path_string { full_path_string; _ } = full_path_string let remote_inet_addr { remote_inet_addr; _ } = remote_inet_addr let forward_ip { forward_ip; _ } = forward_ip let remote_ip_parsed { remote_ip_parsed; _ } = remote_ip_parsed let content_type { content_type; _ } = content_type let origin { origin; _ } = origin let access_control_request_method { access_control_request_method; _ } = access_control_request_method let access_control_request_headers { access_control_request_headers; _ } = access_control_request_headers let request_cache { request_cache; _ } = request_cache let files { files; _ } = files let original_full_path { original_full_path; _ } = original_full_path let cookies { cookies; _ } = cookies let post_params { post_params; _ } = post_params let get_params { get_params; _ } = get_params let initial_get_params { initial_get_params; _ } = initial_get_params let original_full_path_string { original_full_path_string; _ } = original_full_path_string let timeofday { timeofday; _ } = timeofday let accept_language { accept_language; _ } = accept_language let accept_encoding { accept_encoding; _ } = accept_encoding let accept { accept; _ } = accept let connection_closed { connection_closed; _ } = connection_closed ocsigenserver-2.16.0/src/server/ocsigen_request_info.mli000066400000000000000000000227531357715257700235240ustar00rootroot00000000000000open Ocsigen_cookies type ifrange = IR_No | IR_Ifunmodsince of float | IR_ifmatch of string type file_info = { tmp_filename: string; filesize: int64; raw_original_filename: string; original_basename: string ; file_content_type: ((string * string) * (string * string) list) option; } type request_info (** Parsing URLs. This allows to modify the URL in the request_info. (to be used for example with Ext_retry_with or Ext_continue_with) *) val ri_of_url : ?full_rewrite:bool -> string -> request_info -> request_info (** Make a request_info *) val make : url_string:string -> meth:Ocsigen_http_frame.Http_header.http_method -> protocol:Ocsigen_http_frame.Http_header.proto -> ?ssl:bool -> full_path_string:string -> full_path:Ocsigen_lib.Url.path -> ?original_full_path_string:string -> ?original_full_path:Ocsigen_lib.Url.path -> ?sub_path:Ocsigen_lib.Url.path -> ?sub_path_string:Ocsigen_lib.Url.uri -> get_params_string:string option -> host:string option -> port_from_host_field:int option -> get_params:(string * string) list Lazy.t -> ?initial_get_params:(string * string) list Lazy.t -> post_params:(string option * Int64.t option -> (string * string) list Lwt.t) option -> files:(string option * Int64.t option -> (string * file_info) list Lwt.t) option -> remote_inet_addr:Unix.inet_addr -> remote_ip:string -> ?remote_ip_parsed:Ipaddr.t Lazy.t -> remote_port:int -> ?forward_ip:string list -> server_port:int -> user_agent:string -> cookies_string:string option Lazy.t -> cookies:string Ocsigen_cookies.CookiesTable.t Lazy.t -> ifmodifiedsince:float option -> ifunmodifiedsince:float option -> ifnonematch:string list option -> ifmatch:string list option -> content_type:((string * string) * (string * string) list) option -> content_type_string:string option -> content_length:int64 option -> referer:string option Lazy.t -> origin:string option Lazy.t -> access_control_request_method:string option Lazy.t -> access_control_request_headers:string list option Lazy.t -> accept:Http_headers.accept Lazy.t -> accept_charset:(string option * float option) list Lazy.t -> accept_encoding:(string option * float option) list Lazy.t -> accept_language:(string * float option) list Lazy.t -> http_frame:Ocsigen_http_frame.t -> ?request_cache:Polytables.t -> client:Ocsigen_http_com.connection -> range:((int64 * int64) list * int64 option * ifrange) option Lazy.t -> ?timeofday:float -> ?nb_tries:int -> ?connection_closed:unit Lwt.t -> unit -> request_info val update : request_info -> ?url_string:string -> ?meth:Ocsigen_http_frame.Http_header.http_method -> ?protocol:Ocsigen_http_frame.Http_header.proto -> ?ssl:bool -> ?full_path_string:string -> ?full_path:string list -> ?original_full_path_string:string -> ?original_full_path:string list -> ?sub_path:string list -> ?sub_path_string:string -> ?get_params_string:string option -> ?host:string option -> ?port_from_host_field:int option -> ?get_params:(string * string) list Lazy.t -> ?initial_get_params:(string * string) list Lazy.t -> ?post_params:(string option * Int64.t option -> (string * string) list Lwt.t) option -> ?files:(string option * Int64.t option -> (string * file_info) list Lwt.t) option -> ?remote_inet_addr:Unix.inet_addr -> ?remote_ip:string -> ?remote_ip_parsed:Ipaddr.t Lazy.t -> ?remote_port:int -> ?forward_ip:string list -> ?server_port:int -> ?user_agent:string -> ?cookies_string:string option Lazy.t -> ?cookies:string Ocsigen_cookies.CookiesTable.t Lazy.t -> ?ifmodifiedsince:float option -> ?ifunmodifiedsince:float option -> ?ifnonematch:string list option -> ?ifmatch:string list option -> ?content_type:((string * string) * (string * string) list) option -> ?content_type_string:string option -> ?content_length:int64 option -> ?referer:string option Lazy.t -> ?origin:string option Lazy.t -> ?access_control_request_method:string option Lazy.t -> ?access_control_request_headers:string list option Lazy.t -> ?accept:Http_headers.accept Lazy.t -> ?accept_charset:(string option * float option) list Lazy.t -> ?accept_encoding:(string option * float option) list Lazy.t -> ?accept_language:(string * float option) list Lazy.t -> ?http_frame:Ocsigen_http_frame.t -> ?request_cache:Polytables.t -> ?client:Ocsigen_http_com.connection -> ?range:((int64 * int64) list * int64 option * ifrange) option Lazy.t -> ?timeofday:float -> ?nb_tries:int -> ?connection_closed:unit Lwt.t -> unit -> request_info (** Update [nb_tries] slot of [request_info] *) val update_nb_tries : request_info -> int -> unit (** Update cache of [request_info] *) val update_request_cache : request_info -> Polytables.t -> unit (** Accessor for range of request_info *) val range : request_info -> ((int64 * int64) list * int64 option * ifrange) option Lazy.t (** Accessor for url of request_info *) val url_string : request_info -> string (** Accessor for protocol of request_info *) val protocol : request_info -> Ocsigen_http_frame.Http_header.proto (** Accessor for http_frame of request_info *) val http_frame : request_info -> Ocsigen_http_frame.t (** Accessor for method of request_info *) val meth : request_info -> Ocsigen_http_frame.Http_header.http_method (** Accessor for ifmatch of request_info *) val ifmatch : request_info -> string list option (** Accessor for ifunmodifiedsince of request_info *) val ifunmodifiedsince : request_info -> float option (** Accessor for ifnonematch of request_info *) val ifnonematch : request_info -> string list option (** Accessor for ifmodifiedsince of request_info *) val ifmodifiedsince : request_info -> float option (** Accessor for remote_ip of request_info *) val remote_ip : request_info -> string (** Accessor for user_agent of request_info *) val user_agent : request_info -> string (** Accessor for host of request_info *) val host : request_info -> string option (** Accessor for ssl of request_info *) val ssl : request_info -> bool (** Accessor for port_from_host_field of request_info *) val port_from_host_field : request_info -> int option (** Accessor for server_port of request_info *) val server_port : request_info -> int (** Accessor for full_path of request_info *) val full_path : request_info -> string list (** Accessor for get_params_string of request_info *) val get_params_string : request_info -> string option (** Accessor for client of request_info *) val client : request_info -> Ocsigen_http_com.connection (** Accessor for nb_tries of request_info *) val nb_tries : request_info -> int (** Accessor for sub_path of request_info *) val sub_path : request_info -> string list (** Accessor for content_length of request_info *) val content_length : request_info -> int64 option (** Accessor for content_type_string of request_info *) val content_type_string : request_info -> string option (** Accessor for remote_port of request_info *) val remote_port : request_info -> int (** Accessor for sub_path_string of request_info *) val sub_path_string : request_info -> string (** Accessor for full_path_string of request_info *) val full_path_string : request_info -> string (** Accessor for remote_inet_addr of request_info *) val remote_inet_addr : request_info -> Unix.inet_addr (** Accessor for forward_ip of request_info *) val forward_ip : request_info -> string list (** Accessor for remote_ip_parsed of request_info *) val remote_ip_parsed : request_info -> Ipaddr.t Lazy.t (** Accessor for content_type of request_info *) val content_type : request_info -> ((string * string) * (string * string) list) option (** Accessor for origin of request_info *) val origin : request_info -> string option Lazy.t (** Accessor for access_control_request_method of request_info *) val access_control_request_method : request_info -> string option Lazy.t (** Accessor for access_control_request_headers of request_info *) val access_control_request_headers : request_info -> string list option Lazy.t (** Accessor for request_cache of request_info *) val request_cache : request_info -> Polytables.t (** Accessor for files of request_info *) val files : request_info -> ((string option * Int64.t option) -> (string * file_info) list Lwt.t) option (** Accessor for original_full_path of request_info *) val original_full_path : request_info -> string list (** Accessor for cookies of request_info *) val cookies : request_info -> string CookiesTable.t Lazy.t (** Accessor for post_params of request_info *) val post_params : request_info -> ((string option * Int64.t option) -> (string * string) list Lwt.t) option (** Accessor for get_params of request_info *) val get_params : request_info -> (string * string) list Lazy.t (** Accessor for initial_get_params of request_info *) val initial_get_params : request_info -> (string * string) list Lazy.t (** Accessor for original_full_path_string of request_info *) val original_full_path_string : request_info -> string (** Accessor for timeofday of request_info *) val timeofday : request_info -> float (** Accessor for accept_language of request_info *) val accept_language : request_info -> (string * float option) list Lazy.t (** Accessor for accept_encoding of request_info *) val accept_encoding : request_info -> (string option * float option) list Lazy.t (** Accessor for accept of request_info *) val accept : request_info -> Http_headers.accept Lazy.t (** Accessor for connection_closed of request_info *) val connection_closed : request_info -> unit Lwt.t ocsigenserver-2.16.0/src/server/ocsigen_server.ml000066400000000000000000001460361357715257700221570ustar00rootroot00000000000000(* Ocsigen * http://www.ocsigen.org * Copyright (C) 2005 * Vincent Balat, Denis Berthod, Nataliya Guts, Jérôme Vouillon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) open Lwt open Ocsigen_socket open Ocsigen_lib open Ocsigen_extensions open Ocsigen_http_frame open Ocsigen_headers open Ocsigen_http_com open Ocsigen_senders open Ocsigen_config open Ocsigen_parseconfig open Ocsigen_cookies exception Ocsigen_unsupported_media exception Ssl_Exception exception Ocsigen_upload_forbidden exception Socket_closed let shutdown = ref false let () = Random.self_init () let () = Ocsigen_commandline.cmdline (* This is only to have the module Ocsigen_commandline linked when we do not use -linkall *) (* Without the following line, it stops with "Broken Pipe" without raising an exception ... *) let _ = Sys.set_signal Sys.sigpipe Sys.Signal_ignore (* Exit gracefully on SIGINT so that profiling will work *) let _ = Sys.set_signal Sys.sigint (Sys.Signal_handle(fun _ -> exit 0)) let section = Lwt_log.Section.make "ocsigen:main" (* Initialize exception handler for Lwt timeouts: *) let _ = Lwt_timeout.set_exn_handler (fun e -> Lwt_log.ign_error ~section ~exn:e "Uncaught Exception after lwt \ timeout") let sslctx = Ocsigen_http_client.sslcontext let ip_of_sockaddr = function | Unix.ADDR_INET (ip, port) -> ip | _ -> raise (Ocsigen_Internal_Error "ip of unix socket") let port_of_sockaddr = function | Unix.ADDR_INET (ip, port) -> port | _ -> raise (Ocsigen_Internal_Error "port of unix socket") let get_boundary ctparams = List.assoc "boundary" ctparams let find_field field content_disp = let (_, res) = Netstring_pcre.search_forward (Netstring_pcre.regexp (field^"=.([^\"]*).;?")) content_disp 0 in Netstring_pcre.matched_group res 1 content_disp type to_write = No_File of string * Buffer.t | A_File of (string * string * string * Unix.file_descr * ((string * string) * (string * string) list) option) let counter = let c = ref (Random.int 1000000) in fun () -> c := !c + 1 ; !c let warn sockaddr s = Lwt_log.ign_warning_f ~section "While talking to %a:%s" (fun () sockaddr -> Unix.string_of_inet_addr (ip_of_sockaddr sockaddr)) sockaddr s let dbg sockaddr s = Lwt_log.ign_info_f ~section "While talking to %a:%s" (fun () sockaddr -> Unix.string_of_inet_addr (ip_of_sockaddr sockaddr)) sockaddr s let http_url_syntax = Hashtbl.find Neturl.common_url_syntax "http" let rec find_post_params http_frame ct filenames = match http_frame.Ocsigen_http_frame.frame_content with | None -> None | Some body_gen -> let ((ct, cst), ctparams) = match ct with (* RFC 2616, sect. 7.2.1 *) (* If the media type remains unknown, the recipient SHOULD treat it as type "application/octet-stream". *) | None -> (("application", "octet-stream"), []) | Some (c, p) -> (c, p) in match String.lowercase ct, String.lowercase cst with | "application", "x-www-form-urlencoded" -> Some (find_post_params_form_urlencoded body_gen) | "multipart", "form-data" -> Some (find_post_params_multipart_form_data body_gen ctparams filenames) | _ -> None and find_post_params_form_urlencoded body_gen _ = catch (fun () -> let body = Ocsigen_stream.get body_gen in (* BY, adapted from a previous comment. Should this stream be consumed in case of error? *) Ocsigen_stream.string_of_stream (Ocsigen_config.get_maxrequestbodysizeinmemory ()) body >>= fun r -> let r = Url.fixup_url_string r in Lwt.return ((Netencoding.Url.dest_url_encoded_parameters r), []) ) (function | Ocsigen_stream.String_too_large -> fail Input_is_too_large | e -> fail e) and find_post_params_multipart_form_data body_gen ctparams filenames (uploaddir, maxuploadfilesize) = (* Same question here, should this stream be consumed after an error ? *) let body = Ocsigen_stream.get body_gen and bound = get_boundary ctparams and params = ref [] and files = ref [] in let create hs = let content_type = try let ct = List.assoc "content-type" hs in Ocsigen_headers.parse_content_type (Some ct) with _ -> None in let cd = List.assoc "content-disposition" hs in let p_name = find_field "name" cd in try let store = find_field "filename" cd in match uploaddir with | Some dname -> let now = Printf.sprintf "%f-%d" (Unix.gettimeofday ()) (counter ()) in let fname = dname^"/"^now in let fd = Unix.openfile fname [Unix.O_CREAT; Unix.O_TRUNC; Unix.O_WRONLY; Unix.O_NONBLOCK] 0o666 in Lwt_log.ign_info_f ~section "Upload file opened: %s" fname; filenames := fname::!filenames; A_File (p_name, fname, store, fd, content_type) | None -> raise Ocsigen_upload_forbidden with Not_found -> No_File (p_name, Buffer.create 1024) in let rec add where s = match where with | No_File (p_name, to_buf) -> Buffer.add_string to_buf s; return () | A_File (_,_,_,wh,_) -> let len = String.length s in let r = Unix.write_substring wh s 0 len in if r < len then (*XXXX Inefficient if s is long *) add where (String.sub s r (len - r)) else Lwt_unix.yield () in let stop size = function | No_File (p_name, to_buf) -> return (params := !params @ [(p_name, Buffer.contents to_buf)]) (* a la fin ? *) | A_File (p_name,fname,oname,wh, content_type) -> files := !files@[(p_name, {tmp_filename=fname; filesize=size; raw_original_filename=oname; original_basename=(Filename.basename oname); file_content_type = content_type; })]; Unix.close wh; return () in Multipart.scan_multipart_body_from_stream body bound create add stop maxuploadfilesize >>= fun () -> (*VVV Does scan_multipart_body_from_stream read until the end or only what it needs? If we do not consume here, the following request will be read only when this one is finished ... *) Ocsigen_stream.consume body_gen >>= fun () -> Lwt.return (!params, !files) let wrap_stream f x frame_content = Ocsigen_stream.make ~finalize:(fun outcome -> match frame_content with | Some stream -> Ocsigen_stream.finalize stream outcome | None -> Lwt.return () ) (fun () -> f x >>= fun () -> match frame_content with | Some stream -> Ocsigen_stream.next (Ocsigen_stream.get stream) | None -> Ocsigen_stream.empty None ) let handle_100_continue slot frame = { frame with frame_content = Some (wrap_stream send_100_continue slot frame.frame_content) } let handle_expect slot frame = let expect_list = Ocsigen_headers.get_expect frame in let proto = Http_header.get_proto frame.frame_header in List.fold_left (fun frame tok -> match String.lowercase tok with | "100-continue" -> if proto = Http_header.HTTP11 then handle_100_continue slot frame else frame | _ -> raise (Ocsigen_http_error (Ocsigen_cookies.empty_cookieset, 417)) ) frame expect_list (* reading the request *) let get_request_infos meth clientproto url http_frame filenames sockaddr port receiver sender_slot = Lwt.catch (fun () -> let (_, headerhost, headerport, url, path, params, get_params) = Url.parse url in let headerhost, headerport = match headerhost with | None -> get_host_from_host_header http_frame | _ -> headerhost, headerport in (* RFC: 1. If Request-URI is an absoluteURI, the host is part of the Request-URI. Any Host header field value in the request MUST be ignored. 2. If the Request-URI is not an absoluteURI, and the request includes a Host header field, the host is determined by the Host header field value. 3. If the host as determined by rule 1 or 2 is not a valid host on the server, the response MUST be a 400 (Bad Request) error message. *) (* Here we don't trust the port information given by the request. We use the port we are listening on. *) Lwt_log.ign_info_f ~section "host=%s" (match headerhost with None -> "" | Some h -> h); (* Servers MUST report a 400 (Bad Request) error if an HTTP/1.1 request does not include a Host request-header. *) if clientproto = Ocsigen_http_frame.Http_header.HTTP11 && headerhost = None then raise Ocsigen_Bad_Request; let useragent = get_user_agent http_frame in let cookies_string = lazy (get_cookie_string http_frame) in let cookies = lazy (match (Lazy.force cookies_string) with | None -> CookiesTable.empty | Some s -> parse_cookies s) in let ifmodifiedsince = get_if_modified_since http_frame in let ifunmodifiedsince = get_if_unmodified_since http_frame in let ifnonematch = get_if_none_match http_frame in let ifmatch = get_if_match http_frame in let client_inet_addr = ip_of_sockaddr sockaddr in let ct_string = get_content_type http_frame in let ct = Ocsigen_headers.parse_content_type ct_string in let cl = get_content_length http_frame in let referer = lazy (get_referer http_frame) in let origin = lazy (get_origin http_frame) in let access_control_request_method = lazy (get_access_control_request_method http_frame) in let access_control_request_headers = lazy (get_access_control_request_headers http_frame) in let accept = lazy (get_accept http_frame) in let accept_charset = lazy (get_accept_charset http_frame) in let accept_encoding = lazy (get_accept_encoding http_frame) in let accept_language = lazy (get_accept_language http_frame) in let post_params0 = match meth with | Http_header.GET | Http_header.DELETE | Http_header.PUT | Http_header.HEAD -> None | Http_header.POST | Http_header.OPTIONS -> begin match find_post_params http_frame ct filenames with | None -> None | Some f -> let r = ref None in Some (fun ci -> match !r with | None -> let res = f ci in r := Some res; res | Some r -> r) end | _ -> failwith "get_request_infos: HTTP method not implemented" in let post_params = match post_params0 with | None -> None | Some f -> Some (fun ci -> f ci >>= fun (a, _) -> Lwt.return a) in let files = match post_params0 with | None -> None | Some f -> Some (fun ci -> f ci >>= fun (_, b) -> Lwt.return b) in let ipstring = Unix.string_of_inet_addr client_inet_addr in let path_string = Url.string_of_url_path ~encode:true path in Lwt.return (Ocsigen_request_info.make ~url_string:url ~meth:meth ~protocol:http_frame.Ocsigen_http_frame .frame_header.Ocsigen_http_frame.Http_header .proto ~ssl:(Lwt_ssl.is_ssl (Ocsigen_http_com.connection_fd receiver)) ~full_path_string:path_string ~full_path:path ~original_full_path_string:path_string ~original_full_path:path ~sub_path:path ~sub_path_string:(Url.string_of_url_path ~encode:true path) ~get_params_string:params ~host:headerhost ~port_from_host_field:headerport ~get_params:get_params ~initial_get_params:get_params ~post_params:post_params ~files:files ~remote_inet_addr:client_inet_addr ~remote_ip:ipstring ~remote_ip_parsed:(lazy (Ipaddr.of_string_exn ipstring)) ~remote_port:(port_of_sockaddr sockaddr) ~forward_ip:[] ~server_port:port ~user_agent:useragent ~cookies_string:cookies_string ~cookies:cookies ~ifmodifiedsince:ifmodifiedsince ~ifunmodifiedsince:ifunmodifiedsince ~ifnonematch:ifnonematch ~ifmatch:ifmatch ~content_type:ct ~content_type_string:ct_string ~content_length:cl ~referer:referer ~origin:origin ~access_control_request_method:access_control_request_method ~access_control_request_headers:access_control_request_headers ~accept:accept ~accept_charset:accept_charset ~accept_encoding:accept_encoding ~accept_language:accept_language ~http_frame:(handle_expect sender_slot http_frame) ~request_cache:(Polytables.create () ) ~client:(Ocsigen_extensions.client_of_connection receiver) ~range:(lazy (Ocsigen_range.get_range http_frame)) ~timeofday:(Unix.gettimeofday ()) ~nb_tries:0 ~connection_closed:(Ocsigen_http_com.closed receiver) ()) ) (fun e -> Lwt_log.ign_info ~section ~exn:e "Exn during get_request_infos"; Lwt.fail e) (* An http result [res] frame has been computed. Depending on the If-(None-)?Match and If-(Un)?Modified-Since headers of [ri], we return this frame, a 304: Not-Modified, or a 412: Precondition Failed. See RFC 2616, sections 14.24, 14.25, 14.26, 14.28 and 13.3.4 *) let handle_result_frame ri res send = (* Subfonctions to handle each header separately *) let if_unmodified_since unmodified_since = (* Section 14.28 *) if (Result.code res = 412 || (200 <= Result.code res && Result.code res < 300)) then match Result.lastmodified res with | Some r -> if r <= unmodified_since then `Ignore_header else `Precondition_failed | None -> `Ignore_header else `Ignore_header and if_modified_since modified_since = (* Section 14.25 *) if Result.code res = 200 then match Result.lastmodified res with | Some r -> if r <= modified_since then `Unmodified else `Ignore_header | _ -> `Ignore_header else `Ignore_header and if_none_match if_none_match = (* Section 14.26 *) if (Result.code res = 412 || (200 <= Result.code res && Result.code res < 300)) then match Result.etag res with | None -> `Ignore_header | Some e -> if List.mem e if_none_match then if (Ocsigen_request_info.meth ri) = Http_header.GET || (Ocsigen_request_info.meth ri) = Http_header.HEAD then `Unmodified else `Precondition_failed else `Ignore_header_and_ModifiedSince else `Ignore_header and if_match if_match = (* Section 14.24 *) if (Result.code res = 412 || (200 <= Result.code res && Result.code res < 300)) then match Result.etag res with | None -> `Precondition_failed | Some e -> if List.mem e if_match then `Ignore_header else `Precondition_failed else `Ignore_header in let handle_header f h = match h with | None -> `No_header | Some h -> f h in (* Main code *) let r = (* For the cases unspecified with RFC2616. we follow more or less the order used by Apache. See the function modules/http/http_protocol.c/ap_meets_conditions in the Apache source *) match handle_header if_match (Ocsigen_request_info.ifmatch ri) with | `Precondition_failed -> `Precondition_failed | `No_header | `Ignore_header -> match handle_header if_unmodified_since (Ocsigen_request_info.ifunmodifiedsince ri) with | `Precondition_failed -> `Precondition_failed | `No_header | `Ignore_header -> match handle_header if_none_match (Ocsigen_request_info.ifnonematch ri) with | `Precondition_failed -> `Precondition_failed | `Ignore_header_and_ModifiedSince -> `Std | `Unmodified | `No_header as r1 -> (match handle_header if_modified_since (Ocsigen_request_info.ifmodifiedsince ri) with | `Unmodified | `No_header as r2 -> if r1 = `No_header && r2 = `No_header then `Std else `Unmodified | `Ignore_header -> `Std) | `Ignore_header -> (* We cannot return a 304, so there is no need to consult if_modified_since *) `Std in match r with | `Unmodified -> Lwt_log.ign_info ~section "Sending 304 Not modified"; Ocsigen_stream.finalize (fst (Result.stream res)) `Success >>= fun () -> let headers = let keep h headers = try Http_headers.add h (Http_headers.find h (Result.headers res)) headers with Not_found -> headers in Http_headers.(keep cache_control (keep expires empty)) in send (Result.update (Ocsigen_http_frame.Result.empty ()) ~code:304 (* Not modified *) ~lastmodified:(Result.lastmodified res) ~etag:(Result.etag res) ~headers ()) | `Precondition_failed -> Lwt_log.ign_info ~section "Sending 412 Precondition Failed (conditional headers)"; Ocsigen_stream.finalize (fst (Result.stream res)) `Success >>= fun () -> send (Result.update (Ocsigen_http_frame.Result.empty ()) ~code:412 (* Precondition failed *) ()) | `Std -> Ocsigen_range.compute_range ri res >>= send let service receiver sender_slot request meth url port sockaddr = (* sender_slot is here for pipelining: we must wait before sending the page, because the previous one may not be sent *) let head = meth = Http_header.HEAD in let clientproto = Http_header.get_proto request.Ocsigen_http_frame.frame_header in let handle_service_errors e = (* Exceptions during page generation *) Lwt_log.ign_info ~section ~exn:e "Exception during generation/sending"; let send_error ?cookies code = Ocsigen_senders.send_error ~exn:e sender_slot ~clientproto ?cookies ~head ~code ~sender:Ocsigen_http_com.default_sender () in match e with (* EXCEPTIONS WHILE COMPUTING A PAGE *) | Ocsigen_http_error (cookies_to_set, i) -> Lwt_log.ign_info_f ~section "Sending HTTP error %d %s" i (Ocsigen_http_frame.Http_error.expl_of_code i); send_error ~cookies:cookies_to_set i | Ocsigen_stream.Interrupted Ocsigen_stream.Already_read -> Lwt_log.ign_warning ~section "Cannot read the request twice. You probably have \ two incompatible options in configuration, \ or the order of the options in the config file is wrong."; send_error 500 (* Internal error *) | Unix.Unix_error (Unix.EACCES,_,_) | Ocsigen_upload_forbidden -> Lwt_log.ign_info ~section "Sending 403 Forbidden"; send_error 403 | Http_error.Http_exception (code,_,_) -> Ocsigen_http_frame.Http_error.display_http_exception e; send_error code | Ocsigen_Bad_Request -> Lwt_log.ign_info ~section "Sending 400"; send_error 400 | Ocsigen_unsupported_media -> Lwt_log.ign_info ~section "Sending 415"; send_error 415 | Neturl.Malformed_URL -> Lwt_log.ign_info ~section "Sending 400 (Malformed URL)"; send_error 400 | Ocsigen_Request_too_long -> Lwt_log.ign_info ~section "Sending 413 (Entity too large)"; send_error 413 | e -> Lwt_log.ign_warning_f ~section ~exn:e "Exn during page generation (sending 500)"; send_error 500 in let finish_request () = (* We asynchronously finish to read the request contents if this is not done yet so that: - we can handle the next request - there is no dead-lock with the client writing the request and the server writing the response. We need to do this once the request has been handled before sending any reply to the client. *) match request.Ocsigen_http_frame.frame_content with | Some f -> ignore (Lwt.catch (fun () -> Ocsigen_stream.finalize f `Success (* will consume the stream and unlock the mutex if not already done *) ) (function | e -> (match e with | Ocsigen_http_com.Lost_connection _ -> warn sockaddr "connection abruptly closed by peer \ while reading contents" | Ocsigen_http_com.Timeout -> warn sockaddr "timeout while reading contents" | Ocsigen_http_com.Aborted -> dbg sockaddr "reading thread aborted" | Http_error.Http_exception (code, mesg, _) -> warn sockaddr (Http_error.string_of_http_exception e) | _ -> Ocsigen_messages.unexpected_exception e "Server.finish_request" ); Ocsigen_http_com.abort receiver; (* We unlock the receiver in order to resume the reading loop. As the connection has been aborted, the next read will fail and the connection will be closed properly. *) Ocsigen_http_com.unlock_receiver receiver; Lwt.return ())) | None -> () in (* body of service *) if meth <> Http_header.GET && meth <> Http_header.POST && meth <> Http_header.HEAD && meth <> Http_header.OPTIONS && meth <> Http_header.DELETE && meth <> Http_header.PUT then begin (* VVV Warning: This must be done once and only once. Put this somewhere else to ensure that? *) warn sockaddr ("Bad request: \""^url^"\""); Ocsigen_http_com.wakeup_next_request receiver; finish_request (); (* RFC 2616, sect 5.1.1 *) send_error sender_slot ~clientproto ~head ~code:501 ~sender:Ocsigen_http_com.default_sender () end else begin let filenames = ref [] (* All the files sent by the request *) in Lwt.finalize (fun () -> (* *** First of all, we read the whole the request (that will possibly create files) *) Lwt.try_bind (fun () -> get_request_infos meth clientproto url request filenames sockaddr port receiver sender_slot) (fun ri -> (* *** Now we generate the page and send it *) (* Log *) Ocsigen_messages.accesslog (try let x_forwarded_for = Http_headers.find Http_headers.x_forwarded_for (Ocsigen_request_info.http_frame ri) .frame_header.Http_header.headers in Format.sprintf "connection for %s from %s (%s) with X-Forwarded-For: \ %s: %s" (match Ocsigen_request_info.host ri with | None -> "" | Some h -> h) (Ocsigen_request_info.remote_ip ri) (Ocsigen_request_info.user_agent ri) x_forwarded_for (Ocsigen_request_info.url_string ri) with | Not_found -> Format.sprintf "connection for %s from %s (%s): %s" (match Ocsigen_request_info.host ri with | None -> "" | Some h -> h) (Ocsigen_request_info.remote_ip ri) (Ocsigen_request_info.user_agent ri) (Ocsigen_request_info.url_string ri)); let send_aux = send sender_slot ~clientproto ~head ~sender:Ocsigen_http_com.default_sender in (* Generation of pages is delegated to extensions: *) Lwt.try_bind (fun () -> Ocsigen_extensions.compute_result ~awake_next_request:true ri) (fun res -> finish_request (); handle_result_frame ri res send_aux ) (fun e -> finish_request (); match e with | Ocsigen_extensions.Ocsigen_Is_a_directory fun_request -> (* User requested a directory. We redirect it to the correct url (with a slash), so that relative urls become correct *) let new_url = fun_request ri in send_aux (Result.update (Ocsigen_http_frame.Result.empty ()) ~code:301 ~location:(Some (Neturl.string_of_url new_url)) ()) | _ -> handle_service_errors e ) ) (fun e -> warn sockaddr ("Bad request: \""^url^"\""); Ocsigen_http_com.wakeup_next_request receiver; finish_request (); handle_service_errors e )) (fun () -> (* We remove all the files created by the request (files sent by the client) *) if !filenames <> [] then Lwt_log.ign_info ~section "** Removing files"; List.iter (fun a -> try Unix.unlink a with Unix.Unix_error _ as e -> Lwt_log.ign_warning_f ~section ~exn:e "Error while removing \ file %s" a ) !filenames; return ()) end let linger in_ch receiver = Lwt.catch (fun () -> (* We wait for 30 seconds at most and close the connection after 2 seconds without receiving data from the client *) let abort_fun () = Lwt_ssl.abort in_ch Exit in let long_timeout = Lwt_timeout.create 30 abort_fun in let short_timeout = Lwt_timeout.create 2 abort_fun in Lwt_timeout.start long_timeout; let s = String.create 1024 in let rec linger_aux () = Lwt_ssl.wait_read in_ch >>= fun () -> Lwt.try_bind (fun () -> Lwt_timeout.start short_timeout; Lwt_ssl.read in_ch s 0 1024) (fun len -> if len > 0 then linger_aux () else Lwt.return ()) (fun e -> begin match e with Unix.Unix_error(Unix.ECONNRESET,_,_) | Ssl.Read_error (Ssl.Error_syscall | Ssl.Error_ssl) | Exit -> Lwt.return () | _ -> Lwt.fail e end) in (* We start the lingering reads before waiting for the senders to terminate in order to avoid a deadlock *) let linger_thread = linger_aux () in Ocsigen_http_com.wait_all_senders receiver >>= fun () -> Lwt_log.ign_info ~section "** SHUTDOWN"; Lwt_ssl.ssl_shutdown in_ch >>= fun () -> Lwt_ssl.shutdown in_ch Unix.SHUTDOWN_SEND; linger_thread >>= fun () -> Lwt_timeout.stop long_timeout; Lwt_timeout.stop short_timeout; Lwt.return ()) (fun e -> Ocsigen_messages.unexpected_exception e "Server.linger"; Lwt.return ()) let try_bind' f g h = Lwt.try_bind f h g let add_to_receivers_waiting_for_pipeline, remove_from_receivers_waiting_for_pipeline, iter_receivers_waiting_for_pipeline = let l = Clist.create () in ((fun r -> let node = Clist.make r in Clist.insert l node; node), Clist.remove, (fun f -> Clist.fold_left (fun t v -> (*VVV reread this. Is yield here ok? *) t >>= Lwt_unix.yield >>= fun () -> f v) (Lwt.return ()) l)) let handle_connection port in_ch sockaddr = let receiver = Ocsigen_http_com.create_receiver (Ocsigen_config.get_client_timeout ()) Query in_ch in let handle_write_errors e = begin match e with | Lost_connection e' -> warn sockaddr ("connection abruptly closed by peer (" ^ Printexc.to_string e' ^ ")") | Ocsigen_http_com.Timeout -> warn sockaddr "timeout" | Ocsigen_http_com.Aborted -> dbg sockaddr "writing thread aborted" | Ocsigen_stream.Interrupted e' -> warn sockaddr ("interrupted content stream (" ^ Printexc.to_string e' ^ ")") | _ -> Ocsigen_messages.unexpected_exception e "Server.handle_write_errors" end; Ocsigen_http_com.abort receiver; Lwt.fail Ocsigen_http_com.Aborted in let handle_read_errors e = begin match e with | Ocsigen_http_com.Connection_closed -> (* This is the clean way to terminate the connection *) dbg sockaddr "connection closed by peer"; Ocsigen_http_com.abort receiver; Ocsigen_http_com.wait_all_senders receiver | Ocsigen_http_com.Keepalive_timeout -> dbg sockaddr "keepalive timeout"; Ocsigen_http_com.abort receiver; Ocsigen_http_com.wait_all_senders receiver | Ocsigen_http_com.Lost_connection _ -> warn sockaddr "connection abruptly closed by peer"; Ocsigen_http_com.abort receiver; Ocsigen_http_com.wait_all_senders receiver | Ocsigen_http_com.Timeout -> warn sockaddr "timeout"; Ocsigen_http_com.abort receiver; Ocsigen_http_com.wait_all_senders receiver | Ocsigen_http_com.Aborted -> dbg sockaddr "reading thread aborted"; Ocsigen_http_com.wait_all_senders receiver | Http_error.Http_exception (code, mes, _) -> warn sockaddr (Http_error.string_of_http_exception e); Ocsigen_http_com.start_processing receiver (fun slot -> (*XXX We should use the right information for clientproto and head... *) send_error slot ~clientproto:Ocsigen_http_frame.Http_header.HTTP10 ~head:false (* ~keep_alive:false *) ~exn:e ~sender:Ocsigen_http_com.default_sender ()); linger in_ch receiver | _ -> Ocsigen_messages.unexpected_exception e "Server.handle_read_errors"; Ocsigen_http_com.abort receiver; Ocsigen_http_com.wait_all_senders receiver end in let rec handle_request ?receiver_pos () = try_bind' (fun () -> Lwt_log.ign_info ~section "** Receiving HTTP message"; (if Ocsigen_config.get_respect_pipeline () then (* if we lock this mutex, requests from a same connection will be sent to extensions in the same order they are received on pipeline. It is locked only in server. Ocsigen_http_client has its own mutex. (*VVV use the same? *) *) Ocsigen_http_com.block_next_request receiver else Lwt.return ()) >>= fun () -> Ocsigen_http_com.get_http_frame receiver) (fun exn -> (* We remove the receiver from the set of requests waiting for pipeline *) (match receiver_pos with | Some pos -> remove_from_receivers_waiting_for_pipeline pos | None -> ()); handle_read_errors exn) (fun request -> (* As above *) (match receiver_pos with | Some pos -> remove_from_receivers_waiting_for_pipeline pos | None -> ()); let meth, url = match Http_header.get_firstline request.Ocsigen_http_frame.frame_header with | Http_header.Query a -> a | _ -> assert false (*XXX Should be checked in [get_http_frame] *) in Ocsigen_http_com.start_processing receiver (fun slot -> Lwt.catch (fun () -> (*XXX Why do we need the port but not the host name? *) service receiver slot request meth url port sockaddr) handle_write_errors); if not !shutdown && get_keepalive request.Ocsigen_http_frame.frame_header then (* We put the receiver in the set of receiver waiting for pipeline in order to be able to shutdown the connections if the server is shutting down. *) handle_request ~receiver_pos:(add_to_receivers_waiting_for_pipeline receiver) () else (* No keep-alive => no pipeline *) (* We wait for the query to be entirely read and for the reply to be sent *) Ocsigen_http_com.lock_receiver receiver >>= fun () -> Ocsigen_http_com.wait_all_senders receiver >>= fun () -> Lwt_ssl.ssl_shutdown in_ch ) in (* body of handle_connection *) handle_request () let rec wait_connection use_ssl port socket = let handle_exn e = Lwt_unix.yield () >>= fun () -> match e with | Socket_closed -> Lwt_log.ign_info ~section "Socket closed"; Lwt.return () | Unix.Unix_error ((Unix.EMFILE | Unix.ENFILE), _, _) -> (* this should not happen, report it *) Lwt_log.ign_error ~section "Max number of file descriptors reached unexpectedly, please check..."; wait_connection use_ssl port socket | e -> Lwt_log.ign_info_f ~section ~exn:e "Accept failed"; wait_connection use_ssl port socket in try_bind' (fun () -> (* if too much connections, we wait for a signal before accepting again *) let max = get_max_number_of_connections () in (if get_number_of_connected () < max then Lwt.return () else begin Lwt_log.ign_warning_f ~section "Max simultaneous connections (%d) reached." (get_max_number_of_connections ()); wait_fewer_connected max end) >>= fun () -> (* We do several accept(), as explained in "Accept()able strategies ..." by Tim Brecht & al. *) Lwt_unix.accept_n socket 50) handle_exn (fun (l, e) -> let number_of_accepts = List.length l in Lwt_log.ign_info_f ~section "received %d accepts" number_of_accepts; incr_connected number_of_accepts; if e = None then ignore (wait_connection use_ssl port socket); let handle_one (s, sockaddr) = Lwt_log.ign_info ~section "** New CONNECTION"; Lwt.catch (fun () -> Lwt_unix.set_close_on_exec s; Lwt_unix.setsockopt s Unix.TCP_NODELAY true; begin if use_ssl then Lwt_ssl.ssl_accept s !sslctx else Lwt.return (Lwt_ssl.plain s) end >>= fun in_ch -> handle_connection port in_ch sockaddr) (fun e -> Ocsigen_messages.unexpected_exception e "Server.wait_connection (handle connection)"; (match e with | Ssl.Accept_error(Ssl.Error_ssl|Ssl.Error_syscall) -> Ocsigen_messages.warning ("Last SSL error: " ^ Ssl.get_error_string ()) | _ -> ()); return ()) >>= fun () -> Lwt_log.ign_info ~section "** CLOSE"; catch (fun () -> Lwt_unix.close s) (function Unix.Unix_error _ as e -> Ocsigen_messages.unexpected_exception e "Server.wait_connection (close)"; Lwt.return () | e -> Lwt.fail e) >>= decr_connected in Lwt_list.iter_p handle_one l >>= fun () -> match e with | Some e -> handle_exn e | None -> Lwt.return ()) (* fatal errors messages *) let errmsg = function | Dynlink_wrapper.Error e -> (("Fatal - Dynamic linking error: "^(Dynlink_wrapper.error_message e)), 6) | (Unix.Unix_error _) as e -> (("Fatal - "^(Printexc.to_string e)), 9) | Ssl.Private_key_error msg -> (("Fatal - bad password: " ^ msg), 10) | Ocsigen_config.Config_file_error msg | Ocsigen_extensions.Error_in_config_file msg -> (("Fatal - Error in configuration file: "^msg), 50) | Xml.Error (s, loc) -> let begin_char, end_char = Xml.range loc and line = Xml.line loc in raise (Ocsigen_extensions.Error_in_config_file (Printf.sprintf "%s, line %d, characters %d-%d" (Xml.error_msg s) line begin_char end_char)) | Ocsigen_loader.Dynlink_error (s, exn) -> (("Fatal - While loading "^s^": "^(Printexc.to_string exn)), 52) | Ocsigen_loader.Findlib_error _ as e -> (("Fatal - " ^ Printexc.to_string e), 53) | exn -> try ((Ocsigen_extensions.get_init_exn_handler () exn), 20) with exn -> (("Fatal - Uncaught exception: "^Printexc.to_string exn), 100) (* loading new configuration *) let reload_conf s = try Ocsigen_extensions.start_initialisation (); parse_server true s; Ocsigen_extensions.end_initialisation (); with e -> Ocsigen_extensions.end_initialisation (); Lwt_log.ign_error ~section (fst (errmsg e)) (* reloading the config file *) let reload ?file () = (* That function cannot be interrupted??? *) Lwt_log.ign_notice ~section "Reloading config file" ; (try match parse_config ?file () with | [] -> () | s::_ -> reload_conf s with e -> Lwt_log.ign_error ~section (fst (errmsg e))); Lwt_log.ign_notice ~section "Config file reloaded" let shutdown_server s l = try let timeout = match l with | [] -> Ocsigen_config.get_shutdown_timeout () | ["notimeout"] -> None | [t] -> Some (float_of_string t) | _ -> failwith "syntax error in command" in Lwt_log.ign_notice ~section "Shutting down"; List.iter (fun s -> Lwt_unix.abort s Socket_closed) !sockets; List.iter (fun s -> Lwt_unix.abort s Socket_closed) !sslsockets; sockets := []; sslsockets := []; shutdown := true; if Ocsigen_extensions.get_number_of_connected () <= 0 then exit 0; (match timeout with | Some t -> ignore (Lwt_unix.sleep t >>= fun () -> exit 0) | None -> ()); ignore (iter_receivers_waiting_for_pipeline (fun receiver -> (*VVV reread this - why are we using infinite iterators? *) Ocsigen_http_com.wait_all_senders receiver >>= fun () -> Ocsigen_http_com.abort receiver; Lwt.return ())); with Failure e -> Lwt_log.ign_warning_f ~section "Wrong command: %s (%s)" s e let _ = let f s = function | ["reopen_logs"] -> Ocsigen_messages.open_files () >>= fun () -> Lwt_log.ign_notice ~section "Log files reopened"; Lwt.return () | ["reload"] -> reload (); Lwt.return () | ["reload"; file] -> reload ~file (); Lwt.return () | "shutdown"::l -> shutdown_server s l; Lwt.return () | ["gc"] -> Gc.compact (); Lwt_log.ign_notice ~section "Heap compaction requested by user"; Lwt.return () | ["clearcache"] -> Ocsigen_cache.clear_all_caches (); Lwt.return () | _ -> Lwt.fail Ocsigen_command.Unknown_command in Ocsigen_command.register_command_function f exception Stop of int * string (* Copied from deprecated [Lwt_chan]. *) let lwt_chan_input_line ic = let rec loop buf = Lwt_io.read_char_opt ic >>= function | None | Some '\n' -> Lwt.return (Buffer.contents buf) | Some char -> Buffer.add_char buf char; loop buf in Lwt_io.read_char_opt ic >>= function | Some '\n' -> Lwt.return "" | Some char -> let buf = Buffer.create 128 in Buffer.add_char buf char; loop buf | None -> Lwt.fail End_of_file let start_server () = let stop n fmt = Printf.ksprintf (fun s -> raise (Stop (n, s))) fmt in (** Thread waiting for events on a the listening port *) let listen use_ssl (addr, port) wait_end_init = Lwt.catch (fun () -> make_sockets addr port >>= fun sockets -> Lwt_list.iter_s (fun x -> Lwt_unix.listen x 1024; Lwt.return ()) sockets >>= fun () -> Lwt.return sockets) (function | Unix.Unix_error (Unix.EACCES, _, _) -> stop 7 "Fatal - You are not allowed to use port %d." port | Unix.Unix_error (Unix.EADDRINUSE, _, _) -> stop 8 "Fatal - The port %d is already in use." port | exn -> stop 100 "Fatal - Uncaught exception: %s" (Printexc.to_string exn)) >>= fun listening_sockets -> List.iter (fun x -> ignore (wait_end_init >>= fun () -> wait_connection use_ssl port x)) listening_sockets; Lwt.return listening_sockets in try (* initialization functions for modules (Ocsigen extensions or application code) loaded from now on will be executed directly. *) Ocsigen_loader.set_init_on_load true; let config_servers = parse_config () in let number_of_servers = List.length config_servers in if number_of_servers > 1 then Lwt_log.ign_warning ~section "Multiple servers not supported anymore"; let ask_for_passwd sslports _ = print_string "Please enter the password for the HTTPS server listening \ on port(s) "; print_string (String.concat ", " (List.map (fun (_,p) -> string_of_int p) sslports)); print_string ": "; let old_term= Unix.tcgetattr Unix.stdin in let old_echo = old_term.Unix.c_echo in old_term.Unix.c_echo <- false; Unix.tcsetattr Unix.stdin Unix.TCSAFLUSH old_term; try let r = read_line () in print_newline (); old_term.Unix.c_echo <- old_echo; Unix.tcsetattr Unix.stdin Unix.TCSAFLUSH old_term; r with exn -> old_term.Unix.c_echo <- old_echo; Unix.tcsetattr Unix.stdin Unix.TCSAFLUSH old_term; raise exn in let run (user, group) (_, ports, sslports) (minthreads, maxthreads) s = let wait_end_init, wait_end_init_awakener = wait () in (* Listening on all ports: *) let () = Lwt_main.run (Ocsigen_messages.open_files ~user ~group () >>= fun () -> Lwt_list.fold_left_s (fun a i -> listen false i wait_end_init >>= fun l -> Lwt.return (l @ a)) [] ports >>= fun l -> sockets := l; Lwt_list.fold_left_s (fun a i -> listen true i wait_end_init >>= fun l -> Lwt.return (l @ a)) [] sslports >>= fun l -> sslsockets := l; Lwt.return_unit) in begin match ports with | (_, p)::_ -> Ocsigen_config.set_default_port p | _ -> () end; begin match sslports with | (_, p)::_ -> Ocsigen_config.set_default_sslport p | _ -> () end; let current_uid = Unix.getuid () in let gid = match group with | None -> Unix.getgid () | Some group -> (try (Unix.getgrnam group).Unix.gr_gid with Not_found as e -> Lwt_log.ign_error ~section "Error: Wrong group"; raise e) in let uid = match user with | None -> current_uid | Some user -> (try (Unix.getpwnam user).Unix.pw_uid with Not_found as e -> Lwt_log.ign_error ~section "Error: Wrong user"; raise e) in (* A pipe to communicate with the server *) let commandpipe = get_command_pipe () in (try ignore (Unix.stat commandpipe); with Unix.Unix_error _ -> (try let umask = Unix.umask 0 in Unix.mkfifo commandpipe 0o660; Unix.chown commandpipe uid gid; ignore (Unix.umask umask); Lwt_log.ign_notice ~section "Command pipe created"; with e -> Lwt_log.ign_error ~section ~exn:e "Cannot create the command pipe")); (* I change the user for the process *) begin try if current_uid = 0 then begin match user with | None -> () | Some user -> Unix.initgroups user gid end; Unix.setgid gid; Unix.setuid uid; with (Unix.Unix_error _ | Failure _) as e -> Lwt_log.ign_error ~section "Error: Wrong user or group"; raise e end; Ocsigen_config.set_user user; Ocsigen_config.set_group group; (* Je suis fou : let rec f () = print_endline "-"; Lwt_unix.yield () >>= f in f (); *) if maxthreads < minthreads then raise (Config_file_error "maxthreads should be greater than minthreads"); ignore (Ocsigen_config.init_preempt minthreads maxthreads (fun s -> Lwt_log.ign_error ~section s)); (* Now I can load the modules *) Dynlink_wrapper.allow_unsafe_modules true; Ocsigen_extensions.start_initialisation (); parse_server false s; Dynlink_wrapper.prohibit ["Ocsigen_extensions.R"]; (* As libraries are reloaded each time the config file is read, we do not allow to register extensions in libraries *) (* seems it does not work :-/ *) (* Closing stderr, stdout stdin if silent *) if (Ocsigen_config.get_silent ()) then begin (* redirect stdout and stderr to /dev/null *) let devnull = Unix.openfile "/dev/null" [Unix.O_WRONLY] 0 in Unix.dup2 devnull Unix.stdout; Unix.dup2 devnull Unix.stderr; Unix.close devnull; Unix.close Unix.stdin; end; (* detach from the terminal *) if (Ocsigen_config.get_daemon ()) then ignore (Unix.setsid ()); Ocsigen_extensions.end_initialisation (); let pipe = Lwt_io.of_fd ~mode:Lwt_io.input @@ Lwt_unix.of_unix_file_descr @@ Unix.openfile commandpipe [Unix.O_RDWR; Unix.O_NONBLOCK; Unix.O_APPEND] 0o660 in let rec f () = lwt_chan_input_line pipe >>= fun s -> Lwt_log.ign_notice ~section ("Command received: "^s); (Lwt.catch (fun () -> let prefix, c = match String.split ~multisep:true ' ' s with | [] -> raise Ocsigen_command.Unknown_command | a::l -> try let aa, ab = String.sep ':' a in (Some aa, (ab::l)) with Not_found -> None, (a::l) in Ocsigen_command.get_command_function () ?prefix s c) (function | Ocsigen_command.Unknown_command -> Lwt_log.ign_warning ~section "Unknown command"; Lwt.return () | e -> Lwt_log.ign_error ~section ~exn:e "Uncaught Exception after \ command"; Lwt.fail e)) >>= f in ignore (f ()); Lwt.async_exception_hook := (fun e -> (* replace the default "exit 2" behaviour *) Lwt_log.ign_error ~section ~exn:e "Uncaught Exception" ); Lwt.wakeup wait_end_init_awakener (); Lwt_log.ign_notice ~section "Ocsigen has been launched \ (initialisations ok)"; Lwt_main.run @@ fst (Lwt.wait ()) in let set_passwd_if_needed (ssl, ports, sslports) = if sslports <> [] then match ssl with | None | Some {ssl_certificate = None; ssl_privatekey = None} -> () | Some {ssl_certificate = None} -> raise (Ocsigen_config.Config_file_error "SSL certificate is missing") | Some {ssl_privatekey = None} -> raise (Ocsigen_config.Config_file_error "SSL key is missing") | Some {ssl_certificate = Some c; ssl_privatekey = Some k} -> Ssl.set_password_callback !sslctx (ask_for_passwd sslports); Ssl.use_certificate !sslctx c k in let set_ciphers_if_needed = function | Some {ssl_ciphers = Some s}, _, _ :: _ -> (try Ssl.set_cipher_list !sslctx s with Ssl.Cipher_error -> raise (Ocsigen_config.Config_file_error "Invalid cipher string")) | _, _, _ -> () in let set_dhfile_if_needed = function | Some {ssl_dhfile = Some e}, _, _ :: _ -> (try Ssl.init_dh_from_file !sslctx e with Ssl.Diffie_hellman_error -> raise (Ocsigen_config.Config_file_error "Invalid DH file")) | _, _, _ -> () in let set_curve_if_needed = function | Some {ssl_curve = Some c}, _, _ :: _ -> (try Ssl.init_ec_from_named_curve !sslctx c with Ssl.Ec_curve_error -> raise (Ocsigen_config.Config_file_error "Invalid EC curve")) | _, _, _ -> () in let write_pid pid = match Ocsigen_config.get_pidfile () with None -> () | Some p -> let spid = (string_of_int pid)^"\n" in let len = String.length spid in let f = Unix.openfile p [Unix.O_WRONLY; Unix.O_CREAT; Unix.O_TRUNC] 0o640 in ignore (Unix.write_substring f spid 0 len); Unix.close f in let rec launch = function | [] -> () | [h] -> let user_info, sslinfo, threadinfo = extract_info h in set_passwd_if_needed sslinfo; set_ciphers_if_needed sslinfo; set_dhfile_if_needed sslinfo; set_curve_if_needed sslinfo; if (get_daemon ()) then let pid = Lwt_unix.fork () in if pid = 0 then run user_info sslinfo threadinfo h else begin Ocsigen_messages.console (fun () -> "Process "^(string_of_int pid)^" detached"); write_pid pid end else begin write_pid (Unix.getpid ()); run user_info sslinfo threadinfo h end | _ -> () (* Multiple servers not supported any more *) in launch config_servers with | Stop (n, s) -> Lwt_main.run (Lwt_log.error ~section s); exit n | e -> let msg, errno = errmsg e in Lwt_main.run (Lwt_log.error ~section msg); exit errno ocsigenserver-2.16.0/src/server/ocsigen_server.mli000066400000000000000000000021761357715257700223240ustar00rootroot00000000000000(* Ocsigen * http://www.ocsigen.org * Copyright (C) 2005 * Vincent Balat, Denis Berthod, Nataliya Guts, Jérôme Vouillon * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, with linking exception; * either version 2.1 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) (** Reload the configuration of the server. The optional parameter [?file] may be use to read the configuration from another file. *) val reload: ?file:string -> unit -> unit (** Start the server (does not return) *) val start_server: unit -> unit ocsigenserver-2.16.0/src/server/ocsigen_socket.ml000066400000000000000000000050431357715257700221310ustar00rootroot00000000000000open Ocsigen_lib type socket_type = | All | IPv4 of Unix.inet_addr | IPv6 of Unix.inet_addr (** make_ipv6_socket create a socket on an ipv6 address * @param addr address of socket * @param port port of socket * *) let make_ipv6_socket addr port = let socket = Lwt_unix.socket Unix.PF_INET6 Unix.SOCK_STREAM 0 in Lwt_unix.set_close_on_exec socket; (* see http://stackoverflow.com/a/14388707/2200717 for more information * to why set REUSEADDR on socket *) Lwt_unix.setsockopt socket Unix.SO_REUSEADDR true; Lwt_unix.setsockopt socket Unix.IPV6_ONLY true; Lwt_unix.bind socket (Unix.ADDR_INET (addr, port)) >>= fun () -> Lwt.return socket (** make_ipv4_socket create a socket on an ipv4 address * @param addr address of socket * @param port port of socket * *) let make_ipv4_socket addr port = let socket = Lwt_unix.socket Unix.PF_INET Unix.SOCK_STREAM 0 in Lwt_unix.set_close_on_exec socket; Lwt_unix.setsockopt socket Unix.SO_REUSEADDR true; Lwt_unix.bind socket (Unix.ADDR_INET (addr, port)) >>= fun () -> Lwt.return socket let make_sockets addr port = match addr with | All -> (* The user didn't specify a protocol in the configuration file; we try to open an IPv6 socket (listening to IPv6 only) if possible and we open an IPv4 socket anyway. This corresponds to the net.ipv6.bindv6only=0 behaviour on Linux, but is portable and should work with net.ipv6.bindv6only=1 as well. *) Lwt.catch (fun () -> make_ipv6_socket Unix.inet6_addr_any port >>= fun s -> Lwt.return [s]) (function | Unix.Unix_error ((Unix.EAFNOSUPPORT | Unix.EPROTONOSUPPORT | Unix.EADDRINUSE (* GH issue #104 *) ), _, _) -> Lwt.return [] | e -> Lwt.fail e) >>= fun ipv6_sockets -> make_ipv4_socket Unix.inet_addr_any port >>= fun ipv4_socket -> Lwt.return (ipv4_socket :: ipv6_sockets) | IPv4 addr -> make_ipv4_socket addr port >>= fun s -> Lwt.return [s] | IPv6 addr -> make_ipv6_socket addr port >>= fun s -> Lwt.return [s] let ip_of_sockaddr = function | Unix.ADDR_INET (ip, port) -> ip | _ -> raise (Ocsigen_lib_base.Ocsigen_Internal_Error "ip of unix socket") let port_of_sockaddr = function | Unix.ADDR_INET (ip, port) -> port | _ -> raise (Ocsigen_lib_base.Ocsigen_Internal_Error "port of unix socket") let string_of_socket_type = function | All -> Unix.string_of_inet_addr Unix.inet_addr_any | IPv4 u -> Unix.string_of_inet_addr u | IPv6 u -> Unix.string_of_inet_addr u ocsigenserver-2.16.0/src/server/ocsigen_socket.mli000066400000000000000000000014471357715257700223060ustar00rootroot00000000000000(** Abstraction handling sockets IPv4 and IPv6 *) (** type of address *) type socket_type = | All | IPv4 of Unix.inet_addr | IPv6 of Unix.inet_addr (** make_sockets create socket ready to listen in addr:port @param addr type of addresses (All | IPv4 | IPv6) @param port port of socket *) val make_sockets : socket_type -> int -> Lwt_unix.file_descr list Lwt.t (** ip_of_sockaddr accessor for ip @param A Unix.ADDR_INET value or raise error *) val ip_of_sockaddr : Unix.sockaddr -> Unix.inet_addr (** port_of_sockaddr accessor for port @param A Unix.ADDR_INET value or raise error *) val port_of_sockaddr : Unix.sockaddr -> int (** string_of_socket_type cast a Unix.inet_addr in socket_type to a string @param A socket_type *) val string_of_socket_type : socket_type -> string ocsigenserver-2.16.0/src/server/server_main.ml000066400000000000000000000000521357715257700214370ustar00rootroot00000000000000let () = Ocsigen_server.start_server ()