Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
devel:openSUSE:Factory:rpmlint
uhttpmock
uhttpmock-0.11.0.obscpio
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
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 <oss@cogitri.dev> */ #include <gio/gio.h> #include <glib.h> #include <libsoup/soup.h> #include <uhttpmock/uhm.h> 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", &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 (&argc, &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, &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
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor