.. _library_http_cors:

``http_cors``
=============

The ``http_cors`` library provides a transport-neutral CORS layer on top
of the normalized ``http_core`` library. It classifies normalized
requests as CORS or preflight requests, builds direct preflight
responses from CORS policies, and decorates normalized responses with
the relevant ``Access-Control-*`` headers.

This library reuses the ``http_core``, ``http_server_core``, and
``http_router``. It uses normalized request and response terms and can
be called directly from plain handlers or from router hooks and response
middleware.

API documentation
-----------------

Open the
`../../apis/library_index.html#http_cors <../../apis/library_index.html#http_cors>`__
link in a web browser.

Loading
-------

To load the library, load the ``loader.lgt`` file:

::

   | ?- logtalk_load(http_cors(loader)).

Testing
-------

To test this library, load the ``tester.lgt`` file:

::

   | ?- logtalk_load(http_cors(tester)).

Current scope
-------------

The current implementation provides one transport-neutral public object
with five public predicates:

- ``request_origin/2``
- ``is_cors_request/1``
- ``is_preflight_request/1``
- ``preflight_response/3``
- ``add_response_headers/4``

The option DSL follows the ``options`` library conventions and supports:

- ``allowed_origins(any)``
- ``allowed_origins(Origins)``
- ``allowed_methods(any)``
- ``allowed_methods(Methods)``
- ``allowed_headers(any)``
- ``allowed_headers(requested)``
- ``allowed_headers(Headers)``
- ``expose_headers(any)``
- ``expose_headers(Headers)``
- ``allow_credentials(Boolean)``
- ``max_age(none)``
- ``max_age(Seconds)``

The default policy is:

- ``allowed_origins([])``
- ``allowed_methods([get])``
- ``allowed_headers(requested)``
- ``expose_headers([])``
- ``allow_credentials(false)``
- ``max_age(none)``

This library consumes the normalized HTTP terms provided by the
``http_core`` library:

::

   request(Method, Target, Version, Headers, Body, Properties)
   response(Version, Status, Headers, Body, Properties)

It reads normalized request headers such as ``origin``,
``access_control_request_method``, and
``access_control_request_headers`` and emits normalized response headers
such as ``access_control_allow_origin``,
``access_control_allow_methods``, ``access_control_allow_headers``,
``access_control_expose_headers``, and ``vary``.

When policy depends on a specific requesting origin, generated responses
add or preserve the appropriate ``Vary`` metadata. Existing ``Vary: *``
headers are treated as absorbing and remain ``*`` when CORS headers are
merged into a response.

Wildcard header policies can be expressed explicitly using
``allowed_headers(any)`` and ``expose_headers(any)``. For compatibility,
exact ``['*']`` option values are normalized to the same wildcard
behavior. On non-credentialed requests these policies emit ``*`` when
that has the intended CORS wildcard meaning. On credentialed requests,
and for ``Authorization`` preflight requests, the library materializes
explicit header-name lists instead because browsers otherwise treat
``*`` as a literal header name.

Similarly, ``allowed_origins(any)`` emits ``*`` for non-credentialed
requests but reflects the concrete request origin for credentialed
requests and adds ``Vary: Origin`` so caches keep those responses
partitioned correctly.

Wildcard origin policies can also be expressed using pattern atoms
inside ``allowed_origins(Origins)``, for example
``allowed_origins(['https://*.example.com'])``. These patterns match
exactly one leftmost hostname label, require exact scheme matching, and
require exact port matching when the pattern includes an explicit port.
Matching pattern policies always emit the concrete request origin and
add or preserve ``Vary: Origin`` because the response depends on the
requesting origin.

Wildcard method policies can be expressed using
``allowed_methods(any)``. For compatibility, exact ``['*']`` values are
normalized to the same wildcard behavior. Method wildcards expand from
the request ``effective_methods(Methods)`` property after removing
``options``, so they are intended for routed or otherwise annotated
requests that provide that metadata. The generated
``Access-Control-Allow-Methods`` header still contains an explicit
method list.

Route-level overrides
---------------------

When present, the request property ``cors(Options)`` overrides the
caller-supplied default options. This makes it possible to define
router-wide defaults and then refine them per route using
``route_metadata/2``.

Example route metadata:

::

   route_metadata(show_page, [
       cors([
           allowed_origins(['https://app.example.com']),
           expose_headers([x_trace_id]),
           allow_credentials(true)
       ])
   ]).

Router integration
------------------

For actual routed responses, use router response middleware:

::

   response_middleware(cors, add_cors_headers).

   add_cors_headers(Request, Response0, Response) :-
       Defaults = [
           allowed_origins([]),
           allowed_methods([get]),
           allowed_headers(requested),
           expose_headers([]),
           allow_credentials(false),
           max_age(none)
       ],
       http_cors::add_response_headers(Request, Response0, Response, Defaults).

For automatic router ``OPTIONS`` responses, use
``route_automatic_options_response/3`` when the router object wants to
build a direct preflight response:

::

   route_automatic_options_response(Request, _EffectiveMethods, Response) :-
       Defaults = [
           allowed_origins([]),
           allowed_methods([get, post]),
           allowed_headers(requested),
           expose_headers([]),
           allow_credentials(false),
           max_age(none)
       ],
       http_cors::preflight_response(Request, Response, Defaults).

The current ``http_router`` implementation annotates synthetic automatic
``OPTIONS`` requests with ``automatic_options(true)``,
``effective_methods(Methods)``, and route metadata when the match is
unique or the metadata is shared across same-path matches. This allows
``http_cors`` integrations to reuse those annotations directly.

Plain handler integration
-------------------------

For plain handlers using ``http_server_core``, the recommended pattern
is:

1. detect preflight requests with ``is_preflight_request/1``
2. build the direct preflight response with ``preflight_response/3``
3. otherwise build the application response and pass it through
   ``add_response_headers/4``

When ``add_response_headers/4`` denies a request, it does not add
permission headers but it may still preserve or add cache-relevant
``Vary`` metadata. Use ``preflight_response/3`` when an explicit
``403 Forbidden`` preflight response is required.

Current limitations
-------------------

- Origin matching is exact and string-based
- Dynamic callback policies are not supported
