File uhttpmock-0.11.0.obscpio of Package uhttpmock

07070100000000000081A400000000000000000000000166717A5A00000374000000000000000000000000000000000000002000000000uhttpmock-0.11.0/.gitlab-ci.ymlimage: debian:unstable

before_script:
  - apt update -qq
  - apt install -y -qq
      build-essential
      ca-certificates
      gobject-introspection
      git
      gtk-doc-tools
      libgirepository1.0-dev
      libglib2.0-dev
      libsoup-3.0-dev
      libxml2-utils
      meson
      pkg-config
      valac

stages:
  - build

build-check:
  stage: build
  script:
    - unset CI_JOB_JWT
    - meson
        -Dgtk_doc=true
        -Dintrospection=true
        -Dvapi=enabled
        --buildtype debug
        --wrap-mode=nodownload
        --fatal-meson-warnings
        --werror
        _build
    - ninja -C _build
    - meson test -C _build
    - meson dist -C _build

  # The files which are to be made available in GitLab
  artifacts:
    when: always
    expire_in: 1 week
    paths:
      - _build/meson-logs/
      - _build/meson-dist/
      - _build/docs/reference/
07070100000001000081A400000000000000000000000166717A5A00000029000000000000000000000000000000000000001900000000uhttpmock-0.11.0/AUTHORSPhilip Withnall <philip@tecnocode.co.uk>
07070100000002000081A400000000000000000000000166717A5A00006744000000000000000000000000000000000000001900000000uhttpmock-0.11.0/COPYING		  GNU LESSER GENERAL PUBLIC LICENSE
		       Version 2.1, February 1999

 Copyright (C) 1991, 1999 Free Software Foundation, Inc.
 51 Franklin Street, 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.

    <one line to give the library's name and a brief idea of what it does.>
    Copyright (C) <year>  <name of author>

    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 Street, 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.

  <signature of Ty Coon>, 1 April 1990
  Ty Coon, President of Vice

That's all there is to it!


07070100000003000081A400000000000000000000000166717A5A000024D3000000000000000000000000000000000000001900000000uhttpmock-0.11.0/HACKINGFormatting
==========

All code should follow the same formatting standards which are broadly based on the GNU style (http://www.gnu.org/prep/standards.html) with some
additions. Briefly:

 - Tab indents are used and braces for blocks go on the same line as the block statement:

	if (x < foo (y, z)) {
		haha = bar[4] + 5;
	} else {
		while (z) {
			haha += foo (z, z);
			z--;
		}
		return abc (haha);
	}

   Braces should be omitted for single-line blocks, but included for all blocks in a multi-part if statement which has blocks containing more than
   one line (as above).

 - Spaces should be present between function name and argument block, and after commas:

	foo (z, z)

 - In pointer types, the '*' is grouped with the variable name, not with the base type. 

	int *a;

   Not:

	int* a;

   In cases where there is no variable name, for instance, return values, there should be a single space between the base type and the '*'.

   Type casts should have no space between the type and '*', but a space before the variable being cast:

	(gchar*) foobar;
	(gchar**) &foobar;

 - Function and variable names are lower_case_with_underscores, type names are CamelCase and macro names are UPPER_CASE_WITH_UNDERSCORES.

 - Comparisons to NULL, TRUE and FALSE should always be made explicit, for clarity.

 - Code should be wrapped at the 150th column, such that it doesn't require horizontal scrolling on a decent-sized display.
   Don't wrap at the 80th column.

Documentation comments
======================

All public API functions should have inline documentation headers in the gtk-doc style. For more information about gtk-doc comments, see the gtk-doc
manual (http://library.gnome.org/devel/gtk-doc-manual/stable/). There are a few conventions above and beyond the standard gtk-doc formatting which
uhttpmock employs:

 - For API which returns allocated memory, the relevant free/unref function must be mentioned in the "Return value" part of the documentation:

	* Return value: a new #UhmServer; unref with g_object_unref()

   If the function can also return NULL (on error, for example) or some other "default" value (-1, 0, etc.), format it as follows:

	* Return value: (allow-none): a new #UhmServerIdentity, or %NULL; free with uhm_server_identity_free()

   Note that if a function returns NULL as a result of a precondition or assertion failure, this should not be listed in the documentation. The
   precondition itself may be listed in the function documentation prose, but if it's the only reason for a function to return NULL, the "or %NULL"
   clause should be omitted.

 - When adding API, make sure to add a "Since" clause:

	* Since: 0.2.0

 - For object methods, the "self" parameter should be documented simply as "a #GObjectType":

	* @self: a #UhmServer

 - For function parameters which can legitimately be set to NULL (or some other default value), list that as follows:

	* @updated_max: (allow-none): the new maximum update time, or %NULL

 - If numbers, such as -1, are mentioned in documentation as values to be passed around in code, they should be wrapped in a DocBook "code" tag
   (e.g. "<code class="literal">-1</code>"), so that they appear similarly to NULL in the documentation.

 - The documentation explaining the purpose of a property, its limitations, interpretation, etc., should be given in the gtk-doc comment for the
   GObject property itself, not in the documentation for its getter or setter. The documentation for the getter and setter should be stubs which
   refer to the property's documentation. The getter and setter documentation should, however, still include full details about whether NULL values
   are permissible for the function parameters, or are possible as the return value, for example.

Adding public API
=================

 - Ensure it has proper guards against bad parameters:

	g_return_if_fail (UHM_IS_SERVER (self));
	g_return_if_fail (foobar != NULL);

 - All public API must have a gtk-doc comment, and be added to the docs/libuhttpmock-sections.txt file, to include it in the documentation.
   The documentation comment must have a "Since" clause (see "Documentation comments" section).

 - All public functions must start with `uhm_`; all functions with that prefix are exposed as public API from `libuhttpmock.map`.

 - Non-trivial API should have a test case added in the relevant test suite file in libuhttpmock/tests.

 - All GObject properties must have getter/setter functions.

 - All API which returns allocated memory must be tagged with G_GNUC_WARN_UNUSED_RESULT after its declaration, to safeguard against consumers of the
   API forgetting to use (and consequently free) the memory. This is unlikely, but adding the macro costs little and acts as a reminder in the API
   documentation to free the result.

 - All GObject *_get_type function declarations must be tagged with the G_GNUC_CONST macro, as well as any other applicable functions
   (see the gcc documentation: http://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html#index-g_t_0040code_007bconst_007d-function-attribute-2207).

 - New API must never be added in a stable micro release. API additions can only be made in a major or minor release; this is to prevent the LT version
   of one minor version's micro releases exceeding the LT version of the next minor version as almost happened between versions 0.6.3 and 0.7.0.
   See http://www.gnu.org/software/libtool/manual/html_node/Libtool-versioning.html for information about libtool's versioning system. See also the
   “Versioning” section below.

 - Any async function which uses non-async-scope callbacks as well as the async ready callback should provide GDestroyNotify callbacks for destroying
   the user data for those callbacks. See https://bugzilla.gnome.org/show_bug.cgi?id=649728 for details.

Choosing function names
=======================

In general, use common sense. However, there are some specific cases where a standard is enforced:

 - For boolean getters (e.g. for FooBar:is-baz) use foo_bar_is_baz, rather than foo_bar_get_is_baz. Note that the property name should be "is-baz",
   rather than just "baz".

 - For boolean setters use foo_bar_set_is_baz, rather than foo_bar_set_baz.

Deprecating public API
======================

As libuhttpmock moves towards API stability, old API should be deprecated rather than broken or removed entirely. The following should be ensured when
deprecating API:

 - G_GNUC_DEPRECATED_FOR is added to the API in the public header file.

 - A “Deprecated:” line is added to the gtk-doc comment for the API. This should mention what API replaces it, give a brief explanation of why the
   API has been deprecated, and finish with “(Since: [version].)” to list the version the API was deprecated.

 - “#ifndef LIBUHTTPMOCK_DISABLE_DEPRECATED” should be wrapped around the API in the public header file.

 - All references to the API/uses of the API in libuhttpmock code (including demos and the test suite) should be ported to use the replacement API
   instead. If this isn't possible, the deprecated function should be split into a static function which contains all the code, and the public
   symbol should become a simple wrapper of this static function. This allows the static function to be used inside libuhttpmock without causing
   deprecation warnings.

 - Don't remove deprecated symbols from uhttpmock.symbols.

 - Don't forget to also deprecate related symbols, such as the getter/setter for a property (or vice-versa).

Commit messages
===============

libuhttpmock does not use a ChangeLog; it is auto-generated from the git log when packaging a release. Commit messages should follow the GNOME commit
message guidelines (http://live.gnome.org/Git/CommitMessages), with the exception that when a commit closes a bug, the short explanation of the commit
should simply be the bug's title, as copied from Bugzilla (e.g. "Bug 579885 – Add code examples to documentation"). The long explanation should then
be used to give details of the changes. If the bug's title is not relevant, it should be changed before committing the changes.

Unless the short explanation of a commit is a bug title, it should always be prefixed by a tag to describe the part of the library it touches, using
the following format "tag: Short explanation". The following tags are valid:

 - lib: for the core code in the libuhttpmock directory, such as UhmServer.

 - build: for build changes and releases.

 - docs: for documentation changes such as updates to the docs directory, NEWS, README, this file, etc.

 - tests: for changes to the test code in libuhttpmock/tests.

 - demos: for changes to the demo applications in the demos directory.

 - introspection: for introspection annotations and build changes.

The only commits which should not have a tag are translation commits, touching only the po directory.

Versioning
==========

libuhttpmock uses an even–odd/stable–unstable versioning policy, where odd minor version numbers are unstable releases,
released periodically (with increasing micro version numbers) and leading to a stable release with the next even minor version number. API breaks are
allowed in micro releases with an odd minor version number, but not in micro releases with an even minor version number.

It is encouraged to make a new micro release of an odd minor series after each large API addition or break.
07070100000004000081A400000000000000000000000166717A5A0000108B000000000000000000000000000000000000001600000000uhttpmock-0.11.0/NEWSOverview of changes from uhttpmock 0.10.0 to uhttpmock 0.11.0
=============================================================

* Bugs fixed:
  - #14 Dump hosts during record (Jan-Michael Brummer)
  - !13 Add Jan-Michael Brummer to doap file
  - !14 Add HTTP/2 support
  - !15 Add Location field support
  - !17 Fix memory leak in uhm_message_finalize


Overview of changes from uhttpmock 0.9.0 to uhttpmock 0.10.0
============================================================

* Bugs fixed:
  - !11 Add PATCH request method
  - !12 Add CONNECT request method


Overview of changes from uhttpmock 0.5.5 to uhttpmock 0.9.0
===========================================================

This release ports the library to use libsoup 3.x. Note that
you should stick to using the latest 0.5.x version if the
tested piece of software still requires libsoup 2.x as the
2 versions cannot be used in the same programme.

Overview of changes from uhttpmock 0.5.4 to uhttpmock 0.5.5
===========================================================

This release fixes the gtk-doc directory containing the version
number twice.

Overview of changes from uhttpmock 0.5.3 to uhttpmock 0.5.4
===========================================================

This new release drops support for autotools, by using the
meson build system, and older versions of libsoup 2.x.

Overview of changes from uhttpmock 0.5.2 to uhttpmock 0.5.3
===========================================================

Bugs fixed:
 • Add an integration tutorial (by Rasmus Thomsen) (#3)
 • Only specify SOUP_SERVER_LISTEN_HTTPS if TLS is enabled (!9)
 • Expand TLS certificate instructions (!10)


Overview of changes from uhttpmock 0.5.1 to uhttpmock 0.5.2
===========================================================

Bugs fixed:
 • Add GitLab CI
 • Fix autoconf-archive usage due to them breaking API
 • Bump GObject dependency to 2.38 to use newer private data API


Overview of changes from uhttpmock 0.5.0 to uhttpmock 0.5.1
===========================================================

Major changes:
 • Fix header include path in GIR/VAPI file
 • Various build system cleanups


Overview of changes from uhttpmock 0.4.0 to uhttpmock 0.5.0
===========================================================

Major changes:
 • Minor documentation updates

API changes:
 • Add uhm_server_filter_ignore_parameter_values()
 • Add uhm_server_compare_messages_remove_filter()

Bugs fixed:
 • https://gitlab.com/uhttpmock/uhttpmock/issues/2


Overview of changes from uhttpmock 0.3.0 to uhttpmock 0.4.0
===========================================================

Major changes:
 • Add optional support for libsoup 2.47.3’s new SoupServer API
 • Fix some memory leaks
 • Fix parsing of certain trace files
 • Various build system updates
 • Move home page from gitorious to https://gitlab.com/groups/uhttpmock
 • Port from GAsyncResult to GTask, requiring a GIO dependency bump to 2.36.0

Bugs fixed:
 • Bug 748200 — typo in uhttpmock configure.ac
 • https://github.com/pwithnall/uhttpmock/issues/2
 • https://gitlab.com/uhttpmock/uhttpmock/issues/1


Overview of changes from uhttpmock 0.2.0 to uhttpmock 0.3.0
===========================================================

Major changes:
 • Drop useless gthread dependency
 • Thread safety fixes

API changes:
 • Add uhm_server_set_expected_domain_names()
 • Add uhm_server_received_message_chunk_with_direction(),
   uhm_server_received_message_chunk_from_soup()

Bugs fixed:
 • Bug 1026764 — Review Request: uhttpmock - HTTP web service mocking library
 • Bug 731040 — libgdata-0.14.3 tests fail


Overview of changes from uhttpmock 0.1.0 to uhttpmock 0.2.0
===========================================================

Major changes:
 • Rename pkg-config file from libuhttpmock.pc to libuhttpmock-0.0.pc,
   to allow for parallel installation of different versions
 • Rename VAPI file similarly
 • Install header files in a versioned directory


Initial release of uhttpmock 0.1.0
==================================

Major changes:
 • Initial version of the project
 • Support for mocking HTTP and HTTPS web services
 • Support for trace files and manual override messages
07070100000005000081A400000000000000000000000166717A5A00000629000000000000000000000000000000000000001800000000uhttpmock-0.11.0/READMEuhttpmock
=========

uhttpmock is a project for mocking web service APIs which use HTTP or HTTPS. It
provides a library, libuhttpmock, which implements recording and playback of
HTTP request–response traces.

See the test programs in libuhttpmock/tests/ for simple examples of how to use
the code.

libuhttpmock’s API is currently unstable and is likely to change wildly.

Using uhttpmock
===============

Key points:
 1. All requests must be HTTPS or all requests must be HTTP. uhttpmock can’t
    handle a mixture of HTTPS and HTTP requests.
 2. You must override your code to use the port returned by
    uhm_server_get_port(), rather than the default HTTP or HTTPS ports.
 3. You must disable libsoup’s SSL strict mode (SOUP_SESSION_SSL_STRICT)
    because uhttpmock uses self-signed SSL certificates.
 4. You must output all libsoup log data to uhttpmock.

Dependencies
============

 • glib-2.0 ≥ 2.38.0
 • gio-2.0 ≥ 2.36.0
 • libsoup-3.0 ≥ 3.0.1

Deprecation guards
==================

If LIBUHTTPMOCK_DISABLE_DEPRECATED is defined when compiling against
libuhttpmock, all deprecated API will be removed from included headers.

Licensing
=========

libuhttpmock is licensed under the LGPL; see COPYING.LIB for more details.

Bugs
====

Bug reports and merge requests should be submitted on GitLab:
 • https://gitlab.freedesktop.org/pwithnall/uhttpmock/issues/new
 • https://gitlab.freedesktop.org/pwithnall/uhttpmock/merge_requests/new

Contact
=======

Philip Withnall <philip@tecnocode.co.uk>
https://gitlab.freedesktop.org/pwithnall/uhttpmock
07070100000006000041ED00000000000000000000000266717A5A00000000000000000000000000000000000000000000001E00000000uhttpmock-0.11.0/libuhttpmock07070100000007000041ED00000000000000000000000266717A5A00000000000000000000000000000000000000000000002300000000uhttpmock-0.11.0/libuhttpmock/docs07070100000008000081A400000000000000000000000166717A5A00002671000000000000000000000000000000000000003D00000000uhttpmock-0.11.0/libuhttpmock/docs/libuhttpmock-1.0-docs.xml<?xml version="1.0"?>
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN" "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd" [
	<!ENTITY % local.common.attrib "xmlns:xi  CDATA  #FIXED 'http://www.w3.org/2003/XInclude'">
	<!ENTITY version SYSTEM "version.xml">
]>
<book id="index" xmlns:xi="http://www.w3.org/2003/XInclude">
	<bookinfo>
		<title>libuhttpmock Reference Manual</title>
		<releaseinfo>for libuhttpmock &version;.<!-- The latest version of this documentation can be found online at
			<ulink role="online-location" url="http://[SERVER]/libuhttpmock/index.html">http://[SERVER]/libuhttpmock/</ulink>.-->
		</releaseinfo>
	</bookinfo>

	<part>
		<title>Integrating libuhttpmock into new projects</title>
		<chapter>
			<title>Introduction</title>
			<para>
				libuhttpmock works by recording the Soup Session your application uses.
				It traces the requests your application makes and the reply it receives
				from the (online) server you query. After capturing an initial trace,
				libuhttpmock will be able to reply to your application's requests
				the same way the actual servers would and as such help you detect
				unwanted changes in behaviour in your application.
			</para>
			<example>
				<title>A sample implementation of a test with libuhttpmock</title>
				<programlisting>
/*
 * Copyright (C) 2020 Rasmus Thomsen &lt;oss@cogitri.dev&gt;
 */
#include &lt;gio/gio.h&gt;
#include &lt;glib.h&gt;
#include &lt;libsoup/soup.h&gt;
#include &lt;uhttpmock/uhm.h&gt;

UhmServer* mock_server = NULL;

static gboolean
accept_cert (SoupMessage *msg, GTlsCertificate *cert, GTlsCertificateFlags errors, gpointer user_data)
{
	/*
	 * Allow usage of the self-signed certificate we generate in main ().
	 * Only to be done when testing!
	 */
	return uhm_server_get_enable_online (mock_server);
}

void
test_mock (void)
{
	g_autoptr(GError) error = NULL;
	g_autoptr(SoupLogger) soup_logger = NULL;
	g_autoptr(SoupMessage) soup_message = NULL;
	g_autoptr(SoupSession) soup_session = NULL;
	g_autofree gchar *server_url = NULL;
	g_autoptr(GBytes) body = NULL;
	guint http_status_code = 0;

	/*
	 * Start recording the trace if in online mode, otherwise read trace to compare
	 * Replace "testname" with the name of the test. This needs to be unique among
	 * your test suite!
	 */
	uhm_server_start_trace (mock_server, "testname", &amp;error);
	g_assert (error == NULL);

	soup_session = soup_session_new ();

	/* Query actual server if online mode is activated */
	if (uhm_server_get_enable_online (mock_server)) {
		server_url = g_strdup ("https://jsonplaceholder.typicode.com/todos/1");
	} else {
		const gchar *server_address = uhm_server_get_address (mock_server);
		guint server_port = uhm_server_get_port (mock_server);
		server_url = g_strdup_printf ("https://%s:%u", server_address, server_port);
	}

	/* Set uhttpmock as soup logger so it can capture the trace */
	soup_logger = soup_logger_new (SOUP_LOGGER_LOG_BODY);
	soup_logger_set_printer (soup_logger, uhm_server_received_message_chunk_from_soup, mock_server, NULL);
	soup_session_add_feature (soup_session, SOUP_SESSION_FEATURE (soup_logger));

	/*
	 * Send the message - usually your application would process the
	 * response from uhttpmock, do further requests, etc. afterwards
	 */
	soup_message = soup_message_new ("GET", server_url);
	g_signal_connect (soup_message, "accept-certificate", G_CALLBACK (accept_cert), NULL);
	body = soup_session_send_and_read (soup_session, soup_message, NULL, NULL);
	http_status_code = soup_message_get_status (soup_message);
	g_assert (http_status_code == 200);

	/* End the trace - in online mode this will save the trace, otherwise it'd compare it. */
	uhm_server_end_trace (mock_server);
}

int
main (int argc, char **argv)
{
	g_autofree gchar *cert_path = NULL;
	g_autofree gchar *key_path = NULL;
	g_autofree gchar *trace_path = NULL;
	g_autoptr(GError) error = NULL;
	g_autoptr(GFile) trace_dir = NULL;
	g_autoptr(GTlsCertificate) tls_cert = NULL;

	g_test_init (&amp;argc, &amp;argv, NULL);

	/*
	 * Setup the mock server with the directory to write traces to.
	 * replace "testsuitename" with the name of the testsuite. This must
	 * be unique among your project!
	 */
	mock_server = uhm_server_new ();
	trace_path = g_test_build_filename (G_TEST_DIST, "traces", "testsuitename", NULL);
	trace_dir = g_file_new_for_path (trace_path);
	uhm_server_set_trace_directory (mock_server, trace_dir);

	/*
	 * Set up self signed cert for HTTPS. The cert doesn't actually need to be
	 * secret - it can be distributed with your program or be generated during
	 * build time with:  openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -nodes
	 */
	cert_path = g_test_build_filename (G_TEST_DIST, "sslcertdir", "cert.pem", NULL);
	key_path = g_test_build_filename (G_TEST_DIST, "sslcertdir", "key.pem", NULL);
	tls_cert = g_tls_certificate_new_from_files (cert_path, key_path, &amp;error);
	g_assert (error == NULL);
	uhm_server_set_tls_certificate (mock_server, tls_cert);

	/*
	 * TRUE, TRUE => Logging mode, records what actual server replies
	 * FALSE, TRUE => Comparing mode, checks actual server still replies as expected from trace
	 * FALSE, FALSE => Testing mode, requests are answered by mock server as per trace
	 *
	 * As such, you usually want TRUE, TRUE for the initial recording or if you/the server changed
	 * something (and as such expect a legitimate change in the trace) and then FALSE, FALSE
	 * to actually engage the tests.
	 */
	uhm_server_set_enable_logging (mock_server, TRUE);
	uhm_server_set_enable_online (mock_server, TRUE);

	g_test_add_func ("/test/mock", test_mock);
	return g_test_run ();
}
				</programlisting>
			</example>

			<para>
				This example can be compiled using:
			</para>
			<informalexample>
				<programlisting>
gcc -o uhttpsample uhttpsample.c $(pkg-config --cflags --libs gio-2.0 glib-2.0 libsoup-3.0 libuhttpmock-0.0)
				</programlisting>
			</informalexample>
			<para>
				Afterwards, executing <code>./uhttpsample</code> will run the test suite. Initially it will
				record a trace of the requests done by the test_mock function and the responses
				it receives, because <code>uhm_server_set_enable_logging</code> and
				<code>uhm_server_set_enable_online</code> are both set to <code>TRUE</code>. Upon setting
				both of these to <code>FALSE</code>, libuhttpmock will read the trace it
				created earlier and will fail the test (by returning an HTTP status code indicating failure
				for the faulty query your application makes) in case something is different - e.g. because
				your application queried a different URL than expected. Keep in mind that by default
				libuhttpmock will only compare the URI and methods of messages, but no headers and
				not the message bodies. To override this behaviour, you can connect to the
				<code>compare-messages</code> signal on <code>UhmServer</code> to implement your own
				custom handler for checking if messages are as expected.
			</para>
		</chapter>
		<chapter>
			<title>Testing Workflow</title>
			<para>
				Once you have integrated libuhttpmock into your tests, your workflow will consist out of the
				following steps:
			</para>
			<itemizedlist>
				<listitem>
					<para>
						<emphasis role="strong">Log</emphasis>: After doing changes in your application that
						cause wanted changes in behavior (e.g. a different response after the API has changed),
						you want to record a new trace. For this you enable both logging and online modes, as
						described above.
					</para>
				</listitem>
				<listitem>
					<para>
						<emphasis role="strong">Compare</emphasis>: If you want to make sure the server still
						behaves as you expect (sends you the expected responses), you can disable logging and
						enable the online mode. libuhttpmock will then query the actual online server, but won't
						overwrite the trace file and will error out if any response from the server has changed.
					</para>
				</listitem>
				<listitem>
					<para>
						<emphasis role="strong">Test</emphasis>: When running tests in CI or when releasing your
						application for downstreams to package it (and run tests), you want both logging and
						the online mode to be disabled. In this case libuhttpmock will mock the responses of the
						actual online server. That way no internet access is required for your tests, API keys required
						for testing don't have to be shipped and so on.
					</para>
				</listitem>
			</itemizedlist>
		</chapter>
	</part>

	<part>
		<title>libuhttpmock Overview</title>
		<chapter>
			<title>Object Hierarchy</title>
			<xi:include href="xml/tree_index.sgml"/>
		</chapter>
	</part>

	<part>
		<title>Core API</title>
		<chapter>
			<title>Core API</title>
			<xi:include href="xml/uhm-version.xml"/>
			<xi:include href="xml/uhm-server.xml"/>
			<xi:include href="xml/uhm-resolver.xml"/>
		</chapter>
	</part>

	<part>
		<title>Appendices</title>
		<index role="api-index-full">
			<title>API Index</title>
			<xi:include href="xml/api-index-full.xml"><xi:fallback/></xi:include>
		</index>
		<index role="api-index-deprecated">
			<title>Index of deprecated symbols</title>
			<xi:include href="xml/api-index-deprecated.xml"><xi:fallback/></xi:include>
		</index>
		<index role="0.1.0">
			<title>Index of new symbols in 0.1.0</title>
			<xi:include href="xml/api-index-0.1.0.xml"><xi:fallback/></xi:include>
		</index>
		<index role="0.3.0">
			<title>Index of new symbols in 0.3.0</title>
			<xi:include href="xml/api-index-0.3.0.xml"><xi:fallback/></xi:include>
		</index>
		<index role="0.5.0">
			<title>Index of new symbols in 0.5.0</title>
			<xi:include href="xml/api-index-0.5.0.xml"><xi:fallback/></xi:include>
		</index>
		<xi:include href="xml/annotation-glossary.xml"><xi:fallback /></xi:include>
	</part>
</book>
07070100000009000081A400000000000000000000000166717A5A00000000000000000000000000000000000000000000003E00000000uhttpmock-0.11.0/libuhttpmock/docs/libuhttpmock-overrides.txt0707010000000A000081A400000000000000000000000166717A5A0000068B000000000000000000000000000000000000003D00000000uhttpmock-0.11.0/libuhttpmock/docs/libuhttpmock-sections.txt<SECTION>
<FILE>uhm-version</FILE>
<TITLE>Version Information</TITLE>
UHM_MAJOR_VERSION
UHM_MINOR_VERSION
UHM_MICRO_VERSION
UHM_CHECK_VERSION
</SECTION>

<SECTION>
<FILE>uhm-server</FILE>
<TITLE>UhmServer</TITLE>
UhmServer
UhmServerClass
UhmServerError
uhm_server_new
uhm_server_run
uhm_server_stop
uhm_server_start_trace
uhm_server_start_trace_full
uhm_server_end_trace
uhm_server_load_trace
uhm_server_load_trace_async
uhm_server_load_trace_finish
uhm_server_unload_trace
uhm_server_filter_ignore_parameter_values
uhm_server_compare_messages_remove_filter
uhm_server_received_message_chunk
uhm_server_received_message_chunk_with_direction
uhm_server_received_message_chunk_from_soup
uhm_server_get_enable_logging
uhm_server_set_enable_logging
uhm_server_get_enable_online
uhm_server_set_enable_online
uhm_server_get_trace_directory
uhm_server_set_trace_directory
uhm_server_get_tls_certificate
uhm_server_set_tls_certificate
uhm_server_set_default_tls_certificate
uhm_server_set_expected_domain_names
uhm_server_get_address
uhm_server_get_port
uhm_server_get_resolver
<SUBSECTION Standard>
UHM_SERVER
UHM_IS_SERVER
UHM_TYPE_SERVER
uhm_server_get_type
UHM_SERVER_GET_CLASS
UHM_SERVER_CLASS
UHM_IS_SERVER_CLASS
uhm_server_error_quark
UHM_SERVER_ERROR
<SUBSECTION Private>
UhmServerPrivate
</SECTION>

<SECTION>
<FILE>uhm-resolver</FILE>
<TITLE>UhmResolver</TITLE>
UhmResolver
UhmResolverClass
uhm_resolver_new
uhm_resolver_reset
uhm_resolver_add_A
uhm_resolver_add_SRV
<SUBSECTION Standard>
UHM_RESOLVER
UHM_IS_RESOLVER
UHM_TYPE_RESOLVER
uhm_resolver_get_type
UHM_RESOLVER_GET_CLASS
UHM_RESOLVER_CLASS
UHM_IS_RESOLVER_CLASS
<SUBSECTION Private>
UhmResolverPrivate
</SECTION>
0707010000000B000081A400000000000000000000000166717A5A00000215000000000000000000000000000000000000002F00000000uhttpmock-0.11.0/libuhttpmock/docs/meson.buildconfigure_file(
  input: 'version.xml.in',
  output: '@BASENAME@',
  configuration: {
    'UHM_VERSION_MAJOR': uhm_major_version,
    'UHM_VERSION_MINOR': uhm_minor_version,
    'UHM_VERSION_MICRO': uhm_micro_version,
  },
)

gnome.gtkdoc('libuhttpmock',
  module_version: uhm_api_version,
  main_xml: 'libuhttpmock-' + uhm_api_version + '-docs.xml',
  src_dir: include_directories('../'),
  scan_args: [
    '--rebuild-types',
    '--deprecated-guards="LIBUHTTPMOCK_DISABLE_DEPRECATED"',
  ],
  dependencies: libuhm_internal_dep,
)
0707010000000C000081A400000000000000000000000166717A5A0000003C000000000000000000000000000000000000003200000000uhttpmock-0.11.0/libuhttpmock/docs/version.xml.in@UHM_VERSION_MAJOR@.@UHM_VERSION_MINOR@.@UHM_VERSION_MICRO@
0707010000000D000081A400000000000000000000000166717A5A00000022000000000000000000000000000000000000002F00000000uhttpmock-0.11.0/libuhttpmock/libuhttpmock.map{
global:
  uhm_*;
local:
  *;
};
0707010000000E000081A400000000000000000000000166717A5A000007ED000000000000000000000000000000000000002A00000000uhttpmock-0.11.0/libuhttpmock/meson.buildlibuhm_sources = files(
  'uhm-resolver.c',
  'uhm-server.c',
  'uhm-message.c'
)

libuhm_source_headers = files(
  'uhm-resolver.h',
  'uhm-server.h',
  'uhm-message.h'
)

libuhm_headers = [
  libuhm_source_headers,
  uhm_version_h,
  files('uhm.h'),
]

install_headers(libuhm_headers,
  subdir: 'libuhttpmock-@0@'.format(uhm_api_version) / 'uhttpmock',
)

libuhm_public_deps = [
  glib_dep,
  gio_dep,
  soup_dep,
]

libuhm_private_deps = [
]

libuhm = library('uhttpmock-@0@'.format(uhm_api_version),
  libuhm_sources,
  dependencies: libuhm_public_deps + libuhm_private_deps,
  c_args: [
    '-DG_LOG_DOMAIN="libuhttpmock"',
  ],
  link_args: cc.get_supported_link_arguments(
    '-Wl,--version-script,@0@'.format(meson.current_source_dir() / 'libuhttpmock.map'),
  ),
  soversion: uhm_soversion,
  version: uhm_lib_version,
  install: true,
)

libuhm_internal_dep = declare_dependency(
  link_with: libuhm,
  dependencies: libuhm_public_deps,
  include_directories: include_directories('.'),
)

# pkg-config
pkgconfig.generate(libuhm,
  name: 'libuhttpmock',
  filebase: 'libuhttpmock-@0@'.format(uhm_api_version),
  description: 'HTTP web service mocking library',
  subdirs: 'libuhttpmock-@0@'.format(uhm_api_version),
  requires: libuhm_public_deps,
)

# Introspection
if get_option('introspection')
  libuhm_gir = gnome.generate_gir(libuhm,
    sources: [ libuhm_sources, libuhm_source_headers, uhm_version_h, ],
    namespace: 'Uhm',
    nsversion: uhm_api_version,
    includes: [ 'GObject-2.0', 'Soup-3.0' ],
    header: 'uhttpmock/uhm.h',
    export_packages: 'libuhttpmock',
    install: true,
  )

  vapigen = find_program('vapigen', required: get_option('vapi'))

  if vapigen.found()
    libuhm_vapi = gnome.generate_vapi('libuhttpmock-@0@'.format(uhm_api_version),
      sources: libuhm_gir[0],
      metadata_dirs: meson.current_source_dir(),
      packages: [ 'gio-2.0', 'libsoup-3.0', 'libxml-2.0' ],
      install: true,
    )
  endif
endif

subdir('tests')

if get_option('gtk_doc')
  subdir('docs')
endif
0707010000000F000041ED00000000000000000000000266717A5A00000000000000000000000000000000000000000000002400000000uhttpmock-0.11.0/libuhttpmock/tests07070100000010000081A400000000000000000000000166717A5A00000191000000000000000000000000000000000000003000000000uhttpmock-0.11.0/libuhttpmock/tests/meson.builduhm_test_cflags = [
  '-DTEST_FILE_DIR="@0@/"'.format(meson.current_source_dir()),
  '-DG_LOG_DOMAIN="libuhttpmock-tests"',
  '-DLIBUHTTPMOCK_DISABLE_DEPRECATED',
]

uhm_tests = [
  'server',
  'resolver',
]

foreach _test : uhm_tests
  test_bin = executable(_test,
    sources: _test + '.c',
    c_args: uhm_test_cflags,
    dependencies: libuhm_internal_dep,
  )

  test(_test, test_bin)
endforeach
07070100000011000081A400000000000000000000000166717A5A00002663000000000000000000000000000000000000002F00000000uhttpmock-0.11.0/libuhttpmock/tests/resolver.c/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/*
 * uhttpmock
 * Copyright (C) Philip Withnall 2013 <philip@tecnocode.co.uk>
 *
 * uhttpmock 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.
 *
 * uhttpmock 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 uhttpmock.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <glib.h>
#include <locale.h>
#include <string.h>
#include <libsoup/soup.h>
#include <locale.h>

#include "uhm-resolver.h"

typedef struct {
	UhmResolver *resolver;
	GMainLoop *main_loop;
} AsyncData;

/* Construct a new UhmResolver and see if anything explodes. */
static void
test_resolver_construction (void)
{
	UhmResolver *resolver;

	resolver = uhm_resolver_new ();
	g_assert (UHM_IS_RESOLVER (resolver));
	g_object_unref (resolver);
}

static void
assert_single_address_result (GList/*<GInetAddress>*/ *addresses, const gchar *expected_ip_address)
{
	gchar *address_string;

	g_assert_cmpuint (g_list_length (addresses), ==, 1);

	address_string = g_inet_address_to_string (G_INET_ADDRESS (addresses->data));
	g_assert_cmpstr (address_string, ==, expected_ip_address);
	g_free (address_string);
}

/* Add A records and query for existent and non-existent ones. Test that resolution fails after resetting the resolver. */
static void
test_resolver_lookup_by_name (void)
{
	UhmResolver *resolver;
	GError *child_error = NULL;
	GList/*<GInetAddress>*/ *addresses = NULL;

	resolver = uhm_resolver_new ();

	/* Add some domains and then query them. */
	uhm_resolver_add_A (resolver, "example.com", "127.0.0.1");
	uhm_resolver_add_A (resolver, "test.com", "10.0.0.1");

	/* Query for example.com. */
	addresses = g_resolver_lookup_by_name (G_RESOLVER (resolver), "example.com", NULL, &child_error);
	g_assert_no_error (child_error);
	assert_single_address_result (addresses, "127.0.0.1");
	g_resolver_free_addresses (addresses);

	/* Query for nonexistent.com. */
	addresses = g_resolver_lookup_by_name (G_RESOLVER (resolver), "nonexistent.com", NULL, &child_error);
	g_assert_error (child_error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_NOT_FOUND);
	g_assert (addresses == NULL);
	g_clear_error (&child_error);

	/* Reset and then query for example.com again. */
	uhm_resolver_reset (resolver);

	addresses = g_resolver_lookup_by_name (G_RESOLVER (resolver), "example.com", NULL, &child_error);
	g_assert_error (child_error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_NOT_FOUND);
	g_assert (addresses == NULL);
	g_clear_error (&child_error);

	g_object_unref (resolver);
}

static void
resolver_lookup_by_name_async_success_cb (GObject *source_object, GAsyncResult *result, AsyncData *data)
{
	GList/*<GInetAddress>*/ *addresses;
	GError *child_error = NULL;

	addresses = g_resolver_lookup_by_name_finish (G_RESOLVER (data->resolver), result, &child_error);
	g_assert_no_error (child_error);
	assert_single_address_result (addresses, "127.0.0.1");
	g_resolver_free_addresses (addresses);

	g_main_loop_quit (data->main_loop);
}

static void
resolver_lookup_by_name_async_failure_cb (GObject *source_object, GAsyncResult *result, AsyncData *data)
{
	GList/*<GInetAddress>*/ *addresses;
	GError *child_error = NULL;

	addresses = g_resolver_lookup_by_name_finish (G_RESOLVER (data->resolver), result, &child_error);
	g_assert_error (child_error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_NOT_FOUND);
	g_assert (addresses == NULL);
	g_clear_error (&child_error);

	g_main_loop_quit (data->main_loop);
}

/* Add an A record and asynchronously query for existent and non-existent ones. Test that resolution fails after resetting the resolver. */
static void
test_resolver_lookup_by_name_async (void)
{
	AsyncData data;

	data.main_loop = g_main_loop_new (NULL, FALSE);
	data.resolver = uhm_resolver_new ();

	/* Add a domain and query it. */
	uhm_resolver_add_A (data.resolver, "example.com", "127.0.0.1");

	g_resolver_lookup_by_name_async (G_RESOLVER (data.resolver), "example.com", NULL, (GAsyncReadyCallback) resolver_lookup_by_name_async_success_cb, &data);
	g_main_loop_run (data.main_loop);

	/* Query for a non-existent domain. */
	g_resolver_lookup_by_name_async (G_RESOLVER (data.resolver), "nonexistent.com", NULL, (GAsyncReadyCallback) resolver_lookup_by_name_async_failure_cb, &data);
	g_main_loop_run (data.main_loop);

	/* Reset and query for example.com again. */
	uhm_resolver_reset (data.resolver);

	g_resolver_lookup_by_name_async (G_RESOLVER (data.resolver), "example.com", NULL, (GAsyncReadyCallback) resolver_lookup_by_name_async_failure_cb, &data);
	g_main_loop_run (data.main_loop);

	g_object_unref (data.resolver);
	g_main_loop_unref (data.main_loop);
}

static void
assert_single_service_result (GList/*<GSrvTarget>*/ *services, const gchar *expected_ip_address, guint16 expected_port)
{
	g_assert_cmpuint (g_list_length (services), ==, 1);

	g_assert_cmpstr (g_srv_target_get_hostname ((GSrvTarget *) services->data), ==, expected_ip_address);
	g_assert_cmpuint (g_srv_target_get_port ((GSrvTarget *) services->data), ==, expected_port);
}

/* Add SRV records and query for existent and non-existent ones. Test that resolution fails after resetting the resolver. */
static void
test_resolver_lookup_service (void)
{
	UhmResolver *resolver;
	GError *child_error = NULL;
	GList/*<GSrvTarget>*/ *services = NULL;

	resolver = uhm_resolver_new ();

	/* Add some services and then query them. */
	uhm_resolver_add_SRV (resolver, "ldap", "tcp", "example.com", "127.0.0.5", 666);
	uhm_resolver_add_SRV (resolver, "imap", "tcp", "test.com", "10.0.0.1", 1234);

	/* Query for the LDAP service. */
	services = g_resolver_lookup_service (G_RESOLVER (resolver), "ldap", "tcp", "example.com", NULL, &child_error);
	g_assert_no_error (child_error);
	assert_single_service_result (services, "127.0.0.5", 666);
	g_resolver_free_targets (services);

	/* Query for a non-existent XMPP service. */
	services = g_resolver_lookup_service (G_RESOLVER (resolver), "xmpp", "tcp", "jabber.org", NULL, &child_error);
	g_assert_error (child_error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_NOT_FOUND);
	g_assert (services == NULL);
	g_clear_error (&child_error);

	/* Reset and then query for the LDAP service again. */
	uhm_resolver_reset (resolver);

	services = g_resolver_lookup_service (G_RESOLVER (resolver), "ldap", "tcp", "example.com", NULL, &child_error);
	g_assert_error (child_error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_NOT_FOUND);
	g_assert (services == NULL);
	g_clear_error (&child_error);

	g_object_unref (resolver);
}

static void
resolver_lookup_service_async_success_cb (GObject *source_object, GAsyncResult *result, AsyncData *data)
{
	GList/*<GSrvTarget>*/ *services;
	GError *child_error = NULL;

	services = g_resolver_lookup_service_finish (G_RESOLVER (data->resolver), result, &child_error);
	g_assert_no_error (child_error);
	assert_single_service_result (services, "127.0.0.5", 666);
	g_resolver_free_targets (services);

	g_main_loop_quit (data->main_loop);
}

static void
resolver_lookup_service_async_failure_cb (GObject *source_object, GAsyncResult *result, AsyncData *data)
{
	GList/*<GSrvTarget>*/ *services;
	GError *child_error = NULL;

	services = g_resolver_lookup_service_finish (G_RESOLVER (data->resolver), result, &child_error);
	g_assert_error (child_error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_NOT_FOUND);
	g_assert (services == NULL);
	g_clear_error (&child_error);

	g_main_loop_quit (data->main_loop);
}

/* Add an SRV record and asynchronously query for existent and non-existent ones. Test that resolution fails after resetting the resolver. */
static void
test_resolver_lookup_service_async (void)
{
	AsyncData data;

	data.main_loop = g_main_loop_new (NULL, FALSE);
	data.resolver = uhm_resolver_new ();

	/* Add a service and query it. */
	uhm_resolver_add_SRV (data.resolver, "ldap", "tcp", "example.com", "127.0.0.5", 666);

	g_resolver_lookup_service_async (G_RESOLVER (data.resolver), "ldap", "tcp", "example.com", NULL,
	                                 (GAsyncReadyCallback) resolver_lookup_service_async_success_cb, &data);
	g_main_loop_run (data.main_loop);

	/* Query for a non-existent service. */
	g_resolver_lookup_service_async (G_RESOLVER (data.resolver), "xmpp", "tcp", "nonexistent.com", NULL,
	                                 (GAsyncReadyCallback) resolver_lookup_service_async_failure_cb, &data);
	g_main_loop_run (data.main_loop);

	/* Reset and query for the LDAP service again. */
	uhm_resolver_reset (data.resolver);

	g_resolver_lookup_service_async (G_RESOLVER (data.resolver), "ldap", "tcp", "example.com", NULL,
	                                 (GAsyncReadyCallback) resolver_lookup_service_async_failure_cb, &data);
	g_main_loop_run (data.main_loop);

	g_object_unref (data.resolver);
	g_main_loop_unref (data.main_loop);
}

int
main (int argc, char *argv[])
{
	setlocale (LC_ALL, NULL);

	g_test_init (&argc, &argv, NULL);
	g_test_bug_base ("http://bugzilla.gnome.org/show_bug.cgi?id=");

	/* Resolver tests. */
	g_test_add_func ("/resolver/construction", test_resolver_construction);

	g_test_add_func ("/resolver/lookup-by-name", test_resolver_lookup_by_name);
	g_test_add_func ("/resolver/lookup-by-name/async", test_resolver_lookup_by_name_async);
	g_test_add_func ("/resolver/lookup-service", test_resolver_lookup_service);
	g_test_add_func ("/resolver/lookup-service/async", test_resolver_lookup_service_async);

	return g_test_run ();
}
07070100000012000081A400000000000000000000000166717A5A00006AAE000000000000000000000000000000000000002D00000000uhttpmock-0.11.0/libuhttpmock/tests/server.c/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/*
 * uhttpmock
 * Copyright (C) Philip Withnall 2013 <philip@tecnocode.co.uk>
 *
 * uhttpmock 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.
 *
 * uhttpmock 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 uhttpmock.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <glib.h>
#include <locale.h>
#include <string.h>
#include <libsoup/soup.h>
#include <locale.h>

#include "uhm-server.h"

/* Test TLS certificate for use below. */
static const gchar *test_tls_certificate =
	"-----BEGIN CERTIFICATE-----\n"
	"MIID6TCCAtGgAwIBAgIJAI9hYGsc661fMA0GCSqGSIb3DQEBBQUAMIGKMQswCQYD\n"
	"VQQGEwJHQjEVMBMGA1UEBwwMRGVmYXVsdCBDaXR5MREwDwYDVQQKDAhsaWJnZGF0\n"
	"YTEOMAwGA1UECwwFVGVzdHMxFzAVBgNVBAMMDmxpYmdkYXRhIHRlc3RzMSgwJgYJ\n"
	"KoZIhvcNAQkBFhlsaWJnZGF0YS1tYWludEBnbm9tZS5idWdzMB4XDTEzMDcwNjE3\n"
	"NDQxNFoXDTEzMDgwNTE3NDQxNFowgYoxCzAJBgNVBAYTAkdCMRUwEwYDVQQHDAxE\n"
	"ZWZhdWx0IENpdHkxETAPBgNVBAoMCGxpYmdkYXRhMQ4wDAYDVQQLDAVUZXN0czEX\n"
	"MBUGA1UEAwwObGliZ2RhdGEgdGVzdHMxKDAmBgkqhkiG9w0BCQEWGWxpYmdkYXRh\n"
	"LW1haW50QGdub21lLmJ1Z3MwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB\n"
	"AQDCbpdfrtWTz+ZpNaVZuxaeAAY+f/xZz4wEH1gaNBNb3u9CPEWofW+fLNB6izkn\n"
	"f9qhx2K8PrM9LKHDJS4uUU9dkfQHQsrCSffRWqQTeOOnpYHjS21iDYdOt4e//f/J\n"
	"erAEIyWMQAP5eqMt4hp5wrfhSh2ul9lcHz2Lv5u6H+I8ygoUaMyH15WIlEzxOsn4\n"
	"i+lXSkdmso2n1FYbiMyyMYButnnmv+EcPvOdw88PB8Y6PnCN2Ye0fo9HvcJhHEdg\n"
	"GM4SKsejA/L+T8fX0FYCXrsLPYU0Ntm15ZV8nNsxCoZFGmdTs/prL8ztXaI1tYdi\n"
	"lI1RKVTOVxD2DKdrCs5bnxYhAgMBAAGjUDBOMB0GA1UdDgQWBBQwhz/4hEriPnF5\n"
	"F3TDY9TQLzxlnDAfBgNVHSMEGDAWgBQwhz/4hEriPnF5F3TDY9TQLzxlnDAMBgNV\n"
	"HRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAIVnySe76zjb5UHromPibgT9eL\n"
	"n8oZ76aGj6+VMLucpaK8K7U7y2ONAO+BB+wUyLaq48EYb6DmpFKThAxTajYd1f/7\n"
	"14bJIew8papMEooiJHyOVfZPLOePjCldV5+hPfwsfJ3NSDL8dc+IB2PQmgp32nom\n"
	"9uvQMOdD56hHSIP5zFZwNiWH75piWyUNs/+cbHKgnyVGWaoVd7z3E5dNelOMopbo\n"
	"qvWk2MM3nHpiQqMZyliTkq5uD2Q8WiXBD4rPUeEU55NaPslB8xKwldmrAlcwYvIg\n"
	"2SDhAsTPLcCgjNuHlr/v4VC29YsF37oYCGfmWvLwGFGufsxcxXkrVhhNr2WB\n"
	"-----END CERTIFICATE-----"
	"\n"
	"-----BEGIN PRIVATE KEY-----\n"
	"MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDCbpdfrtWTz+Zp\n"
	"NaVZuxaeAAY+f/xZz4wEH1gaNBNb3u9CPEWofW+fLNB6izknf9qhx2K8PrM9LKHD\n"
	"JS4uUU9dkfQHQsrCSffRWqQTeOOnpYHjS21iDYdOt4e//f/JerAEIyWMQAP5eqMt\n"
	"4hp5wrfhSh2ul9lcHz2Lv5u6H+I8ygoUaMyH15WIlEzxOsn4i+lXSkdmso2n1FYb\n"
	"iMyyMYButnnmv+EcPvOdw88PB8Y6PnCN2Ye0fo9HvcJhHEdgGM4SKsejA/L+T8fX\n"
	"0FYCXrsLPYU0Ntm15ZV8nNsxCoZFGmdTs/prL8ztXaI1tYdilI1RKVTOVxD2DKdr\n"
	"Cs5bnxYhAgMBAAECggEAaMkhW7fl8xuAmgMHciyaK9zngJeJcP2iADbETJr0M/ca\n"
	"CyBgikXP+oE0ela+HsORGM9ULw+7maSMKZfII74+f7dBRQiCLeOfY3zuIHBugNN6\n"
	"BP2JneacnZfb2WUSjYtJgXFPsx5tBe9KMlhA3I5Me2ZuSMIdqsBLcx141/6G9ysZ\n"
	"qSfez4FCAmPB3CO6zjUyMUMwYkAilkZgpmSMvE/HtW5e/NDnCKk0/n30E2xthCUC\n"
	"eWwAMvekpyssKyAHxEHk7XZoIjMldUI7erFHRgsr5HFypp2Q7Gcd5KXVeotV8Y67\n"
	"O/aAKXURhdESeqJUS0D9ezJg3ES6Q2YOgA61OcK74QKBgQD8O5HM/MyN86RQV69s\n"
	"VduYpndq+tKnXBAnLxD9D2cjEI9wUXJFOZJTfVYzeVxMY9iWmuKJq9IwJLG4/Zkp\n"
	"s66pukesnJvLki2hhTcoOFfMzT4ZT/CaDPOO6PAgvdBL581uX68Uh8rPn9bYqxCA\n"
	"IG0n8sA/1j4H86YQVWzpI4tPtwKBgQDFVgQ54TT/yRTVdhNYooPHzSOQIf/BfNBe\n"
	"JW3B1FWONeznwV81TNdvTmNiSU0SKF5/VMuM6g8QOqXYLCo0i9DD65tH16XaArhw\n"
	"ctxtHN5ivDDFxEP9mgsO5jeVvk+e3516S9o/8BzzmTwo1zTz6MMT/JedIC6shhSW\n"
	"OnT6cBcY5wKBgQD8DEbQ8VkzDGGIy2aHunAa5VX1uDjidnPJxBWU21xzxKuhUDIB\n"
	"DNu0xE1sWHyr9SZMsO9pJSJ/a1uRARGZg20pO/U9fq2MSkGA4w7QCSVriTjhsGk8\n"
	"d262wvyZqzPHdhZpkgHxYRSATzgxARgXANAzGDeWUu9foNC0B7kya4tdlwKBgQCm\n"
	"qY0MLS4L0ZIs7npMY4UU3CZq9qwAiB+bQ9U83M4dO2IIIgL9CxbwRK4fNnVHHp0g\n"
	"wUbgjlWGiWHD/xjuJB9/OJ9+v5ytUZrgLcIIzVbs4K/4d1hM+SrZvIm5iG/KaGWi\n"
	"Aioj0fFBs2thutBYJ3+Kg8ywwZtpzhvY/SoK0VxQhQKBgQCB2e3HKOFnMFBO93dZ\n"
	"IyaUmhTM0ad+cP94mX/7REVdmN2NUsG3brIoibkgESL1h+2UbEkeOKIYWD4+ZZYJ\n"
	"V4ZhTsRcefOgTqU+EOAYSwNvNCld3X5oBi+b9Ie6y1teT6As5zGnH4YspTa5iCAk\n"
	"M97r3MWTAb4wpTnkHHoJ0GIHpA==\n"
	"-----END PRIVATE KEY-----"
;

static void
assert_server_load_trace (UhmServer *server, const gchar *trace_file_name)
{
	gchar *_trace_file_name;
	GFile *trace_file;
	GError *child_error = NULL;

	_trace_file_name = g_strconcat (TEST_FILE_DIR, trace_file_name, NULL);
	trace_file = g_file_new_for_path (_trace_file_name);

	uhm_server_load_trace (server, trace_file, NULL, &child_error);
	g_assert_no_error (child_error);

	g_object_unref (trace_file);
	g_free (_trace_file_name);
}

/* Construct a new UhmServer and see if anything explodes. */
static void
test_server_construction (void)
{
	UhmServer *server;

	server = uhm_server_new ();
	g_assert (UHM_IS_SERVER (server));
	g_object_unref (server);
}

static void
notify_emitted_cb (GObject *obj, GParamSpec *pspec, guint *counter)
{
	*counter = *counter + 1;
}

/* Test getting and setting the UhmServer:trace-directory property. */
static void
test_server_properties_trace_directory (void)
{
	UhmServer *server;
	GFile *trace_directory, *new_trace_directory;
	gchar *uri1, *uri2;
	guint counter;

	server = uhm_server_new ();

	counter = 0;
	g_signal_connect (G_OBJECT (server), "notify::trace-directory", (GCallback) notify_emitted_cb, &counter);

	/* Check the default value. */
	g_assert (uhm_server_get_trace_directory (server) == NULL);
	g_object_get (G_OBJECT (server), "trace-directory", &trace_directory, NULL);
	g_assert (trace_directory == NULL);

	/* Set the trace directory to an arbitrary, existent, directory. */
	new_trace_directory = g_file_new_for_path ("/"); /* arbitrary directory */
	uhm_server_set_trace_directory (server, new_trace_directory);
	g_assert_cmpuint (counter, ==, 1);
	g_object_unref (new_trace_directory);

	/* Check the new directory can be retrieved via the getter. */
	trace_directory = uhm_server_get_trace_directory (server);
	g_assert (G_IS_FILE (trace_directory));

	uri1 = g_file_get_uri (trace_directory);
	uri2 = g_file_get_uri (new_trace_directory);
	g_assert_cmpstr (uri1, ==, uri2);
	g_free (uri2);
	g_free (uri1);

	/* Check the new directory can be retrieved as a property. */
	g_object_get (G_OBJECT (server), "trace-directory", &trace_directory, NULL);
	g_assert (G_IS_FILE (trace_directory));

	uri1 = g_file_get_uri (trace_directory);
	uri2 = g_file_get_uri (new_trace_directory);
	g_assert_cmpstr (uri1, ==, uri2);
	g_free (uri2);
	g_free (uri1);

	g_object_unref (trace_directory);

	/* Set the directory to NULL again, this time using the GObject setter. */
	g_object_set (G_OBJECT (server), "trace-directory", NULL, NULL);
	g_assert_cmpuint (counter, ==, 2);
	g_assert (uhm_server_get_trace_directory (server) == NULL);

	g_object_unref (server);
}

/* Test getting and setting the UhmServer:enable-online property. */
static void
test_server_properties_enable_online (void)
{
	UhmServer *server;
	gboolean enable_online;
	guint counter;

	server = uhm_server_new ();

	counter = 0;
	g_signal_connect (G_OBJECT (server), "notify::enable-online", (GCallback) notify_emitted_cb, &counter);

	/* Check the default value. */
	g_assert (uhm_server_get_enable_online (server) == FALSE);
	g_object_get (G_OBJECT (server), "enable-online", &enable_online, NULL);
	g_assert (enable_online == FALSE);

	/* Toggle the value. */
	uhm_server_set_enable_online (server, TRUE);
	g_assert_cmpuint (counter, ==, 1);

	/* Check the new value can be retrieved via the getter and as a property. */
	g_assert (uhm_server_get_enable_online (server) == TRUE);
	g_object_get (G_OBJECT (server), "enable-online", &enable_online, NULL);
	g_assert (enable_online == TRUE);

	/* Toggle the value again, this time using the GObject setter. */
	g_object_set (G_OBJECT (server), "enable-online", FALSE, NULL);
	g_assert_cmpuint (counter, ==, 2);
	g_assert (uhm_server_get_enable_online (server) == FALSE);

	g_object_unref (server);
}

/* Test getting and setting UhmServer:enable-logging property. */
static void
test_server_properties_enable_logging (void)
{
	UhmServer *server;
	gboolean enable_logging;
	guint counter;

	server = uhm_server_new ();

	counter = 0;
	g_signal_connect (G_OBJECT (server), "notify::enable-logging", (GCallback) notify_emitted_cb, &counter);

	/* Check the default value. */
	g_assert (uhm_server_get_enable_logging (server) == FALSE);
	g_object_get (G_OBJECT (server), "enable-logging", &enable_logging, NULL);
	g_assert (enable_logging == FALSE);

	/* Toggle the value. */
	uhm_server_set_enable_logging (server, TRUE);
	g_assert_cmpuint (counter, ==, 1);

	/* Check the new value can be retrieved via the getter and as a property. */
	g_assert (uhm_server_get_enable_logging (server) == TRUE);
	g_object_get (G_OBJECT (server), "enable-logging", &enable_logging, NULL);
	g_assert (enable_logging == TRUE);

	/* Toggle the value again, this time using the GObject setter. */
	g_object_set (G_OBJECT (server), "enable-logging", FALSE, NULL);
	g_assert_cmpuint (counter, ==, 2);
	g_assert (uhm_server_get_enable_logging (server) == FALSE);

	g_object_unref (server);
}

/* Test getting the UhmServer:address property. */
static void
test_server_properties_address (void)
{
	UhmServer *server;
	gchar *address;

	server = uhm_server_new ();

	/* Check the default value. */
	g_assert (uhm_server_get_address (server) == NULL);
	g_object_get (G_OBJECT (server), "address", &address, NULL);
	g_assert (address == NULL);

	/* The address is set when the server is taken online, which is tested separately. */

	g_object_unref (server);
}

/* Test getting the UhmServer:port property. */
static void
test_server_properties_port (void)
{
	UhmServer *server;
	guint port;

	server = uhm_server_new ();

	/* Check the default value. */
	g_assert (uhm_server_get_port (server) == 0);
	g_object_get (G_OBJECT (server), "port", &port, NULL);
	g_assert (port == 0);

	/* The port is set when the server is taken online, which is tested separately. */

	g_object_unref (server);
}

/* Test getting the UhmServer:resolver property. */
static void
test_server_properties_resolver (void)
{
	UhmServer *server;
	UhmResolver *resolver;

	server = uhm_server_new ();

	/* Check the default value. */
	g_assert (uhm_server_get_resolver (server) == NULL);
	g_object_get (G_OBJECT (server), "resolver", &resolver, NULL);
	g_assert (resolver == NULL);

	/* The resolver is set when the server is taken online, which is tested separately. */

	g_object_unref (server);
}

/* Test getting and setting the UhmServer:tls-certificate property. */
static void
test_server_properties_tls_certificate (void)
{
	UhmServer *server;
	GTlsCertificate *tls_certificate, *new_tls_certificate;
	guint counter;
	GError *child_error = NULL;

	server = uhm_server_new ();

	counter = 0;
	g_signal_connect (G_OBJECT (server), "notify::tls-certificate", (GCallback) notify_emitted_cb, &counter);

	/* Check the default value. */
	g_assert (uhm_server_get_tls_certificate (server) == NULL);
	g_object_get (G_OBJECT (server), "tls-certificate", &tls_certificate, NULL);
	g_assert (tls_certificate == NULL);

	/* Set the tls-certificate to an arbitrary, existent, directory. */
	new_tls_certificate = g_tls_certificate_new_from_pem (test_tls_certificate, -1, &child_error);
	g_assert_no_error (child_error);
	uhm_server_set_tls_certificate (server, new_tls_certificate);
	g_assert_cmpuint (counter, ==, 1);

	/* Check the new certificate can be retrieved via the getter. */
	tls_certificate = uhm_server_get_tls_certificate (server);
	g_assert (G_IS_TLS_CERTIFICATE (tls_certificate));
	g_assert (g_tls_certificate_is_same (tls_certificate, new_tls_certificate) == TRUE);

	/* Check the new certificate can be retrieved as a property. */
	g_object_get (G_OBJECT (server), "tls-certificate", &tls_certificate, NULL);
	g_assert (G_IS_TLS_CERTIFICATE (tls_certificate));
	g_assert (g_tls_certificate_is_same (tls_certificate, new_tls_certificate) == TRUE);

	g_object_unref (tls_certificate);
	g_object_unref (new_tls_certificate);

	/* Set the certificate to NULL again, this time using the GObject setter. */
	g_object_set (G_OBJECT (server), "tls-certificate", NULL, NULL);
	g_assert_cmpuint (counter, ==, 2);
	g_assert (uhm_server_get_tls_certificate (server) == NULL);

	/* Set the certificate to the default. */
	new_tls_certificate = uhm_server_set_default_tls_certificate (server);
	g_assert (G_IS_TLS_CERTIFICATE (new_tls_certificate));
	tls_certificate = uhm_server_get_tls_certificate (server);
	g_assert (g_tls_certificate_is_same (tls_certificate, new_tls_certificate) == TRUE);
	g_assert_cmpuint (counter, ==, 3);

	g_object_unref (server);
}

typedef struct {
	UhmServer *server;
	SoupSession *session;
	GMainLoop *main_loop;
} LoggingData;

static void
set_up_logging (LoggingData *data, gconstpointer user_data)
{
	UhmResolver *resolver;

	data->server = uhm_server_new ();
	data->main_loop = g_main_loop_new (NULL, FALSE);

	uhm_server_set_enable_logging (data->server, TRUE);
	uhm_server_set_enable_online (data->server, TRUE);
	uhm_server_set_default_tls_certificate (data->server);

	if (user_data != NULL) {
		g_signal_connect (G_OBJECT (data->server), "handle-message", (GCallback) user_data, NULL);
	}

	uhm_server_run (data->server);

	resolver = uhm_server_get_resolver (data->server);
	uhm_resolver_add_A (resolver, "example.com", uhm_server_get_address (data->server));

	data->session = soup_session_new ();
}

static gboolean
accept_cert (SoupMessage *msg, GTlsCertificate *certificate, GTlsCertificateFlags errors, gpointer user_data)
{
	return TRUE;
}

static void
tear_down_logging (LoggingData *data, gconstpointer user_data)
{
	g_object_unref (data->session);
	uhm_server_stop (data->server);

	g_main_loop_unref (data->main_loop);
	g_object_unref (data->server);
}

static gboolean
server_logging_no_trace_success_handle_message_cb (UhmServer *server, UhmMessage *message)
{
	uhm_message_set_status (message, SOUP_STATUS_OK, "OK");
	soup_message_body_append (uhm_message_get_response_body (message), SOUP_MEMORY_STATIC, "This is a success response.", strlen ("This is a success response."));
	soup_message_body_complete (uhm_message_get_response_body (message));

	return TRUE;
}

static SoupStatus
send_message (SoupSession *session, SoupMessage *msg, GBytes **response)
{
	g_autoptr(GInputStream) stream;

	stream = soup_session_send (session, msg, NULL, NULL);

	if (!stream) {
		*response = NULL;
		return SOUP_STATUS_NONE;
	}

	if (response) {
		/* possible FIXME: do not read fixed size? just tests though */
		*response = g_input_stream_read_bytes (stream, 4096, NULL, NULL);

		if (!*response)
			return SOUP_STATUS_NONE;
	}

	return soup_message_get_status (msg);
}

static gboolean
server_logging_no_trace_success_cb (LoggingData *data)
{
	SoupMessage *message;
	g_autoptr(GUri) uri = NULL;

	/* Dummy unit test code. */
	uri = g_uri_build (SOUP_HTTP_URI_FLAGS, "https", NULL, "example.com", uhm_server_get_port (data->server), "/test-file", NULL, NULL);
	message = soup_message_new_from_uri (SOUP_METHOD_GET, uri);

	g_signal_connect (message, "accept-certificate", G_CALLBACK (accept_cert), NULL);

	g_assert_cmpuint (send_message (data->session, message, NULL), ==, SOUP_STATUS_OK);

	g_object_unref (message);

	g_main_loop_quit (data->main_loop);

	return FALSE;
}

/* Test a server in onling/logging mode returning a success response from a custom signal handler. */
static void
test_server_logging_no_trace_success (LoggingData *data, gconstpointer user_data)
{
	g_idle_add ((GSourceFunc) server_logging_no_trace_success_cb, data);
	g_main_loop_run (data->main_loop);
}

static gboolean
server_logging_no_trace_failure_handle_message_cb (UhmServer *server, UhmMessage *message)
{
	uhm_message_set_status (message, SOUP_STATUS_PRECONDITION_FAILED, "Precondition Failed");
	soup_message_body_append (uhm_message_get_response_body (message), SOUP_MEMORY_STATIC, "This is a failure response.", strlen ("This is a failure response."));
	soup_message_body_complete (uhm_message_get_response_body (message));

	return TRUE;
}

static gboolean
server_logging_no_trace_failure_cb (LoggingData *data)
{
	SoupMessage *message;
	g_autoptr(GUri) uri = NULL;

	/* Dummy unit test code. */
	uri = g_uri_build (SOUP_HTTP_URI_FLAGS, "https", NULL, "example.com", uhm_server_get_port (data->server), "/test-file", NULL, NULL);
	message = soup_message_new_from_uri (SOUP_METHOD_GET, uri);

	g_signal_connect (message, "accept-certificate", G_CALLBACK (accept_cert), NULL);

	g_assert_cmpuint (send_message (data->session, message, NULL), ==, SOUP_STATUS_PRECONDITION_FAILED);

	g_object_unref (message);

	g_main_loop_quit (data->main_loop);

	return FALSE;
}

/* Test a server in onling/logging mode returning a failure response from a custom signal handler. */
static void
test_server_logging_no_trace_failure (LoggingData *data, gconstpointer user_data)
{
	g_idle_add ((GSourceFunc) server_logging_no_trace_failure_cb, data);
	g_main_loop_run (data->main_loop);
}

static gboolean
server_logging_trace_success_normal_cb (LoggingData *data)
{
	SoupMessage *message;
	g_autoptr(GUri) uri = NULL;

	/* Load the trace. */
	assert_server_load_trace (data->server, "server_logging_trace_success_normal");

	/* Dummy unit test code. */
	uri = g_uri_build (SOUP_HTTP_URI_FLAGS, "https", NULL, "example.com", uhm_server_get_port (data->server), "/test-file", NULL, NULL);
	message = soup_message_new_from_uri (SOUP_METHOD_GET, uri);

	g_signal_connect (message, "accept-certificate", G_CALLBACK (accept_cert), NULL);

	g_assert_cmpuint (send_message (data->session, message, NULL), ==, SOUP_STATUS_NOT_FOUND);

	g_object_unref (message);

	g_main_loop_quit (data->main_loop);

	return FALSE;
}

/* Test a server in onling/logging mode returning a success response from a single-message trace. */
static void
test_server_logging_trace_success_normal (LoggingData *data, gconstpointer user_data)
{
	g_idle_add ((GSourceFunc) server_logging_trace_success_normal_cb, data);
	g_main_loop_run (data->main_loop);
}

static gboolean
server_logging_trace_success_multiple_messages_cb (LoggingData *data)
{
	guint i;
	SoupStatus expected_status_codes[] = {
		SOUP_STATUS_OK,
		SOUP_STATUS_OK,
		SOUP_STATUS_NOT_FOUND,
	};

	/* Load the trace. */
	assert_server_load_trace (data->server, "server_logging_trace_success_multiple-messages");

	/* Dummy unit test code. Send three messages. */
	for (i = 0; i < G_N_ELEMENTS (expected_status_codes); i++) {
		g_autoptr(GUri) uri = NULL;
		g_autofree char *uri_path = NULL;
		g_autoptr(SoupMessage) message = NULL;


		uri_path = g_strdup_printf ("/test-file%u", i);
		uri = g_uri_build (SOUP_HTTP_URI_FLAGS, "https", NULL, "example.com", uhm_server_get_port (data->server), uri_path, NULL, NULL);

		message = soup_message_new_from_uri (SOUP_METHOD_GET, uri);
		g_signal_connect (message, "accept-certificate", G_CALLBACK (accept_cert), NULL);
		g_assert_cmpuint (send_message (data->session, message, NULL), ==, expected_status_codes[i]);
	}

	g_main_loop_quit (data->main_loop);

	return FALSE;
}

/* Test a server in onling/logging mode returning several responses from a multi-message trace. */
static void
test_server_logging_trace_success_multiple_messages (LoggingData *data, gconstpointer user_data)
{
	g_idle_add ((GSourceFunc) server_logging_trace_success_multiple_messages_cb, data);
	g_main_loop_run (data->main_loop);
}

static gboolean
server_logging_trace_failure_method_cb (LoggingData *data)
{
	g_autoptr(SoupMessage) message = NULL;
	g_autoptr(GUri) uri = NULL;
	g_autoptr(GBytes) body = NULL;

	/* Load the trace. */
	assert_server_load_trace (data->server, "server_logging_trace_failure_method");

	/* Dummy unit test code. */
	uri = g_uri_build (SOUP_HTTP_URI_FLAGS, "https", NULL, "example.com", uhm_server_get_port (data->server), "/test-file", NULL, NULL);
	message = soup_message_new_from_uri (SOUP_METHOD_PUT, uri); /* Note: we use PUT here, which doesn’t match the trace */

	g_signal_connect (message, "accept-certificate", G_CALLBACK (accept_cert), NULL);

	g_assert_cmpuint (send_message (data->session, message, &body), ==, SOUP_STATUS_BAD_REQUEST);
	g_assert_cmpstr (g_bytes_get_data (body, NULL), !=, "The document was not found. Ha.");
	g_assert_cmpstr (g_bytes_get_data (body, NULL), ==, "Expected GET URI ‘/test-file’, but got PUT ‘/test-file’.");
	if (strstr (soup_message_headers_get_one (soup_message_get_response_headers (message), "X-Mock-Trace-File"), "server_logging_trace_failure_method") == NULL) {
		/* Report the error. */
		g_assert_cmpstr (soup_message_headers_get_one (soup_message_get_response_headers (message), "X-Mock-Trace-File"), ==, "server_logging_trace_failure_method");
	}
	g_assert_cmpstr (soup_message_headers_get_one (soup_message_get_response_headers (message), "X-Mock-Trace-File-Offset"), ==, "1");

	g_main_loop_quit (data->main_loop);

	return FALSE;
}

/* Test a server in onling/logging mode returning a non-matching response from a trace, not matching by method. */
static void
test_server_logging_trace_failure_method (LoggingData *data, gconstpointer user_data)
{
	g_idle_add ((GSourceFunc) server_logging_trace_failure_method_cb, data);
	g_main_loop_run (data->main_loop);
}

static gboolean
server_logging_trace_failure_uri_cb (LoggingData *data)
{
	g_autoptr(SoupMessage) message = NULL;
	g_autoptr(GUri) uri = NULL;
	g_autoptr(GBytes) body = NULL;

	/* Load the trace. */
	assert_server_load_trace (data->server, "server_logging_trace_failure_uri");

	/* Dummy unit test code. */
	uri = g_uri_build (SOUP_HTTP_URI_FLAGS, "https", NULL, "example.com", uhm_server_get_port (data->server), "/test-file-wrong-uri", NULL, NULL); /* Note: wrong URI */
	message = soup_message_new_from_uri (SOUP_METHOD_GET, uri);

	g_signal_connect (message, "accept-certificate", G_CALLBACK (accept_cert), NULL);

	g_assert_cmpuint (send_message (data->session, message, &body), ==, SOUP_STATUS_BAD_REQUEST);
	g_assert_cmpstr (g_bytes_get_data (body, NULL), !=, "The document was not found. Ha.");
	g_assert_cmpstr (g_bytes_get_data (body, NULL), ==, "Expected GET URI ‘/test-file’, but got GET ‘/test-file-wrong-uri’.");
	if (strstr (soup_message_headers_get_one (soup_message_get_response_headers (message), "X-Mock-Trace-File"), "server_logging_trace_failure_uri") == NULL) {
		/* Report the error. */
		g_assert_cmpstr (soup_message_headers_get_one (soup_message_get_response_headers (message), "X-Mock-Trace-File"), ==, "server_logging_trace_failure_uri");
	}
	g_assert_cmpstr (soup_message_headers_get_one (soup_message_get_response_headers (message), "X-Mock-Trace-File-Offset"), ==, "1");

	g_main_loop_quit (data->main_loop);

	return FALSE;
}

/* Test a server in onling/logging mode returning a non-matching response from a trace, not matching by URI. */
static void
test_server_logging_trace_failure_uri (LoggingData *data, gconstpointer user_data)
{
	g_idle_add ((GSourceFunc) server_logging_trace_failure_uri_cb, data);
	g_main_loop_run (data->main_loop);
}

static gboolean
server_logging_trace_failure_unexpected_request_cb (LoggingData *data)
{
	g_autoptr(SoupMessage) message = NULL;
	g_autoptr(GUri) uri = NULL;
	g_autoptr(GBytes) body = NULL;

	/* Load the trace. */
	assert_server_load_trace (data->server, "server_logging_trace_failure_unexpected-request");

	/* Dummy unit test code. */
	uri = g_uri_build (SOUP_HTTP_URI_FLAGS, "https", NULL, "example.com", uhm_server_get_port (data->server), "/test-file-unexpected", NULL, NULL); /* Note: unexpected request; not in the trace file */
	message = soup_message_new_from_uri (SOUP_METHOD_GET, uri);

	g_signal_connect (message, "accept-certificate", G_CALLBACK (accept_cert), NULL);

	g_assert_cmpuint (send_message (data->session, message, &body), ==, SOUP_STATUS_BAD_REQUEST);
	g_assert_cmpstr (g_bytes_get_data (body, NULL), !=, "The document was not found. Ha.");
	g_assert_cmpstr (g_bytes_get_data (body, NULL), ==, "Expected no request, but got GET ‘/test-file-unexpected’.");
	if (strstr (soup_message_headers_get_one (soup_message_get_response_headers (message), "X-Mock-Trace-File"), "server_logging_trace_failure_unexpected-request") == NULL) {
		/* Report the error. */
		g_assert_cmpstr (soup_message_headers_get_one (soup_message_get_response_headers (message), "X-Mock-Trace-File"), ==, "server_logging_trace_failure_unexpected-request");
	}
	g_assert_cmpstr (soup_message_headers_get_one (soup_message_get_response_headers (message), "X-Mock-Trace-File-Offset"), ==, "0");

	g_main_loop_quit (data->main_loop);

	return FALSE;
}

/* Test a server in onling/logging mode by making a request which doesn't exist in the trace file. */
static void
test_server_logging_trace_failure_unexpected_request (LoggingData *data, gconstpointer user_data)
{
	g_idle_add ((GSourceFunc) server_logging_trace_failure_unexpected_request_cb, data);
	g_main_loop_run (data->main_loop);
}

int
main (int argc, char *argv[])
{
	setlocale (LC_ALL, NULL);

	g_test_init (&argc, &argv, NULL);
	g_test_bug_base ("http://bugzilla.gnome.org/show_bug.cgi?id=");

	/* Server tests. */
	g_test_add_func ("/server/construction", test_server_construction);

	g_test_add_func ("/server/properties/trace-directory", test_server_properties_trace_directory);
	g_test_add_func ("/server/properties/enable-online", test_server_properties_enable_online);
	g_test_add_func ("/server/properties/enable-logging", test_server_properties_enable_logging);
	g_test_add_func ("/server/properties/address", test_server_properties_address);
	g_test_add_func ("/server/properties/port", test_server_properties_port);
	g_test_add_func ("/server/properties/resolver", test_server_properties_resolver);
	g_test_add_func ("/server/properties/tls-certificate", test_server_properties_tls_certificate);

	g_test_add ("/server/logging/no-trace/success", LoggingData, server_logging_no_trace_success_handle_message_cb,
	            set_up_logging, test_server_logging_no_trace_success, tear_down_logging);
	g_test_add ("/server/logging/no-trace/failure", LoggingData, server_logging_no_trace_failure_handle_message_cb,
	            set_up_logging, test_server_logging_no_trace_failure, tear_down_logging);
	g_test_add ("/server/logging/trace/success/normal", LoggingData, NULL,
	            set_up_logging, test_server_logging_trace_success_normal, tear_down_logging);
	g_test_add ("/server/logging/trace/success/multiple-messages", LoggingData, NULL,
	            set_up_logging, test_server_logging_trace_success_multiple_messages, tear_down_logging);
	g_test_add ("/server/logging/trace/failure/method", LoggingData, NULL,
	            set_up_logging, test_server_logging_trace_failure_method, tear_down_logging);
	g_test_add ("/server/logging/trace/failure/uri", LoggingData, NULL,
	            set_up_logging, test_server_logging_trace_failure_uri, tear_down_logging);
	g_test_add ("/server/logging/trace/failure/unexpected-request", LoggingData, NULL,
	            set_up_logging, test_server_logging_trace_failure_unexpected_request, tear_down_logging);

	return g_test_run ();
}
07070100000013000081A400000000000000000000000166717A5A00000229000000000000000000000000000000000000004800000000uhttpmock-0.11.0/libuhttpmock/tests/server_logging_trace_failure_method> GET /test-file HTTP/1.1
> Host: example.com
> Accept-Encoding: gzip, deflate
> Connection: Keep-Alive
  
< HTTP/1.1 404 Not Found
< Content-Type: text/plain; charset=UTF-8
< Expires: Tue, 30 Jul 2013 14:51:48 GMT
< Date: Tue, 30 Jul 2013 14:51:48 GMT
< Cache-control: private, max-age=0, must-revalidate, no-transform
< Vary: Accept
< ETag: W/"D04ESXg7eCt7ImA9WhFWEUQ."
< X-Content-Type-Options: nosniff
< X-Frame-Options: SAMEORIGIN
< X-XSS-Protection: 1; mode=block
< Server: GSE
< Transfer-Encoding: chunked
< 
< The document was not found. Ha.
  
07070100000014000081A400000000000000000000000166717A5A00000000000000000000000000000000000000000000005400000000uhttpmock-0.11.0/libuhttpmock/tests/server_logging_trace_failure_unexpected-request07070100000015000081A400000000000000000000000166717A5A00000229000000000000000000000000000000000000004500000000uhttpmock-0.11.0/libuhttpmock/tests/server_logging_trace_failure_uri> GET /test-file HTTP/1.1
> Host: example.com
> Accept-Encoding: gzip, deflate
> Connection: Keep-Alive
  
< HTTP/1.1 404 Not Found
< Content-Type: text/plain; charset=UTF-8
< Expires: Tue, 30 Jul 2013 14:51:48 GMT
< Date: Tue, 30 Jul 2013 14:51:48 GMT
< Cache-control: private, max-age=0, must-revalidate, no-transform
< Vary: Accept
< ETag: W/"D04ESXg7eCt7ImA9WhFWEUQ."
< X-Content-Type-Options: nosniff
< X-Frame-Options: SAMEORIGIN
< X-XSS-Protection: 1; mode=block
< Server: GSE
< Transfer-Encoding: chunked
< 
< The document was not found. Ha.
  
07070100000016000081A400000000000000000000000166717A5A00000229000000000000000000000000000000000000004100000000uhttpmock-0.11.0/libuhttpmock/tests/server_logging_trace_success> GET /test-file HTTP/1.1
> Host: example.com
> Accept-Encoding: gzip, deflate
> Connection: Keep-Alive
  
< HTTP/1.1 404 Not Found
< Content-Type: text/plain; charset=UTF-8
< Expires: Tue, 30 Jul 2013 14:51:48 GMT
< Date: Tue, 30 Jul 2013 14:51:48 GMT
< Cache-control: private, max-age=0, must-revalidate, no-transform
< Vary: Accept
< ETag: W/"D04ESXg7eCt7ImA9WhFWEUQ."
< X-Content-Type-Options: nosniff
< X-Frame-Options: SAMEORIGIN
< X-XSS-Protection: 1; mode=block
< Server: GSE
< Transfer-Encoding: chunked
< 
< The document was not found. Ha.
  
07070100000017000081A400000000000000000000000166717A5A00000691000000000000000000000000000000000000005300000000uhttpmock-0.11.0/libuhttpmock/tests/server_logging_trace_success_multiple-messages> GET /test-file0 HTTP/1.1
> Host: example.com
> Accept-Encoding: gzip, deflate
> Connection: Keep-Alive
  
< HTTP/1.1 200 OK
< Content-Type: text/plain; charset=UTF-8
< Expires: Tue, 30 Jul 2013 14:51:48 GMT
< Date: Tue, 30 Jul 2013 14:51:48 GMT
< Cache-control: private, max-age=0, must-revalidate, no-transform
< Vary: Accept
< ETag: W/"D04ESXg7eCt7ImA9WhFWEUQ."
< X-Content-Type-Options: nosniff
< X-Frame-Options: SAMEORIGIN
< X-XSS-Protection: 1; mode=block
< Server: GSE
< Transfer-Encoding: chunked
< 
< The first document was found. Marvellous!
  
> GET /test-file1 HTTP/1.1
> Host: example.com
> Accept-Encoding: gzip, deflate
> Connection: Keep-Alive
  
< HTTP/1.1 200 OK
< Content-Type: text/plain; charset=UTF-8
< Expires: Tue, 30 Jul 2013 14:51:48 GMT
< Date: Tue, 30 Jul 2013 14:51:48 GMT
< Cache-control: private, max-age=0, must-revalidate, no-transform
< Vary: Accept
< ETag: W/"D04ESXg7eCt7ImA9WhFWEUQ."
< X-Content-Type-Options: nosniff
< X-Frame-Options: SAMEORIGIN
< X-XSS-Protection: 1; mode=block
< Server: GSE
< Transfer-Encoding: chunked
< 
< The second document was also found. Marvellous!
  
> GET /test-file2 HTTP/1.1
> Host: example.com
> Accept-Encoding: gzip, deflate
> Connection: Keep-Alive
  
< HTTP/1.1 404 Not Found
< Content-Type: text/plain; charset=UTF-8
< Expires: Tue, 30 Jul 2013 14:51:48 GMT
< Date: Tue, 30 Jul 2013 14:51:48 GMT
< Cache-control: private, max-age=0, must-revalidate, no-transform
< Vary: Accept
< ETag: W/"D04ESXg7eCt7ImA9WhFWEUQ."
< X-Content-Type-Options: nosniff
< X-Frame-Options: SAMEORIGIN
< X-XSS-Protection: 1; mode=block
< Server: GSE
< Transfer-Encoding: chunked
< 
< The third document was not found. Boo.
  
07070100000018000081A400000000000000000000000166717A5A00000229000000000000000000000000000000000000004800000000uhttpmock-0.11.0/libuhttpmock/tests/server_logging_trace_success_normal> GET /test-file HTTP/1.1
> Host: example.com
> Accept-Encoding: gzip, deflate
> Connection: Keep-Alive
  
< HTTP/1.1 404 Not Found
< Content-Type: text/plain; charset=UTF-8
< Expires: Tue, 30 Jul 2013 14:51:48 GMT
< Date: Tue, 30 Jul 2013 14:51:48 GMT
< Cache-control: private, max-age=0, must-revalidate, no-transform
< Vary: Accept
< ETag: W/"D04ESXg7eCt7ImA9WhFWEUQ."
< X-Content-Type-Options: nosniff
< X-Frame-Options: SAMEORIGIN
< X-XSS-Protection: 1; mode=block
< Server: GSE
< Transfer-Encoding: chunked
< 
< The document was not found. Ha.
  
07070100000019000081A400000000000000000000000166717A5A00000DB8000000000000000000000000000000000000003C00000000uhttpmock-0.11.0/libuhttpmock/uhm-default-tls-certificate.h/* Manually constructed by concatenating and quoting cert.pem and key.pem.
 * The PEM files were generated using:
 *     openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -nodes \
 *         -subj "/C=GB/L=Default City/O=uhttpmock/CN=uhttpmock"
 */
static const gchar *uhm_default_tls_certificate =
	"-----BEGIN CERTIFICATE-----\n"
	"MIIDazCCAlOgAwIBAgIJAJKDBYaIm2SBMA0GCSqGSIb3DQEBBQUAMEwxCzAJBgNV\n"
	"BAYTAlVLMRUwEwYDVQQHDAxEZWZhdWx0IENpdHkxEjAQBgNVBAoMCXVodHRwbW9j\n"
	"azESMBAGA1UEAwwJdWh0dHBtb2NrMB4XDTEzMDgwNTA5MTgwOVoXDTEzMDkwNDA5\n"
	"MTgwOVowTDELMAkGA1UEBhMCVUsxFTATBgNVBAcMDERlZmF1bHQgQ2l0eTESMBAG\n"
	"A1UECgwJdWh0dHBtb2NrMRIwEAYDVQQDDAl1aHR0cG1vY2swggEiMA0GCSqGSIb3\n"
	"DQEBAQUAA4IBDwAwggEKAoIBAQDfAjSOnRNZlDGN21zeSsdqt8efsHJVVEKq3iC/\n"
	"hgYQ5lLQKeSF7z4c+uCvWYEOwj6gWxQG8Nzp6pxnHkGoextAkoaOFM6I1OKB8Hkz\n"
	"fYJo8Q3ax9GoykxCFJRlw5ceQ67rYkQOR346xgxyN2Y8fCWO7kVNzQFFFA1dPcTJ\n"
	"Wy9OMHtRPiLmS8CW3QZrFFijyWHrkcQ7GrxYDYMLQ8iso9jdcwv2US0B0Msmxlr7\n"
	"VWzhaZPhr2PaWQMk21j7w1rAuYng7vuWEkLWCTcy+N83qUP+DPWZemso0VuFhanw\n"
	"g1etnhVFCw9wsxIoXLzijRj+/JLuLCWlPAiXtsnVxo/5Y+ytAgMBAAGjUDBOMB0G\n"
	"A1UdDgQWBBRvMD4pnfrMgmzOftZMBtIMGHdcnjAfBgNVHSMEGDAWgBRvMD4pnfrM\n"
	"gmzOftZMBtIMGHdcnjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQB+\n"
	"OcT8GNwlxAraicePg/C6N7ZYvxxm4xAWpID0nD5I5jfJw7LuyTPUHoVPBOYE6bW6\n"
	"IyhScGG9TiVCZISMWZGJ5oTjm9n1txRcUOsXHnROFSVN2FWgXIx4ep2MQWMB4WSF\n"
	"ZDzSjCt706vejUyh+HkAWkswNtoKpn3T7vSVg25yJE8k4D120rru9iMaXzvIZad1\n"
	"kn3YQoy3X+tAxlVMTWXn44iswECvrOYf5yxGMi/+AppkEGfWIH8pqUAAvbTqOk9P\n"
	"9L4Bu1AIXfF/rLv8VlU8zojQ70MKOr039TvjuCOCUMX9tXqAC4jYwiIAF/dfRaHe\n"
	"vsDxdSKLB/1rR1vCXLhD\n"
	"-----END CERTIFICATE-----"
	"\n"
	"-----BEGIN PRIVATE KEY-----\n"
	"MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDfAjSOnRNZlDGN\n"
	"21zeSsdqt8efsHJVVEKq3iC/hgYQ5lLQKeSF7z4c+uCvWYEOwj6gWxQG8Nzp6pxn\n"
	"HkGoextAkoaOFM6I1OKB8HkzfYJo8Q3ax9GoykxCFJRlw5ceQ67rYkQOR346xgxy\n"
	"N2Y8fCWO7kVNzQFFFA1dPcTJWy9OMHtRPiLmS8CW3QZrFFijyWHrkcQ7GrxYDYML\n"
	"Q8iso9jdcwv2US0B0Msmxlr7VWzhaZPhr2PaWQMk21j7w1rAuYng7vuWEkLWCTcy\n"
	"+N83qUP+DPWZemso0VuFhanwg1etnhVFCw9wsxIoXLzijRj+/JLuLCWlPAiXtsnV\n"
	"xo/5Y+ytAgMBAAECggEBAKyqEnNRHsg+u1dwPqlGELyJ6p+zh/KVOMD/Fy/y3wN6\n"
	"sSfHJ86je94ISKq7i/cXYlHZ8tmk6Aacxdf5TzDZkDIgwNmFNpwu5+lffRfm12/V\n"
	"I7r+90/OwdhwAq4AECM4mFhbTwIXTJ7I9J4CUsAGBzZxOWuRjKglbGuDBbg5R5kq\n"
	"8kPWMGZHT/Osiivaa6f19DKNbQace8DBfqcescGhDaXXMTZnSishEik34uPXI4rm\n"
	"O+qBk/jbPKHIb8HKm23QcM1l4e1m++0wLjeSOBDzq6O8mHgV+GAo57uZmSdnJ2mm\n"
	"OMIiSuVdhDyNUfbIFqgko3tgH37EhwufHlFx0WOytwECgYEA9mWCv2IjvhuTXAL5\n"
	"hR/f5whiO26O3asELwUcYwO1V8/8mFvPmVjwjVpgBNCNr50r7VDZvSJoJ03YisO2\n"
	"PZNNWh9GFafA54NcmHjpzbT4zxYj3dVw9vzPhOV9ndCZWmWhNangMq/xFnUFixjL\n"
	"VpqlqHb/SkdUK6mT3C4FEJmUg00CgYEA57NV0ai+ugx5Wl5lHPsE+aYZ5JRv2OsQ\n"
	"qB+N63msxQvdPFnvoN2fH1g1zUi1MlA6q38EicwyLPNJ36Iy7RXKuBY5B+X6iI6E\n"
	"4ziB/5Bp0mUo5HvPAjjQusiWaslT9AOojA6VqZtuiewkExpl4VkC/0BbWTKtzfnH\n"
	"nzNfxOifnuECgYEA8IJHrM57+x5pqb/Rlet2H8rkMAUL+T3seIUxn0jIY23Gr2W2\n"
	"74WMUT3tSeXU153AegaYc3C5X9wxycmeAt7c+2JZg9vahWGJKd+kwMGsuF9xJSEq\n"
	"Ajzlx2BHTJuIhV98i6hFCtUIrJYPkiXinaeYmieFrBiSBYiipqJZGUoAWGUCgYAF\n"
	"cOWsBb1s2wwifiL7uj1Uq3ziLEYwHt0GRa9sfy/6dJveZfJFoc6xyr2As5tlshKe\n"
	"ol316nCnM5NhiAqQHLnk9siiEdl/SXF/cH1FBhwmD7AVJX8n+zOTn1BA87df/JIB\n"
	"r/n9wKOo4325YR5RW2jBm75JavI/6wSwDWHLWvccoQKBgEwjhN5yW8tqHl6BUtn+\n"
	"brqvwk3azURyy5A9GlgYifVprQ2qAnxJZxYibcBmTOH3ewDfmWB3t7piHy028E9y\n"
	"sXEnj6LaYbfLX36Pp8GgGKDGHxwjviMGWEW8nnrZgAvtWm7DRSjzLzeajYL3SE5a\n"
	"3IrgLWcOTfnUg6FzQ6xAAlxa\n"
	"-----END PRIVATE KEY-----"
;
0707010000001A000081A400000000000000000000000166717A5A000003E9000000000000000000000000000000000000003400000000uhttpmock-0.11.0/libuhttpmock/uhm-message-private.h/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/*
 * uhttpmock
 * Copyright (C) Igalia S.L. 2021 <dkolesa@igalia.com>
 *
 * uhttpmock 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.
 *
 * uhttpmock 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 uhttpmock.  If not, see <http://www.gnu.org/licenses/>.
 */

#pragma once

#include "uhm-message.h"

UhmMessage *uhm_message_new_from_uri (const gchar *method, GUri *uri);
UhmMessage *uhm_message_new_from_server_message (SoupServerMessage *smsg);
0707010000001B000081A400000000000000000000000166717A5A000018D7000000000000000000000000000000000000002C00000000uhttpmock-0.11.0/libuhttpmock/uhm-message.c/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/*
 * uhttpmock
 * Copyright (C) Igalia S.L. 2021 <dkolesa@igalia.com>
 *
 * uhttpmock 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.
 *
 * uhttpmock 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 uhttpmock.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "uhm-message-private.h"

struct _UhmMessage {
	GObject parent;

	char *method;
	SoupHTTPVersion http_version;
	guint status_code;
	char *reason_phrase;
	GUri *uri;
	SoupMessageBody *request_body;
	SoupMessageHeaders *request_headers;
	SoupMessageBody *response_body;
	SoupMessageHeaders *response_headers;
};

struct _UhmMessageClass {
	GObjectClass parent_class;
};

G_DEFINE_FINAL_TYPE (UhmMessage, uhm_message, G_TYPE_OBJECT)

enum {
	PROP_URI = 1,
	PROP_METHOD
};

static void
uhm_message_init (UhmMessage *msg)
{
	msg->http_version = SOUP_HTTP_1_0;
	msg->status_code = SOUP_STATUS_NONE;

	msg->request_body = soup_message_body_new ();
	msg->request_headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_REQUEST);
	msg->response_body = soup_message_body_new ();
	msg->response_headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_RESPONSE);
}

static void
free_request_and_response (UhmMessage *msg)
{
	soup_message_body_unref (msg->request_body);
	soup_message_headers_unref (msg->request_headers);
	soup_message_body_unref (msg->response_body);
	soup_message_headers_unref (msg->response_headers);
}

static void
uhm_message_finalize (GObject *obj)
{
	UhmMessage *msg = UHM_MESSAGE (obj);

	g_free (msg->method);
	g_free (msg->reason_phrase);
	g_clear_pointer (&msg->uri, g_uri_unref);

	free_request_and_response (msg);

	G_OBJECT_CLASS (uhm_message_parent_class)->finalize (obj);
}

static void
uhm_message_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
{
	UhmMessage *msg = UHM_MESSAGE (object);

	switch (property_id) {
	case PROP_URI:
		g_value_set_boxed (value, g_uri_ref (msg->uri));
		break;
	case PROP_METHOD:
		g_value_set_string (value, msg->method);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
		break;
	}
}

static void
uhm_message_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
{
	UhmMessage *msg = UHM_MESSAGE (object);

	switch (property_id) {
	case PROP_URI:
		g_clear_pointer (&msg->uri, g_uri_unref);
		msg->uri = g_value_dup_boxed (value);
		break;
	case PROP_METHOD:
		g_clear_pointer (&msg->method, g_free);
		msg->method = g_value_dup_string (value);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
		break;
	}
}

static void
uhm_message_class_init (UhmMessageClass *klass)
{
	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

	gobject_class->finalize = uhm_message_finalize;
	gobject_class->set_property = uhm_message_set_property;
	gobject_class->get_property = uhm_message_get_property;

	g_object_class_install_property (gobject_class, PROP_URI,
	                                 g_param_spec_boxed ("uri",
							     "URI", "URI.",
							     G_TYPE_URI,
							     G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

	g_object_class_install_property (gobject_class, PROP_METHOD,
	                                 g_param_spec_string ("method",
	                                                      "Method", "Method.",
	                                                      "POST",
	                                                      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
}

/* private */

UhmMessage *
uhm_message_new_from_uri (const gchar *method, GUri *uri)
{
	return g_object_new (UHM_TYPE_MESSAGE,
			     "method", method,
			     "uri", uri,
			     NULL);
}

UhmMessage *
uhm_message_new_from_server_message (SoupServerMessage *smsg)
{
	UhmMessage *msg;

	msg = g_object_new (UHM_TYPE_MESSAGE,
			    "method", soup_server_message_get_method (smsg),
			    "uri", soup_server_message_get_uri (smsg),
			    NULL);

	msg->http_version = soup_server_message_get_http_version (smsg);
	msg->status_code = soup_server_message_get_status (smsg);
	msg->reason_phrase = g_strdup (soup_server_message_get_reason_phrase (smsg));

	free_request_and_response (msg);
	msg->request_body = soup_message_body_ref (soup_server_message_get_request_body (smsg));
	msg->request_headers = soup_message_headers_ref (soup_server_message_get_request_headers (smsg));
	msg->response_body = soup_message_body_ref (soup_server_message_get_response_body (smsg));
	msg->response_headers = soup_message_headers_ref (soup_server_message_get_response_headers (smsg));

	return msg;
}

void uhm_message_set_status (UhmMessage *message, guint status, const char *reason_phrase)
{
	message->status_code = status;
	g_clear_pointer (&message->reason_phrase, g_free);
	message->reason_phrase = g_strdup (reason_phrase);
}

void uhm_message_set_http_version (UhmMessage *message, SoupHTTPVersion version)
{
	message->http_version = version;
}

/* public */

const gchar *
uhm_message_get_method (UhmMessage *message)
{
	return message->method;
}

SoupHTTPVersion
uhm_message_get_http_version (UhmMessage *message)
{
	return message->http_version;
}

guint uhm_message_get_status (UhmMessage *message)
{
	return message->status_code;
}

const gchar *uhm_message_get_reason_phrase (UhmMessage *message)
{
	return message->reason_phrase;
}

GUri *uhm_message_get_uri (UhmMessage *message)
{
	return message->uri;
}

SoupMessageBody *uhm_message_get_request_body (UhmMessage *message)
{
	return message->request_body;
}

SoupMessageBody *uhm_message_get_response_body (UhmMessage *message)
{
	return message->response_body;
}

SoupMessageHeaders *uhm_message_get_request_headers (UhmMessage *message)
{
	return message->request_headers;
}

SoupMessageHeaders *uhm_message_get_response_headers (UhmMessage *message)
{
	return message->response_headers;
}

0707010000001C000081A400000000000000000000000166717A5A0000073A000000000000000000000000000000000000002C00000000uhttpmock-0.11.0/libuhttpmock/uhm-message.h/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/*
 * uhttpmock
 * Copyright (C) Igalia S.L. 2021 <dkolesa@igalia.com>
 *
 * uhttpmock 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.
 *
 * uhttpmock 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 uhttpmock.  If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef UHM_MESSAGE_H
#define UHM_MESSAGE_H

#include <glib.h>
#include <libsoup/soup.h>

G_BEGIN_DECLS

#define UHM_TYPE_MESSAGE		(uhm_message_get_type ())

G_DECLARE_FINAL_TYPE (UhmMessage, uhm_message, UHM, MESSAGE, GObject)

const gchar *uhm_message_get_method (UhmMessage *message);

SoupHTTPVersion uhm_message_get_http_version (UhmMessage *message);
void uhm_message_set_http_version (UhmMessage *message, SoupHTTPVersion version);

guint uhm_message_get_status (UhmMessage *message);
const gchar *uhm_message_get_reason_phrase (UhmMessage *message);
void uhm_message_set_status (UhmMessage *message, guint status, const gchar *reason_phrase);

GUri *uhm_message_get_uri (UhmMessage *message);

SoupMessageBody *uhm_message_get_request_body (UhmMessage *message);
SoupMessageBody *uhm_message_get_response_body (UhmMessage *message);

SoupMessageHeaders *uhm_message_get_request_headers (UhmMessage *message);
SoupMessageHeaders *uhm_message_get_response_headers (UhmMessage *message);

G_END_DECLS

#endif /* !UHM_MESSAGE_H */
0707010000001D000081A400000000000000000000000166717A5A000033BD000000000000000000000000000000000000002D00000000uhttpmock-0.11.0/libuhttpmock/uhm-resolver.c/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/*
 * uhttpmock
 * Copyright (C) Philip Withnall 2013, 2015 <philip@tecnocode.co.uk>
 * Copyright (C) Collabora Ltd. 2009
 * 
 * uhttpmock 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.
 *
 * uhttpmock 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 uhttpmock.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Original author: Vivek Dasmohapatra <vivek@collabora.co.uk>
 */

/*
 * This code is heavily based on code originally by Vivek Dasmohapatra, found here:
 * http://cgit.collabora.com/git/user/sjoerd/telepathy-gabble.git/plain/tests/twisted/test-resolver.c
 * It was originally licenced under LGPLv2.1+.
 */

/**
 * SECTION:uhm-resolver
 * @short_description: mock DNS resolver
 * @stability: Unstable
 * @include: libuhttpmock/uhm-resolver.h
 *
 * A mock DNS resolver which resolves according to specified host-name–IP-address pairs, and raises an error for all non-specified host name requests.
 * This allows network connections for expected services to be redirected to a different server, such as a local mock server on a loopback interface.
 *
 * Since: 0.1.0
 */

#include <stdio.h>
#include <glib.h>
#include <gio/gio.h>

#ifdef G_OS_WIN32
#include <windows.h>
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#endif

#include "uhm-resolver.h"

static void uhm_resolver_finalize (GObject *object);

static GList *uhm_resolver_lookup_by_name_with_flags (GResolver *resolver, const gchar *hostname, GResolverNameLookupFlags flags, GCancellable *cancellable, GError **error);
static GList *uhm_resolver_lookup_by_name (GResolver *resolver, const gchar *hostname, GCancellable *cancellable, GError **error);
static void uhm_resolver_lookup_by_name_with_flags_async (GResolver *resolver, const gchar *hostname, GResolverNameLookupFlags flags, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data);
static void uhm_resolver_lookup_by_name_async (GResolver *resolver, const gchar *hostname, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data);
static GList *uhm_resolver_lookup_by_name_finish (GResolver *resolver, GAsyncResult *result, GError **error);
static GList *uhm_resolver_lookup_service (GResolver *resolver, const gchar *rrname, GCancellable *cancellable, GError **error);
static void uhm_resolver_lookup_service_async (GResolver *resolver, const gchar *rrname, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data);
static GList *uhm_resolver_lookup_service_finish (GResolver *resolver, GAsyncResult *result, GError **error);

typedef struct {
	gchar *key;
	gchar *addr;
} FakeHost;

typedef struct {
	char *key;
	GSrvTarget *srv;
} FakeService;

struct _UhmResolverPrivate {
	GList *fake_A;
	GList *fake_SRV;
};

G_DEFINE_TYPE_WITH_PRIVATE (UhmResolver, uhm_resolver, G_TYPE_RESOLVER)

static void
uhm_resolver_class_init (UhmResolverClass *klass)
{
	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
	GResolverClass *resolver_class = G_RESOLVER_CLASS (klass);

	gobject_class->finalize = uhm_resolver_finalize;

	resolver_class->lookup_by_name = uhm_resolver_lookup_by_name;
	resolver_class->lookup_by_name_async = uhm_resolver_lookup_by_name_async;
	resolver_class->lookup_by_name_finish = uhm_resolver_lookup_by_name_finish;
	resolver_class->lookup_by_name_with_flags = uhm_resolver_lookup_by_name_with_flags;
	resolver_class->lookup_by_name_with_flags_async = uhm_resolver_lookup_by_name_with_flags_async;
	resolver_class->lookup_by_name_with_flags_finish = uhm_resolver_lookup_by_name_finish;
	resolver_class->lookup_service = uhm_resolver_lookup_service;
	resolver_class->lookup_service_async = uhm_resolver_lookup_service_async;
	resolver_class->lookup_service_finish = uhm_resolver_lookup_service_finish;
}

static void
uhm_resolver_init (UhmResolver *self)
{
	self->priv = uhm_resolver_get_instance_private (self);
}

static void
uhm_resolver_finalize (GObject *object)
{
	uhm_resolver_reset (UHM_RESOLVER (object));

	/* Chain up to the parent class */
	G_OBJECT_CLASS (uhm_resolver_parent_class)->finalize (object);
}

static gchar *
_service_rrname (const char *service, const char *protocol, const char *domain)
{
	gchar *rrname, *ascii_domain;

	ascii_domain = g_hostname_to_ascii (domain);
	rrname = g_strdup_printf ("_%s._%s.%s", service, protocol, ascii_domain);
	g_free (ascii_domain);

	return rrname;
}

static GList *
find_fake_services (UhmResolver *self, const char *name)
{
	GList *fake = NULL;
	GList *rval = NULL;

	for (fake = self->priv->fake_SRV; fake != NULL; fake = g_list_next (fake)) {
		FakeService *entry = fake->data;
		if (entry != NULL && !g_strcmp0 (entry->key, name)) {
			rval = g_list_append (rval, g_srv_target_copy (entry->srv));
		}
	}

	return rval;
}

static void
fake_services_free (GList/*<owned GSrvTarget>*/ *services)
{
	g_list_free_full (services, (GDestroyNotify) g_object_unref);
}

static GList *
find_fake_hosts (UhmResolver *self, const char *name, GResolverNameLookupFlags flags)
{
	GList *fake = NULL;
	GList *rval = NULL;

	for (fake = self->priv->fake_A; fake != NULL; fake = g_list_next (fake)) {
		FakeHost *entry = fake->data;
		if (entry != NULL && !g_strcmp0 (entry->key, name)) {
			g_autoptr (GInetAddress) addr;
			GSocketFamily fam;
			addr = g_inet_address_new_from_string (entry->addr);
			fam = g_inet_address_get_family (addr);
			switch (flags) {
				case G_RESOLVER_NAME_LOOKUP_FLAGS_IPV4_ONLY:
					if (fam == G_SOCKET_FAMILY_IPV6)
						continue;
					break;
				case G_RESOLVER_NAME_LOOKUP_FLAGS_IPV6_ONLY:
					if (fam == G_SOCKET_FAMILY_IPV4)
						continue;
					break;
				case G_RESOLVER_NAME_LOOKUP_FLAGS_DEFAULT:
				default:
					break;
			}
			rval = g_list_append (rval, g_steal_pointer (&addr));
		}
	}

	return rval;
}

static void
fake_hosts_free (GList/*<owned GInetAddress>*/ *addrs)
{
	g_list_free_full (addrs, (GDestroyNotify) g_object_unref);
}

static GList *
uhm_resolver_lookup_by_name_with_flags (GResolver *resolver, const gchar *hostname, GResolverNameLookupFlags flags, GCancellable *cancellable, GError **error)
{
	GList *result;

	result = find_fake_hosts (UHM_RESOLVER (resolver), hostname, flags);

	if (result == NULL) {
		g_set_error (error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_NOT_FOUND, "No fake hostname record registered for ‘%s’.", hostname);
	}

	return result;
}

static GList *
uhm_resolver_lookup_by_name (GResolver *resolver, const gchar *hostname, GCancellable *cancellable, GError **error)
{
	return uhm_resolver_lookup_by_name_with_flags (resolver, hostname, G_RESOLVER_NAME_LOOKUP_FLAGS_DEFAULT, cancellable, error);
}

static void
uhm_resolver_lookup_by_name_with_flags_async (GResolver *resolver, const gchar *hostname, GResolverNameLookupFlags flags, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data)
{
	GTask *task = NULL;  /* owned */
	GList/*<owned GInetAddress>*/ *addr = NULL;  /* owned */
	GError *error = NULL;

	task = g_task_new (resolver, cancellable, callback, user_data);
	g_task_set_source_tag (task, uhm_resolver_lookup_by_name_async);

	addr = uhm_resolver_lookup_by_name_with_flags (resolver, hostname, flags, NULL, &error);

	if (addr != NULL) {
		g_task_return_pointer (task, addr,
		                       (GDestroyNotify) fake_hosts_free);
	} else {
		g_task_return_error (task, error);
	}

	g_object_unref (task);
}

static void
uhm_resolver_lookup_by_name_async (GResolver *resolver, const gchar *hostname, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data)
{
	uhm_resolver_lookup_by_name_with_flags_async (resolver, hostname, G_RESOLVER_NAME_LOOKUP_FLAGS_DEFAULT, cancellable, callback, user_data);
}

static GList *
uhm_resolver_lookup_by_name_finish (GResolver *resolver, GAsyncResult *result, GError **error)
{
	GTask *task;  /* unowned */

	g_return_val_if_fail (g_task_is_valid (result, resolver), NULL);
	g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) ==
	                      uhm_resolver_lookup_by_name_async, NULL);

	task = G_TASK (result);

	return g_task_propagate_pointer (task, error);
}

static GList *
uhm_resolver_lookup_service (GResolver *resolver, const gchar *rrname, GCancellable *cancellable, GError **error)
{
	GList *result;

	result = find_fake_services (UHM_RESOLVER (resolver), rrname);

	if (result == NULL) {
		g_set_error (error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_NOT_FOUND, "No fake service records registered for ‘%s’.", rrname);
	}

	return result;
}

static void
uhm_resolver_lookup_service_async (GResolver *resolver, const gchar *rrname, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data)
{
	GList/*<owned GSrvTarget>*/ *addrs = NULL;  /* owned */
	GTask *task = NULL;  /* owned */
	GError *error = NULL;

	task = g_task_new (resolver, cancellable, callback, user_data);
	g_task_set_source_tag (task, uhm_resolver_lookup_service_async);

	addrs = uhm_resolver_lookup_service (resolver, rrname,
	                                     cancellable, &error);

	if (addrs != NULL) {
		g_task_return_pointer (task, addrs,
		                       (GDestroyNotify) fake_services_free);
	} else {
		g_task_return_error (task, error);
	}

	g_object_unref (task);
}

static GList *
uhm_resolver_lookup_service_finish (GResolver *resolver, GAsyncResult *result, GError **error)
{
	GTask *task;  /* unowned */

	g_return_val_if_fail (g_task_is_valid (result, resolver), NULL);
	g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) ==
	                      uhm_resolver_lookup_service_async, NULL);

	task = G_TASK (result);

	return g_task_propagate_pointer (task, error);
}

/**
 * uhm_resolver_new:
 *
 * Creates a new #UhmResolver with default property values.
 *
 * Return value: (transfer full): a new #UhmResolver; unref with g_object_unref()
 */
UhmResolver *
uhm_resolver_new (void)
{
	return g_object_new (UHM_TYPE_RESOLVER, NULL);
}

/**
 * uhm_resolver_reset:
 * @self: a #UhmResolver
 *
 * Resets the state of the #UhmResolver, deleting all records added with uhm_resolver_add_A() and uhm_resolver_add_SRV().
 */
void
uhm_resolver_reset (UhmResolver *self)
{
	GList *fake = NULL;

	g_return_if_fail (UHM_IS_RESOLVER (self));

	for (fake = self->priv->fake_A; fake != NULL; fake = g_list_next (fake)) {
		FakeHost *entry = fake->data;
		g_free (entry->key);
		g_free (entry->addr);
		g_free (entry);
	}
	g_list_free (self->priv->fake_A);
	self->priv->fake_A = NULL;

	for (fake = self->priv->fake_SRV; fake != NULL; fake = g_list_next (fake)) {
		FakeService *entry = fake->data;
		g_free (entry->key);
		g_srv_target_free (entry->srv);
		g_free (entry);
	}
	g_list_free (self->priv->fake_SRV);
	self->priv->fake_SRV = NULL;
}

/**
 * uhm_resolver_add_A:
 * @self: a #UhmResolver
 * @hostname: the hostname to match
 * @addr: the IP address to resolve to
 *
 * Adds a resolution mapping from the host name @hostname to the IP address @addr.
 *
 * Return value: %TRUE on success; %FALSE otherwise
 *
 * Since: 0.1.0
 */
gboolean
uhm_resolver_add_A (UhmResolver *self, const gchar *hostname, const gchar *addr)
{
	FakeHost *entry;

	g_return_val_if_fail (UHM_IS_RESOLVER (self), FALSE);
	g_return_val_if_fail (hostname != NULL && *hostname != '\0', FALSE);
	g_return_val_if_fail (addr != NULL && *addr != '\0', FALSE);

	entry = g_new0 (FakeHost, 1);
	entry->key = g_strdup (hostname);
	entry->addr = g_strdup (addr);
	self->priv->fake_A = g_list_append (self->priv->fake_A, entry);

	return TRUE;
}

/**
 * uhm_resolver_add_SRV:
 * @self: a #UhmResolver
 * @service: the service name to match
 * @protocol: the protocol name to match
 * @domain: the domain name to match
 * @addr: the IP address to resolve to
 * @port: the port to resolve to
 *
 * Adds a resolution mapping the given @service (on @protocol and @domain) to the IP address @addr and given @port.
 *
 * Return value: %TRUE on success; %FALSE otherwise
 *
 * Since: 0.1.0
 */
gboolean
uhm_resolver_add_SRV (UhmResolver *self, const gchar *service, const gchar *protocol, const gchar *domain, const gchar *addr, guint16 port)
{
	gchar *key;
	GSrvTarget *serv;
	FakeService *entry;

	g_return_val_if_fail (UHM_IS_RESOLVER (self), FALSE);
	g_return_val_if_fail (service != NULL && *service != '\0', FALSE);
	g_return_val_if_fail (protocol != NULL && *protocol != '\0', FALSE);
	g_return_val_if_fail (domain != NULL && *domain != '\0', FALSE);
	g_return_val_if_fail (addr != NULL && *addr != '\0', FALSE);
	g_return_val_if_fail (port > 0, FALSE);

	key = _service_rrname (service, protocol, domain);
	entry = g_new0 (FakeService, 1);
	serv = g_srv_target_new (addr, port, 0, 0);
	entry->key = key;
	entry->srv = serv;
	self->priv->fake_SRV = g_list_append (self->priv->fake_SRV, entry);

	return TRUE;
}
0707010000001E000081A400000000000000000000000166717A5A0000099A000000000000000000000000000000000000002D00000000uhttpmock-0.11.0/libuhttpmock/uhm-resolver.h/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/*
 * uhttpmock
 * Copyright (C) Philip Withnall 2013 <philip@tecnocode.co.uk>
 *
 * uhttpmock 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.
 *
 * uhttpmock 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 uhttpmock.  If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef UHM_RESOLVER_H
#define UHM_RESOLVER_H

#include <glib.h>
#include <glib-object.h>
#include <gio/gio.h>

G_BEGIN_DECLS

#define UHM_TYPE_RESOLVER		(uhm_resolver_get_type ())
#define UHM_RESOLVER(o)			(G_TYPE_CHECK_INSTANCE_CAST ((o), UHM_TYPE_RESOLVER, UhmResolver))
#define UHM_RESOLVER_CLASS(k)		(G_TYPE_CHECK_CLASS_CAST((k), UHM_TYPE_RESOLVER, UhmResolverClass))
#define UHM_IS_RESOLVER(o)		(G_TYPE_CHECK_INSTANCE_TYPE ((o), UHM_TYPE_RESOLVER))
#define UHM_IS_RESOLVER_CLASS(k)	(G_TYPE_CHECK_CLASS_TYPE ((k), UHM_TYPE_RESOLVER))
#define UHM_RESOLVER_GET_CLASS(o)	(G_TYPE_INSTANCE_GET_CLASS ((o), UHM_TYPE_RESOLVER, UhmResolverClass))

typedef struct _UhmResolverPrivate	UhmResolverPrivate;

/**
 * UhmResolver:
 *
 * All the fields in the #UhmResolver structure are private and should never be accessed directly.
 *
 * Since: 0.1.0
 */
typedef struct {
	/*< private >*/
	GResolver parent;
	UhmResolverPrivate *priv;
} UhmResolver;

/**
 * UhmResolverClass:
 *
 * All the fields in the #UhmResolverClass structure are private and should never be accessed directly.
 *
 * Since: 0.1.0
 */
typedef struct {
	/*< private >*/
	GResolverClass parent;
} UhmResolverClass;

GType uhm_resolver_get_type (void) G_GNUC_CONST;

UhmResolver *uhm_resolver_new (void) G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT;

void uhm_resolver_reset (UhmResolver *self);

gboolean uhm_resolver_add_A (UhmResolver *self, const gchar *hostname, const gchar *addr);
gboolean uhm_resolver_add_SRV (UhmResolver *self, const gchar *service, const gchar *protocol, const gchar *domain, const gchar *addr, guint16 port);

G_END_DECLS

#endif /* !UHM_RESOLVER_H */
0707010000001F000081A400000000000000000000000166717A5A0001487E000000000000000000000000000000000000002B00000000uhttpmock-0.11.0/libuhttpmock/uhm-server.c/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/*
 * uhttpmock
 * Copyright (C) Philip Withnall 2013, 2015 <philip@tecnocode.co.uk>
 *
 * uhttpmock 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.
 *
 * uhttpmock 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 uhttpmock.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * SECTION:uhm-server
 * @short_description: mock HTTP(S) server
 * @stability: Unstable
 * @include: libuhttpmock/uhm-server.h
 *
 * This is a mock HTTPS server which can be used to run unit tests of network client code on a loopback interface rather than on the real Internet.
 * At its core, it's a simple HTTPS server which runs on a loopback address on an arbitrary port. The code under test must be modified to send its
 * requests to this port, although #UhmResolver may be used to transparently redirect all IP addresses to the mock server.
 * A convenience layer on the mock server provides loading of and recording to trace files, which are sequences of request–response HTTPS message pairs
 * where each request is expected by the server (in order). On receiving an expected request, the mock server will return the relevant response and move
 * to expecting the next request in the trace file.
 *
 * The mock server currently only operates on a single network interface, on HTTPS (if #UhmServer:tls-certificate is set) or HTTP otherwise.
 * This may change in future. Your own TLS certificate can be provided to authenticate the server using #UhmServer:tls-certificate, or a dummy
 * TLS certificate can be used by calling uhm_server_set_default_tls_certificate(). This certificate is not signed by a CA, so the
 * #SoupSession:ssl-strict property must be set to %FALSE in client code
 * during (and only during!) testing.
 *
 * The server can operate in three modes: logging, testing, and comparing. These are set by #UhmServer:enable-logging and #UhmServer:enable-online.
 *  • Logging mode (#UhmServer:enable-logging: %TRUE, #UhmServer:enable-online: %TRUE): Requests are sent to the real server online, and the
 *    request–response pairs recorded to a log file.
 *  • Testing mode (#UhmServer:enable-logging: %FALSE, #UhmServer:enable-online: %FALSE): Requests are sent to the mock server, which responds
 *    from the trace file.
 *  • Comparing mode (#UhmServer:enable-logging: %FALSE, #UhmServer:enable-online: %TRUE): Requests are sent to the real server online, and
 *    the request–response pairs are compared against those in an existing log file to see if the log file is up-to-date.
 *
 * Starting with version 0.11.0 hosts are automatically extracted and stored in hosts trace files. These files are
 * used during replay so no host configuration using uhm_resolver_add_A() is necessary in code any more.
 * Requires libsoup 3.5.1 or higher. If that version of libsoup is not available, uhm_resolver_add_A() must continue
 * to be used.
 *
 * Since: 0.1.0
 */

#include <glib.h>
#include <glib/gi18n.h>
#include <libsoup/soup.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>

#include "uhm-default-tls-certificate.h"
#include "uhm-resolver.h"
#include "uhm-server.h"
#include "uhm-message-private.h"

GQuark
uhm_server_error_quark (void)
{
	return g_quark_from_static_string ("uhm-server-error-quark");
}

static void uhm_server_dispose (GObject *object);
static void uhm_server_finalize (GObject *object);
static void uhm_server_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
static void uhm_server_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec);

static gboolean real_handle_message (UhmServer *self, UhmMessage *message);
static gboolean real_compare_messages (UhmServer *self, UhmMessage *expected_message, UhmMessage *actual_message);

static void server_handler_cb (SoupServer *server, SoupServerMessage *message, const gchar *path, GHashTable *query, gpointer user_data);
static void load_file_stream_thread_cb (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable);
static void load_file_iteration_thread_cb (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable);

static GDataInputStream *load_file_stream (GFile *trace_file, GCancellable *cancellable, GError **error);
static UhmMessage *load_file_iteration (GDataInputStream *input_stream, GUri *base_uri, GCancellable *cancellable, GError **error);

static void apply_expected_domain_names (UhmServer *self);

struct _UhmServerPrivate {
	/* UhmServer is based around HTTP/HTTPS, and cannot be extended to support other application-layer protocols.
	 * If libuhttpmock is extended to support other protocols (e.g. IMAP) in future, a new UhmImapServer should be
	 * created. It may be possible to share code with UhmServer, but the APIs for the two would be sufficiently
	 * different to not be mergeable.
	 *
	 * The SoupServer is *not* thread safe, so all calls to it must be marshalled through the server_context, which
	 * it runs in. */
	SoupServer *server;
	UhmResolver *resolver;
	GThread *server_thread;
	GMainContext *server_context;
	GMainLoop *server_main_loop;

	/* TLS certificate. */
	GTlsCertificate *tls_certificate;

	/* Server interface. */
	GSocketAddress *address;  /* owned */
	gchar *address_string;  /* owned; cache */
	guint port;

	/* Expected resolver domain names. */
	gchar **expected_domain_names;

	GFile *trace_file;
	GDataInputStream *input_stream;
	GFileOutputStream *output_stream;
	UhmMessage *next_message;
	guint message_counter; /* ID of the message within the current trace file */

	GFile *trace_directory;
	gboolean enable_online;
	gboolean enable_logging;

	GFile *hosts_trace_file;
	GFileOutputStream *hosts_output_stream;
	GHashTable *hosts;

	GByteArray *comparison_message;
	enum {
		UNKNOWN,
		REQUEST_DATA,
		REQUEST_TERMINATOR,
		RESPONSE_DATA,
		RESPONSE_TERMINATOR,
	} received_message_state;
};

enum {
	PROP_TRACE_DIRECTORY = 1,
	PROP_ENABLE_ONLINE,
	PROP_ENABLE_LOGGING,
	PROP_ADDRESS,
	PROP_PORT,
	PROP_RESOLVER,
	PROP_TLS_CERTIFICATE,
};

enum {
	SIGNAL_HANDLE_MESSAGE = 1,
	SIGNAL_COMPARE_MESSAGES,
	LAST_SIGNAL,
};

static guint signals[LAST_SIGNAL] = { 0, };

G_DEFINE_TYPE_WITH_PRIVATE (UhmServer, uhm_server, G_TYPE_OBJECT)

static void
uhm_server_class_init (UhmServerClass *klass)
{
	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

	gobject_class->get_property = uhm_server_get_property;
	gobject_class->set_property = uhm_server_set_property;
	gobject_class->dispose = uhm_server_dispose;
	gobject_class->finalize = uhm_server_finalize;

	klass->handle_message = real_handle_message;
	klass->compare_messages = real_compare_messages;

	/**
	 * UhmServer:trace-directory:
	 *
	 * Directory relative to which all trace files specified in calls to uhm_server_start_trace() will be resolved.
	 * This is not used for any other methods, but must be non-%NULL if uhm_server_start_trace() is called.
	 *
	 * Since: 0.1.0
	 */
	g_object_class_install_property (gobject_class, PROP_TRACE_DIRECTORY,
	                                 g_param_spec_object ("trace-directory",
	                                                      "Trace Directory", "Directory relative to which all trace files will be resolved.",
	                                                      G_TYPE_FILE,
	                                                      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

	/**
	 * UhmServer:enable-online:
	 *
	 * %TRUE if network traffic should reach the Internet as normal; %FALSE to redirect it to the local mock server.
	 * Use this in conjunction with #UhmServer:enable-logging to either log online traffic, or replay logged traffic locally.
	 *
	 * Since: 0.1.0
	 */
	g_object_class_install_property (gobject_class, PROP_ENABLE_ONLINE,
	                                 g_param_spec_boolean ("enable-online",
	                                                       "Enable Online", "Whether network traffic should reach the Internet as normal.",
	                                                       FALSE,
	                                                       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

	/**
	 * UhmServer:enable-logging:
	 *
	 * %TRUE if network traffic should be logged to a trace file (specified by calling uhm_server_start_trace()). This operates independently
	 * of whether traffic is online or being handled locally by the mock server.
	 * Use this in conjunction with #UhmServer:enable-online to either log online traffic, or replay logged traffic locally.
	 *
	 * Since: 0.1.0
	 */
	g_object_class_install_property (gobject_class, PROP_ENABLE_LOGGING,
	                                 g_param_spec_boolean ("enable-logging",
	                                                       "Enable Logging", "Whether network traffic should be logged to a trace file.",
	                                                       FALSE,
	                                                       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

	/**
	 * UhmServer:address:
	 *
	 * Address of the local mock server if it's running, or %NULL otherwise. This will be non-%NULL between calls to uhm_server_run() and
	 * uhm_server_stop(). The address is a physical IP address, e.g. <code class="literal">127.0.0.1</code>.
	 *
	 * This should not normally need to be passed into client code under test, unless the code references IP addresses specifically. The mock server
	 * runs a DNS resolver which automatically redirects client requests for known domain names to this address (#UhmServer:resolver).
	 *
	 * Since: 0.1.0
	 */
	g_object_class_install_property (gobject_class, PROP_ADDRESS,
	                                 g_param_spec_string ("address",
	                                                      "Server Address", "Address of the local mock server if it's running.",
	                                                      NULL,
	                                                      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));

	/**
	 * UhmServer:port:
	 *
	 * Port of the local mock server if it's running, or <code class="literal">0</code> otherwise. This will be non-<code class="literal">0</code> between
	 * calls to uhm_server_run() and uhm_server_stop().
	 *
	 * It is intended that this port be passed into the client code under test, to substitute for the default HTTPS port (443) which it would otherwise
	 * use.
	 *
	 * Since: 0.1.0
	 */
	g_object_class_install_property (gobject_class, PROP_PORT,
	                                 g_param_spec_uint ("port",
	                                                    "Server Port", "Port of the local mock server if it's running",
	                                                    0, G_MAXUINT, 0,
	                                                    G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));

	/**
	 * UhmServer:resolver:
	 *
	 * Mock resolver used to redirect HTTP requests from specified domain names to the local mock server instance. This will always be set while the
	 * server is running (between calls to uhm_server_run() and uhm_server_stop()), and is %NULL otherwise.
	 *
	 * Use the resolver specified in this property to add domain names which are expected to be requested by the current trace. Domain names not added
	 * to the resolver will be rejected by the mock server. The set of domain names in the resolver will be reset when uhm_server_stop() is
	 * called.
	 *
	 * Since: 0.1.0
	 */
	g_object_class_install_property (gobject_class, PROP_RESOLVER,
	                                 g_param_spec_object ("resolver",
	                                                      "Resolver", "Mock resolver used to redirect HTTP requests to the local mock server instance.",
	                                                      UHM_TYPE_RESOLVER,
	                                                      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));

	/**
	 * UhmServer:tls-certificate:
	 *
	 * TLS certificate for the mock server to use when serving HTTPS pages. If this is non-%NULL, the server will always use HTTPS. If it is %NULL,
	 * the server will always use HTTP. The TLS certificate may be changed after constructing the #UhmServer, but changes to the property will not
	 * take effect until the next call to uhm_server_run().
	 *
	 * A certificate and private key may be generated by executing:
	 * <code>openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -nodes</code>. These files may then be used to construct a
	 * #GTlsCertificate by calling g_tls_certificate_new_from_files().
	 *
	 * Alternatively, a default #GTlsCertificate which wraps a dummy certificate (not signed by any certificate authority) may be set by
	 * calling uhm_server_set_default_tls_certificate(). This may be used as the #UhmServer:tls-certificate if the code under test has no specific
	 * requirements of the certificate used by the mock server it's tested against.
	 *
	 * Since: 0.1.0
	 */
	g_object_class_install_property (gobject_class, PROP_TLS_CERTIFICATE,
	                                 g_param_spec_object ("tls-certificate",
	                                                      "TLS Certificate", "TLS certificate for the mock server to use when serving HTTPS pages.",
	                                                      G_TYPE_TLS_CERTIFICATE,
	                                                      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

	/**
	 * UhmServer::handle-message:
	 * @self: a #UhmServer
	 * @message: a message containing the incoming HTTP(S) request, and which the outgoing HTTP(S) response should be set on
	 *
	 * Emitted whenever the mock server is running and receives a request from a client. Test code may connect to this signal and implement a handler
	 * which builds and returns a suitable response for a given message. The default handler reads a request–response pair from the current trace file,
	 * matches the requests and then returns the given response. If the requests don't match, an error is raised.
	 *
	 * Signal handlers should return %TRUE if they have handled the request and set an appropriate response; and %FALSE otherwise.
	 *
	 * Since: 0.1.0
	 */
	signals[SIGNAL_HANDLE_MESSAGE] = g_signal_new ("handle-message", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST,
	                                               G_STRUCT_OFFSET (UhmServerClass, handle_message),
	                                               g_signal_accumulator_true_handled, NULL,
	                                               g_cclosure_marshal_generic,
	                                               G_TYPE_BOOLEAN, 1,
	                                               UHM_TYPE_MESSAGE);

	/**
	 * UhmServer::compare-messages:
	 * @self: a #UhmServer
	 * @expected_message: a message containing the expected HTTP(S) message provided by #UhmServer::handle-message
	 * @actual_message: a message containing the incoming HTTP(S) request
	 *
	 * Emitted whenever the mock server must compare two #UhmMessage<!-- -->s for equality; e.g. when in the testing or comparison modes.
	 * Test code may connect to this signal and implement a handler which checks custom properties of the messages. The default handler compares
	 * the URI and method of the messages, but no headers and not the message bodies.
	 *
	 * Signal handlers should return %TRUE if the messages match; and %FALSE otherwise. The first signal handler executed when
	 * this signal is emitted wins.
	 *
	 * Since: 1.0.0
	 */
	signals[SIGNAL_COMPARE_MESSAGES] = g_signal_new ("compare-messages", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST,
	                                               G_STRUCT_OFFSET (UhmServerClass, compare_messages),
	                                               g_signal_accumulator_first_wins, NULL,
	                                               g_cclosure_marshal_generic,
	                                               G_TYPE_BOOLEAN, 2,
	                                               UHM_TYPE_MESSAGE, UHM_TYPE_MESSAGE);
}

static void
uhm_server_init (UhmServer *self)
{
	self->priv = uhm_server_get_instance_private (self);
	self->priv->hosts = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
}

static void
uhm_server_dispose (GObject *object)
{
	UhmServerPrivate *priv = UHM_SERVER (object)->priv;

	g_clear_object (&priv->resolver);
	g_clear_object (&priv->server);
	g_clear_pointer (&priv->server_context, g_main_context_unref);
	g_clear_pointer (&priv->hosts, g_hash_table_unref);
	g_clear_object (&priv->hosts_trace_file);
	g_clear_object (&priv->hosts_output_stream);
	g_clear_object (&priv->trace_file);
	g_clear_object (&priv->input_stream);
	g_clear_object (&priv->output_stream);
	g_clear_object (&priv->next_message);
	g_clear_object (&priv->trace_directory);
	g_clear_pointer (&priv->server_thread, g_thread_unref);
	g_clear_pointer (&priv->comparison_message, g_byte_array_unref);
	g_clear_object (&priv->tls_certificate);

	/* Chain up to the parent class */
	G_OBJECT_CLASS (uhm_server_parent_class)->dispose (object);
}

static void
uhm_server_finalize (GObject *object)
{
	UhmServerPrivate *priv = UHM_SERVER (object)->priv;

	g_strfreev (priv->expected_domain_names);

	/* Chain up to the parent class */
	G_OBJECT_CLASS (uhm_server_parent_class)->finalize (object);
}

static void
uhm_server_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
{
	UhmServerPrivate *priv = UHM_SERVER (object)->priv;

	switch (property_id) {
		case PROP_TRACE_DIRECTORY:
			g_value_set_object (value, priv->trace_directory);
			break;
		case PROP_ENABLE_ONLINE:
			g_value_set_boolean (value, priv->enable_online);
			break;
		case PROP_ENABLE_LOGGING:
			g_value_set_boolean (value, priv->enable_logging);
			break;
		case PROP_ADDRESS:
			g_value_set_string (value, uhm_server_get_address (UHM_SERVER (object)));
			break;
		case PROP_PORT:
			g_value_set_uint (value, priv->port);
			break;
		case PROP_RESOLVER:
			g_value_set_object (value, priv->resolver);
			break;
		case PROP_TLS_CERTIFICATE:
			g_value_set_object (value, priv->tls_certificate);
			break;
		default:
			/* We don't have any other property... */
			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
			break;
	}
}

static void
uhm_server_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
{
	UhmServer *self = UHM_SERVER (object);

	switch (property_id) {
		case PROP_TRACE_DIRECTORY:
			uhm_server_set_trace_directory (self, g_value_get_object (value));
			break;
		case PROP_ENABLE_ONLINE:
			uhm_server_set_enable_online (self, g_value_get_boolean (value));
			break;
		case PROP_ENABLE_LOGGING:
			uhm_server_set_enable_logging (self, g_value_get_boolean (value));
			break;
		case PROP_TLS_CERTIFICATE:
			uhm_server_set_tls_certificate (self, g_value_get_object (value));
			break;
		case PROP_ADDRESS:
		case PROP_PORT:
		case PROP_RESOLVER:
			/* Read-only. */
		default:
			/* We don't have any other property... */
			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
			break;
	}
}

typedef struct {
	GDataInputStream *input_stream;
	GUri *base_uri;
} LoadFileIterationData;

static void
load_file_iteration_data_free (LoadFileIterationData *data)
{
	g_object_unref (data->input_stream);
	g_uri_unref (data->base_uri);
	g_slice_free (LoadFileIterationData, data);
}

static char *
uri_get_path_query (GUri *uri)
{
	const char *path = g_uri_get_path (uri);
	return g_strconcat (path[0] ? path : "/", g_uri_get_query (uri), NULL);
}

static GUri * /* transfer full */
build_base_uri (UhmServer *self)
{
	UhmServerPrivate *priv = self->priv;
	gchar *base_uri_string;
	GUri *base_uri;

	if (priv->enable_online == FALSE) {
		GSList *uris;  /* owned */
		uris = soup_server_get_uris (priv->server);
		if (uris == NULL) {
			return NULL;
		}
		base_uri_string = g_uri_to_string_partial (uris->data, G_URI_HIDE_PASSWORD);
		g_slist_free_full (uris, (GDestroyNotify) g_uri_unref);
	} else {
		base_uri_string = g_strdup ("https://localhost"); /* arbitrary */
	}

	base_uri = g_uri_parse (base_uri_string, SOUP_HTTP_URI_FLAGS, NULL);
	g_free (base_uri_string);

	return base_uri;
}

static inline gboolean
parts_equal (const char *one, const char *two, gboolean insensitive)
{
	if (!one && !two)
		return TRUE;
	if (!one || !two)
		return FALSE;
	return insensitive ? !g_ascii_strcasecmp (one, two) : !strcmp (one, two);
}

static gboolean
real_compare_messages (UhmServer *server, UhmMessage *expected_message, UhmMessage *actual_message)
{
	GUri *expected_uri, *actual_uri;

	/* Compare method. */
	if (g_strcmp0 (uhm_message_get_method (expected_message), uhm_message_get_method (actual_message)) != 0) {
		return FALSE;
	}

	/* Compare URIs. */
	expected_uri = uhm_message_get_uri (expected_message);
	actual_uri = uhm_message_get_uri (actual_message);

	if (!parts_equal (g_uri_get_user (expected_uri), g_uri_get_user (actual_uri), FALSE) ||
	    !parts_equal (g_uri_get_password (expected_uri), g_uri_get_password (actual_uri), FALSE) ||
	    !parts_equal (g_uri_get_path (expected_uri), g_uri_get_path (actual_uri), FALSE) ||
	    !parts_equal (g_uri_get_query (expected_uri), g_uri_get_query (actual_uri), FALSE) ||
	    !parts_equal (g_uri_get_fragment (expected_uri), g_uri_get_fragment (actual_uri), FALSE)) {
		return FALSE;
	}

	return TRUE;
}

/* strcmp()-like return value: 0 means the messages compare equal. */
static gint
compare_incoming_message (UhmServer *self, UhmMessage *expected_message, UhmMessage *actual_message)
{
	gboolean messages_equal = FALSE;

	g_signal_emit (self, signals[SIGNAL_COMPARE_MESSAGES], 0, expected_message, actual_message, &messages_equal);

	return (messages_equal == TRUE) ? 0 : 1;
}

static void
header_append_cb (const gchar *name, const gchar *value, gpointer user_data)
{
	UhmMessage *message = user_data;

	soup_message_headers_append (uhm_message_get_response_headers (message), name, value);
}

static void
server_response_append_headers (UhmServer *self, UhmMessage *message)
{
	UhmServerPrivate *priv = self->priv;
	gchar *trace_file_name, *trace_file_offset;

	trace_file_name = g_file_get_uri (priv->trace_file);
	soup_message_headers_append (uhm_message_get_response_headers (message), "X-Mock-Trace-File", trace_file_name);
	g_free (trace_file_name);

	trace_file_offset = g_strdup_printf ("%u", priv->message_counter);
	soup_message_headers_append (uhm_message_get_response_headers (message), "X-Mock-Trace-File-Offset", trace_file_offset);
	g_free (trace_file_offset);
}

static void
server_process_message (UhmServer *self, UhmMessage *message)
{
	UhmServerPrivate *priv = self->priv;
	g_autoptr(GBytes) message_body = NULL;
	goffset expected_content_length;
	g_autoptr(GError) error = NULL;
	const char *location_header = NULL;

	g_assert (priv->next_message != NULL);
	priv->message_counter++;

	if (compare_incoming_message (self, priv->next_message, message) != 0) {
		gchar *body, *next_uri, *actual_uri;

		/* Received message is not what we expected. Return an error. */
		uhm_message_set_status (message, SOUP_STATUS_BAD_REQUEST,
		                        "Unexpected request to mock server");

		next_uri = uri_get_path_query (uhm_message_get_uri (priv->next_message));
		actual_uri = uri_get_path_query (uhm_message_get_uri (message));
		body = g_strdup_printf ("Expected %s URI ‘%s’, but got %s ‘%s’.",
		                        uhm_message_get_method (priv->next_message),
		                        next_uri, uhm_message_get_method (message), actual_uri);
		g_free (actual_uri);
		g_free (next_uri);
		soup_message_body_append_take (uhm_message_get_response_body (message), (guchar *) body, strlen (body) + 1);

		server_response_append_headers (self, message);

		return;
	}

	/* The incoming message matches what we expected, so copy the headers and body from the expected response and return it. */
	uhm_message_set_http_version (message, uhm_message_get_http_version (priv->next_message));
	uhm_message_set_status (message, uhm_message_get_status (priv->next_message),
	                        uhm_message_get_reason_phrase (priv->next_message));

	/* Rewrite Location headers to use the uhttpmock server details */
	location_header = soup_message_headers_get_one (uhm_message_get_response_headers (priv->next_message), "Location");
	if (location_header) {
		g_autoptr(GUri) uri = NULL;
		g_autoptr(GUri) modified_uri = NULL;
		g_autofree char *uri_str = NULL;

		uri = g_uri_parse (location_header, G_URI_FLAGS_ENCODED, &error);
		if (uri) {
			modified_uri = g_uri_build (G_URI_FLAGS_ENCODED,
				                    g_uri_get_scheme (uri),
				                    g_uri_get_userinfo (uri),
				                    g_uri_get_host (uri),
				                    priv->port,
				                    g_uri_get_path (uri),
				                    g_uri_get_query (uri),
				                    g_uri_get_fragment (uri));

			uri_str = g_uri_to_string (modified_uri);
			soup_message_headers_replace (uhm_message_get_response_headers (priv->next_message), "Location", uri_str);
		} else {
			g_debug ("Failed to rewrite Location header ‘%s’ to use new port", location_header);
		}
	}
	soup_message_headers_foreach (uhm_message_get_response_headers (priv->next_message), header_append_cb, message);

	/* Add debug headers to identify the message and trace file. */
	server_response_append_headers (self, message);

	message_body = soup_message_body_flatten (uhm_message_get_response_body (priv->next_message));
	if (g_bytes_get_size (message_body) > 0)
		soup_message_body_append_bytes (uhm_message_get_response_body (message), message_body);

	/* If the log file doesn't contain the full response body (e.g. because it's a huge binary file containing a nul byte somewhere),
	 * make one up (all zeros). */
	expected_content_length = soup_message_headers_get_content_length (uhm_message_get_response_headers (message));
	if (expected_content_length > 0 && g_bytes_get_size (message_body) < (gsize) expected_content_length) {
		guint8 *buf;

		buf = g_malloc0 (expected_content_length - g_bytes_get_size (message_body));
		soup_message_body_append_take (uhm_message_get_response_body (message), buf, expected_content_length - g_bytes_get_size (message_body));
	}

	soup_message_body_complete (uhm_message_get_response_body (message));

	/* Clear the expected message. */
	g_clear_object (&priv->next_message);
}

static void
server_handler_cb (SoupServer *server, SoupServerMessage *message, const gchar *path, GHashTable *query, gpointer user_data)
{
	UhmServer *self = user_data;
	UhmMessage *umsg;
	gboolean message_handled = FALSE;

	soup_server_message_pause (message);
	umsg = uhm_message_new_from_server_message (message);

	g_signal_emit (self, signals[SIGNAL_HANDLE_MESSAGE], 0, umsg, &message_handled);

	soup_server_message_set_http_version (message, uhm_message_get_http_version (umsg));
	soup_server_message_set_status (message, uhm_message_get_status (umsg), uhm_message_get_reason_phrase (umsg));

	g_object_unref (umsg);
	soup_server_message_unpause (message);

	/* The message should always be handled by real_handle_message() at least. */
	g_assert (message_handled == TRUE);
}

static gboolean
real_handle_message (UhmServer *self, UhmMessage *message)
{
	UhmServerPrivate *priv = self->priv;
	gboolean handled = FALSE;

	/* Asynchronously load the next expected message from the trace file. */
	if (priv->next_message == NULL) {
		GTask *task;
		LoadFileIterationData *data;
		GError *child_error = NULL;

		data = g_slice_new (LoadFileIterationData);
		data->input_stream = g_object_ref (priv->input_stream);
		data->base_uri = build_base_uri (self);

		task = g_task_new (self, NULL, NULL, NULL);
		g_task_set_task_data (task, data, (GDestroyNotify) load_file_iteration_data_free);
		g_task_run_in_thread_sync (task, load_file_iteration_thread_cb);

		/* Handle the results. */
		priv->next_message = g_task_propagate_pointer (task, &child_error);

		g_object_unref (task);

		if (child_error != NULL) {
			gchar *body;

			uhm_message_set_status (message, SOUP_STATUS_INTERNAL_SERVER_ERROR,
			                        "Error loading expected request");

			body = g_strdup_printf ("Error: %s", child_error->message);
			soup_message_body_append_take (uhm_message_get_response_body (message), (guchar *) body, strlen (body) + 1);
			handled = TRUE;

			g_error_free (child_error);

			server_response_append_headers (self, message);
		} else if (priv->next_message == NULL) {
			gchar *body, *actual_uri;

			/* Received message is not what we expected. Return an error. */
			uhm_message_set_status (message, SOUP_STATUS_BAD_REQUEST,
			                        "Unexpected request to mock server");

			actual_uri = uri_get_path_query (uhm_message_get_uri (message));
			body = g_strdup_printf ("Expected no request, but got %s ‘%s’.", uhm_message_get_method (message), actual_uri);
			g_free (actual_uri);
			soup_message_body_append_take (uhm_message_get_response_body (message), (guchar *) body, strlen (body) + 1);
			handled = TRUE;

			server_response_append_headers (self, message);
		}
	}

	/* Process the actual message if we already know the expected message. */
	g_assert (priv->next_message != NULL || handled == TRUE);
	if (handled == FALSE) {
		server_process_message (self, message);
		handled = TRUE;
	}

	g_assert (handled == TRUE);
	return handled;
}

/**
 * uhm_server_new:
 *
 * Creates a new #UhmServer with default properties.
 *
 * Return value: (transfer full): a new #UhmServer; unref with g_object_unref()
 *
 * Since: 0.1.0
 */
UhmServer *
uhm_server_new (void)
{
	return g_object_new (UHM_TYPE_SERVER, NULL);
}

static gboolean
trace_to_soup_message_headers_and_body (SoupMessageHeaders *message_headers, SoupMessageBody *message_body, const gchar message_direction, const gchar **_trace)
{
	const gchar *i;
	const gchar *trace = *_trace;

	/* Parse headers. */
	while (TRUE) {
		gchar *header_name, *header_value;

		if (*trace == '\0') {
			/* No body. */
			goto done;
		} else if (*trace == ' ' && *(trace + 1) == ' ' && *(trace + 2) == '\n') {
			/* No body. */
			trace += 3;
			goto done;
		} else if (*trace != message_direction || *(trace + 1) != ' ') {
			g_warning ("Unrecognised start sequence ‘%c%c’.", *trace, *(trace + 1));
			goto error;
		}
		trace += 2;

		if (*trace == '\n') {
			/* Reached the end of the headers. */
			trace++;
			break;
		}

		i = strchr (trace, ':');
		if (i == NULL || *(i + 1) != ' ') {
			g_warning ("Missing spacer ‘: ’.");
			goto error;
		}

		header_name = g_strndup (trace, i - trace);
		trace += (i - trace) + 2;

		i = strchr (trace, '\n');
		if (i == NULL) {
			g_warning ("Missing spacer ‘\\n’.");
			goto error;
		}

		header_value = g_strndup (trace, i - trace);
		trace += (i - trace) + 1;

		/* Append the header. */
		soup_message_headers_append (message_headers, header_name, header_value);

		g_free (header_value);
		g_free (header_name);
	}

	/* Parse the body. */
	while (TRUE) {
		if (*trace == ' ' && *(trace + 1) == ' ' && *(trace + 2) == '\n') {
			/* End of the body. */
			trace += 3;
			break;
		} else if (*trace == '\0') {
			/* End of the body. */
			break;
		} else if (*trace != message_direction || *(trace + 1) != ' ') {
			g_warning ("Unrecognised start sequence ‘%c%c’.", *trace, *(trace + 1));
			goto error;
		}
		trace += 2;

		i = strchr (trace, '\n');
		if (i == NULL) {
			g_warning ("Missing spacer ‘\\n’.");
			goto error;
		}

		soup_message_body_append (message_body, SOUP_MEMORY_COPY, trace, i - trace + 1); /* include trailing \n */
		trace += (i - trace) + 1;
	}

done:
	/* Done. Update the output trace pointer. */
	soup_message_body_complete (message_body);
	*_trace = trace;

	return TRUE;

error:
	return FALSE;
}

/* base_uri is the base URI for the server, e.g. https://127.0.0.1:1431. */
static UhmMessage *
trace_to_soup_message (const gchar *trace, GUri *base_uri)
{
	UhmMessage *message = NULL;
	const gchar *i, *j, *method;
	gchar *uri_string, *response_message;
	SoupHTTPVersion http_version;
	guint response_status;
	g_autoptr(GUri) uri = NULL;

	g_return_val_if_fail (trace != NULL, NULL);

	/* The traces look somewhat like this:
	 * > POST /unauth HTTP/1.1
	 * > Soup-Debug-Timestamp: 1200171744
	 * > Soup-Debug: SoupSessionAsync 1 (0x612190), SoupMessage 1 (0x617000), SoupSocket 1 (0x612220)
	 * > Host: localhost
	 * > Content-Type: text/plain
	 * > Connection: close
	 * > 
	 * > This is a test.
	 *   
	 * < HTTP/1.1 201 Created
	 * < Soup-Debug-Timestamp: 1200171744
	 * < Soup-Debug: SoupMessage 1 (0x617000)
	 * < Date: Sun, 12 Jan 2008 21:02:24 GMT
	 * < Content-Length: 0
	 *
	 * This function parses a single request–response pair.
	 */

	/* Parse the method, URI and HTTP version first. */
	if (*trace != '>' || *(trace + 1) != ' ') {
		g_warning ("Unrecognised start sequence ‘%c%c’.", *trace, *(trace + 1));
		goto error;
	}
	trace += 2;

	/* Parse “POST /unauth HTTP/1.1”. */
	if (strncmp (trace, "POST", strlen ("POST")) == 0) {
		method = SOUP_METHOD_POST;
		trace += strlen ("POST");
	} else if (strncmp (trace, "GET", strlen ("GET")) == 0) {
		method = SOUP_METHOD_GET;
		trace += strlen ("GET");
	} else if (strncmp (trace, "DELETE", strlen ("DELETE")) == 0) {
		method = SOUP_METHOD_DELETE;
		trace += strlen ("DELETE");
	} else if (strncmp (trace, "PUT", strlen ("PUT")) == 0) {
		method = SOUP_METHOD_PUT;
		trace += strlen ("PUT");
	} else if (strncmp (trace, "PATCH", strlen ("PATCH")) == 0) {
		method = "PATCH";
		trace += strlen ("PATCH");
	} else if (strncmp (trace, "CONNECT", strlen ("CONNECT")) == 0) {
		method = "CONNECT";
		trace += strlen ("CONNECT");
	} else {
		g_warning ("Unknown method ‘%s’.", trace);
		goto error;
	}

	if (*trace != ' ') {
		g_warning ("Unrecognised spacer ‘%c’.", *trace);
		goto error;
	}
	trace++;

	i = strchr (trace, ' ');
	if (i == NULL) {
		g_warning ("Missing spacer ‘ ’.");
		goto error;
	}

	uri_string = g_strndup (trace, i - trace);
	trace += (i - trace) + 1;

	if (strncmp (trace, "HTTP/1.1", strlen ("HTTP/1.1")) == 0) {
		http_version = SOUP_HTTP_1_1;
		trace += strlen ("HTTP/1.1");
	} else if (strncmp (trace, "HTTP/1.0", strlen ("HTTP/1.0")) == 0) {
		http_version = SOUP_HTTP_1_0;
		trace += strlen ("HTTP/1.0");
	} else if (strncmp (trace, "HTTP/2", strlen ("HTTP/2")) == 0) {
		http_version = SOUP_HTTP_2_0;
		trace += strlen ("HTTP/2");
	} else {
		g_warning ("Unrecognised HTTP version ‘%s’.", trace);
		http_version = SOUP_HTTP_1_1;
	}

	if (*trace != '\n') {
		g_warning ("Unrecognised spacer ‘%c’.", *trace);
		goto error;
	}
	trace++;

	/* Build the message. */
	uri = g_uri_parse_relative (base_uri, uri_string, SOUP_HTTP_URI_FLAGS, NULL);
	message = uhm_message_new_from_uri (method, uri);

	if (message == NULL) {
		g_warning ("Invalid URI ‘%s’.", uri_string);
		goto error;
	}

	uhm_message_set_http_version (message, http_version);
	g_free (uri_string);

	/* Parse the request headers and body. */
	if (trace_to_soup_message_headers_and_body (uhm_message_get_request_headers (message), uhm_message_get_request_body (message), '>', &trace) == FALSE) {
		goto error;
	}

	/* Parse the response, starting with “HTTP/1.1 201 Created”. */
	if (*trace != '<' || *(trace + 1) != ' ') {
		g_warning ("Unrecognised start sequence ‘%c%c’.", *trace, *(trace + 1));
		goto error;
	}
	trace += 2;

	if (strncmp (trace, "HTTP/1.1", strlen ("HTTP/1.1")) == 0) {
		http_version = SOUP_HTTP_1_1;
		trace += strlen ("HTTP/1.1");
	} else if (strncmp (trace, "HTTP/1.0", strlen ("HTTP/1.0")) == 0) {
		http_version = SOUP_HTTP_1_0;
		trace += strlen ("HTTP/1.0");
	} else if (strncmp (trace, "HTTP/2", strlen ("HTTP/2")) == 0) {
		http_version = SOUP_HTTP_2_0;
		trace += strlen ("HTTP/2");
	} else {
		g_warning ("Unrecognised HTTP version ‘%s’.", trace);
	}

	if (*trace != ' ') {
		g_warning ("Unrecognised spacer ‘%c’.", *trace);
		goto error;
	}
	trace++;

	i = strchr (trace, ' ');
	if (i == NULL) {
		g_warning ("Missing spacer ‘ ’.");
		goto error;
	}

	response_status = g_ascii_strtoull (trace, (gchar **) &j, 10);
	if (j != i) {
		g_warning ("Invalid status ‘%s’.", trace);
		goto error;
	}
	trace += (i - trace) + 1;

	i = strchr (trace, '\n');
	if (i == NULL) {
		g_warning ("Missing spacer ‘\n’.");
		goto error;
	}

	response_message = g_strndup (trace, i - trace);
	trace += (i - trace) + 1;

	uhm_message_set_status (message, response_status, response_message);

	g_free (response_message);

	/* Parse the response headers and body. */
	if (trace_to_soup_message_headers_and_body (uhm_message_get_response_headers (message), uhm_message_get_response_body (message), '<', &trace) == FALSE) {
		goto error;
	}

	return message;

error:
	g_clear_object (&message);

	return NULL;
}

static GDataInputStream *
load_file_stream (GFile *trace_file, GCancellable *cancellable, GError **error)
{
	GFileInputStream *base_stream = NULL;  /* owned */
	GDataInputStream *data_stream = NULL;  /* owned */

	base_stream = g_file_read (trace_file, cancellable, error);

	if (base_stream == NULL)
		return NULL;

	data_stream = g_data_input_stream_new (G_INPUT_STREAM (base_stream));
	g_data_input_stream_set_byte_order (data_stream, G_DATA_STREAM_BYTE_ORDER_LITTLE_ENDIAN);
	g_data_input_stream_set_newline_type (data_stream, G_DATA_STREAM_NEWLINE_TYPE_LF);

	g_object_unref (base_stream);

	return data_stream;
}

static gboolean
load_message_half (GDataInputStream *input_stream, GString *current_message, GCancellable *cancellable, GError **error)
{
	gsize len;
	gchar *line = NULL;  /* owned */
	GError *child_error = NULL;

	while (TRUE) {
		line = g_data_input_stream_read_line (input_stream, &len, cancellable, &child_error);

		if (line == NULL && child_error != NULL) {
			/* Error. */
			g_propagate_error (error, child_error);
			return FALSE;
		} else if (line == NULL) {
			/* EOF. Try again to grab a response. */
			return TRUE;
		} else {
			gboolean reached_eom;

			reached_eom = (g_strcmp0 (line, "  ") == 0);

			g_string_append_len (current_message, line, len);
			g_string_append_c (current_message, '\n');

			g_free (line);

			if (reached_eom) {
				/* Reached the end of the message. */
				return TRUE;
			}
		}
	}
}

static UhmMessage *
load_file_iteration (GDataInputStream *input_stream, GUri *base_uri, GCancellable *cancellable, GError **error)
{
	UhmMessage *output_message = NULL;
	GString *current_message = NULL;

	current_message = g_string_new (NULL);

	do {
		/* Start loading from the stream. */
		g_string_truncate (current_message, 0);

		/* We should be at the start of a request; grab it. */
		if (!load_message_half (input_stream, current_message, cancellable, error) ||
		    !load_message_half (input_stream, current_message, cancellable, error)) {
			goto done;
		}

		if (current_message->len > 0) {
			output_message = trace_to_soup_message (current_message->str, base_uri);
		} else {
			/* Reached the end of the file. */
			output_message = NULL;
		}
	} while (output_message != NULL && uhm_message_get_status (output_message) == SOUP_STATUS_NONE);

done:
	/* Tidy up. */
	g_string_free (current_message, TRUE);

	/* Postcondition: (output_message != NULL) => (*error == NULL). */
	g_assert (output_message == NULL || (error == NULL || *error == NULL));

	return output_message;
}

static void
load_file_stream_thread_cb (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable)
{
	GFile *trace_file;
	GDataInputStream *input_stream;
	GError *child_error = NULL;

	trace_file = task_data;
	g_assert (G_IS_FILE (trace_file));

	input_stream = load_file_stream (trace_file, cancellable, &child_error);

	if (child_error != NULL) {
		g_task_return_error (task, child_error);
	} else {
		g_task_return_pointer (task, input_stream, g_object_unref);
	}
}

static void
load_file_iteration_thread_cb (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable)
{
	LoadFileIterationData *data = task_data;
	GDataInputStream *input_stream;
	UhmMessage *output_message;
	GUri *base_uri;
	GError *child_error = NULL;

	input_stream = data->input_stream;
	g_assert (G_IS_DATA_INPUT_STREAM (input_stream));
	base_uri = data->base_uri;

	output_message = load_file_iteration (input_stream, base_uri, cancellable, &child_error);

	if (child_error != NULL) {
		g_task_return_error (task, child_error);
	} else {
		g_task_return_pointer (task, output_message, g_object_unref);
	}
}

/**
 * uhm_server_unload_trace:
 * @self: a #UhmServer
 *
 * Unloads the current trace file of network messages, as loaded by uhm_server_load_trace() or uhm_server_load_trace_async().
 *
 * Since: 0.1.0
 */
void
uhm_server_unload_trace (UhmServer *self)
{
	UhmServerPrivate *priv = self->priv;

	g_return_if_fail (UHM_IS_SERVER (self));

	g_clear_object (&priv->next_message);
	g_clear_object (&priv->input_stream);
	g_clear_object (&priv->trace_file);
	g_clear_pointer (&priv->comparison_message, g_byte_array_unref);
	priv->message_counter = 0;
	priv->received_message_state = UNKNOWN;
}

/**
 * uhm_server_load_trace:
 * @self: a #UhmServer
 * @trace_file: trace file to load
 * @cancellable: (allow-none): a #GCancellable, or %NULL
 * @error: (allow-none): return location for a #GError, or %NULL
 *
 * Synchronously loads the given @trace_file of network messages, ready to simulate a network conversation by matching
 * requests against the file and returning the associated responses. Call uhm_server_run() to start the mock
 * server afterwards.
 *
 * Loading the trace file may be cancelled from another thread using @cancellable.
 *
 * On error, @error will be set and the state of the #UhmServer will not change. A #GIOError will be set if there is
 * a problem reading the trace file.
 *
 * Since: 0.1.0
 */
void
uhm_server_load_trace (UhmServer *self, GFile *trace_file, GCancellable *cancellable, GError **error)
{
	UhmServerPrivate *priv = self->priv;
	g_autoptr(GUri) base_uri = NULL;
	g_autoptr(GError) local_error = NULL;
	g_autofree char *content = NULL;
	g_autofree char *trace_path = NULL;
	g_autofree char *trace_hosts = NULL;
	g_auto(GStrv) split = NULL;
	gsize len;

	g_return_if_fail (UHM_IS_SERVER (self));
	g_return_if_fail (G_IS_FILE (trace_file));
	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
	g_return_if_fail (error == NULL || *error == NULL);
	g_return_if_fail (priv->trace_file == NULL && priv->input_stream == NULL && priv->next_message == NULL);

	base_uri = build_base_uri (self);

	/* Trace File */
	priv->trace_file = g_object_ref (trace_file);
	priv->input_stream = load_file_stream (priv->trace_file, cancellable, error);

	if (priv->input_stream != NULL) {
		GError *child_error = NULL;

		priv->next_message = load_file_iteration (priv->input_stream, base_uri, cancellable, &child_error);
		priv->message_counter = 0;
		priv->comparison_message = g_byte_array_new ();
		priv->received_message_state = UNKNOWN;

		if (child_error != NULL) {
			g_clear_object (&priv->trace_file);
			g_propagate_error (error, child_error);
			return;
		}
	} else {
		/* Error. */
		g_clear_object (&priv->trace_file);
		return;
	}

	/* Host file */
	trace_path = g_file_get_path (trace_file);
	trace_hosts = g_strconcat (trace_path, ".hosts", NULL);
	priv->hosts_trace_file = g_file_new_for_path (trace_hosts);

	if (g_file_load_contents (priv->hosts_trace_file, cancellable, &content, &len, NULL, &local_error)) {
		split = g_strsplit (content, "\n", -1);
		for (gsize i = 0; split != NULL && split[i] != NULL; i++) {
			if (*(split[i]) != '\0') {
				uhm_resolver_add_A (priv->resolver, split[i], uhm_server_get_address (self));
			}
		}
	} else if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) {
		/* It's not fatal that this file cannot be loaded as these hosts can be added in code */
		g_clear_error (&local_error);

	} else {
		/* Other I/O errors are fatal. */
		g_propagate_error (error, g_steal_pointer (&local_error));
		return;
	}
}

typedef struct {
	GAsyncReadyCallback callback;
	gpointer user_data;
	GUri *base_uri;
} LoadTraceData;

static void
load_trace_async_cb (GObject *source_object, GAsyncResult *result, gpointer user_data)
{
	UhmServer *self = UHM_SERVER (source_object);
	LoadTraceData *data = user_data;
	LoadFileIterationData *iteration_data;
	GTask *task;
	GError *child_error = NULL;

	g_return_if_fail (UHM_IS_SERVER (self));
	g_return_if_fail (G_IS_ASYNC_RESULT (result));
	g_return_if_fail (g_task_is_valid (result, self));

	self->priv->input_stream = g_task_propagate_pointer (G_TASK (result), &child_error);

	iteration_data = g_slice_new (LoadFileIterationData);
	iteration_data->input_stream = g_object_ref (self->priv->input_stream);
	iteration_data->base_uri = data->base_uri; /* transfer ownership */
	data->base_uri = NULL;

	task = g_task_new (g_task_get_source_object (G_TASK (result)), g_task_get_cancellable (G_TASK (result)), data->callback, data->user_data);
	g_task_set_task_data (task, iteration_data, (GDestroyNotify) load_file_iteration_data_free);

	if (child_error != NULL) {
		g_task_return_error (task, child_error);
	} else {
		g_task_run_in_thread (task, load_file_iteration_thread_cb);
	}

	g_object_unref (task);

	g_slice_free (LoadTraceData, data);
}

/**
 * uhm_server_load_trace_async:
 * @self: a #UhmServer
 * @trace_file: trace file to load
 * @cancellable: (allow-none): a #GCancellable, or %NULL
 * @callback: function to call once the async operation is complete
 * @user_data: (allow-none): user data to pass to @callback, or %NULL
 *
 * Asynchronous version of uhm_server_load_trace(). In @callback, call uhm_server_load_trace_finish() to complete the operation.
 *
 * Since: 0.1.0
 */
void
uhm_server_load_trace_async (UhmServer *self, GFile *trace_file, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data)
{
	GTask *task;
	LoadTraceData *data;

	g_return_if_fail (UHM_IS_SERVER (self));
	g_return_if_fail (G_IS_FILE (trace_file));
	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
	g_return_if_fail (self->priv->trace_file == NULL && self->priv->input_stream == NULL && self->priv->next_message == NULL);

	self->priv->trace_file = g_object_ref (trace_file);

	data = g_slice_new (LoadTraceData);
	data->callback = callback;
	data->user_data = user_data;
	data->base_uri = build_base_uri (self);

	task = g_task_new (self, cancellable, load_trace_async_cb, data);
	g_task_set_task_data (task, g_object_ref (self->priv->trace_file), g_object_unref);
	g_task_run_in_thread (task, load_file_stream_thread_cb);
	g_object_unref (task);
}

/**
 * uhm_server_load_trace_finish:
 * @self: a #UhmServer
 * @result: asynchronous operation result passed to the callback
 * @error: (allow-none): return location for a #GError, or %NULL
 *
 * Finishes an asynchronous operation started by uhm_server_load_trace_async().
 *
 * On error, @error will be set and the state of the #UhmServer will not change.
 * See uhm_server_load_trace() for details on the error domains used.
 *
 * Since: 0.1.0
 */
void
uhm_server_load_trace_finish (UhmServer *self, GAsyncResult *result, GError **error)
{
	g_return_if_fail (UHM_IS_SERVER (self));
	g_return_if_fail (G_IS_ASYNC_RESULT (result));
	g_return_if_fail (error == NULL || *error == NULL);
	g_return_if_fail (g_task_is_valid (result, self));

	self->priv->next_message = g_task_propagate_pointer (G_TASK (result), error);
	self->priv->message_counter = 0;
	self->priv->comparison_message = g_byte_array_new ();
	self->priv->received_message_state = UNKNOWN;
}

/* Must only be called in the server thread. */
static gboolean
server_thread_quit_cb (gpointer user_data)
{
	UhmServer *self = user_data;
	UhmServerPrivate *priv = self->priv;

	g_main_loop_quit (priv->server_main_loop);

	return G_SOURCE_REMOVE;
}

static gpointer
server_thread_cb (gpointer user_data)
{
	UhmServer *self = user_data;
	UhmServerPrivate *priv = self->priv;

	g_main_context_push_thread_default (priv->server_context);

	/* Run the server. This will create a main loop and iterate the server_context until soup_server_quit() is called. */
	g_main_loop_run (priv->server_main_loop);

	g_main_context_pop_thread_default (priv->server_context);

	return NULL;
}

/**
 * uhm_server_run:
 * @self: a #UhmServer
 *
 * Runs the mock server, binding to a loopback TCP/IP interface and preparing a HTTPS server which is ready to accept requests.
 * The TCP/IP address and port number are chosen randomly out of the loopback addresses, and are exposed as #UhmServer:address and #UhmServer:port
 * once this function has returned. A #UhmResolver (exposed as #UhmServer:resolver) is set as the default #GResolver while the server is running.
 *
 * The server is started in a worker thread, so this function returns immediately and the server continues to run in the background. Use uhm_server_stop()
 * to shut it down.
 *
 * This function always succeeds.
 *
 * Since: 0.1.0
 */
void
uhm_server_run (UhmServer *self)
{
	UhmServerPrivate *priv = self->priv;
	g_autoptr(GError) error = NULL;
	GSList *sockets;  /* owned */
	GSocket *socket;

	g_return_if_fail (UHM_IS_SERVER (self));
	g_return_if_fail (priv->resolver == NULL);
	g_return_if_fail (priv->server == NULL);

	/* Set up the server. If (priv->tls_certificate != NULL) it will be a HTTPS server;
	 * otherwise it will be a HTTP server. */
	priv->server_context = g_main_context_new ();
	priv->server = soup_server_new ("tls-certificate", priv->tls_certificate,
	                                "raw-paths", TRUE,
	                                NULL);
	soup_server_add_handler (priv->server, "/", server_handler_cb, self, NULL);

	g_main_context_push_thread_default (priv->server_context);

	/* Try listening on either IPv4 or IPv6. If that fails, try on IPv4 only
	 * as listening on IPv6 while inside a Docker container (as happens in
	 * CI) can fail if the container isn’t bridged properly. */
	priv->server_main_loop = g_main_loop_new (priv->server_context, FALSE);
	if (!soup_server_listen_local (priv->server, 0, (priv->tls_certificate != NULL) ? SOUP_SERVER_LISTEN_HTTPS : 0, NULL))
		soup_server_listen_local (priv->server, 0, SOUP_SERVER_LISTEN_IPV4_ONLY | ((priv->tls_certificate != NULL) ? SOUP_SERVER_LISTEN_HTTPS : 0), &error);
	g_assert_no_error (error);  /* binding to localhost should never really fail */

	g_main_context_pop_thread_default (priv->server_context);

	/* Grab the randomly selected address and port. */
	sockets = soup_server_get_listeners (priv->server);
	g_assert (sockets != NULL);

	socket = sockets->data;
	priv->address = g_socket_get_local_address (socket, &error);
	g_assert_no_error (error);
	priv->port = g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (priv->address));

	g_slist_free (sockets);

	/* Set up the resolver. It is expected that callers will grab the resolver (by calling uhm_server_get_resolver())
	 * immediately after this function returns, and add some expected hostnames by calling uhm_resolver_add_A() one or
	 * more times, before starting the next test.Or they could call uhm_server_set_expected_domain_names() any time. */
	priv->resolver = uhm_resolver_new ();
	g_resolver_set_default (G_RESOLVER (priv->resolver));

	/* Note: This must be called before notify::resolver, so the user can add extra domain names in that callback if desired. */
	apply_expected_domain_names (self);

	g_object_freeze_notify (G_OBJECT (self));
	g_object_notify (G_OBJECT (self), "address");
	g_object_notify (G_OBJECT (self), "port");
	g_object_notify (G_OBJECT (self), "resolver");
	g_object_thaw_notify (G_OBJECT (self));

	/* Start the network thread. */
	priv->server_thread = g_thread_new ("mock-server-thread", server_thread_cb, self);
}

/**
 * uhm_server_stop:
 * @self: a #UhmServer
 *
 * Stops a mock server started by calling uhm_server_run(). This shuts down the server's worker thread and unbinds it from its TCP/IP socket.
 *
 * This unloads any trace file loaded by calling uhm_server_load_trace() (or its asynchronous counterpart). It also resets the set of domain
 * names loaded into the #UhmServer:resolver.
 *
 * This function always succeeds.
 *
 * Since: 0.1.0
 */
void
uhm_server_stop (UhmServer *self)
{
	UhmServerPrivate *priv = self->priv;
	GSource *idle;

	g_return_if_fail (UHM_IS_SERVER (self));
	g_return_if_fail (priv->server != NULL);
	g_return_if_fail (priv->resolver != NULL);

	/* Stop the server. */
	idle = g_idle_source_new ();
	g_source_set_callback (idle, server_thread_quit_cb, self, NULL);
	g_source_attach (idle, priv->server_context);
	g_source_unref (idle);

	g_thread_join (priv->server_thread);
	priv->server_thread = NULL;
	uhm_resolver_reset (priv->resolver);

	g_clear_pointer (&priv->server_main_loop, g_main_loop_unref);
	g_clear_pointer (&priv->server_context, g_main_context_unref);
	g_clear_object (&priv->server);
	g_clear_object (&priv->resolver);

	g_clear_object (&priv->address);
	g_free (priv->address_string);
	priv->address_string = NULL;
	priv->port = 0;

	g_object_freeze_notify (G_OBJECT (self));
	g_object_notify (G_OBJECT (self), "address");
	g_object_notify (G_OBJECT (self), "port");
	g_object_notify (G_OBJECT (self), "resolver");
	g_object_thaw_notify (G_OBJECT (self));

	/* Reset the trace file. */
	uhm_server_unload_trace (self);
}

/**
 * uhm_server_get_trace_directory:
 * @self: a #UhmServer
 *
 * Gets the value of the #UhmServer:trace-directory property.
 *
 * Return value: (allow-none) (transfer none): the directory to load/store trace files from, or %NULL
 *
 * Since: 0.1.0
 */
GFile *
uhm_server_get_trace_directory (UhmServer *self)
{
	g_return_val_if_fail (UHM_IS_SERVER (self), NULL);

	return self->priv->trace_directory;
}

/**
 * uhm_server_set_trace_directory:
 * @self: a #UhmServer
 * @trace_directory: (allow-none) (transfer none): a directory to load/store trace files from, or %NULL to unset it
 *
 * Sets the value of the #UhmServer:trace-directory property.
 *
 * Since: 0.1.0
 */
void
uhm_server_set_trace_directory (UhmServer *self, GFile *trace_directory)
{
	g_return_if_fail (UHM_IS_SERVER (self));
	g_return_if_fail (trace_directory == NULL || G_IS_FILE (trace_directory));

	if (trace_directory != NULL) {
		g_object_ref (trace_directory);
	}

	g_clear_object (&self->priv->trace_directory);
	self->priv->trace_directory = trace_directory;
	g_object_notify (G_OBJECT (self), "trace-directory");
}

/**
 * uhm_server_start_trace:
 * @self: a #UhmServer
 * @trace_name: name of the trace
 * @error: (allow-none): return location for a #GError, or %NULL
 *
 * Starts a mock server which follows the trace file of filename @trace_name in the #UhmServer:trace-directory directory.
 * See uhm_server_start_trace_full() for further documentation.
 *
 * This function has undefined behaviour if #UhmServer:trace-directory is %NULL.
 *
 * On failure, @error will be set and the #UhmServer state will remain unchanged. See uhm_server_start_trace_full() for
 * details of the error domains used.
 *
 * Since: 0.1.0
 */
void
uhm_server_start_trace (UhmServer *self, const gchar *trace_name, GError **error)
{
	GFile *trace_file;

	g_return_if_fail (UHM_IS_SERVER (self));
	g_return_if_fail (trace_name != NULL && *trace_name != '\0');
	g_return_if_fail (error == NULL || *error == NULL);

	g_assert (self->priv->trace_directory != NULL);

	trace_file = g_file_get_child (self->priv->trace_directory, trace_name);
	uhm_server_start_trace_full (self, trace_file, error);
	g_object_unref (trace_file);
}

/**
 * uhm_server_start_trace_full:
 * @self: a #UhmServer
 * @trace_file: a trace file to load
 * @error: (allow-none): return location for a #GError, or %NULL
 *
 * Convenience function to start logging to or reading from the given @trace_file, depending on the values of #UhmServer:enable-logging and
 * #UhmServer:enable-online.
 *
 * If #UhmServer:enable-logging is %TRUE, a log handler will be set up to redirect all client network activity into the given @trace_file.
 * If @trace_file already exists, it will be overwritten.
 *
 * If #UhmServer:enable-online is %FALSE, the given @trace_file is loaded using uhm_server_load_trace() and then a mock server is
 * started using uhm_server_run().
 *
 * On failure, @error will be set and the #UhmServer state will remain unchanged. A #GIOError will be set if logging is enabled
 * (#UhmServer:enable-logging) and there is a problem writing to the trace file; or if a trace needs to be loaded and there is a problem
 * reading from the trace file.
 *
 * Since: 0.1.0
 */
void
uhm_server_start_trace_full (UhmServer *self, GFile *trace_file, GError **error)
{
	UhmServerPrivate *priv = self->priv;
	GError *child_error = NULL;

	g_return_if_fail (UHM_IS_SERVER (self));
	g_return_if_fail (G_IS_FILE (trace_file));
	g_return_if_fail (error == NULL || *error == NULL);

	if (priv->output_stream != NULL) {
		g_warning ("%s: Nested trace files are not supported. Call uhm_server_end_trace() before calling %s again.", G_STRFUNC, G_STRFUNC);
	}
	g_return_if_fail (priv->output_stream == NULL);

	if (priv->enable_online == TRUE) {
		priv->message_counter = 0;
	    priv->comparison_message = g_byte_array_new ();
		priv->received_message_state = UNKNOWN;
	}

	/* Start writing out a trace file if logging is enabled. */
	if (priv->enable_logging == TRUE) {
		GFileOutputStream *output_stream;
		g_autofree char *trace_path = g_file_get_path (trace_file);
		g_autofree char *trace_hosts = g_strconcat (trace_path, ".hosts", NULL);
		priv->hosts_trace_file = g_file_new_for_path (trace_hosts);

		output_stream = g_file_replace (trace_file, NULL, FALSE, G_FILE_CREATE_NONE, NULL, &child_error);

		if (child_error != NULL) {
			g_propagate_prefixed_error (error, g_steal_pointer (&child_error),
			             "Error replacing trace file ‘%s’: ", trace_path);
			return;
		} else {
			/* Change state. */
			priv->output_stream = output_stream;
		}

		/* Host trace file */
		output_stream = g_file_replace (priv->hosts_trace_file, NULL, FALSE, G_FILE_CREATE_NONE, NULL, &child_error);
		if (child_error != NULL) {
			g_autofree char *hosts_trace_file_path = g_file_get_path (priv->hosts_trace_file);
			g_propagate_prefixed_error (error, g_steal_pointer (&child_error),
			             "Error replacing trace hosts file ‘%s’: ", hosts_trace_file_path);
			return;
		} else {
			/* Change state. */
			priv->hosts_output_stream = output_stream;
		}
	}

	/* Start reading from a trace file if online testing is disabled or if we need to compare server responses to the trace file. */
	if (priv->enable_online == FALSE) {
		uhm_server_run (self);
		uhm_server_load_trace (self, trace_file, NULL, &child_error);

		if (child_error != NULL) {
			gchar *trace_file_path = g_file_get_path (trace_file);
			g_set_error (error, child_error->domain, child_error->code,
			             "Error loading trace file ‘%s’: %s", trace_file_path, child_error->message);
			g_free (trace_file_path);

			g_error_free (child_error);

			uhm_server_stop (self);
			g_clear_object (&priv->output_stream);

			return;
		}
	} else if (priv->enable_online == TRUE && priv->enable_logging == FALSE) {
		uhm_server_load_trace (self, trace_file, NULL, &child_error);

		if (child_error != NULL) {
			gchar *trace_file_path = g_file_get_path (trace_file);
			g_set_error (error, child_error->domain, child_error->code,
			             "Error loading trace file ‘%s’: %s", trace_file_path, child_error->message);
			g_free (trace_file_path);

			g_error_free (child_error);

			g_clear_object (&priv->output_stream);

			return;
		}
	}
}

/**
 * uhm_server_end_trace:
 * @self: a #UhmServer
 *
 * Convenience function to finish logging to or reading from a trace file previously passed to uhm_server_start_trace() or
 * uhm_server_start_trace_full().
 *
 * If #UhmServer:enable-online is %FALSE, this will shut down the mock server (as if uhm_server_stop() had been called).
 *
 * Since: 0.1.0
 */
void
uhm_server_end_trace (UhmServer *self)
{
	UhmServerPrivate *priv = self->priv;

	g_return_if_fail (UHM_IS_SERVER (self));

	if (priv->enable_online == FALSE) {
		uhm_server_stop (self);
	} else if (priv->enable_online == TRUE && priv->enable_logging == FALSE) {
		uhm_server_unload_trace (self);
	}

	if (priv->enable_logging == TRUE) {
		g_clear_object (&self->priv->output_stream);
		g_clear_object (&self->priv->hosts_output_stream);
	}
}

/**
 * uhm_server_get_enable_online:
 * @self: a #UhmServer
 *
 * Gets the value of the #UhmServer:enable-online property.
 *
 * Return value: %TRUE if the server does not intercept and handle network connections from client code; %FALSE otherwise
 *
 * Since: 0.1.0
 */
gboolean
uhm_server_get_enable_online (UhmServer *self)
{
	g_return_val_if_fail (UHM_IS_SERVER (self), FALSE);

	return self->priv->enable_online;
}

/**
 * uhm_server_set_enable_online:
 * @self: a #UhmServer
 * @enable_online: %TRUE to not intercept and handle network connections from client code; %FALSE otherwise
 *
 * Sets the value of the #UhmServer:enable-online property.
 *
 * Since: 0.1.0
 */
void
uhm_server_set_enable_online (UhmServer *self, gboolean enable_online)
{
	g_return_if_fail (UHM_IS_SERVER (self));

	self->priv->enable_online = enable_online;
	g_object_notify (G_OBJECT (self), "enable-online");
}

/**
 * uhm_server_get_enable_logging:
 * @self: a #UhmServer
 *
 * Gets the value of the #UhmServer:enable-logging property.
 *
 * Return value: %TRUE if client network traffic is being logged to a trace file; %FALSE otherwise
 *
 * Since: 0.1.0
 */
gboolean
uhm_server_get_enable_logging (UhmServer *self)
{
	g_return_val_if_fail (UHM_IS_SERVER (self), FALSE);

	return self->priv->enable_logging;
}

/**
 * uhm_server_set_enable_logging:
 * @self: a #UhmServer
 * @enable_logging: %TRUE to log client network traffic to a trace file; %FALSE otherwise
 *
 * Sets the value of the #UhmServer:enable-logging property.
 *
 * Since: 0.1.0
 */
void
uhm_server_set_enable_logging (UhmServer *self, gboolean enable_logging)
{
	g_return_if_fail (UHM_IS_SERVER (self));

	self->priv->enable_logging = enable_logging;
	g_object_notify (G_OBJECT (self), "enable-logging");
}

/**
 * uhm_server_received_message_chunk:
 * @self: a #UhmServer
 * @message_chunk: single line of a message which was received
 * @message_chunk_length: length of @message_chunk in bytes
 * @error: (allow-none): return location for a #GError, or %NULL
 *
 * Indicates to the mock server that a single new line of a message was received from the real server. The message line may be
 * appended to the current trace file if logging is enabled (#UhmServer:enable-logging is %TRUE), adding a newline character
 * at the end. If logging is disabled but online mode is enabled (#UhmServer:enable-online is %TRUE), the message line will
 * be compared to the next expected line in the existing trace file. Otherwise, this function is a no-op.
 *
 * On failure, @error will be set and the #UhmServer state will remain unchanged apart from the parse state machine, which will remain
 * in the state reached after parsing @message_chunk. A %G_IO_ERROR will be returned if writing to the trace file failed. If in
 * comparison mode and the received message chunk corresponds to an unexpected message in the trace file, a %UHM_SERVER_ERROR will
 * be returned.
 *
 * <note><para>In common cases where message log data only needs to be passed to a #UhmServer and not (for example) logged to an
 * application-specific file or the command line as  well, it is simpler to use uhm_server_received_message_chunk_from_soup(), passing
 * it directly to soup_logger_set_printer(). See the documentation for uhm_server_received_message_chunk_from_soup() for details.</para></note>
 *
 * Since: 0.1.0
 */
void
uhm_server_received_message_chunk (UhmServer *self, const gchar *message_chunk, goffset message_chunk_length, GError **error)
{
	UhmServerPrivate *priv = self->priv;
	GError *child_error = NULL;
	g_autoptr(UhmMessage) online_message = NULL;
	g_autoptr(GUri) base_uri = NULL;

	g_return_if_fail (UHM_IS_SERVER (self));
	g_return_if_fail (message_chunk != NULL);
	g_return_if_fail (error == NULL || *error == NULL);

	/* Silently ignore the call if logging is disabled and we're offline, or if a trace file hasn't been specified. */
	if ((priv->enable_logging == FALSE && priv->enable_online == FALSE) || (priv->enable_logging == TRUE && priv->output_stream == NULL)) {
		return;
	}

	/* Simple state machine to track where we are in the soup log format. */
	switch (priv->received_message_state) {
		case UNKNOWN:
			if (strncmp (message_chunk, "> ", 2) == 0) {
				priv->received_message_state = REQUEST_DATA;
			}
			break;
		case REQUEST_DATA:
			if (strcmp (message_chunk, "  ") == 0) {
				priv->received_message_state = REQUEST_TERMINATOR;
			} else if (strncmp (message_chunk, "> ", 2) != 0) {
				priv->received_message_state = UNKNOWN;
			}
			break;
		case REQUEST_TERMINATOR:
			if (strncmp (message_chunk, "< ", 2) == 0) {
				priv->received_message_state = RESPONSE_DATA;
			} else {
				priv->received_message_state = UNKNOWN;
			}
			break;
		case RESPONSE_DATA:
			if (strcmp (message_chunk, "  ") == 0) {
				priv->received_message_state = RESPONSE_TERMINATOR;
			} else if (strncmp (message_chunk, "< ", 2) != 0) {
				priv->received_message_state = UNKNOWN;
			}
			break;
		case RESPONSE_TERMINATOR:
			if (strncmp (message_chunk, "> ", 2) == 0) {
				priv->received_message_state = REQUEST_DATA;
			} else {
				priv->received_message_state = UNKNOWN;
			}
			break;
		default:
			g_assert_not_reached ();
	}

	/* Silently ignore responses outputted by libsoup before the requests. This can happen when a SoupMessage is cancelled part-way through
	 * sending the request; in which case libsoup logs only a response of the form:
	 *     < HTTP/1.1 1 Cancelled
	 *     < Soup-Debug-Timestamp: 1375190963
	 *     < Soup-Debug: SoupMessage 0 (0x7fffe00261c0)
	 */
	if (priv->received_message_state == UNKNOWN) {
		return;
	}

	/* Append to the trace file. */
	if (priv->enable_logging == TRUE &&
	    (!g_output_stream_write_all (G_OUTPUT_STREAM (priv->output_stream), message_chunk, message_chunk_length, NULL, NULL, &child_error) ||
	     !g_output_stream_write_all (G_OUTPUT_STREAM (priv->output_stream), "\n", 1, NULL, NULL, &child_error))) {
		gchar *trace_file_path = g_file_get_path (priv->trace_file);
		g_set_error (error, child_error->domain, child_error->code,
		             "Error appending to log file ‘%s’: %s", trace_file_path, child_error->message);
		g_free (trace_file_path);

		g_error_free (child_error);

		return;
	}

	/* Update comparison message */
	if (priv->enable_online == TRUE) {
		/* Build up the message to compare. We explicitly don't escape nul bytes, because we want the trace
		 * files to be (pretty much) ASCII. File uploads are handled by zero-extending the responses according
		 * to the traced Content-Length. */
		g_byte_array_append (priv->comparison_message, (const guint8 *) message_chunk, message_chunk_length);
		g_byte_array_append (priv->comparison_message, (const guint8 *) "\n", 1);

		if (priv->received_message_state == RESPONSE_TERMINATOR) {
			/* End of a message. */
			base_uri = build_base_uri (self);
			online_message = trace_to_soup_message ((const gchar *) priv->comparison_message->data, base_uri);

			g_byte_array_set_size (priv->comparison_message, 0);
			priv->received_message_state = UNKNOWN;
		}
	}

	/* Append to the hosts file */
	if (online_message != NULL && priv->enable_online == TRUE && priv->enable_logging == TRUE) {
		const char *host = soup_message_headers_get_one (uhm_message_get_request_headers (online_message), "Soup-Host");

		if (!g_output_stream_write_all (G_OUTPUT_STREAM (priv->hosts_output_stream), host, strlen (host), NULL, NULL, &child_error)  ||
		    !g_output_stream_write_all (G_OUTPUT_STREAM (priv->hosts_output_stream), "\n", 1, NULL, NULL, &child_error)) {
			g_autofree gchar *hosts_trace_file_path = g_file_get_path (priv->hosts_trace_file);
			g_warning ("Error appending to host log file ‘%s’: %s", hosts_trace_file_path, child_error->message);
		}

		if (host != NULL)
			g_hash_table_add (priv->hosts, g_strdup (host));
	}

	/* Or compare to the existing trace file. */
	if (online_message != NULL && priv->enable_logging == FALSE && priv->enable_online == TRUE) {
		/* Received the last chunk of the response, so compare the message from the trace file and that from online. */
		g_assert (priv->next_message != NULL);

		/* Compare the message from the server with the message in the log file. */
		if (compare_incoming_message (self, online_message, priv->next_message) != 0) {
			gchar *next_uri, *actual_uri;

			next_uri = uri_get_path_query (uhm_message_get_uri (priv->next_message));
			actual_uri = uri_get_path_query (uhm_message_get_uri (online_message));
			g_set_error (error, UHM_SERVER_ERROR, UHM_SERVER_ERROR_MESSAGE_MISMATCH,
			             "Expected URI ‘%s’, but got ‘%s’.", next_uri, actual_uri);
			g_free (actual_uri);
			g_free (next_uri);

			g_object_unref (online_message);
			return;
		}
	}
}

/**
 * uhm_server_received_message_chunk_with_direction:
 * @self: a #UhmServer
 * @direction: single character indicating the direction of message transmission
 * @data: single line of a message which was received
 * @data_length: length of @data in bytes
 * @error: (allow-none): return location for a #GError, or %NULL
 *
 * Convenience version of uhm_server_received_message_chunk() which takes the
 * message @direction and @data separately, as provided by libsoup in a
 * #SoupLoggerPrinter callback.
 *
 * <informalexample><programlisting>
 * UhmServer *mock_server;
 * SoupSession *session;
 * SoupLogger *logger;
 *
 * static void
 * soup_log_printer (SoupLogger *logger, SoupLoggerLogLevel level, char direction, const char *data, gpointer user_data)
 * {
 * 	/<!-- -->* Pass the data to libuhttpmock. *<!-- -->/
 *	UhmServer *mock_server = UHM_SERVER (user_data);
 * 	uhm_server_received_message_chunk_with_direction (mock_server, direction, data, strlen (data), NULL);
 * }
 *
 * mock_server = uhm_server_new ();
 * session = soup_session_new ();
 *
 * logger = soup_logger_new (SOUP_LOGGER_LOG_BODY, -1);
 * soup_logger_set_printer (logger, (SoupLoggerPrinter) soup_log_printer, g_object_ref (mock_server), g_object_unref);
 * soup_session_add_feature (session, SOUP_SESSION_FEATURE (logger));
 * g_object_unref (logger);
 *
 * /<!-- -->* Do something with mock_server here. *<!-- -->/
 * </programlisting></informalexample>
 *
 * Since: 0.3.0
 */
void
uhm_server_received_message_chunk_with_direction (UhmServer *self, char direction, const gchar *data, goffset data_length, GError **error)
{
	gchar *message_chunk;

	g_return_if_fail (UHM_IS_SERVER (self));
	g_return_if_fail (direction == '<' || direction == '>' || direction == ' ');
	g_return_if_fail (data != NULL);
	g_return_if_fail (data_length >= -1);
	g_return_if_fail (error == NULL || *error == NULL);

	/* This is inefficient and not nul-safe, but it’ll do for now. */
	message_chunk = g_strdup_printf ("%c %s", direction, data);
	uhm_server_received_message_chunk (self, message_chunk, (data_length > -1) ? data_length + 2 : -1, error);
	g_free (message_chunk);
}

/**
 * uhm_server_received_message_chunk_from_soup:
 * @logger: a #SoupLogger
 * @level: the detail level of this log message
 * @direction: the transmission direction of the message
 * @data: message data
 * @user_data: (allow-none): user data passed to the #SoupLogger, or %NULL
 *
 * Convenience version of uhm_server_received_message_chunk() which can be passed directly to soup_logger_set_printer()
 * to forward all libsoup traffic logging to a #UhmServer. The #UhmServer must be passed to soup_logger_set_printer() as
 * its user data.
 *
 * <informalexample><programlisting>
 * UhmServer *mock_server;
 * SoupSession *session;
 * SoupLogger *logger;
 *
 * mock_server = uhm_server_new ();
 * session = soup_session_new ();
 *
 * logger = soup_logger_new (SOUP_LOGGER_LOG_BODY, -1);
 * soup_logger_set_printer (logger, uhm_server_received_message_chunk_from_soup, g_object_ref (mock_server), g_object_unref);
 * soup_session_add_feature (session, SOUP_SESSION_FEATURE (logger));
 * g_object_unref (logger);
 *
 * /<!-- -->* Do something with mock_server here. *<!-- -->/
 * </programlisting></informalexample>
 *
 * Since: 0.3.0
 */
void
uhm_server_received_message_chunk_from_soup (SoupLogger *logger, SoupLoggerLogLevel level, char direction, const char *data, gpointer user_data)
{
	/* Deliberately don’t do strict validation of parameters here, since we can’t be entirely sure what libsoup throws our way. */
	UhmServer *mock_server = UHM_SERVER (user_data);
	uhm_server_received_message_chunk_with_direction (mock_server, direction, data, strlen (data), NULL);
}

/**
 * uhm_server_get_address:
 * @self: a #UhmServer
 *
 * Gets the value of the #UhmServer:address property.
 *
 * Return value: (allow-none) (transfer none): the physical address of the listening socket the server is currently bound to; or %NULL if the server is not running
 *
 * Since: 0.1.0
 */
const gchar *
uhm_server_get_address (UhmServer *self)
{
	GInetAddress *addr;

	g_return_val_if_fail (UHM_IS_SERVER (self), NULL);

	if (self->priv->address == NULL) {
		return NULL;
	}

	g_free (self->priv->address_string);
	addr = g_inet_socket_address_get_address (G_INET_SOCKET_ADDRESS (self->priv->address));
	self->priv->address_string = g_inet_address_to_string (addr);
	return self->priv->address_string;
}

/**
 * uhm_server_get_port:
 * @self: a #UhmServer
 *
 * Gets the value of the #UhmServer:port property.
 *
 * Return value: the port of the listening socket the server is currently bound to; or <code class="literal">0</code> if the server is not running
 *
 * Since: 0.1.0
 */
guint
uhm_server_get_port (UhmServer *self)
{
	g_return_val_if_fail (UHM_IS_SERVER (self), 0);

	return self->priv->port;
}

/**
 * uhm_server_get_resolver:
 * @self: a #UhmServer
 *
 * Gets the value of the #UhmServer:resolver property.
 *
 * Return value: (allow-none) (transfer none): the mock resolver in use by the mock server, or %NULL if no resolver is active
 *
 * Since: 0.1.0
 */
UhmResolver *
uhm_server_get_resolver (UhmServer *self)
{
	g_return_val_if_fail (UHM_IS_SERVER (self), NULL);

	return self->priv->resolver;
}

/**
 * uhm_server_get_tls_certificate:
 * @self: a #UhmServer
 *
 * Gets the value of the #UhmServer:tls-certificate property.
 *
 * Return value: (transfer none) (allow-none): the server's current TLS certificate; or %NULL if it's serving HTTP only
 *
 * Since: 0.1.0
 */
GTlsCertificate *
uhm_server_get_tls_certificate (UhmServer *self)
{
	g_return_val_if_fail (UHM_IS_SERVER (self), NULL);

	return self->priv->tls_certificate;
}

/**
 * uhm_server_set_tls_certificate:
 * @self: a #UhmServer
 * @tls_certificate: (allow-none): TLS certificate for the server to use; or %NULL to serve HTTP only
 *
 * Sets the value of the #UhmServer:tls-certificate property.
 *
 * Since: 0.1.0
 */
void
uhm_server_set_tls_certificate (UhmServer *self, GTlsCertificate *tls_certificate)
{
	UhmServerPrivate *priv;

	g_return_if_fail (UHM_IS_SERVER (self));
	g_return_if_fail (tls_certificate == NULL || G_IS_TLS_CERTIFICATE (tls_certificate));

	priv = self->priv;

	if (tls_certificate != NULL) {
		g_object_ref (tls_certificate);
	}

	g_clear_object (&priv->tls_certificate);
	priv->tls_certificate = tls_certificate;
	g_object_notify (G_OBJECT (self), "tls-certificate");
}

/**
 * uhm_server_set_default_tls_certificate:
 * @self: a #UhmServer
 *
 * Sets the value of the #UhmServer:tls-certificate property to the default TLS certificate built in to libuhttpmock.
 * This default certificate is not signed by any certificate authority, and contains minimal metadata details. It may
 * be used by clients which have no special certificate requirements; clients which have special requirements should
 * construct a custom #GTlsCertificate and pass it to uhm_server_set_tls_certificate().
 *
 * Return value: (transfer none): the default certificate set as #UhmServer:tls-certificate
 *
 * Since: 0.1.0
 */
GTlsCertificate *
uhm_server_set_default_tls_certificate (UhmServer *self)
{
	GTlsCertificate *cert;
	GError *child_error = NULL;

	g_return_val_if_fail (UHM_IS_SERVER (self), NULL);

	/* Build the certificate. */
	cert = g_tls_certificate_new_from_pem (uhm_default_tls_certificate, -1, &child_error);
	g_assert_no_error (child_error);

	/* Set it as the property. */
	uhm_server_set_tls_certificate (self, cert);
	g_object_unref (cert);

	return cert;
}

static void
apply_expected_domain_names (UhmServer *self)
{
	UhmServerPrivate *priv = self->priv;
	const gchar *ip_address;
	guint i;

	if (priv->resolver == NULL) {
		return;
	}

	uhm_resolver_reset (priv->resolver);

	if (priv->expected_domain_names == NULL) {
		return;
	}

	ip_address = uhm_server_get_address (self);
	g_assert (ip_address != NULL);

	for (i = 0; priv->expected_domain_names[i] != NULL; i++) {
		uhm_resolver_add_A (priv->resolver, priv->expected_domain_names[i], ip_address);
	}
}

/**
 * uhm_server_set_expected_domain_names:
 * @self: a #UhmServer
 * @domain_names: (array zero-terminated=1) (allow-none) (element-type utf8): %NULL-terminated array of domain names to expect, or %NULL to not expect any
 *
 * Set the domain names which are expected to have requests made of them by the client code interacting with this #UhmServer.
 * This is a convenience method which calls uhm_resolver_add_A() on the server’s #UhmResolver for each of the domain names
 * listed in @domain_names. It associates them with the server’s current IP address, and automatically updates the mappings
 * if the IP address or resolver change.
 *
 * Note that this will reset all records on the server’s #UhmResolver, replacing all of them with the provided @domain_names.
 *
 * It is safe to add further domain names to the #UhmResolver in a callback for the #GObject::notify signal for #UhmServer:resolver;
 * that signal is emitted after the resolver is cleared and these @domain_names are added.
 *
 * Since: 0.3.0
 */
void
uhm_server_set_expected_domain_names (UhmServer *self, const gchar * const *domain_names)
{
	gchar **new_domain_names;

	g_return_if_fail (UHM_IS_SERVER (self));

	new_domain_names = g_strdupv ((gchar **) domain_names);  /* may be NULL */
	g_strfreev (self->priv->expected_domain_names);
	self->priv->expected_domain_names = new_domain_names;

	apply_expected_domain_names (self);
}

static gboolean
compare_messages_ignore_parameter_values_cb (UhmServer *server,
                                             UhmMessage *expected_message,
                                             UhmMessage *actual_message,
                                             gpointer user_data)
{
	GUri *expected_uri, *actual_uri;
	const gchar * const *ignore_query_param_values = user_data;
	gboolean retval;
	GHashTable/*<string, string>*/ *expected_params = NULL;  /* owned */
	GHashTable/*<string, string>*/ *actual_params = NULL;  /* owned */
	GHashTableIter iter;
	const gchar *key, *expected_value;
	guint i;

	/* Compare method. */
	if (g_strcmp0 (uhm_message_get_method (expected_message), uhm_message_get_method (actual_message)) != 0) {
		return FALSE;
	}

	/* Compare URIs, excluding query parameters. */
	expected_uri = uhm_message_get_uri (expected_message);
	actual_uri = uhm_message_get_uri (actual_message);

	if (!parts_equal (g_uri_get_user (expected_uri), g_uri_get_user (actual_uri), FALSE) ||
	    !parts_equal (g_uri_get_password (expected_uri), g_uri_get_password (actual_uri), FALSE) ||
	    !parts_equal (g_uri_get_path (expected_uri), g_uri_get_path (actual_uri), FALSE) ||
	    !parts_equal (g_uri_get_fragment (expected_uri), g_uri_get_fragment (actual_uri), FALSE)) {
		return FALSE;
	}

	/* Compare query parameters, excluding the ignored ones. Note that we
	 * expect the ignored parameters to exist, but their values may
	 * differ. */
	expected_params = soup_form_decode (g_uri_get_query (expected_uri));
	actual_params = soup_form_decode (g_uri_get_query (actual_uri));

	/* Check the presence of ignored parameters. */
	for (i = 0; ignore_query_param_values[i] != NULL; i++) {
		const gchar *name = ignore_query_param_values[i];

		if (g_hash_table_contains (expected_params, name) &&
		    !g_hash_table_contains (expected_params, name)) {
			retval = FALSE;
			goto done;
		}

		/* Remove them to simplify the comparison below. */
		g_hash_table_remove (expected_params, name);
		g_hash_table_remove (actual_params, name);
	}

	if (g_hash_table_size (actual_params) !=
	    g_hash_table_size (expected_params)) {
		retval = FALSE;
		goto done;
	}

	g_hash_table_iter_init (&iter, expected_params);

	while (g_hash_table_iter_next (&iter, (gpointer) &key,
	                               (gpointer) &expected_value)) {
		if (g_strcmp0 (expected_value,
		               g_hash_table_lookup (actual_params, key)) != 0) {
			retval = FALSE;
			goto done;
		}
	}

	retval = TRUE;

done:
	g_hash_table_unref (actual_params);
	g_hash_table_unref (expected_params);

	return retval;
}

static void
parameter_names_closure_notify (gpointer  data,
                                GClosure *closure)
{
	gchar **parameter_names = data;
	g_strfreev (parameter_names);
}

/**
 * uhm_server_filter_ignore_parameter_values:
 * @self: a #UhmServer
 * @parameter_names: (array zero-terminated=1): %NULL-terminated array of
 *    parameter names to ignore
 *
 * Install a #UhmServer:compare-messages filter function which will override the
 * default comparison function to one which ignores differences in the values of
 * the given query @parameter_names. The named parameters must still be present
 * in the query, however.
 *
 * The filter will remain in place for the lifetime of the #UhmServer, until
 * @uhm_server_compare_messages_remove_filter() is called with the returned
 * filter ID.
 *
 * Note that currently only one of the installed comparison functions will be
 * used. This may change in future.
 *
 * Returns: opaque filter ID used with
 *    uhm_server_compare_messages_remove_filter() to remove the filter later
 * Since: 0.5.0
 */
gulong
uhm_server_filter_ignore_parameter_values (UhmServer *self,
                                           const gchar * const *parameter_names)
{
	g_return_val_if_fail (UHM_IS_SERVER (self), 0);
	g_return_val_if_fail (parameter_names != NULL, 0);

	/* FIXME: What are the semantics of multiple installed compare-messages
	 * callbacks? Should they be aggregate-true? */
	return g_signal_connect_data (self, "compare-messages",
	                              (GCallback) compare_messages_ignore_parameter_values_cb,
	                              g_strdupv ((gchar **) parameter_names),
	                              parameter_names_closure_notify,
	                              0  /* connect flags */);
}

/**
 * uhm_server_compare_messages_remove_filter:
 * @self: a #UhmServer
 * @filter_id: filter ID returned by the filter addition function
 *
 * Remove a #UhmServer:compare-messages filter function installed previously by
 * calling something like uhm_server_filter_ignore_parameter_values().
 *
 * It is an error to call this function with an invalid @filter_id.
 *
 * Since: 0.5.0
 */
void
uhm_server_compare_messages_remove_filter (UhmServer *self,
                                           gulong filter_id)
{
	g_return_if_fail (UHM_IS_SERVER (self));
	g_return_if_fail (filter_id != 0);

	g_signal_handler_disconnect (self, filter_id);
}
07070100000020000081A400000000000000000000000166717A5A00001655000000000000000000000000000000000000002B00000000uhttpmock-0.11.0/libuhttpmock/uhm-server.h/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/*
 * uhttpmock
 * Copyright (C) Philip Withnall 2013 <philip@tecnocode.co.uk>
 *
 * uhttpmock 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.
 *
 * uhttpmock 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 uhttpmock.  If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef UHM_SERVER_H
#define UHM_SERVER_H

#include <glib.h>
#include <glib-object.h>
#include <gio/gio.h>
#include <libsoup/soup.h>

#include "uhm-resolver.h"
#include "uhm-message.h"

G_BEGIN_DECLS

/**
 * UhmServerError:
 * @UHM_SERVER_ERROR_MESSAGE_MISMATCH: In comparison mode, a message received from the client did not match the next message in the current trace file.
 *
 * Error codes for #UhmServer operations.
 **/
typedef enum {
	UHM_SERVER_ERROR_MESSAGE_MISMATCH = 1,
} UhmServerError;

#define UHM_SERVER_ERROR		uhm_server_error_quark ()

GQuark uhm_server_error_quark (void) G_GNUC_CONST;

#define UHM_TYPE_SERVER			(uhm_server_get_type ())
#define UHM_SERVER(o)			(G_TYPE_CHECK_INSTANCE_CAST ((o), UHM_TYPE_SERVER, UhmServer))
#define UHM_SERVER_CLASS(k)		(G_TYPE_CHECK_CLASS_CAST((k), UHM_TYPE_SERVER, UhmServerClass))
#define UHM_IS_SERVER(o)		(G_TYPE_CHECK_INSTANCE_TYPE ((o), UHM_TYPE_SERVER))
#define UHM_IS_SERVER_CLASS(k)		(G_TYPE_CHECK_CLASS_TYPE ((k), UHM_TYPE_SERVER))
#define UHM_SERVER_GET_CLASS(o)		(G_TYPE_INSTANCE_GET_CLASS ((o), UHM_TYPE_SERVER, UhmServerClass))

typedef struct _UhmServerPrivate	UhmServerPrivate;

/**
 * UhmServer:
 *
 * All the fields in the #UhmServer structure are private and should never be accessed directly.
 *
 * Since: 0.1.0
 */
typedef struct {
	/*< private >*/
	GObject parent;
	UhmServerPrivate *priv;
} UhmServer;

/**
 * UhmServerClass:
 * @handle_message: Class handler for the #UhmServer::handle-message signal. Subclasses may implement this to override the
 * default handler for the signal. The default handler should always return %TRUE to indicate that it has handled
 * the @message from @client by setting an appropriate response on the #SoupServerMessage.
 * @compare_messages: Class handler for the #UhmServer::compare-messages signal. Subclasses may implement this to override
 * the default handler for the signal. The handler should return %TRUE if @expected_message and @actual_message compare
 * equal, and %FALSE otherwise.
 *
 * Most of the fields in the #UhmServerClass structure are private and should never be accessed directly.
 *
 * Since: 0.1.0
 */
typedef struct {
	/*< private >*/
	GObjectClass parent;

	/*< public >*/
	gboolean (*handle_message) (UhmServer *self, UhmMessage *message);
	gboolean (*compare_messages) (UhmServer *self, UhmMessage *expected_message, UhmMessage *actual_message);
} UhmServerClass;

GType uhm_server_get_type (void) G_GNUC_CONST;

UhmServer *uhm_server_new (void) G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT;

void uhm_server_load_trace (UhmServer *self, GFile *trace_file, GCancellable *cancellable, GError **error);
void uhm_server_load_trace_async (UhmServer *self, GFile *trace_file, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data);
void uhm_server_load_trace_finish (UhmServer *self, GAsyncResult *result, GError **error);
void uhm_server_unload_trace (UhmServer *self);

void uhm_server_run (UhmServer *self);
void uhm_server_stop (UhmServer *self);

GFile *uhm_server_get_trace_directory (UhmServer *self);
void uhm_server_set_trace_directory (UhmServer *self, GFile *trace_directory);

void uhm_server_start_trace (UhmServer *self, const gchar *trace_name, GError **error);
void uhm_server_start_trace_full (UhmServer *self, GFile *trace_file, GError **error);
void uhm_server_end_trace (UhmServer *self);

gboolean uhm_server_get_enable_online (UhmServer *self);
void uhm_server_set_enable_online (UhmServer *self, gboolean enable_online);

gboolean uhm_server_get_enable_logging (UhmServer *self);
void uhm_server_set_enable_logging (UhmServer *self, gboolean enable_logging);

void uhm_server_received_message_chunk (UhmServer *self, const gchar *message_chunk, goffset message_chunk_length, GError **error);
void uhm_server_received_message_chunk_with_direction (UhmServer *self, char direction, const gchar *data, goffset data_length, GError **error);
void uhm_server_received_message_chunk_from_soup (SoupLogger *logger, SoupLoggerLogLevel level, char direction, const char *data, gpointer user_data);

const gchar *uhm_server_get_address (UhmServer *self);
guint uhm_server_get_port (UhmServer *self);

UhmResolver *uhm_server_get_resolver (UhmServer *self);

GTlsCertificate *uhm_server_get_tls_certificate (UhmServer *self);
void uhm_server_set_tls_certificate (UhmServer *self, GTlsCertificate *tls_certificate);

GTlsCertificate *uhm_server_set_default_tls_certificate (UhmServer *self) G_GNUC_MALLOC;

void uhm_server_set_expected_domain_names (UhmServer *self, const gchar * const *domain_names);

gulong uhm_server_filter_ignore_parameter_values (UhmServer *self,
                                                  const gchar * const *parameter_names);
void uhm_server_compare_messages_remove_filter (UhmServer *self,
                                                gulong filter_id);

G_END_DECLS

#endif /* !UHM_SERVER_H */
07070100000021000081A400000000000000000000000166717A5A0000098D000000000000000000000000000000000000002F00000000uhttpmock-0.11.0/libuhttpmock/uhm-version.h.in/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/*
 * uhttpmock
 * Copyright (C) Holger Berndt 2011 <hb@gnome.org>
 * Copyright (C) Philip Withnall 2013 <philip@tecnocode.co.uk>
 *
 * uhttpmock 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.
 *
 * uhttpmock 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 uhttpmock.  If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef UHM_VERSION_H
#define UHM_VERSION_H

/**
 * SECTION:uhm-version
 * @Short_description: Macros to check the libuhttpmock version
 * @Title: Version Information
 *
 * uhttpmock provides compile-time version information.
 *
 * Since: 0.1.0
 */

/**
 * UHM_MAJOR_VERSION:
 *
 * Evaluates to the major version of the libuhttpmock headers at compile time.
 * (e.g. in libuhttpmock version 1.2.3 this is 1).
 *
 * Since: 0.1.0
 */
#define UHM_MAJOR_VERSION (@UHM_VERSION_MAJOR@)

/**
 * UHM_MINOR_VERSION:
 *
 * Evaluates to the minor version of the libuhttpmock headers at compile time.
 * (e.g. in libuhttpmock version 1.2.3 this is 2).
 *
 * Since: 0.1.0
 */
#define UHM_MINOR_VERSION (@UHM_VERSION_MINOR@)

/**
 * UHM_MICRO_VERSION:
 *
 * Evaluates to the micro version of the libuhttpmock headers at compile time.
 * (e.g. in libuhttpmock version 1.2.3 this is 3).
 *
 * Since: 0.1.0
 */
#define UHM_MICRO_VERSION (@UHM_VERSION_MICRO@)

/**
 * UHM_CHECK_VERSION:
 * @major: major version (e.g. 1 for version 1.2.3)
 * @minor: minor version (e.g. 2 for version 1.2.3)
 * @micro: micro version (e.g. 3 for version 1.2.3)
 *
 * Evaluates to %TRUE if the version of the libuhttpmock header files
 * is the same as or newer than the passed-in version.
 *
 * Since: 0.1.0
 */
#define UHM_CHECK_VERSION(major,minor,micro) \
    (UHM_MAJOR_VERSION > (major) || \
     (UHM_MAJOR_VERSION == (major) && UHM_MINOR_VERSION > (minor)) || \
     (UHM_MAJOR_VERSION == (major) && UHM_MINOR_VERSION == (minor) && \
      UHM_MICRO_VERSION >= (micro)))

#endif /* !UHM_VERSION_H */
07070100000022000081A400000000000000000000000166717A5A00000406000000000000000000000000000000000000002400000000uhttpmock-0.11.0/libuhttpmock/uhm.h/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/*
 * uhttpmock
 * Copyright (C) Philip Withnall 2013 <philip@tecnocode.co.uk>
 *
 * uhttpmock 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.
 *
 * uhttpmock 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 uhttpmock.  If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef UHM_H
#define UHM_H

/* Core files */
#include <uhttpmock/uhm-message.h>
#include <uhttpmock/uhm-server.h>
#include <uhttpmock/uhm-resolver.h>
#include <uhttpmock/uhm-version.h>

#endif /* !UHM_H */
07070100000023000081A400000000000000000000000166717A5A000005D5000000000000000000000000000000000000001D00000000uhttpmock-0.11.0/meson.buildproject('uhttpmock', 'c',
  version: '0.11.0',
  default_options: [
    'warning_level=2',
    'c_std=gnu99',
  ],
)

# Modules
gnome = import('gnome')
pkgconfig = import('pkgconfig')

# Compiler
cc = meson.get_compiler('c')
add_project_arguments([
  '-Wno-unused-parameter',
  '-Wno-missing-field-initializers',
], language: 'c')

# Versioning
uhm_version_split = meson.project_version().split('.')
uhm_major_version = uhm_version_split[0].to_int()
uhm_minor_version = uhm_version_split[1].to_int()
uhm_micro_version = uhm_version_split[2].to_int()

uhm_api_version = '1.0'

uhm_soversion = 1

# Before making a release, uhm_lib_version should be modified.
# The string is of the form X.Y.Z
# - If the interface is the same as the previous version, change to X.Y.Z+1
# - If interfaces have been changed or added, but binary compatibility has
#   been preserved, change to X.Y+1.0
# - If binary compatibility has been broken (eg removed or changed interfaces)
#   change to X+1.0.0
uhm_lib_version = '@0@.2.3'.format(uhm_soversion)

uhm_version_h = configure_file(
  input: 'libuhttpmock/uhm-version.h.in',
  output: '@BASENAME@',
  configuration: {
    'UHM_VERSION_MAJOR': uhm_major_version,
    'UHM_VERSION_MINOR': uhm_minor_version,
    'UHM_VERSION_MICRO': uhm_micro_version,
  },
)

# Dependencies
glib_dep = dependency('glib-2.0', version: '>= 2.38')
gio_dep = dependency('gio-2.0', version: '>= 2.38')
soup_dep = dependency('libsoup-3.0', version: '>= 3.1.2')

subdir('libuhttpmock')
07070100000024000081A400000000000000000000000166717A5A00000189000000000000000000000000000000000000002300000000uhttpmock-0.11.0/meson_options.txtoption('introspection',
  type: 'boolean',
  value: true,
  description: 'Whether to build GObject Introspection (GIR) files',
)
option('vapi',
  type: 'feature',
  value: 'auto',
  description: 'Whether to build Vala bindings (requires introspection)',
)
option('gtk_doc',
  type: 'boolean',
  value: true,
  description: 'Whether to render the reference documentation (requires gtk-doc)',
)
07070100000025000081A400000000000000000000000166717A5A0000050D000000000000000000000000000000000000002000000000uhttpmock-0.11.0/uhttpmock.doap<Project xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#" xmlns:foaf="http://xmlns.com/foaf/0.1/" xmlns:gnome="http://api.gnome.org/doap-extensions#" xmlns="http://usefulinc.com/ns/doap#">
	<name xml:lang="en">uhttpmock</name>
	<shortdesc xml:lang="en">uhttpmock is a project for mocking web service APIs which use HTTP or HTTPS</shortdesc>
	<description xml:lang="en">uhttpmock is a project for mocking web service APIs which use HTTP or HTTPS. It provides a library, libuhttpmock, which implements recording and playback of HTTP request–response traces.</description>
	<homepage rdf:resource="https://gitlab.freedesktop.org/pwithnall/uhttpmock"/>
	<license rdf:resource="http://usefulinc.com/doap/licenses/lgpl"/>
	<download-page rdf:resource="http://tecnocode.co.uk/downloads/uhttpmock/"/>
	<maintainer>
		<foaf:Person>
			<foaf:name>Philip Withnall</foaf:name>
			<foaf:mbox rdf:resource="mailto:philip@tecnocode.co.uk"/>
			<gnome:userid>pwithnall</gnome:userid>
		</foaf:Person>
	</maintainer>
	<maintainer>
		<foaf:Person>
			<foaf:name>Jan-Michael Brummer</foaf:name>
			<foaf:mbox rdf:resource="mailto:jan-michael.brummer1@volkswagen.de"/>
			<gnome:userid>jbrummer</gnome:userid>
		</foaf:Person>
	</maintainer>
</Project>
07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!448 blocks
openSUSE Build Service is sponsored by